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 AddressProgram 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 KurveAdresse 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 KurveAdresse 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-AbleitungPDA-Ableitung

Verwenden Sie die folgenden Funktionen aus den jeweiligen SDKs, um eine PDA abzuleiten.

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

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

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

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

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 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 diesem Beispiel umfassen die seeds für die PDA-Ableitung den festen String data und die Adresse des userKontos, das in der Anweisung bereitgestellt wird. Das Anchor Framework findet automatisch den kanonischen 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>,

Die initEinschrä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.

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

Die Testdatei enthält den Typescript-Code zur Ableitung der PDA.

Derive PDA
const [PDA] = PublicKey.findProgramAddressSync(
[Buffer.from("data"), user.publicKey.toBuffer()],
program.programId
);

Die Transaktion in der Testdatei ruft die initializeAnweisung 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.

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

Fetch Account
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 initializeAnweisung mehr als einmal mit derselben userAdresse als seed aufrufst. Dies geschieht, weil an der abgeleiteten Adresse bereits ein Konto existiert.

Is this page helpful?

Inhaltsverzeichnis

Seite bearbeiten