Tóm tắt
PDA được dẫn xuất bằng cách băm seed + program ID + bump qua SHA-256 cho đến khi kết quả nằm ngoài đường cong Ed25519. Bump chuẩn là giá trị đầu tiên tạo ra địa chỉ ngoài đường cong. Tối đa 16 seed, tối đa 32 byte mỗi seed.
Kiến thức nền tảng
Các giá trị
Keypair
trên Solana là các điểm trên đường cong Ed25519.
Một keypair bao gồm public key (dùng làm địa chỉ tài khoản) và secret key (dùng
để tạo chữ ký). Bất kỳ ai có secret key đều có thể ký giao dịch cho địa chỉ đó.
Hai tài khoản với địa chỉ trên đường cong
PDA được dẫn xuất có chủ đích để rơi ngoài đường cong Ed25519. Vì nó không
phải là điểm hợp lệ trên đường cong, không tồn tại secret key nào, và không bên
ngoài nào có thể tạo chữ ký. Chỉ program dẫn xuất mới có thể ủy quyền các thao
tác trên PDA thông qua invoke_signed.
Địa chỉ ngoài đường cong
Tài khoản PDA so với tài khoản keypair
| Thuộc tính | Tài khoản keypair | Tài khoản PDA |
|---|---|---|
| Loại địa chỉ | Trên đường cong Ed25519 | Ngoài đường cong Ed25519 |
| Có private key | Có | Không |
| Có thể ký giao dịch | Có (với private key) | Không |
| Có thể ký trong CPI | Không (trừ khi chữ ký được bao gồm trong giao dịch) | Có (qua invoke_signed) |
| Dẫn xuất | Tạo keypair Ed25519 | Xác định từ seed + program ID |
| Sử dụng điển hình | Ví người dùng, Program ID | Tài khoản dữ liệu thuộc sở hữu program |
Seed tùy chọn
Các seed tùy chọn là các chuỗi byte do người dùng định nghĩa, đóng vai trò là
đầu vào cho quá trình tạo PDA. Chúng tạo ra các địa chỉ xác định duy nhất trong
phạm vi của một chương trình. Ví dụ, sử dụng ["user", user_pubkey] làm seed sẽ
tạo ra một PDA khác nhau cho mỗi người dùng.
Các seed phải tuân theo những ràng buộc sau:
- Tối đa 16 seed cho mỗi lần tạo (
MAX_SEEDS) - Tối đa 32 byte cho mỗi seed (
MAX_SEED_LEN)
Bump seed
Bump seed là một byte đơn (0-255) được thêm vào các seed tùy chọn trong quá
trình tạo.
find_program_address
tìm kiếm từ 255 xuống 0, gọi create_program_address với mỗi giá trị cho
đến khi kết quả nằm ngoài đường cong Ed25519. Giá trị đầu tiên thành công chính
là canonical bump.
Các chương trình nên luôn sử dụng canonical bump để đảm bảo ánh xạ duy nhất, xác định từ seed đến địa chỉ.
Luôn sử dụng canonical bump khi tạo PDA. Sử dụng bump không chuẩn sẽ tạo ra địa chỉ hợp lệ thứ hai cho cùng một bộ seed, có thể dẫn đến lỗ hổng bảo mật khi kẻ tấn công thay thế một tài khoản khác với tài khoản mong đợi.
PDA Derivation
Thuật toán tạo
Quá trình tạo PDA được triển khai trong hàm
create_program_address
của SDK. Thuật toán hoạt động như sau:
- Xác thực rằng số lượng seed không vượt quá
MAX_SEEDS(16) và không có seed riêng lẻ nào vượt quáMAX_SEED_LEN(32 byte). Nếu một trong hai kiểm tra thất bại, trả vềPubkeyError::MaxSeedLengthExceeded. - Hash SHA-256 tất cả các seed, program ID và chuỗi
"ProgramDerivedAddress"với nhau để tạo ra kết quả 32 byte. - Kiểm tra xem kết quả có phải là điểm hợp lệ trên đường cong Ed25519 hay không.
- Nếu kết quả NẰM trên đường cong, trả về
PubkeyError::InvalidSeeds(địa chỉ sẽ có khóa riêng tương ứng, điều này vi phạm thuộc tính bảo mật của PDA). - Nếu kết quả KHÔNG NẰM trên đường cong, trả về nó làm PDA.
Chi phí compute unit
Syscall trên chuỗi
cho create_program_address tính phí
1.500 CU
mỗi lần gọi.
Syscall try_find_program_address
tính phí 1.500 CU khi bắt đầu (trước vòng lặp), sau đó thêm 1.500 CU cho mỗi lần
thử bump thất bại trong vòng lặp.
Các mẫu seed phổ biến
Seed phụ thuộc vào ứng dụng cụ thể. Các mẫu phổ biến bao gồm:
| Mẫu | Seed | Trường hợp sử dụng |
|---|---|---|
| Singleton toàn cục | ["global"] | Tài khoản cấu hình duy nhất cho toàn bộ chương trình |
| Tài khoản theo người dùng | ["user", user_pubkey] | Một tài khoản cho mỗi người dùng trên mỗi chương trình |
| Theo người dùng và thực thể | ["vault", user_pubkey, mint_pubkey] | Kho token, theo người dùng và token |
| Bộ đếm / tuần tự | ["order", user_pubkey, &order_id.to_le_bytes()] | Bản ghi tuần tự theo người dùng |
Các seed được nối lại trước khi băm, do đó ["ab", "cd"] và ["abcd"] tạo ra
cùng một PDA. Sử dụng seed có độ dài cố định hoặc ký tự phân cách để tránh
xung đột. Ví dụ, ["ab", "-", "cd"] là rõ ràng.
Ví dụ: Tạo PDA
Việc tạo PDA chỉ tính toán địa chỉ. Nó không tạo tài khoản trên chuỗi tại địa
chỉ đó. Tài khoản phải được tạo rõ ràng thông qua một instruction riêng biệt
(thường là create_account thông qua CPI).
Các SDK của Solana cung cấp các hàm để tạo PDA. Mỗi hàm nhận:
- Program ID: Địa chỉ của chương trình được sử dụng để tạo PDA. Chương trình này có thể ký thay mặt cho PDA.
- Seed tùy chọn: Các đầu vào được xác định trước như chuỗi, số hoặc địa chỉ tài khoản khác.
| SDK | Hàm |
|---|---|
@solana/kit (TypeScript) | getProgramDerivedAddress |
@solana/web3.js (TypeScript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
Các ví dụ dưới đây tạo PDA bằng cách sử dụng các SDK của Solana. Nhấp ▷ Run để thực thi mã.
Tạo PDA với seed chuỗi
Ví dụ dưới đây tạo một PDA sử dụng program ID và một 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 PDA với seed địa chỉ
Ví dụ dưới đây tạo một PDA sử dụng program ID và một 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 PDA với nhiều seed
Ví dụ dưới đây tạo một PDA sử dụng program ID và 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}`);
Lặp qua tất cả các bump
Các ví dụ sau đây cho thấy việc tạo PDA sử dụng tất cả các bump seed có thể (từ
255 đến 0), minh họa cách find_program_address trả về
canonical bump:
Ví dụ Kit không được bao gồm vì hàm
createProgramDerivedAddress
không được export.
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
Trong ví dụ này, bump 255 tạo ra một địa chỉ trên đường cong và thất bại. Bump hợp lệ đầu tiên là 254, khiến nó trở thành bump chuẩn.
Is this page helpful?