Solana —— PDA 与 CPI

Program Derived Address (PDA)

核心概念

PDA 是 Solana 中一种特殊的地址,具有两个关键特性:

确定性派生: 使用预定义的”种子”和程序 ID 组合,可以确定性地生成地址。就像用固定的配方总能做出同样的菜,给定相同的输入就能得到相同的地址。

程序签名能力: 虽然 PDA 没有私钥,但 Solana 运行时允许程序代表其派生的 PDA 进行签名。这是 PDA 最强大的特性。

为什么需要 PDA?

传统区块链中,程序要控制资产需要管理私钥,这既复杂又不安全。PDA 解决了这个问题:

  • 无需私钥管理,程序本身就能控制账户
  • 地址可预测,容易查找和管理
  • 类似链上的哈希映射,用种子映射到地址

派生机制

PDA 派生需要三个输入:

可选种子: 任意数据,如字符串 "user_profile" 或用户地址。可以使用多个种子组合,例如 ["vault", user_address, "v1"]

Bump 种子: 一个 0-255 的数字,用于确保地址落在 Ed25519 曲线之外(即没有私钥)。系统从 255 开始递减查找,第一个有效的值称为”规范 bump”。

程序 ID: 派生 PDA 的程序地址。只有这个程序能代表该 PDA 签名。

规范 Bump(Canonical Bump)的重要性

同一组种子可能有多个 bump 值能生成有效的 PDA,但这些 PDA 的地址各不相同。

安全风险

如果程序不验证是否使用规范 bump,攻击者可能:

  • 使用非规范 bump 创建”影子账户”
  • 传入这个影子 PDA 地址而非预期的账户
  • 绕过程序的安全检查和业务逻辑
  • 导致资金损失、状态错误或权限绕过

**注意:**始终使用规范 bump,并将其存储在账户数据中供后续验证。

关键要点

  • 派生 PDA 只是计算地址,不会自动创建账户
  • 账户必须通过程序指令显式创建
  • PDA 账户的所有权属于系统程序,数据所有权属于创建它的程序
  • 种子设计要考虑唯一性和可预测性

Cross Program Invocation (CPI)

核心概念

CPI 是一个程序调用另一个程序的指令,类似于 API 调用 API。这是 Solana 程序可组合性的基础。

当程序 A 通过 CPI 调用程序 B 时,原始交易中的权限会自动传递给程序 B,实现了安全的程序间协作。

权限传递机制

假设用户发起交易调用程序 A:

  • 用户钱包是签名者且可写
  • 某个数据账户只读
  • 某个代币账户可写

当程序 A 通过 CPI 调用程序 B 时:

  • 程序 B 可以使用用户钱包的签名权限
  • 程序 B 可以读取数据账户
  • 程序 B 可以写入代币账户
  • 程序 B 还能继续 CPI 调用程序 C

这种自动权限传递确保了安全性和便利性的平衡。

调用深度限制

Solana 限制 CPI 调用深度为 4 层:

  • 第 1 层:用户原始交易
  • 第 2-5 层:最多 4 次 CPI 调用

超过限制会导致交易失败。这个设计防止无限递归,控制计算成本。

两种 CPI 类型

普通 CPI: 只传递原始交易中的签名者权限。适用于简单的中介场景,如用户通过程序 A 向程序 B 转账。

带 PDA 签名的 CPI: 程序可以代表其控制的 PDA 签名。这需要提供 PDA 的派生种子,Solana 运行时会验证 PDA 的有效性后授予签名权限。

安全考虑

执行 CPI 前必须验证:

  • 账户所有权是否正确
  • 账户状态是否有效
  • 调用者是否有权限
  • PDA 派生是否使用规范 bump

总结

PDA 提供了确定性地址生成和程序签名能力,使程序能够拥有和控制账户。

CPI 实现了程序间的可组合性,通过权限传递机制实现安全的跨程序调用。

Hooray!Solana PDA 与 CPI 小节完成!!!