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 AddressProgram 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 KurvaAlamat 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 KurvaAlamat 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 PDAPenurunan PDA

Gunakan fungsi berikut dari SDK masing-masing untuk menurunkan PDA.

SDKFungsi
@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}`);
Click to execute the code.

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}`);
Click to execute the code.

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}`);
Click to execute the code.

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);
}
}
Click to execute the code.
bump 255: Error: Invalid seeds, address must fall off the curve
bump 254: 46GZzzetjCURsdFPb7rcnspbEMnCBXe9kpjrsZAkKb6X
bump 253: GBNWBGxKmdcd7JrMnBdZke9Fumj9sir4rpbruwEGmR4y
bump 252: THfBMgduMonjaNsCisKa7Qz2cBoG1VCUYHyso7UXYHH
bump 251: EuRrNqJAofo7y3Jy6MGvF7eZAYegqYTwH2dnLCwDDGdP
bump 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 bump
account_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 PDA
seeds = [b"data", user.key().as_ref()],
// use the canonical bump
bump,
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.

pda_account
#[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.

pda_account
#[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.

Derive 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.

Invoke Initialize Instruction
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.

Fetch Account
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?

Daftar Isi

Edit Halaman