Alamat Turunan Program
Alamat akun Solana menunjuk ke lokasi akun di blockchain. Banyak alamat akun adalah kunci publik dari keypair, dalam hal ini kunci privat yang sesuai digunakan untuk menandatangani transaksi yang melibatkan akun tersebut.
Alternatif yang berguna untuk alamat kunci publik adalah alamat turunan program (PDA). PDA menyediakan metode mudah untuk menyimpan, memetakan, dan mengambil status program. PDA adalah alamat yang dibuat secara deterministik menggunakan ID program dan kombinasi input yang telah ditentukan sebelumnya yang bersifat opsional. PDA terlihat mirip dengan alamat kunci publik, tetapi tidak memiliki kunci privat yang sesuai.
Runtime Solana memungkinkan program untuk menandatangani PDA tanpa memerlukan kunci privat. Menggunakan PDA menghilangkan kebutuhan untuk melacak alamat akun. Sebagai gantinya, Anda dapat mengingat input spesifik yang digunakan untuk derivasi PDA. (Untuk mempelajari bagaimana program menggunakan PDA untuk penandatanganan, lihat bagian Pemanggilan Lintas Program.)
Latar Belakang
Keypair Solana adalah titik pada kurva Ed25519 (kriptografi kurva eliptik). Keypair terdiri dari kunci publik dan kunci privat. Kunci publik menjadi alamat akun, dan kunci privat digunakan untuk menghasilkan tanda tangan yang valid untuk akun.
Dua akun dengan alamat pada kurva
PDA sengaja diturunkan agar berada di luar kurva Ed25519. Ini berarti PDA tidak memiliki kunci privat yang sesuai yang valid dan tidak dapat melakukan operasi kriptografi. (Seperti menyediakan tanda tangan.) Namun, Solana memungkinkan program untuk menandatangani PDA tanpa memerlukan kunci privat.
Off Curve Address
Anda dapat menganggap PDA sebagai cara untuk membuat struktur seperti hashmap di on-chain menggunakan serangkaian input yang telah ditentukan sebelumnya. (Misalnya, string, angka, dan alamat akun lainnya.)
Program Derived Address
Menurunkan PDA
Sebelum membuat akun dengan PDA, Anda harus terlebih dahulu menurunkan alamatnya. Menurunkan PDA tidak secara otomatis membuat akun on-chain di alamat tersebut— akun harus secara eksplisit dibuat melalui program yang digunakan untuk menurunkan PDA. Anda dapat menganggap PDA seperti alamat pada peta: hanya karena alamat itu ada tidak berarti ada sesuatu yang dibangun di sana.
SDK Solana mendukung pembuatan PDA dengan fungsi yang ditunjukkan dalam tabel di bawah ini. Setiap fungsi menerima input berikut:
- Program ID: Alamat program yang digunakan untuk menurunkan PDA. Program ini dapat menandatangani atas nama PDA.
- Optional seeds: Input yang telah ditentukan sebelumnya, seperti string, angka atau alamat akun lainnya.
| SDK | Function |
|---|---|
@solana/kit (Typescript) | getProgramDerivedAddress |
@solana/web3.js (Typescript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
Fungsi ini menggunakan program ID dan optional seeds, kemudian melakukan iterasi melalui nilai bump untuk mencoba membuat alamat program yang valid. Iterasi nilai bump dimulai dari 255 dan berkurang sebesar 1 hingga PDA yang valid ditemukan. Setelah PDA yang valid ditemukan, fungsi mengembalikan PDA dan bump seed.
Bump seed adalah byte tambahan yang ditambahkan ke optional seeds untuk memastikan alamat off-curve yang valid dihasilkan.
Derivasi PDA
Canonical bump
Bump seed adalah byte tambahan yang ditambahkan pada seeds opsional. Fungsi derivasi melakukan iterasi melalui nilai-nilai bump, dimulai dari 255 dan dikurangi 1, hingga menghasilkan alamat off-curve yang valid. Nilai bump pertama yang menghasilkan alamat off-curve yang valid disebut "canonical bump."
Contoh berikut menunjukkan derivasi PDA menggunakan semua kemungkinan bump seed (255 hingga 0):
Contoh Kit tidak disertakan karena fungsi createProgramDerivedAddress tidak diekspor.
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
Dalam contoh ini, bump seed pertama menghasilkan error. Bump seed pertama yang menghasilkan PDA valid adalah 254. Bump seed 253-251 juga menghasilkan PDA valid yang unik.
Ini berarti bahwa dengan seeds opsional yang sama dan programId, bump seed
dengan nilai yang berbeda masih dapat menghasilkan PDA yang valid.
Selalu sertakan pemeriksaan keamanan untuk memastikan PDA yang diteruskan ke program diturunkan dari canonical bump. Kegagalan melakukan hal ini dapat memunculkan kerentanan yang memungkinkan akun yang tidak diharapkan digunakan dalam instruksi program. Praktik terbaik adalah hanya menggunakan canonical bump saat menurunkan PDA.
Contoh
Contoh di bawah ini menurunkan PDA menggunakan Solana SDK. Klik ▷ Run untuk menjalankan kode.
Menurunkan PDA dengan string seed
Contoh di bawah ini menurunkan PDA menggunakan program ID dan string seed opsional.
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}`);
Menurunkan PDA dengan seed alamat
Contoh di bawah ini menurunkan PDA menggunakan ID program dan seed alamat opsional.
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}`);
Menurunkan PDA dengan beberapa seed
Contoh di bawah ini menurunkan PDA menggunakan ID program dan beberapa seed opsional.
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}`);
Membuat akun PDA
Contoh di bawah ini menggunakan
framework Anchor untuk membuat akun baru
dengan alamat yang diturunkan dari program. Program ini mencakup satu instruksi
initialize untuk membuat akun baru, yang akan menyimpan
alamat pengguna dan bump seed yang
digunakan untuk menurunkan 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 bumpdaccount_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,}
Batasan init memberi tahu Anchor untuk
memanggil System Program untuk membuat
akun baru menggunakan PDA sebagai alamat. Seeds yang digunakan
untuk membuat PDA adalah:
- Alamat akun pengguna yang disediakan dalam instruksi
- String tetap: "data"
- Bump seed kanonik
Dalam contoh ini, batasan bump tidak diberi nilai, sehingga Anchor akan
menggunakan find_program_address untuk menurunkan PDA dan menemukan bump.
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,
File pengujian di bawah ini berisi transaksi yang memanggil instruksi
initialize untuk membuat akun baru dengan
program-derived address. File tersebut berisi kode untuk
menurunkan PDA.
Contoh ini juga menunjukkan cara mengambil akun baru yang akan dibuat.
import * as anchor from "@coral-xyz/anchor";import { Program } from "@coral-xyz/anchor";import { PdaAccount } from "../target/types/pda_account";import { PublicKey } from "@solana/web3.js";describe("pda-account", () => {const provider = anchor.AnchorProvider.env();anchor.setProvider(provider);const program = anchor.workspace.PdaAccount as Program<PdaAccount>;const user = provider.wallet as anchor.Wallet;// Derive the PDA address using the seeds specified on the programconst [PDA] = PublicKey.findProgramAddressSync([Buffer.from("data"), user.publicKey.toBuffer()],program.programId);it("Is initialized!", async () => {const transactionSignature = await program.methods.initialize().accounts({user: user.publicKey}).rpc();console.log("Transaction Signature:", transactionSignature);});it("Fetch Account", async () => {const pdaAccount = await program.account.dataAccount.fetch(PDA);console.log(JSON.stringify(pdaAccount, null, 2));});});
Jika Anda memanggil instruksi initialize lagi dengan seed alamat user yang
sama, transaksi akan gagal. Ini terjadi karena akun sudah ada di alamat yang
diturunkan.
Is this page helpful?