---
title: "什么是程序派生地址（PDA）？"
description: "了解什么是程序派生地址、PDA 与普通地址的区别，以及 PDA 的使用场景"
---

# 什么是程序派生地址（PDA）？

Program Derived Addresses（PDAs）是 Solana 区块链上具有特殊属性的账户。正确使用 PDA 能让[Solana dApp 开发变得快速](https://www.alchemy.com/overviews/learn-solana-development)且高效，因为它们有助于跨程序通信。

本文将解释什么是 PDA、它们解决了哪些问题、其工作原理，以及它们与[Solana 账户模型](https://www.alchemy.com/overviews/solana-account-model)中其他账户的区别。

## **什么是程序派生地址（pda）？**

**Program Derived Address 是 Solana 区块链上没有私钥的账户**。由于 PDA 不是公钥，其账户地址是通过程序 ID、SHA-512 哈希函数、种子数组和一个特殊的 bump seed 计算得出的。

### **Solana 上的标准账户是什么？**

**一个标准的 Solana 账户同时拥有私钥和公钥（各 32 字节），两者共同组成一个密钥对，长度为 64 字节，位于椭圆曲线（ED25519）上**。密钥对要有效，必须位于这条曲线上。

以下是 ED25519 椭圆曲线的示意图：

<ImageBlock
  src="https://media.alchemy.com/1703701173-elliptic-curve-on-solana.png"
  alt="用于 Solana 账户密钥对的 ED25519 椭圆曲线图"
  width={1600}
  height={900}
  caption="Solana 上的 ED25519 椭圆曲线"
/>

## **程序地址是如何派生的？**

PDA 需要三个主要组成部分：

1. **父程序 ID** - 创建该 PDA 的父程序的 ID
1. **种子（Seeds）** - 一个字符串数组
1. **Bump Seed** - 确保该 PDA 没有私钥

**要生成一个有效的 Program Derived Address，需要将父程序的 ID 和种子数组一起输入 SHA-512 哈希函数运算**。

然而，大约有 50% 的情况下，该哈希运算的结果会落在 _ED25519 椭圆曲线_ 上，形成一个密钥对。由于 PDA 不能有私钥，Program Derived Address **不能落在椭圆曲线上**。为防止 PDA 拥有私钥，会使用一个特殊的 _bump_ seed 将哈希结果“撞”离曲线。

bump seed 本质上只是一个数字，从 255 开始。如果即便加上 bump seed，哈希结果仍然落在曲线上，就会再次运行哈希函数，将 bump 依次改为 254、253，以此类推，直到生成的结果不在曲线上为止。

注意：种子可以是任意字符串，但开发者通常会结合父程序的状态变量来使用它们，以此构建出类似哈希表的结构。

## **PDA 解决了什么问题？**

**Program Derived Address 通过以编程方式生成交易签名来简化交易确认流程，从而帮助 DeFi 账户等去信任化服务顺畅运行。**

以下是一个假设的 PDA 使用场景。

设想一个 Solana 程序，允许用户将某个 NFT 设置为默认头像（PFP）。该程序由两个部分组成：

1. **PFP 程序** - 创建账户以存储用户选择的头像
1. **核心程序** - 充当用户输入和 PFP 程序之间的代理

若要让 PFP 程序更新用户所选的头像，它需要用自己的私钥对交易签名以完成头像的更改。但这也意味着该程序需要将其私钥存储在链上。

Solana 程序不能用自己的私钥代表自己对交易签名，因为该密钥本身会被存储在链上，对所有人可见。如果这样做，该私钥就可能被用来代表该程序签署交易，从而更改任意用户的头像。

设想 PFP 程序管理着数百万枚 SOL 代币。这样的漏洞会演变成一场重大黑客攻击。Program Derived Address 正是为解决这一问题而生。

## **PDA 为什么重要？**

Program Derived Address 在 Solana 编程中扮演着重要角色，因为它们有助于不同程序之间的通信（跨程序调用），并且可以充当哈希表，用于存储特定数据，而其父程序可以轻松更新和修改这些数据。

### **1. 存储程序的状态变量**

PDA 让 Solana 开发者能够存储并追踪与特定用户相关的一个或一组变量。PDA 最适合的用途是为其父程序存储状态变量或数据，因为默认情况下它已授权父程序代表自己进行修改。

### **2. 将 PDA 用作哈希表**

映射（Mapping）表示一组键值对，用于根据某个键便捷地查找与之关联的信息。在 Solana 开发中，可以利用 PDA 的种子和相应字符串实现同样的效果。

**让我们回到之前钱包头像的例子。**

一旦用户选定了钱包的 PFP，PFP 程序会取用所选图片和用户的地址作为“种子”，创建一个用于存储用户选择的 PDA。

一旦哈希算法成功找到该 Program Derived Address，其公钥就会“映射”到用户的地址和所选的 NFT 头像上。

通过再提供一个 PDA 作为第三个种子，这种哈希表功能可以得到更好的利用。我们可以把所有可用的头像存储在一个单独的 PDA 中，将每张头像作为其种子传入，最终得到一个存储了所有头像的 PDA。

这样一来，当用户前来选择头像时，这个 PDA 看起来就像一个哈希表，因为通过传入的种子，你可以看出：在众多头像中（PFP 组 PDA），我们作为第一个种子传入的用户地址，选择了我们作为第二个种子传入的那张头像。

在此基础上，还可以进一步构建更深层次的哈希表结构。

<ImageBlock
  src="https://media.alchemy.com/1703701259-pda-as-hashmap-program-flow.jpeg"
  alt="将 PDA 用作哈希表的程序流程 - NFT 示例"
  width={1024}
  height={768}
  caption="将 PDA 用作哈希表的程序流程 - NFT 示例"
/>

### **3. 跨程序调用**

[跨程序调用（Cross Program Invocations，CPI）](https://www.alchemy.com/overviews/cross-program-invocation)是指一个程序调用另一个程序中函数的过程。CPI 之所以有用，是因为它们让代码具备更好的可组合性。

回到我们的例子，假设某用户想把头像从 Degen Ape 换成 Solana Monkey Business 头像。

**其底层运作方式如下：**

用户登录钱包后，核心程序会取用户的地址（公钥），并查找一个已经创建的、其种子中包含该用户公钥的 PDA。

找到之后，核心程序会调用 PFP 程序中名为 changePFP() 的函数（这就是一次跨程序调用），并将核心程序已经“选定”的 PDA 作为参数传入。

函数被调用后，被选中的 PDA 会检查“请求”该变更的账户是否为其父程序。如果不匹配，交易将被拒绝，因为只有父程序才能修改 PDA 的数据。

由于 PFP 程序正是该 PDA 的父程序，因此它被允许将用户所选的头像从 Degen Ape 更改为 SMB 头像。

<ImageBlock
  src="https://media.alchemy.com/1703701345-cross-program-invocation-with-pda.jpeg"
  alt="使用 PDA 进行跨程序调用 - 修改 NFT 图片"
  width={1600}
  height={901}
  caption="使用 PDA 进行跨程序调用 - 修改 NFT 图片"
/>

Program Derived Address 让其父程序能够代表自己签名，并可用于存储程序状态、构建哈希表，以及用于跨程序调用。PDA 是 Solana 编程领域中的一个基础性主题，它使快速、高效的 dApp 开发成为可能。

## **Program Derived Address 常见问题**

在使用 Program Derived Address 时，了解 Solana 如何处理交易和数据会很有帮助。账户主要分为两种类型：可执行账户和不可执行账户。

### **什么是可执行账户？**

可执行账户，也被称为**_程序（programs）_**，类似于 Ethereum 智能合约——一段在账户与其交互时会改变自身状态的代码。

### **什么是不可执行账户？**

不可执行的数据账户仅用于存储数据（例如账户持有的 SOL 数量、NFT、代币余额等），本质上就是某个程序的状态变量。

### **Solana 的程序数据存储方式与 Ethereum 智能合约有何不同？**

Ethereum 与 Solana 之间一个根本性的区别在于可执行代码的存储组织方式。Ethereum 上的智能合约自带“预建”的**存储**，[智能合约会将其所有状态变量存储在其中](https://www.alchemy.com/docs/smart-contract-storage-layout)。相比之下，Solana 上的程序没有预建的存储，而是拥有独立的数据账户，用于存放它们想要存储和引用的各种状态变量。
