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 curveTwee 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 AddressOff 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 AddressProgram 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.
SDKFunctie
@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 AfleidingPDA 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);
}
}
Console
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

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

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

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

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.

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

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.

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

Test
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 program
const [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?

Inhoudsopgave

Pagina Bewerken

Beheerd door

© 2025 Solana Foundation.
Alle rechten voorbehouden.
Blijf Verbonden