Solana中 PDA、ATA 与 普通Account 的区别与关系

  • cfanbo
  • 发布于 1天前
  • 阅读 188

普通账户地址对于账户地址的创建是由一个密钥对来生成的,但在Solana中账户地址与以太坊中的账户地址还是有一些区别的。以太坊账户地址以太坊账户地址的生成过程:通过私钥生成公钥对公钥进行Keccak-256哈希取哈希值的最后160位(20字节)作为地址将地址以0x开头,并

普通账户地址

对于账户地址的创建是由一个密钥对来生成的,但在Solana中账户地址与以太坊中的账户地址还是有一些区别的。

以太坊账户地址

以太坊账户地址的生成过程:

  1. 通过私钥生成公钥
  2. 对公钥进行 Keccak-256 哈希
  3. 取哈希值的最后 160 位(20 字节)作为地址
  4. 将地址以 0x 开头,并根据需要选择是否使用 EIP-55 格式

地址中通常是小写字母,但也有大写字母的变种,称为 EIP-55 格式。在 EIP-55 中,某些字符会根据哈希值的大小写进行区分,从而增加地址的错误检查能力。

Solana 账户地址

Solana账户地址的生成过程:

  1. 通过私钥生成公钥,一般通过调用 Keypair.generate() 生成
  2. 公钥直接映射为账户的地址,长度为 32 字节
  3. 为了使用方便,一般对其进行 Base58 编码,将公钥转换为地址字符串

代码:

const { Keypair } = require('@solana/web3.js');

// 生成一个新的密钥对
const keypair = Keypair.generate();

// 获取公钥,实际上就是账户地址
const publicKey = keypair.publicKey;

// 转换公钥为 Base58 编码的字符串(即账户地址)
const address = publicKey.toBase58();

console.log("Solana Account Address (Base58):", address);

可以看到 Solana 中的账户地址就是公钥,平时使用的账户地址,一般都是指 Base58 编码后的字符串。

总结

特点 Solana 以太坊
生成 公钥直接作为地址 公钥通过 Keccak-256 哈希,取哈希的最后 160 位
长度 32 字节(256 位),Base58 编码后 43 字符 20 字节(160 位),16 进制编码后 40 字符
哈希算法 无哈希处理,直接使用公钥 使用 Keccak-256 哈希处理公钥
编码方式 Base58 编码 16 进制编码,0x 前缀
示例 4Erv6yZoXckm6QqsbU6y6TbS8o8wVkAXw7KwHjsAVB9D 0x5b1a49d2c631eeed5d2b8e6abdd87e07d8e1a3b3

虽然 Solana 和以太坊都基于公钥生成地址,但它们在地址生成的具体方式、哈希算法和编码格式上存在不同。

Solana 地址直接使用公钥并通过 Base58 编码,而以太坊则先对公钥进行哈希处理,并且使用 16 进制编码生成地址。

PDA 账户地址

对于PDA账户地址的样子与普通账户是一样的,用户根本无法肉眼看出来一个地址是哪一种账户类型。

当创建一个交易时,一般都需要签名,对于普通账户,可以使用私钥的对交易签名。而对于 PDA 地址,虽然没有私钥,但在 Soalan中它允许以编程的方式对交易进行签名。

一个普通的账户地址是指在Ed25519 曲线(椭圆曲线加密)上的一个点,如 Alice 和 Bob 它们都属于基本账户,都有公钥和私钥。

On Curve Address

而 PDA 账户是表示在 Ed25519 曲线之外的点,它只有公钥,没有私钥。需要借助一些预定义的种子集来推导生成。

Off Curve Address

PDA 地址生成公式

PDA = pda_hash(program_id, seeds)

其中 program_id 字段是指当前程序ID(合约), 以保证生成的pda账户只对当前程序有效。seeds 它是一些种子的集合,如 ["abc", "xyz", "888"]

示例

import { PublicKey } from "@solana/web3.js";

const programId = new PublicKey("11111111111111111111111111111111");
const string = "helloWorld";

const [PDA, bump] = PublicKey.findProgramAddressSync(
  [Buffer.from(string)],
  programId,
);

console.log(`PDA: ${PDA}`);
console.log(`Bump: ${bump}`);

对于 bump 字段,它是由于在生成地址过程中,程序自动引入一个bump种子,它是这个0-255的数字。首先计算时先从255开始,如果计算得出的地址不是有效的 PDA 地址,则减少一,直到找到有效的地址为止。

PDA 地址的计算过程为

PDA Derivation

ATA账户

ATA(Associated Token Account) 账户是一个特殊的 PDA 账户,同样也是没有私钥的。它与pda 账户唯一的区别在于在生成pda地址的过程中,添加了一个新的种子,而这个种子其实就是 Mint Account 地址,而 Mint Account 只有 spl-token 才有的概念,因此对于 ATA 账户只能用在 spl-token 代币相关的地方。

其地址生成是使用 Owner 地址和 Mint Account 地址确定性通过 PDA 派生的,它是一个 spl-token 代币地址。可以将关联代币账户视为特定铸币所有者的“默认”代币账户。

Associated Token Account

示例

import { getAssociatedTokenAddressSync } from "@solana/spl-token";

const associatedTokenAccountAddress = getAssociatedTokenAddressSync(
  USDC_MINT_ADDRESS,
  OWNER_ADDRESS,
);

函数定义 <https://github.com/solana-labs/solana-program-library/blob/d72289c79/token/js/src/state/mint.ts#L190>

/**
 * Get the address of the associated token account for a given mint and owner
 *
 * @param mint                     Token mint account
 * @param owner                    Owner of the new account
 * @param allowOwnerOffCurve       Allow the owner account to be a PDA (Program Derived Address)
 * @param programId                SPL Token program account
 * @param associatedTokenProgramId SPL Associated Token program account
 *
 * @return Address of the associated token account
 */
export function getAssociatedTokenAddressSync(
    mint: PublicKey,
    owner: PublicKey,
    allowOwnerOffCurve = false,
    programId = TOKEN_PROGRAM_ID,
    associatedTokenProgramId = ASSOCIATED_TOKEN_PROGRAM_ID
): PublicKey {
    if (!allowOwnerOffCurve && !PublicKey.isOnCurve(owner.toBuffer())) throw new TokenOwnerOffCurveError();

    const [address] = PublicKey.findProgramAddressSync(
        [owner.toBuffer(), programId.toBuffer(), mint.toBuffer()],
        associatedTokenProgramId
    );

    return address;
}

可以看到,这里和生成 PDA 地址时调用的函数都是 PublicKey.findProgramAddressSync() 函数,唯一的区别就是参数不一样而已。

这里主要有两点:

  1. 种子使用固定的 owner、 mint 和 programId,用户没有办法自定义种子。
  2. 第二个参数程序ID 是 associatedTokenProgram,而不是原来的 TokenProgram 或 Token 2022 Program

对于底层最终的实现,就是将一系的值连接起来并sha256计算,使用这个结果创建 PublicKey 对象,实现代码参考 createProgramAddressSync(seeds: Array\<Buffer | Uint8Array>, programId: PublicKey) 函数。

从ATA账号的创建原理可以看到,ATA 账户也是一个PDA账户,只不过是一个特殊的PDA账户,它只能用在 spl-token 代币程序中,同时与 associatedTokenProgram 有一定的关系。

参考资料

  • 原创
  • 学分: 7
  • 分类: Solana
  • 标签:
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
cfanbo
cfanbo
江湖只有他的大名,没有他的介绍。