Program Derived Address (PDA)
Program Derived Addresses (PDAs) cung cấp cho các nhà phát triển trên Solana hai trường hợp sử dụng chính:
- Địa chỉ tài khoản xác định: PDAs cung cấp cơ chế để tạo địa chỉ một cách xác định bằng cách sử dụng kết hợp các "seeds" tùy chọn (các đầu vào được xác định trước) và một ID chương trình cụ thể.
- Cho phép chương trình ký: Môi trường chạy Solana cho phép các chương trình "ký" cho các PDAs được tạo ra từ địa chỉ của chương trình.
Bạn có thể xem PDAs như một cách để tạo cấu trúc giống như hashmap trên chuỗi từ một tập hợp đầu vào được xác định trước (ví dụ: chuỗi, số và các địa chỉ tài khoản khác).
Lợi ích của phương pháp này là nó loại bỏ nhu cầu theo dõi một địa chỉ chính xác. Thay vào đó, bạn chỉ cần nhớ các đầu vào cụ thể được sử dụng để tạo ra nó.
Program Derived Address
Điều quan trọng cần hiểu là việc chỉ tạo ra một Program Derived Address (PDA) không tự động tạo ra một tài khoản trên chuỗi tại địa chỉ đó. Các tài khoản có PDA làm địa chỉ trên chuỗi phải được tạo ra một cách rõ ràng thông qua chương trình được sử dụng để tạo địa chỉ. Bạn có thể xem việc tạo PDA như tìm một địa chỉ trên bản đồ. Chỉ có địa chỉ không có nghĩa là có thứ gì đó được xây dựng tại vị trí đó.
Phần này đề cập đến chi tiết về cách tạo PDAs. Phần về Cross Program Invocations (CPIs) giải thích cách các chương trình sử dụng PDAs để ký.
Điểm chính
- PDAs là các địa chỉ được tạo ra một cách xác định bằng cách sử dụng kết hợp các seeds được xác định trước, một bump seed và ID của một chương trình.
- PDAs là các địa chỉ nằm ngoài đường cong Ed25519 và không có khóa riêng tương ứng.
- Các chương trình Solana có thể ký thay mặt cho PDAs được tạo ra từ ID chương trình của nó.
- Việc tạo ra một PDA không tự động tạo ra một tài khoản trên chuỗi.
- Một tài khoản sử dụng PDA làm địa chỉ của nó phải được tạo thông qua một chỉ thị trong chương trình Solana.
PDA là gì
PDA là các địa chỉ được tạo ra một cách xác định trông giống như các khóa công khai, nhưng không có khóa riêng tư. Điều này có nghĩa là không thể tạo ra chữ ký hợp lệ cho địa chỉ đó. Tuy nhiên, môi trường chạy của Solana cho phép các chương trình "ký" cho PDA mà không cần khóa riêng tư.
Để hiểu rõ hơn, Solana Keypairs là các điểm trên đường cong Ed25519 (mật mã đường cong elliptic) với khóa công khai và khóa riêng tư tương ứng. Khóa công khai được sử dụng làm địa chỉ (định danh duy nhất) cho các tài khoản trên chuỗi.
Địa chỉ trên đường cong
PDA là một điểm được tạo ra có chủ đích để nằm ngoài đường cong Ed25519 bằng cách sử dụng một tập hợp các đầu vào được xác định trước. Một điểm không nằm trên đường cong Ed25519 không có khóa riêng tư tương ứng hợp lệ và không thể thực hiện các hoạt động mật mã (ký).
PDA có thể đóng vai trò như địa chỉ (định danh duy nhất) cho một tài khoản trên chuỗi, cung cấp một phương pháp để dễ dàng lưu trữ, ánh xạ và truy xuất trạng thái chương trình.
Địa chỉ ngoài đường cong
Cách tạo ra một PDA
Việc tạo ra một PDA đòi hỏi ba đầu vào:
- seed tùy chọn: Các đầu vào được xác định trước (ví dụ: chuỗi, số, địa chỉ tài khoản khác) để tạo ra PDA.
- bump seed: Một byte bổ sung được thêm vào seed tùy chọn để đảm bảo một PDA hợp lệ (ngoài đường cong) được tạo ra. bump seed bắt đầu từ 255 và giảm dần 1 cho đến khi tìm thấy một PDA hợp lệ.
- Program ID: Địa chỉ của chương trình mà PDA được tạo ra từ đó. Chương trình này có thể ký thay mặt cho PDA.
Quá trình tạo PDA
Sử dụng các hàm sau đây từ các SDK tương ứng để tạo ra một PDA.
SDK | Hàm |
---|---|
@solana/kit (Typescript) | getProgramDerivedAddress |
@solana/web3.js (Typescript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
Để tạo một PDA, cung cấp các đầu vào sau cho hàm SDK:
- Các seed tùy chọn đã định nghĩa trước được chuyển đổi thành bytes
- Program ID (địa chỉ) được sử dụng để tạo
Khi một PDA hợp lệ được tìm thấy, hàm sẽ trả về cả địa chỉ (PDA) và bump seed được sử dụng để tạo.
Ví dụ
Các ví dụ sau đây cho thấy cách tạo một PDA sử dụng các SDK tương ứng.
Nhấp vào nút "Run" để thực thi mã.
Tạo một PDA với seed chuỗi tùy chọn
import { Address, getProgramDerivedAddress } from "@solana/kit";const programAddress = "11111111111111111111111111111111" as Address;const seeds = ["helloWorld"];const [pda, bump] = await getProgramDerivedAddress({programAddress,seeds});console.log(`PDA: ${pda}`);console.log(`Bump: ${bump}`);
Tạo một PDA với seed địa chỉ tùy chọn
import {Address,getAddressEncoder,getProgramDerivedAddress} from "@solana/kit";const programAddress = "11111111111111111111111111111111" as Address;const addressEncoder = getAddressEncoder();const optionalSeedAddress = addressEncoder.encode("B9Lf9z5BfNPT4d5KMeaBFx8x1G4CULZYR1jA2kmxRDka" as Address);const seeds = [optionalSeedAddress];const [pda, bump] = await getProgramDerivedAddress({programAddress,seeds});console.log(`PDA: ${pda}`);console.log(`Bump: ${bump}`);
Tạo một PDA với nhiều seed tùy chọn
import {Address,getAddressEncoder,getProgramDerivedAddress} from "@solana/kit";const programAddress = "11111111111111111111111111111111" as Address;const optionalSeedString = "helloWorld";const addressEncoder = getAddressEncoder();const optionalSeedAddress = addressEncoder.encode("B9Lf9z5BfNPT4d5KMeaBFx8x1G4CULZYR1jA2kmxRDka" as Address);const seeds = [optionalSeedString, optionalSeedAddress];const [pda, bump] = await getProgramDerivedAddress({programAddress,seeds});console.log(`PDA: ${pda}`);console.log(`Bump: ${bump}`);
Canonical Bump
Quá trình tạo PDA yêu cầu một "bump seed", một byte bổ sung được thêm vào các seeds tùy chọn. Hàm tạo sẽ lặp qua các giá trị bump, bắt đầu từ 255 và giảm dần 1 đơn vị, cho đến khi một giá trị tạo ra một địa chỉ off-curve hợp lệ. Giá trị bump đầu tiên tạo ra địa chỉ off-curve hợp lệ được gọi là "canonical bump."
Các ví dụ sau đây cho thấy quá trình tạo PDA sử dụng tất cả các bump seeds có thể có (từ 255 đến 0):
Ví dụ Kit không được đưa vào vì hàm createProgramDerivedAddress không được xuất ra.
import { PublicKey } from "@solana/web3.js";const programId = new PublicKey("11111111111111111111111111111111");const optionalSeed = "helloWorld";// Loop through all bump seeds (255 down to 0)for (let bump = 255; bump >= 0; bump--) {try {const PDA = PublicKey.createProgramAddressSync([Buffer.from(optionalSeed), Buffer.from([bump])],programId);console.log("bump " + bump + ": " + PDA);} catch (error) {console.log("bump " + bump + ": " + error);}}
bump 255: Error: Invalid seeds, address must fall off the curvebump 254: 46GZzzetjCURsdFPb7rcnspbEMnCBXe9kpjrsZAkKb6Xbump 253: GBNWBGxKmdcd7JrMnBdZke9Fumj9sir4rpbruwEGmR4ybump 252: THfBMgduMonjaNsCisKa7Qz2cBoG1VCUYHyso7UXYHHbump 251: EuRrNqJAofo7y3Jy6MGvF7eZAYegqYTwH2dnLCwDDGdPbump 250: Error: Invalid seeds, address must fall off the curve...// remaining bump outputs
Bump seed 255 gây ra lỗi và bump seed đầu tiên tạo ra PDA hợp lệ là 254.
Lưu ý rằng các bump seeds 253-251 đều tạo ra các PDA hợp lệ với các địa chỉ khác
nhau. Điều này có nghĩa là với cùng một seeds tùy chọn và programId
, một bump
seed với giá trị khác vẫn có thể tạo ra một PDA hợp lệ.
Khi xây dựng các chương trình Solana, luôn đưa vào các kiểm tra bảo mật để đảm bảo PDA được truyền vào chương trình được tạo từ canonical bump. Việc không đưa vào các kiểm tra này có thể tạo ra các lỗ hổng cho phép các tài khoản không mong muốn được sử dụng trong các lệnh của chương trình. Thông lệ tốt nhất là chỉ sử dụng canonical bump khi tạo PDA.
Tạo tài khoản PDA
Chương trình ví dụ dưới đây cho thấy cách tạo một tài khoản sử dụng PDA làm địa chỉ của tài khoản mới. Chương trình ví dụ sử dụng Anchor framework.
Chương trình bao gồm một lệnh initialize
duy nhất để tạo một tài khoản mới sử
dụng PDA làm địa chỉ của tài khoản. Tài khoản mới lưu trữ địa chỉ của user
và
seed bump
được sử dụng để tạo PDA.
use anchor_lang::prelude::*;declare_id!("75GJVCJNhaukaa2vCCqhreY31gaphv7XTScBChmr1ueR");#[program]pub mod pda_account {use super::*;pub fn initialize(ctx: Context<Initialize>) -> Result<()> {let account_data = &mut ctx.accounts.pda_account;// store the address of the `user`account_data.user = *ctx.accounts.user.key;// store the canonical bumpaccount_data.bump = ctx.bumps.pda_account;Ok(())}}#[derive(Accounts)]pub struct Initialize<'info> {#[account(mut)]pub user: Signer<'info>,#[account(init,// define the seeds to derive the PDAseeds = [b"data", user.key().as_ref()],// use the canonical bumpbump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,pub system_program: Program<'info, System>,}#[account]#[derive(InitSpace)]pub struct DataAccount {pub user: Pubkey,pub bump: u8,}
Trong ví dụ này, các seed cho việc tạo PDA bao gồm chuỗi cố định data
và địa
chỉ của tài khoản user
được cung cấp trong chỉ thị. Framework Anchor tự động
tìm seed bump
chuẩn.
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,
Ràng buộc init
hướng dẫn Anchor gọi System Program để tạo một tài khoản mới sử
dụng PDA làm địa chỉ. Anchor thực hiện điều này thông qua CPI.
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,
File kiểm thử chứa mã Typescript để tạo PDA.
const [PDA] = PublicKey.findProgramAddressSync([Buffer.from("data"), user.publicKey.toBuffer()],program.programId);
Giao dịch trong file kiểm thử gọi chỉ thị initialize
để tạo một tài khoản
on-chain mới sử dụng PDA làm địa chỉ. Trong ví dụ này, Anchor có thể suy ra địa
chỉ PDA trong các tài khoản chỉ thị, vì vậy không cần phải cung cấp nó một cách
rõ ràng.
it("Is initialized!", async () => {const transactionSignature = await program.methods.initialize().accounts({user: user.publicKey}).rpc();console.log("Transaction Signature:", transactionSignature);});
File kiểm thử cũng cho thấy cách lấy tài khoản on-chain được tạo tại địa chỉ đó sau khi giao dịch được gửi.
it("Fetch Account", async () => {const pdaAccount = await program.account.dataAccount.fetch(PDA);console.log(JSON.stringify(pdaAccount, null, 2));});
Lưu ý rằng trong ví dụ này, nếu bạn gọi chỉ thị initialize
nhiều hơn một lần
sử dụng cùng một địa chỉ user
làm seed, thì giao dịch sẽ thất bại. Điều này
xảy ra vì đã tồn tại một tài khoản tại địa chỉ được tạo.
Is this page helpful?