Program Derived Address (PDA)
Program Derived Addresses (PDA's) bieden ontwikkelaars op Solana twee belangrijke toepassingen:
- Deterministische accountadressen: PDA's bieden een mechanisme om deterministisch een adres te creëren met behulp van een combinatie van optionele "seeds" (vooraf gedefinieerde inputs) en een specifieke program ID.
- Programmaondertekening mogelijk maken: De Solana runtime stelt programma's in staat om te "tekenen" voor PDA's die zijn afgeleid van het adres van het programma.
Je kunt PDA's zien als een manier om hashmap-achtige structuren on-chain te maken vanuit een vooraf gedefinieerde set inputs (bijv. strings, getallen en andere accountadressen).
Het voordeel van deze aanpak is dat het de noodzaak elimineert om een exact adres bij te houden. In plaats daarvan hoef je alleen de specifieke inputs te onthouden die zijn gebruikt voor de afleiding ervan.
Program Derived Address
Het is belangrijk om te begrijpen dat het simpelweg afleiden van een Program Derived Address (PDA) niet automatisch een on-chain account op dat adres creëert. Accounts met een PDA als on-chain adres moeten expliciet worden aangemaakt via het programma dat is gebruikt om het adres af te leiden. Je kunt het afleiden van een PDA zien als het vinden van een adres op een kaart. Alleen een adres hebben betekent niet dat er iets gebouwd is op die locatie.
Dit gedeelte behandelt de details van het afleiden van PDA's. Het gedeelte over Cross Program Invocations (CPI's) legt uit hoe programma's PDA's gebruiken voor ondertekening.
Belangrijke punten
- PDA's zijn adressen die deterministisch worden afgeleid met behulp van een combinatie van vooraf gedefinieerde seeds, een bump seed, en een program ID.
- PDA's zijn adressen die buiten de Ed25519-curve vallen en geen corresponderende private key hebben.
- Solana-programma's kunnen tekenen namens PDA's die zijn afgeleid van hun program ID.
- Het afleiden van een PDA creëert niet automatisch een on-chain account.
- Een account dat een PDA als adres gebruikt, moet worden aangemaakt via een instructie binnen een Solana-programma.
Wat is een PDA
PDA's zijn adressen die deterministisch worden afgeleid en eruitzien als publieke sleutels, maar geen privésleutels hebben. Dit betekent dat het niet mogelijk is om een geldige handtekening voor het adres te genereren. De Solana runtime stelt programma's echter in staat om te "tekenen" voor PDA's zonder dat er een privésleutel nodig is.
Ter context, Solana Keypairs zijn punten op de Ed25519-curve (elliptische-curve cryptografie) met een publieke sleutel en bijbehorende privésleutel. Publieke sleutels worden gebruikt als adressen (unieke identificatie) voor on-chain accounts.
Adres op curve
Een PDA is een punt dat opzettelijk wordt afgeleid om buiten de Ed25519-curve te vallen met behulp van een vooraf gedefinieerde set inputs. Een punt dat niet op de Ed25519-curve ligt heeft geen geldige bijbehorende privésleutel en kan geen cryptografische operaties (ondertekening) uitvoeren.
Een PDA kan dienen als het adres (unieke identificatie) voor een on-chain account, waardoor een methode wordt geboden om programma-status eenvoudig op te slaan, te mappen en op te halen.
Adres buiten curve
Hoe een PDA afleiden
Voor het afleiden van een PDA zijn drie inputs nodig:
- Optionele seeds: Vooraf gedefinieerde inputs (bijv. strings, getallen, andere account adressen) voor PDA-afleiding.
- Bump seed: Een extra byte die aan de optionele seeds wordt toegevoegd om ervoor te zorgen dat een geldige PDA (buiten de curve) wordt gegenereerd. De bump seed begint bij 255 en wordt met 1 verlaagd totdat een geldige PDA wordt gevonden.
- Program ID: Het adres van het programma waarvan de PDA is afgeleid. Dit programma kan namens de PDA ondertekenen.
PDA-afleiding
Gebruik de volgende functies uit de respectievelijke SDK's om een PDA af te leiden.
SDK | Functie |
---|---|
@solana/kit (Typescript) | getProgramDerivedAddress |
@solana/web3.js (Typescript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
Om een PDA af te leiden, geef de volgende inputs aan de SDK-functie:
- De vooraf gedefinieerde optionele seeds omgezet naar bytes
- De program ID (adres) gebruikt voor afleiding
Zodra een geldige PDA is gevonden, geeft de functie zowel het adres (PDA) als de bump seed terug die voor de afleiding is gebruikt.
Voorbeelden
De volgende voorbeelden laten zien hoe je een PDA kunt afleiden met de respectievelijke SDK's.
Klik op de "Run"-knop om de code uit te voeren.
Een PDA afleiden met optionele string seed
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}`);
Een PDA afleiden met optionele address seed
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}`);
Een PDA afleiden met meerdere optionele seeds
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
PDA-afleiding vereist een "bump seed", een extra byte die wordt toegevoegd aan de optionele seeds. De afleidingsfunctie doorloopt bump-waarden, beginnend bij 255 en aftellend met 1, totdat een waarde een geldig off-curve adres produceert. De eerste bump-waarde die een geldig off-curve adres produceert is de "canonical bump."
De volgende voorbeelden tonen PDA-afleiding met alle mogelijke bump seeds (255 tot 0):
Kit voorbeeld is niet inbegrepen omdat de createProgramDerivedAddress functie niet wordt geëxporteerd.
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
De bump seed 255 geeft een foutmelding en de eerste bump seed die een geldige PDA afleidt is 254.
Merk op dat bump seeds 253-251 allemaal geldige PDA's afleiden met verschillende adressen. Dit betekent dat met dezelfde optionele seeds en programma-ID, een bump seed met een andere waarde nog steeds een geldige PDA kan afleiden.
Bij het bouwen van Solana-programma's moet je altijd beveiligingscontroles opnemen om ervoor te zorgen dat een PDA die aan het programma wordt doorgegeven, is afgeleid van de canonical bump. Als je deze controles niet opneemt, kunnen er kwetsbaarheden ontstaan waardoor onverwachte accounts kunnen worden gebruikt in de programma-instructies. Het is een best practice om alleen de canonical bump te gebruiken bij het afleiden van PDA's.
PDA-accounts aanmaken
Het onderstaande voorbeeldprogramma laat zien hoe je een account kunt aanmaken met een PDA als het adres van het nieuwe account. Het voorbeeldprogramma gebruikt het Anchor framework.
Het programma bevat een enkele initialize instructie om een nieuw account aan te maken met een PDA als het adres van het account. Het nieuwe account slaat het adres op van de payer en de name seed die gebruikt is om de PDA af te leiden.
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,}
In dit voorbeeld omvatten de seeds voor PDA-afleiding de vaste string data
en
het adres van het user
account dat in de instructie is opgegeven. Het Anchor
framework vindt automatisch de canonieke bump
seed.
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,
De init
beperking instrueert Anchor om het System Program aan te roepen om een
nieuw account te maken met de PDA als adres. Anchor doet dit via een
CPI.
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,
Het testbestand bevat de Typescript-code om de PDA af te leiden.
const [PDA] = PublicKey.findProgramAddressSync([Buffer.from("data"), user.publicKey.toBuffer()],program.programId);
De transactie in het testbestand roept de initialize
instructie aan om een
nieuw on-chain account te maken met de PDA als adres. In dit voorbeeld kan
Anchor het PDA-adres in de instructie-accounts afleiden, dus het hoeft niet
expliciet te worden opgegeven.
it("Is initialized!", async () => {const transactionSignature = await program.methods.initialize().accounts({user: user.publicKey}).rpc();console.log("Transaction Signature:", transactionSignature);});
Het testbestand laat ook zien hoe je het on-chain account kunt ophalen dat op dat adres is gemaakt nadat de transactie is verzonden.
it("Fetch Account", async () => {const pdaAccount = await program.account.dataAccount.fetch(PDA);console.log(JSON.stringify(pdaAccount, null, 2));});
Merk op dat in dit voorbeeld, als je de initialize
instructie meer dan éénmaal
aanroept met hetzelfde user
adres als seed, de transactie mislukt. Dit gebeurt
omdat er al een account bestaat op het afgeleide adres.
Is this page helpful?