Programm-abgeleitete Adresse
Eine Solana Kontenadresse verweist auf den Speicherort des Kontos in der Blockchain. Viele Kontenadressen sind der öffentliche Schlüssel eines keypair, wobei der entsprechende private Schlüssel verwendet wird, um Transaktionen zu signieren, die das Konto betreffen.
Eine nützliche Alternative zu einer öffentlichen Schlüsseladresse ist eine programm-abgeleitete Adresse (PDA). PDAs bieten eine einfache Methode zum Speichern, Zuordnen und Abrufen des Programmzustands. Eine PDA ist eine Adresse, die deterministisch unter Verwendung einer Programm-ID und einer Kombination optionaler vordefinierter Eingaben erstellt wird. PDAs sehen ähnlich aus wie öffentliche Schlüsseladressen, haben aber keinen entsprechenden privaten Schlüssel.
Die Solana-Laufzeitumgebung ermöglicht es Programmen, für PDAs zu signieren, ohne einen privaten Schlüssel zu benötigen. Die Verwendung einer PDA beseitigt die Notwendigkeit, die Adresse des Kontos zu verfolgen. Stattdessen können Sie die spezifischen Eingaben abrufen, die für die Ableitung der PDA verwendet wurden. (Um zu erfahren, wie Programme PDAs zum Signieren verwenden, siehe den Abschnitt Cross Program Invocations.)
Hintergrund
Solana keypairs sind Punkte auf der Ed25519-Kurve (elliptische Kurven- Kryptographie). Sie bestehen aus einem öffentlichen Schlüssel und einem privaten Schlüssel. Der öffentliche Schlüssel wird zur Kontenadresse, und der private Schlüssel wird verwendet, um gültige Signaturen für das Konto zu generieren.
Zwei Konten mit Adressen auf der Kurve
Eine PDA wird absichtlich so abgeleitet, dass sie außerhalb der Ed25519-Kurve liegt. Das bedeutet, sie hat keinen gültigen entsprechenden privaten Schlüssel und kann keine kryptografischen Operationen durchführen. (Wie zum Beispiel das Bereitstellen einer Signatur.) Solana ermöglicht es Programmen jedoch, für PDAs zu signieren, ohne einen privaten Schlüssel zu benötigen.
Off Curve Address
Man kann sich PDAs als eine Möglichkeit vorstellen, hashmap-ähnliche Strukturen on-chain mit einem vordefinierten Satz von Eingaben zu erstellen. (Zum Beispiel Strings, Zahlen und andere Konten-Adressen.)
Program Derived Address
Eine PDA ableiten
Bevor ein Konto mit einer PDA erstellt wird, muss zuerst die Adresse abgeleitet werden. Das Ableiten einer PDA erstellt nicht automatisch ein On-Chain-Konto an dieser Adresse – das Konto muss explizit durch das Programm erstellt werden, das zur Ableitung der PDA verwendet wurde. Man kann sich eine PDA wie eine Adresse auf einer Karte vorstellen: nur weil eine Adresse existiert, bedeutet das nicht, dass dort etwas gebaut wurde.
Die Solana SDKs unterstützen die PDA-Erstellung mit den in der folgenden Tabelle gezeigten Funktionen. Jede Funktion erhält folgende Eingaben:
- Program ID: Die Adresse des Programms, das zur Ableitung der PDA verwendet wird. Dieses Programm kann im Namen der PDA signieren.
- Optional seeds: Vordefinierte Eingaben wie Strings, Zahlen oder andere Konten-Adressen.
| SDK | Funktion |
|---|---|
@solana/kit (Typescript) | getProgramDerivedAddress |
@solana/web3.js (Typescript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
Die Funktion verwendet die Program ID und optionale seeds und iteriert dann durch bump-Werte, um zu versuchen, eine gültige Programm-Adresse zu erstellen. Die Iteration der bump-Werte beginnt bei 255 und wird um 1 verringert, bis eine gültige PDA gefunden wird. Nachdem eine gültige PDA gefunden wurde, gibt die Funktion die PDA und den bump seed zurück.
Der bump seed ist ein zusätzliches Byte, das an die optionalen seeds angehängt wird, um sicherzustellen, dass eine gültige Off-Curve-Adresse generiert wird.
PDA-Ableitung
Kanonischer Bump
Ein bump seed ist 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 Wert, der eine gültige Off-Curve-Adresse erzeugt, wird als "kanonischer Bump" bezeichnet.
Die folgenden Beispiele zeigen die PDA-Ableitung mit allen möglichen bump seeds (255 bis 0):
Kit-Beispiel ist 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
In diesem Beispiel wirft der erste bump seed einen Fehler. Der erste bump seed, der eine gültige PDA ableitet, ist 254. Die bump seeds 253-251 leiten ebenfalls eindeutige, gültige PDAs ab.
Das bedeutet, dass bei gleichen optionalen seeds und programId, ein bump seed
mit einem anderen Wert immer noch eine gültige PDA ableiten kann.
Fügen Sie immer Sicherheitsprüfungen ein, um sicherzustellen, dass eine an das Programm übergebene PDA vom kanonischen Bump abgeleitet wird. Wenn dies nicht erfolgt, können Schwachstellen entstehen, die es ermöglichen, unerwartete Konten in den Anweisungen des Programms zu verwenden. Es ist bewährte Praxis, nur den kanonischen Bump bei der Ableitung von PDAs zu verwenden.
Beispiele
Die folgenden Beispiele leiten eine PDA mit den Solana SDKs ab. Klicken Sie auf ▷ Run, um den Code auszuführen.
Ableiten einer PDA mit einem String-Seed
Das folgende Beispiel leitet eine PDA mit einer Programm-ID und einem optionalen String-Seed ab.
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}`);
Ableiten einer PDA mit einer Adresse als seed
Das folgende Beispiel leitet eine PDA unter Verwendung einer Programm-ID und eines optionalen Adress-seeds ab.
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}`);
Ableiten einer PDA mit mehreren seeds
Das folgende Beispiel leitet eine PDA unter Verwendung einer Programm-ID und mehrerer optionaler seeds ab.
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}`);
Erstellen eines PDA-Kontos
Das folgende Beispiel verwendet das
Anchor Framework, um ein neues Konto mit
einer programmabgeleiteten Adresse zu erstellen. Das Programm enthält eine
einzelne initialize Anweisung, um das neue Konto zu
erstellen, das die Benutzeradresse und den
bump seed speichert, die zur Ableitung der PDA verwendet wurden.
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,}
Die init Einschränkung weist Anchor an,
das System Program aufzurufen, um ein
neues Konto mit der PDA als Adresse zu erstellen. Die seeds,
die zur Erstellung der PDA verwendet werden, sind:
- Die Adresse des in der Anweisung bereitgestellten Benutzerkontos
- Der feste String: "data"
- Der kanonische bump seed
In diesem Beispiel wird der bump-Einschränkung kein Wert zugewiesen, daher
verwendet Anchor find_program_address, um die PDA abzuleiten und den bump zu
finden.
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,
Die unten stehende Testdatei enthält eine Transaktion, die die
initialize Anweisung aufruft, um ein neues Konto mit
einer programmabgeleiteten Adresse zu erstellen. Die Datei enthält Code zum
Ableiten der PDA.
Das Beispiel zeigt auch, wie man das neue Konto, das erstellt wird, abrufen kann.
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));});});
Wenn Sie die initialize Anweisung erneut mit demselben user Adress- seed
aufrufen, wird die Transaktion fehlschlagen. Dies geschieht, weil bereits ein
Konto an der abgeleiteten Adresse existiert.
Is this page helpful?