Zusammenfassung
PDAs werden abgeleitet, indem Seeds + Programm-ID + Bump über SHA-256 gehasht werden, bis das Ergebnis außerhalb der Ed25519-Kurve liegt. Der kanonische Bump ist der erste Wert, der eine Off-Curve-Adresse erzeugt. Max. 16 Seeds, max. 32 Bytes pro Seed.
Hintergrund
Solana-
Keypair-Werte
sind Punkte auf der Ed25519-Kurve. Ein Keypair
besteht aus einem öffentlichen Schlüssel (verwendet als Kontenadresse) und einem
geheimen Schlüssel (verwendet zur Erzeugung von Signaturen). Jeder, der den
geheimen Schlüssel besitzt, kann Transaktionen für diese Adresse signieren.
Zwei Konten mit On-Curve-Adressen
Eine PDA wird absichtlich so abgeleitet, dass sie außerhalb der Ed25519-Kurve
liegt. Da sie kein gültiger Kurvenpunkt ist, existiert kein geheimer Schlüssel,
und keine externe Partei kann eine Signatur erzeugen. Nur das ableitende
Programm kann Operationen auf der PDA durch invoke_signed autorisieren.
Off-Curve-Adresse
PDA- vs. Keypair-Konten
| Eigenschaft | Keypair-Konto | PDA-Konto |
|---|---|---|
| Adresstyp | Auf Ed25519-Kurve | Außerhalb Ed25519-Kurve |
| Hat privaten Schlüssel | Ja | Nein |
| Kann Transaktionen signieren | Ja (mit privatem Schlüssel) | Nein |
| Kann während CPI signieren | Nein (außer Signatur in Transaktion enthalten) | Ja (über invoke_signed) |
| Ableitung | Ed25519-Keypair generieren | Deterministisch aus Seeds + Programm-ID |
| Typische Verwendung | Benutzer-Wallets, Programm-ID | Programmeigene Datenkonten |
Optionale Seeds
Die optionalen Seeds sind benutzerdefinierte Byte-Strings, die als Eingaben für
die PDA-Ableitung dienen. Sie erstellen eindeutige, deterministische Adressen,
die auf ein Programm beschränkt sind. Beispielsweise leitet die Verwendung von
["user", user_pubkey] als Seeds eine andere PDA für jeden Benutzer ab.
Seeds müssen folgende Einschränkungen erfüllen:
- Maximal 16 Seeds pro Ableitung (
MAX_SEEDS) - Maximal 32 Bytes pro Seed (
MAX_SEED_LEN)
Bump Seed
Der Bump Seed ist ein einzelnes Byte (0-255), das während der Ableitung an die
optionalen Seeds angehängt wird.
find_program_address
sucht von 255 abwärts bis 0 und ruft create_program_address mit jedem Wert
auf, bis das Ergebnis außerhalb der Ed25519-Kurve liegt. Der erste Wert, der
erfolgreich ist, ist der kanonische Bump.
Programme sollten immer den kanonischen Bump verwenden, um eine eindeutige, deterministische Zuordnung von Seeds zu Adressen sicherzustellen.
Verwenden Sie beim Ableiten von PDAs immer den kanonischen Bump. Die Verwendung eines nicht-kanonischen Bumps erstellt eine zweite gültige Adresse für dieselben Seeds, was zu Sicherheitslücken führen kann, bei denen ein Angreifer ein anderes Konto als erwartet einsetzt.
PDA-Ableitung
Ableitungsalgorithmus
Die PDA-Ableitung ist in der SDK-Funktion
create_program_address
implementiert. Der Algorithmus funktioniert wie folgt:
- Validieren Sie, dass die Anzahl der Seeds
MAX_SEEDS(16) nicht überschreitet und kein einzelner SeedMAX_SEED_LEN(32 Bytes) überschreitet. Wenn eine der Prüfungen fehlschlägt, geben SiePubkeyError::MaxSeedLengthExceededzurück. - Hashen Sie alle Seeds, die Programm-ID und den String
"ProgramDerivedAddress"mit SHA-256 zusammen, um ein 32-Byte-Ergebnis zu erzeugen. - Prüfen Sie, ob das Ergebnis ein gültiger Punkt auf der Ed25519-Kurve ist.
- Wenn das Ergebnis AUF der Kurve liegt, geben Sie
PubkeyError::InvalidSeedszurück (die Adresse hätte einen entsprechenden privaten Schlüssel, was die PDA-Sicherheitseigenschaft verletzt). - Wenn das Ergebnis NICHT auf der Kurve liegt, geben Sie es als PDA zurück.
Compute Unit-Kosten
Der
On-Chain-Syscall
für create_program_address berechnet
1.500 CUs
pro Aufruf.
Der
try_find_program_address-Syscall
berechnet 1.500 CUs beim Eintritt (vor der Schleife) und dann zusätzliche 1.500
CUs für jeden fehlgeschlagenen Bump-Versuch innerhalb der Schleife.
Gängige Seed-Muster
Seeds sind anwendungsspezifisch. Gängige Muster umfassen:
| Muster | Seeds | Anwendungsfall |
|---|---|---|
| Globales Singleton | ["global"] | Einzelnes programmweites Konfigurations-Konto |
| Pro-Benutzer-Konto | ["user", user_pubkey] | Ein Konto pro Benutzer pro Programm |
| Pro-Benutzer-pro-Entität | ["vault", user_pubkey, mint_pubkey] | Token-Vaults, pro Benutzer pro Token |
| Zähler / sequenziell | ["order", user_pubkey, &order_id.to_le_bytes()] | Sequenzielle Datensätze pro Benutzer |
Seeds werden vor dem Hashing verkettet, daher erzeugen ["ab", "cd"] und
["abcd"] dieselbe PDA. Verwenden Sie Seeds mit fester Länge oder ein
Trennzeichen, um Kollisionen zu vermeiden. Zum Beispiel ist ["ab", "-", "cd"] eindeutig.
Beispiele: Eine PDA ableiten
Das Ableiten einer PDA berechnet nur eine Adresse. Es erstellt kein
On-Chain-Konto an dieser Adresse. Das Konto muss explizit durch eine separate
Anweisung erstellt werden (typischerweise create_account via CPI).
Die Solana-SDKs bieten Funktionen für die PDA-Ableitung. Jede Funktion benötigt:
- Programm-ID: Die Adresse des Programms, das zur Ableitung der PDA verwendet wird. Dieses Programm kann im Namen der PDA signieren.
- Optionale 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 folgenden Beispiele leiten eine PDA mithilfe der Solana-SDKs ab. Klicken Sie auf ▷ Ausführen, um den Code auszuführen.
Eine PDA mit einem String-Seed ableiten
Das folgende Beispiel leitet eine PDA unter Verwendung einer Programm-ID und eines optionalen String-Seeds 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}`);
Eine PDA mit einem Adressen-Seed ableiten
Das folgende Beispiel leitet eine PDA unter Verwendung einer Programm-ID und eines optionalen Adressen-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}`);
Eine PDA mit mehreren Seeds ableiten
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}`);
Iteration über alle Bumps
Die folgenden Beispiele zeigen die PDA-Ableitung unter Verwendung aller
möglichen Bump-Seeds (255 bis 0) und veranschaulichen, wie
find_program_address den kanonischen Bump zurückgibt:
Das Kit-Beispiel ist nicht enthalten, da die Funktion
createProgramDerivedAddress
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 erzeugt Bump 255 eine On-Curve-Adresse und schlägt fehl. Der erste gültige Bump ist 254, was ihn zum kanonischen Bump macht.
Is this page helpful?