pda

微信扫码分享

PDAs (程序派生地址)

PDAsProgram 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 账户

相关概念与技术

  • Solana: PDA 所属的区块链平台
  • Anchor: Solana 智能合约开发框架
  • SPL Token: Solana 代币标准
  • 关联代币账户(ATA): 基于 PDA 的代币账户实现