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 AddressProgram 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 curveAdres 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 curveAdres 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-afleidingPDA-afleiding

Gebruik de volgende functies uit de respectievelijke SDK's om een PDA af te leiden.

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

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

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

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);
}
}
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

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 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,
}

In dit voorbeeld omvatten de seeds voor PDA-afleiding de vaste string data en het adres van het useraccount dat in de instructie is opgegeven. Het Anchor framework vindt automatisch de canonieke bump seed.

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>,

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.

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>,

Het testbestand bevat de Typescript-code om de PDA af te leiden.

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

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

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

Inhoudsopgave

Pagina Bewerken