PDAs (程序派生地址)
PDAs(Program Derived Addresses,程序派生地址)是 Solana 中一种特殊的账户地址,它由程序 ID 和一组种子(seeds)通过确定性算法生成,而不需要对应的私钥。PDAs 是 Solana 智能合约开发中最重要的概念之一,用于创建可由程序控制的账户。
核心特性
1. 确定性生成 PDA 地址是通过以下公式确定性生成的:
PDA = hash(seeds + program_id + bump)
其中:
seeds:一组字节数组,可以是字符串、公钥、数字等program_id:程序的地址bump:一个 0-255 的值,用于确保生成的地址不在 Ed25519 曲线上
2. 无私钥 PDA 地址刻意设计为不在 Ed25519 椭圆曲线上,这意味着不存在对应的私钥。因此,只有派生它的程序可以代表这个地址签名。
3. 程序签名 当程序需要以 PDA 的身份执行操作时,可以通过提供正确的 seeds 和 bump 来证明所有权,运行时会自动为 PDA 签名。
使用场景
1. 用户数据存储 为每个用户创建独立的数据账户:
let (pda, bump) = Pubkey::find_program_address(
&[
b"user_data",
user_pubkey.as_ref(),
],
program_id
);
2. Token 账户管理 创建程序控制的 Token 账户,用于托管资产:
let (vault_pda, bump) = Pubkey::find_program_address(
&[
b"vault",
pool_id.as_ref(),
],
program_id
);
3. 权限管理 作为程序的授权账户,执行需要签名的操作(如转账、铸造代币)。
4. 去中心化应用状态 存储应用级别的状态数据,如流动性池、质押池等。
生成方法
查找 PDA
use solana_program::pubkey::Pubkey;
let (pda, bump_seed) = Pubkey::find_program_address(
&[b"my_seed", other_key.as_ref()],
&program_id
);
find_program_address 会自动从 255 开始递减尝试不同的 bump 值,直到找到一个不在曲线上的地址。返回的 bump_seed 需要保存,以便后续使用。
使用 PDA 签名
在程序中,可以通过 invoke_signed 让 PDA 签名:
invoke_signed(
&transfer_instruction,
&[source_account, dest_account, pda_account],
&[&[b"vault", pool_id.as_ref(), &[bump]]], // PDA 签名种子
)?;
最佳实践
1. 使用有意义的种子 选择能够清晰表达关系的种子:
// 好的实践
&[b"user_profile", user_pubkey.as_ref()]
// 不推荐
&[b"account1", &[42]]
2. 缓存 bump 值 将 bump 值存储在账户数据中,避免每次都重新计算:
#[account]
pub struct VaultData {
pub bump: u8,
pub authority: Pubkey,
// ... 其他字段
}
3. 避免冲突 使用多个种子组合,确保不同用途的 PDA 不会冲突:
// 用户配置
&[b"config", user_pubkey.as_ref()]
// 用户余额
&[b"balance", user_pubkey.as_ref()]
4. 验证 PDA 派生 在程序中始终验证传入的 PDA 是否正确派生:
let (expected_pda, bump) = Pubkey::find_program_address(
&[b"vault", pool_id.as_ref()],
ctx.program_id
);
require!(expected_pda == *vault.key, ErrorCode::InvalidPDA);
Anchor 框架中的 PDA
Anchor 提供了简化的 PDA 操作:
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = user,
space = 8 + UserData::SIZE,
seeds = [b"user_data", user.key().as_ref()],
bump
)]
pub user_data: Account<'info, UserData>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
Anchor 会自动:
- 查找正确的 bump 值
- 验证 PDA 派生
- 将 bump 存储在账户中
与传统账户的对比
| 特性 | PDA | 普通账户 |
|---|---|---|
| 私钥 | 无 | 有 |
| 签名方式 | 程序签名 | 私钥签名 |
| 地址生成 | 确定性 | 随机 |
| 所有权 | 程序 | 持有私钥的人 |
| 用途 | 程序控制的资产和数据 | 用户控制的资产 |
相关概念
- CPI(跨程序调用):PDA 常用于 CPI 中代表程序签名
- 种子(Seeds):用于派生 PDA 的输入参数
- Bump Seed:确保 PDA 不在曲线上的调整值
- Associated Token Account:一种特殊的 PDA,用于用户的 Token 账户