Programma-afgeleide adres
Een Solana accountadres verwijst naar de locatie van het account op de blockchain. Veel accountadressen zijn de publieke sleutel van een keypair, waarbij de bijbehorende privésleutel wordt gebruikt om transacties met het account te ondertekenen.
Een nuttig alternatief voor een publieke sleuteladres is een programma-afgeleid adres (PDA). PDA's bieden een eenvoudige methode om programmastatus op te slaan, te mappen en op te halen. Een PDA is een adres dat deterministisch wordt gemaakt met behulp van een programma-ID en een combinatie van optionele vooraf gedefinieerde inputs. PDA's lijken op publieke sleuteladressen, maar hebben geen bijbehorende privésleutel.
De Solana runtime stelt programma's in staat om voor PDA's te ondertekenen zonder dat er een privésleutel nodig is. Door een PDA te gebruiken, hoef je het adres van het account niet bij te houden. In plaats daarvan kun je de specifieke inputs die voor de afleiding van de PDA zijn gebruikt, oproepen. (Om te leren hoe programma's PDA's gebruiken voor ondertekening, zie de Cross Program Invocations sectie.)
Achtergrond
Solana keypairs zijn punten op de Ed25519 curve (elliptische-curve cryptografie). Ze bestaan uit een publieke sleutel en een privésleutel. De publieke sleutel wordt het accountadres, en de privésleutel wordt gebruikt om geldige handtekeningen voor het account te genereren.
Twee accounts met adressen op de curve
Een PDA wordt opzettelijk afgeleid om buiten de Ed25519 curve te vallen. Dit betekent dat het geen geldige bijbehorende privésleutel heeft en geen cryptografische operaties kan uitvoeren. (Zoals het leveren van een handtekening.) Solana stelt programma's echter in staat om voor PDA's te ondertekenen zonder dat er een privésleutel nodig is.
Off Curve Address
Je kunt PDAs zien als een manier om hashmap-achtige structuren op de blockchain te creëren met behulp van een vooraf gedefinieerde set inputs. (Bijvoorbeeld strings, getallen en andere accountadressen.)
Program Derived Address
Een PDA afleiden
Voordat je een account met een PDA aanmaakt, moet je eerst het adres afleiden. Het afleiden van een PDA creëert niet automatisch een on-chain account op dat adres - het account moet expliciet worden aangemaakt via het programma dat gebruikt wordt om de PDA af te leiden. Je kunt een PDA zien als een adres op een kaart: alleen omdat een adres bestaat, betekent niet dat er iets gebouwd is.
De Solana SDK's ondersteunen PDA-creatie met de functies die in de onderstaande tabel worden getoond. Elke functie ontvangt de volgende input:
- Program ID: Het adres van het programma dat wordt gebruikt om de PDA af te leiden. Dit programma kan namens de PDA ondertekenen.
- Optionele seeds: Vooraf gedefinieerde inputs, zoals strings, getallen of andere accountadressen.
| SDK | Functie |
|---|---|
@solana/kit (Typescript) | getProgramDerivedAddress |
@solana/web3.js (Typescript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
De functie gebruikt de program ID en optionele seeds, en itereert vervolgens door bump-waarden om te proberen een geldig programma-adres te creëren. De iteratie van bump-waarden begint bij 255 en wordt met 1 verlaagd totdat een geldige PDA wordt gevonden. Nadat een geldige PDA is gevonden, geeft de functie de PDA en de bump seed terug.
De bump seed is een extra byte die aan de optionele seeds wordt toegevoegd om ervoor te zorgen dat een geldig off-curve adres wordt gegenereerd.
PDA Afleiding
Canonieke bump
Een bump seed is een extra byte die wordt toegevoegd aan de optionele seeds. De afleidingsfunctie doorloopt bump-waarden, beginnend bij 255 en verlagend met 1, totdat een waarde een geldig off-curve adres produceert. De eerste waarde die een geldig off-curve adres produceert wordt de "canonieke bump" genoemd.
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
In dit voorbeeld geeft de eerste bump seed een foutmelding. De eerste bump seed die een geldige PDA afleidt is 254. Bump seeds 253-251 leiden ook unieke, geldige PDA's af.
Dit betekent dat met dezelfde optionele seeds en programId, een bump seed met
een andere waarde nog steeds een geldige PDA kan afleiden.
Voeg altijd beveiligingscontroles toe om ervoor te zorgen dat een PDA die aan het programma wordt doorgegeven, is afgeleid van de canonieke bump. Als u dit niet doet, kunnen er kwetsbaarheden ontstaan waardoor onverwachte accounts kunnen worden gebruikt in de instructies van het programma. Het is een best practice om alleen de canonieke bump te gebruiken bij het afleiden van PDA's.
Voorbeelden
De onderstaande voorbeelden leiden een PDA af met behulp van de Solana SDK's. Klik op ▷ Run om de code uit te voeren.
Een PDA afleiden met een string seed
Het onderstaande voorbeeld leidt een PDA af met behulp van een programma-ID en een 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 een adres seed
Het onderstaande voorbeeld leidt een PDA af met behulp van een program ID en een optionele adres 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 seeds
Het onderstaande voorbeeld leidt een PDA af met behulp van een program ID en 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}`);
Een PDA-account aanmaken
Het onderstaande voorbeeld gebruikt het
Anchor framework om een nieuw account aan te
maken met een program-derived address. Het programma bevat een enkele
initialize instructie om het nieuwe account aan te
maken, dat het gebruikersadres en de
bump seed zal opslaan die gebruikt zijn 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 bumpdaccount_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,}
De init beperking instrueert Anchor om
het System Program aan te roepen om
een nieuw account aan te maken met het PDA als adres. De seeds
die gebruikt worden om de PDA aan te maken zijn:
- Het adres van het gebruikersaccount dat in de instructie is opgegeven
- De vaste string: "data"
- De canonieke bump seed
In dit voorbeeld krijgt de bump-beperking geen waarde toegewezen, dus Anchor zal
find_program_address gebruiken om de PDA af te leiden en de bump te vinden.
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,
Het onderstaande testbestand bevat een transactie die de
initialize instructie aanroept om een nieuw account aan
te maken met een programma-afgeleid adres. Het bestand bevat code om
de PDA af te leiden.
Het voorbeeld laat ook zien hoe je het nieuwe account dat zal worden aangemaakt kunt ophalen.
import * as anchor from "@coral-xyz/anchor";import { Program } from "@coral-xyz/anchor";import { PdaAccount } from "../target/types/pda_account";import { PublicKey } from "@solana/web3.js";describe("pda-account", () => {const provider = anchor.AnchorProvider.env();anchor.setProvider(provider);const program = anchor.workspace.PdaAccount as Program<PdaAccount>;const user = provider.wallet as anchor.Wallet;// Derive the PDA address using the seeds specified on the programconst [PDA] = PublicKey.findProgramAddressSync([Buffer.from("data"), user.publicKey.toBuffer()],program.programId);it("Is initialized!", async () => {const transactionSignature = await program.methods.initialize().accounts({user: user.publicKey}).rpc();console.log("Transaction Signature:", transactionSignature);});it("Fetch Account", async () => {const pdaAccount = await program.account.dataAccount.fetch(PDA);console.log(JSON.stringify(pdaAccount, null, 2));});});
Als je de initialize instructie opnieuw aanroept met dezelfde user adres
seed, zal de transactie mislukken. Dit gebeurt omdat er al een account bestaat
op het afgeleide adres.
Is this page helpful?