Program Derived Address (PDA)
Program Derived Addresses (PDAs) bieten Entwicklern auf Solana zwei hauptsächliche Anwendungsfälle:
- Deterministische Kontoadressen: PDAs bieten einen Mechanismus zur deterministischen Erstellung einer Adresse unter Verwendung einer Kombination aus optionalen "seeds" (vordefinierten Eingaben) und einer spezifischen Programm-ID.
- Ermöglichen der Programmunterzeichnung: Die Solana-Laufzeitumgebung ermöglicht es Programmen, für PDAs zu "unterschreiben", die von der Adresse des Programms abgeleitet sind.
Man kann sich PDAs als eine Möglichkeit vorstellen, hashmap-ähnliche Strukturen on-chain aus einem vordefinierten Satz von Eingaben (z.B. Strings, Zahlen und andere Kontoadressen) zu erstellen.
Der Vorteil dieses Ansatzes ist, dass es nicht notwendig ist, eine exakte Adresse zu verfolgen. Stattdessen müssen Sie sich nur an die spezifischen Eingaben erinnern, die für die Ableitung verwendet wurden.
Program Derived Address
Es ist wichtig zu verstehen, dass das bloße Ableiten einer Program Derived Address (PDA) nicht automatisch ein On-Chain-Konto an dieser Adresse erstellt. Konten mit einer PDA als On-Chain-Adresse müssen explizit durch das Programm erstellt werden, das zur Ableitung der Adresse verwendet wurde. Sie können sich das Ableiten einer PDA wie das Finden einer Adresse auf einer Karte vorstellen. Nur weil man eine Adresse hat, bedeutet das nicht, dass an diesem Ort etwas gebaut ist.
Dieser Abschnitt behandelt die Details der Ableitung von PDAs. Der Abschnitt über Cross Program Invocations (CPIs) erklärt, wie Programme PDAs zur Unterzeichnung verwenden.
Kernpunkte
- PDAs sind Adressen, die deterministisch abgeleitet werden unter Verwendung einer Kombination aus vordefinierten seeds, einem bump seed und einer Programm-ID.
- PDAs sind Adressen, die außerhalb der Ed25519-Kurve liegen und keinen entsprechenden privaten Schlüssel haben.
- Solana-Programme können im Namen von PDAs unterschreiben, die von ihrer Programm-ID abgeleitet sind.
- Das Ableiten einer PDA erstellt nicht automatisch ein On-Chain-Konto.
- Ein Konto, das eine PDA als Adresse verwendet, muss durch eine Anweisung innerhalb eines Solana-Programms erstellt werden.
Was ist eine PDA
PDAs sind deterministisch abgeleitete Adressen, die wie öffentliche Schlüssel aussehen, aber keine privaten Schlüssel haben. Das bedeutet, dass es nicht möglich ist, eine gültige Signatur für die Adresse zu generieren. Die Solana-Laufzeitumgebung ermöglicht es Programmen jedoch, für PDAs zu "signieren", ohne einen privaten Schlüssel zu benötigen.
Zum Kontext: Solana Keypairs sind Punkte auf der Ed25519-Kurve (Elliptische-Kurven-Kryptographie) mit einem öffentlichen Schlüssel und einem entsprechenden privaten Schlüssel. Öffentliche Schlüssel werden als Adressen (eindeutige Kennungen) für On-Chain-Konten verwendet.
Adresse auf der Kurve
Eine PDA ist ein Punkt, der absichtlich so abgeleitet wird, dass er außerhalb der Ed25519-Kurve liegt, indem ein vordefinierter Satz von Eingaben verwendet wird. Ein Punkt, der nicht auf der Ed25519-Kurve liegt, hat keinen gültigen entsprechenden privaten Schlüssel und kann keine kryptographischen Operationen (Signieren) durchführen.
Eine PDA kann als Adresse (eindeutige Kennung) für ein On-Chain-Konto dienen und bietet eine Methode, um Programmzustände einfach zu speichern, zuzuordnen und abzurufen.
Adresse außerhalb der Kurve
Wie man eine PDA ableitet
Die Ableitung einer PDA erfordert drei Eingaben:
- Optionale seeds: Vordefinierte Eingaben (z.B. Zeichenketten, Zahlen, andere Kontoadressen) für die PDA-Ableitung.
- Bump seed: Ein zusätzliches Byte, das an die optionalen seeds angehängt wird, um sicherzustellen, dass eine gültige PDA (außerhalb der Kurve) generiert wird. Das bump seed beginnt bei 255 und wird um 1 verringert, bis eine gültige PDA gefunden wird.
- Program ID: Die Adresse des Programms, von dem die PDA abgeleitet wird. Dieses Programm kann im Namen der PDA signieren.
PDA-Ableitung
Verwenden Sie die folgenden Funktionen aus den jeweiligen SDKs, um eine PDA abzuleiten.
SDK | Funktion |
---|---|
@solana/kit (Typescript) | getProgramDerivedAddress |
@solana/web3.js (Typescript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
Um eine PDA abzuleiten, gib folgende Eingaben für die SDK-Funktion an:
- Die vordefinierten optionalen seeds, die in Bytes umgewandelt wurden
- Die Programm-ID (Adresse), die für die Ableitung verwendet wird
Sobald eine gültige PDA gefunden wurde, gibt die Funktion sowohl die Adresse (PDA) als auch den bump seed zurück, der für die Ableitung verwendet wurde.
Beispiele
Die folgenden Beispiele zeigen, wie man eine PDA mit den jeweiligen SDKs ableitet.
Klicke auf den "Run"-Button, um den Code auszuführen.
Eine PDA mit optionalem String-seed ableiten
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}`);
Eine PDA mit optionalem Adress-seed ableiten
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}`);
Eine PDA mit mehreren optionalen seeds ableiten
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}`);
Kanonischer Bump
Die PDA-Ableitung erfordert einen "bump seed", ein zusätzliches Byte, das an die optionalen seeds angehängt wird. Die Ableitungsfunktion iteriert durch Bump-Werte, beginnend bei 255 und dekrementiert um 1, bis ein Wert eine gültige Off-Curve-Adresse erzeugt. Der erste Bump-Wert, der eine gültige Off-Curve-Adresse erzeugt, ist der "canonical bump."
Die folgenden Beispiele zeigen die PDA-Ableitung mit allen möglichen bump seeds (255 bis 0):
Kit-Beispiel nicht enthalten, da die createProgramDerivedAddress Funktion nicht exportiert wird.
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
Der bump seed 255 wirft einen Fehler und der erste bump seed, der eine gültige PDA ableitet, ist 254.
Beachten Sie, dass die bump seeds 253-251 alle gültige PDAs mit
unterschiedlichen Adressen ableiten. Das bedeutet, dass bei gleichen optionalen
seeds und programId
, ein bump seed mit einem anderen Wert immer noch eine
gültige PDA ableiten kann.
Beim Erstellen von Solana-Programmen sollten immer Sicherheitsprüfungen eingebaut werden, um sicherzustellen, dass eine an das Programm übergebene PDA vom kanonischen bump abgeleitet wird. Wenn diese Prüfungen fehlen, können Sicherheitslücken entstehen, die es ermöglichen, unerwartete Konten in den Programmanweisungen zu verwenden. Es ist bewährte Praxis, nur den kanonischen bump bei der Ableitung von PDAs zu verwenden.
PDA-Konten erstellen
Das folgende Beispielprogramm zeigt, wie man ein Konto erstellt, das eine PDA als Adresse des neuen Kontos verwendet. Das Beispielprogramm verwendet das Anchor Framework.
Das Programm enthält eine einzelne initialize
Anweisung zum Erstellen eines
neuen Kontos mit einer PDA als Adresse des Kontos. Das neue Konto speichert die
Adresse des user
und den bump
seed, der zur Ableitung der PDA verwendet
wurde.
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 diesem Beispiel umfassen die seeds für die PDA-Ableitung den festen String
data
und die Adresse des user
Kontos, das in der Anweisung bereitgestellt
wird. Das Anchor Framework findet automatisch den kanonischen 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>,
Die init
Einschränkung weist Anchor an, das System Program aufzurufen, um ein
neues Konto mit der PDA als Adresse zu erstellen. Anchor macht dies durch einen
CPI.
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,
Die Testdatei enthält den Typescript-Code zur Ableitung der PDA.
const [PDA] = PublicKey.findProgramAddressSync([Buffer.from("data"), user.publicKey.toBuffer()],program.programId);
Die Transaktion in der Testdatei ruft die initialize
Anweisung auf, um ein
neues On-Chain-Konto mit der PDA als Adresse zu erstellen. In diesem Beispiel
kann Anchor die PDA-Adresse in den Anweisungskonten ableiten, sodass sie nicht
explizit angegeben werden muss.
it("Is initialized!", async () => {const transactionSignature = await program.methods.initialize().accounts({user: user.publicKey}).rpc();console.log("Transaction Signature:", transactionSignature);});
Die Testdatei zeigt auch, wie man das an dieser Adresse erstellte On-Chain-Konto abruft, nachdem die Transaktion gesendet wurde.
it("Fetch Account", async () => {const pdaAccount = await program.account.dataAccount.fetch(PDA);console.log(JSON.stringify(pdaAccount, null, 2));});
Beachte, dass in diesem Beispiel die Transaktion fehlschlägt, wenn du die
initialize
Anweisung mehr als einmal mit derselben user
Adresse als seed
aufrufst. Dies geschieht, weil an der abgeleiteten Adresse bereits ein Konto
existiert.
Is this page helpful?