Ringkasan
PDA diturunkan dengan melakukan hashing seed + program ID + bump melalui SHA-256 hingga hasilnya berada di luar kurva Ed25519. Bump kanonik adalah nilai pertama yang menghasilkan alamat di luar kurva. Maksimal 16 seed, maksimal 32 byte per seed.
Latar belakang
Nilai
Keypair
Solana adalah titik-titik pada kurva Ed25519.
Sebuah keypair terdiri dari kunci publik (digunakan sebagai alamat akun) dan
kunci rahasia (digunakan untuk menghasilkan tanda tangan). Siapa pun yang
memiliki kunci rahasia dapat menandatangani transaksi untuk alamat tersebut.
Dua akun dengan alamat pada kurva
PDA sengaja diturunkan agar berada di luar kurva Ed25519. Karena bukan titik
kurva yang valid, tidak ada kunci rahasia, dan tidak ada pihak eksternal yang
dapat menghasilkan tanda tangan. Hanya program yang menurunkan yang dapat
mengotorisasi operasi pada PDA melalui invoke_signed.
Alamat di luar kurva
Akun PDA vs keypair
| Properti | Akun keypair | Akun PDA |
|---|---|---|
| Jenis alamat | Pada kurva Ed25519 | Di luar kurva Ed25519 |
| Memiliki kunci privat | Ya | Tidak |
| Dapat menandatangani transaksi | Ya (dengan kunci privat) | Tidak |
| Dapat menandatangani saat CPI | Tidak (kecuali tanda tangan disertakan dalam transaksi) | Ya (melalui invoke_signed) |
| Derivasi | Menghasilkan keypair Ed25519 | Deterministik dari seed + program ID |
| Penggunaan umum | Dompet pengguna, program ID | Akun data milik program |
Seed opsional
Seed opsional adalah string byte yang ditentukan pengguna yang berfungsi sebagai
input untuk derivasi PDA. Mereka membuat alamat unik dan deterministik yang
terikat pada sebuah program. Misalnya, menggunakan ["user", user_pubkey]
sebagai seed akan menghasilkan PDA yang berbeda untuk setiap pengguna.
Seed harus mengikuti batasan berikut:
- Maksimal 16 seed per derivasi (
MAX_SEEDS) - Maksimal 32 byte per seed (
MAX_SEED_LEN)
Bump seed
Bump seed adalah satu byte (0-255) yang ditambahkan ke seed opsional selama
derivasi.
find_program_address
mencari dari 255 turun ke 0, memanggil create_program_address dengan
setiap nilai hingga hasilnya jatuh dari kurva Ed25519. Nilai pertama yang
berhasil adalah bump kanonik.
Program harus selalu menggunakan bump kanonik untuk memastikan pemetaan unik dan deterministik dari seed ke alamat.
Selalu gunakan bump kanonik saat melakukan derivasi PDA. Menggunakan bump non-kanonik akan membuat alamat valid kedua untuk seed yang sama, yang dapat menyebabkan kerentanan di mana penyerang mengganti akun yang berbeda dari yang diharapkan.
Derivasi PDA
Algoritma derivasi
Derivasi PDA diimplementasikan dalam fungsi
create_program_address
SDK. Algoritma bekerja sebagai berikut:
- Validasi bahwa jumlah seed tidak melebihi
MAX_SEEDS(16) dan tidak ada seed individual yang melebihiMAX_SEED_LEN(32 byte). Jika salah satu pemeriksaan gagal, kembalikanPubkeyError::MaxSeedLengthExceeded. - Hash SHA-256 semua seed, ID program, dan string
"ProgramDerivedAddress"bersama-sama untuk menghasilkan hasil 32-byte. - Periksa apakah hasilnya adalah titik yang valid pada kurva Ed25519.
- Jika hasilnya ADA pada kurva, kembalikan
PubkeyError::InvalidSeeds(alamat tersebut akan memiliki kunci privat yang sesuai, yang melanggar properti keamanan PDA). - Jika hasilnya TIDAK pada kurva, kembalikan sebagai PDA.
Biaya compute unit
Syscall on-chain
untuk create_program_address mengenakan biaya
1.500 CU
per panggilan.
Syscall try_find_program_address
mengenakan biaya 1.500 CU saat masuk (sebelum loop), kemudian tambahan 1.500 CU
untuk setiap percobaan bump yang gagal dalam loop.
Pola seed umum
Seed bersifat spesifik untuk aplikasi. Pola umum meliputi:
| Pola | Seed | Kasus penggunaan |
|---|---|---|
| Singleton global | ["global"] | Akun konfigurasi tunggal untuk seluruh program |
| Akun per pengguna | ["user", user_pubkey] | Satu akun per pengguna per program |
| Per pengguna per entitas | ["vault", user_pubkey, mint_pubkey] | Vault token, per pengguna per token |
| Counter / berurutan | ["order", user_pubkey, &order_id.to_le_bytes()] | Catatan berurutan per pengguna |
Seed digabungkan sebelum hashing, sehingga ["ab", "cd"] dan ["abcd"]
menghasilkan PDA yang sama. Gunakan seed dengan panjang tetap atau separator
untuk menghindari tabrakan. Misalnya, ["ab", "-", "cd"] tidak ambigu.
Contoh: Menurunkan PDA
Menurunkan PDA hanya menghitung alamat. Ini tidak membuat akun on-chain pada
alamat tersebut. Akun harus dibuat secara eksplisit melalui instruksi terpisah
(biasanya create_account melalui CPI).
SDK Solana menyediakan fungsi untuk penurunan PDA. Setiap fungsi menerima:
- Program ID: Alamat program yang digunakan untuk menurunkan PDA. Program ini dapat menandatangani atas nama PDA.
- Seed opsional: Input yang telah ditentukan seperti string, angka, atau alamat akun lainnya.
| SDK | Fungsi |
|---|---|
@solana/kit (TypeScript) | getProgramDerivedAddress |
@solana/web3.js (TypeScript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
Contoh di bawah ini menurunkan PDA menggunakan SDK Solana. Klik ▷ Run untuk mengeksekusi kode.
Turunkan PDA dengan seed string
Contoh di bawah ini menurunkan PDA menggunakan ID program dan 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}`);
Turunkan 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}`);
Turunkan 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}`);
Iterasi semua bump
Contoh berikut menunjukkan penurunan PDA menggunakan semua kemungkinan seed bump
(255 hingga 0), mengilustrasikan bagaimana find_program_address
mengembalikan canonical bump:
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 255 menghasilkan alamat on-curve dan gagal. Bump valid pertama adalah 254, menjadikannya bump kanonik.
Is this page helpful?