本文对比了 Solana 中的程序派生地址(PDA)和密钥对账户,分析了两者的创建方式、安全性、权限模型及适用场景,推荐优先使用 PDA 因其可预测性和广泛应用。
在 Solana 中,程序派生地址(PDA)是由程序地址和 seeds 派生出的账户地址,此前文章中我们主要使用 PDA。本文将介绍另一种账户类型——密钥对账户(Keypair Account),它在程序外部创建并传入程序初始化。我们将探讨两者的区别、安全性及使用场景。
密钥对账户拥有私钥,但这并不带来预期的安全隐患。让我们深入研究。
我们先回顾 PDA 的创建方式,这是 Solana 中的标准模式:
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("41ktTpNonvrbJJ2eH3SkxWwBfXyUJqZhDjYBKiq5RrVW");
#[program]
pub mod keypair_vs_pda {
use super::*;
pub fn initialize_pda(ctx: Context<InitializePDA>) -> Result<()> {
Ok(())
}
}
#[derive(Accounts)]
pub struct InitializePDA<'info> {
// This is the program derived address
#[account(init,
payer = signer,
space=size_of::<MyPDA>() + 8,
seeds = [],
bump)]
pub my_pda: Account<'info, MyPDA>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct MyPDA {
x: u64,
}
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { KeypairVsPda } from "../target/types/keypair_vs_pda";
describe("keypair_vs_pda", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.KeypairVsPda as Program<KeypairVsPda>;
it("Is initialized -- PDA version", async () => {
const seeds = []
const [myPda, _bump] = anchor.web3.PublicKey.findProgramAddressSync(seeds, program.programId);
console.log("the storage account address is", myPda.toBase58());
const tx = await program.methods.initializePda().accounts({myPda: myPda}).rpc();
});
});
解析
密钥对账户在程序外部生成后传入初始化,缺少 seeds 和 bump。
Rust 实现
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("41ktTpNonvrbJJ2eH3SkxWwBfXyUJqZhDjYBKiq5RrVW");
#[program]
pub mod keypair_vs_pda {
use super::*;
pub fn initialize_keypair_account(ctx: Context<InitializeKeypairAccount>) -> Result<()> {
Ok(())
}
}
#[derive(Accounts)]
pub struct InitializeKeypairAccount<'info> {
// This is the program derived address
#[account(init,
payer = signer,
space = size_of::<MyKeypairAccount>() + 8,)]
pub my_keypair_account: Account<'info, MyKeypairAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct MyKeypairAccount {
x: u64,
}
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { KeypairVsPda } from "../target/types/keypair_vs_pda";
// this airdrops sol to an address
async function airdropSol(publicKey, amount) {
let airdropTx = await anchor.getProvider().connection.requestAirdrop(publicKey, amount * anchor.web3.LAMPORTS_PER_SOL);
await confirmTransaction(airdropTx);
}
async function confirmTransaction(tx) {
const latestBlockHash = await anchor.getProvider().connection.getLatestBlockhash();
await anchor.getProvider().connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: tx,
});
}
describe("keypair_vs_pda", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.KeypairVsPda as Program<KeypairVsPda>;
it("Is initialized -- keypair version", async () => {
const newKeypair = anchor.web3.Keypair.generate();
await airdropSol(newKeypair.publicKey, 1e9); // 1 SOL
console.log("the keypair account address is", newKeypair.publicKey.toBase58());
await program.methods.initializeKeypairAccount()
.accounts({myKeypairAccount: newKeypair.publicKey})
.signers([newKeypair]) // the signer must be the keypair
.rpc();
});
});
关键点
若传入无私钥的地址(如另一个 Keypair 的公钥),测试失败:
const secondKeypair = anchor.web3.Keypair.generate();
await program.methods.initializeKeypairAccount()
.accounts({myKeypairAccount: secondKeypair.publicKey})
.signers([newKeypair])
.rpc();
错误:
Error: unknown signer: FarARzHZccVsjH6meT6TmTzZK1zQ9dAgapvuHEXpkEJ2
若传入 PDA(如 findProgramAddressSync 派生的地址):
const [pda, _bump] = anchor.web3.PublicKey.findProgramAddressSync([], program.pro...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!