Program Derived Address (PDA)
Program Derived Addresses (PDAs) menyediakan dua kasus penggunaan utama bagi pengembang di Solana:
- Alamat Akun Deterministik: PDAs menyediakan mekanisme untuk membuat alamat secara deterministik menggunakan kombinasi "seeds" opsional (input yang telah ditentukan) dan ID program tertentu.
- Memungkinkan Penandatanganan Program: Runtime Solana memungkinkan program untuk "menandatangani" PDAs yang diturunkan dari alamat program.
Anda dapat menganggap PDAs sebagai cara untuk membuat struktur seperti hashmap di blockchain dari serangkaian input yang telah ditentukan (misalnya string, angka, dan alamat akun lainnya).
Keuntungan dari pendekatan ini adalah menghilangkan kebutuhan untuk melacak alamat yang tepat. Sebagai gantinya, Anda hanya perlu mengingat input spesifik yang digunakan untuk derivasinya.
Program Derived Address
Penting untuk dipahami bahwa hanya menurunkan Program Derived Address (PDA) tidak secara otomatis membuat akun on-chain di alamat tersebut. Akun dengan PDA sebagai alamat on-chain harus secara eksplisit dibuat melalui program yang digunakan untuk menurunkan alamat tersebut. Anda dapat menganggap menurunkan PDA seperti menemukan alamat di peta. Hanya memiliki alamat tidak berarti ada sesuatu yang dibangun di lokasi tersebut.
Bagian ini membahas detail tentang menurunkan PDAs. Bagian tentang Cross Program Invocations (CPIs) menjelaskan bagaimana program menggunakan PDAs untuk penandatanganan.
Poin-poin Penting
- PDAs adalah alamat yang diturunkan secara deterministik menggunakan kombinasi seeds yang telah ditentukan, bump seed, dan ID program.
- PDAs adalah alamat yang berada di luar kurva Ed25519 dan tidak memiliki kunci privat yang sesuai.
- Program Solana dapat menandatangani atas nama PDAs yang diturunkan dari ID programnya.
- Menurunkan PDA tidak secara otomatis membuat akun on-chain.
- Akun yang menggunakan PDA sebagai alamatnya harus dibuat melalui instruksi dalam program Solana.
Apa itu PDA
PDA adalah alamat yang diturunkan secara deterministik yang terlihat seperti kunci publik, tetapi tidak memiliki kunci privat. Ini berarti tidak mungkin untuk menghasilkan tanda tangan yang valid untuk alamat tersebut. Namun, runtime Solana memungkinkan program untuk "menandatangani" PDA tanpa memerlukan kunci privat.
Sebagai konteks, Solana Keypairs adalah titik-titik pada kurva Ed25519 (kriptografi kurva eliptik) dengan kunci publik dan kunci privat yang sesuai. Kunci publik digunakan sebagai alamat (pengenal unik) untuk akun on-chain.
Alamat Pada Kurva
PDA adalah titik yang sengaja diturunkan agar berada di luar kurva Ed25519 menggunakan serangkaian input yang telah ditentukan. Titik yang tidak berada pada kurva Ed25519 tidak memiliki kunci privat yang valid dan tidak dapat melakukan operasi kriptografi (penandatanganan).
PDA dapat berfungsi sebagai alamat (pengenal unik) untuk akun on-chain, menyediakan metode untuk menyimpan, memetakan, dan mengambil status program dengan mudah.
Alamat Di Luar Kurva
Cara menurunkan PDA
Penurunan PDA memerlukan tiga input:
- seed: Input yang telah ditentukan (misalnya string, angka, alamat akun lain) untuk penurunan PDA.
- bump seed: Byte tambahan yang ditambahkan ke seed opsional untuk memastikan PDA yang valid (di luar kurva) dihasilkan. Bump seed dimulai dari 255 dan berkurang 1 hingga PDA yang valid ditemukan.
- Program ID: Alamat program dari mana PDA diturunkan. Program ini dapat menandatangani atas nama PDA.
Penurunan PDA
Gunakan fungsi berikut dari SDK masing-masing untuk menurunkan PDA.
SDK | Fungsi |
---|---|
@solana/kit (Typescript) | getProgramDerivedAddress |
@solana/web3.js (Typescript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
Untuk mendapatkan PDA, berikan input berikut ke fungsi SDK:
- Seed opsional yang telah ditentukan sebelumnya dikonversi menjadi bytes
- Program ID (alamat) yang digunakan untuk derivasi
Setelah PDA yang valid ditemukan, fungsi akan mengembalikan alamat (PDA) dan bump seed yang digunakan untuk derivasi.
Contoh
Contoh berikut menunjukkan cara mendapatkan PDA menggunakan SDK masing-masing.
Klik tombol "Run" untuk menjalankan kode.
Mendapatkan PDA dengan seed string 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}`);
Mendapatkan PDA dengan 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}`);
Mendapatkan PDA dengan 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}`);
Canonical Bump
Derivasi PDA memerlukan "bump seed", yaitu byte tambahan yang ditambahkan ke seeds opsional. Fungsi derivasi melakukan iterasi melalui nilai-nilai bump, dimulai dari 255 dan dikurangi 1, sampai nilai tersebut 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
Bump seed 255 menghasilkan error dan bump seed pertama yang menghasilkan PDA valid adalah 254.
Perhatikan bahwa bump seed 253-251 semuanya menghasilkan PDA valid dengan alamat
yang berbeda. Ini berarti bahwa dengan seeds opsional yang sama dan programId
,
bump seed dengan nilai yang berbeda masih dapat menghasilkan PDA yang valid.
Saat membangun program Solana, selalu sertakan pemeriksaan keamanan untuk memastikan PDA yang diteruskan ke program berasal dari canonical bump. Kegagalan menyertakan pemeriksaan ini dapat memperkenalkan kerentanan yang memungkinkan akun yang tidak diharapkan digunakan dalam instruksi program. Praktik terbaik adalah hanya menggunakan canonical bump saat melakukan derivasi PDA.
Membuat Akun PDA
Program contoh di bawah ini menunjukkan cara membuat akun menggunakan PDA sebagai alamat akun baru. Program contoh ini menggunakan framework Anchor.
Program ini menyertakan satu instruksi initialize
untuk membuat akun baru
menggunakan PDA sebagai alamat akun. Akun baru menyimpan alamat user
dan seed
bump
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 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,}
Dalam contoh ini, seed untuk derivasi PDA mencakup string tetap data
dan
alamat akun user
yang disediakan dalam instruksi. Framework Anchor secara
otomatis menemukan seed bump
kanonik.
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,
Batasan init
menginstruksikan Anchor untuk memanggil System Program untuk
membuat akun baru menggunakan PDA sebagai alamat. Anchor melakukan ini melalui
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 pengujian berisi kode Typescript untuk menurunkan PDA.
const [PDA] = PublicKey.findProgramAddressSync([Buffer.from("data"), user.publicKey.toBuffer()],program.programId);
Transaksi dalam file pengujian memanggil instruksi initialize
untuk membuat
akun on-chain baru menggunakan PDA sebagai alamat. Dalam contoh ini, Anchor
dapat menyimpulkan alamat PDA dalam akun instruksi, sehingga tidak perlu
disediakan secara eksplisit.
it("Is initialized!", async () => {const transactionSignature = await program.methods.initialize().accounts({user: user.publicKey}).rpc();console.log("Transaction Signature:", transactionSignature);});
File pengujian juga menunjukkan cara mengambil akun on-chain yang dibuat di alamat tersebut setelah transaksi dikirim.
it("Fetch Account", async () => {const pdaAccount = await program.account.dataAccount.fetch(PDA);console.log(JSON.stringify(pdaAccount, null, 2));});
Perhatikan bahwa dalam contoh ini, jika Anda memanggil instruksi initialize
lebih dari satu kali menggunakan alamat user
yang sama sebagai seed, maka
transaksi akan gagal. Ini terjadi karena akun sudah ada di alamat yang
diturunkan.
Is this page helpful?