Wyprowadzanie PDA

Podsumowanie

PDA są wyprowadzane przez haszowanie seedów + program ID + bump za pomocą SHA-256, aż wynik znajdzie się poza krzywą Ed25519. Kanoniczny bump to pierwsza wartość, która daje adres poza krzywą. Maksymalnie 16 seedów, maksymalnie 32 bajty na seed.

Tło

Wartości Solana Keypair są punktami na krzywej Ed25519. Keypair składa się z klucza publicznego (używanego jako adres konta) oraz klucza prywatnego (służącego do generowania podpisów). Każdy, kto posiada klucz prywatny, może podpisywać transakcje dla tego adresu.

Dwa konta z adresami na krzywejDwa konta z adresami na krzywej

PDA jest celowo wyprowadzane tak, aby znajdowało się poza krzywą Ed25519. Ponieważ nie jest to prawidłowy punkt na krzywej, nie istnieje żaden klucz prywatny i żadna zewnętrzna strona nie może wygenerować podpisu. Tylko program wyprowadzający może autoryzować operacje na PDA przez invoke_signed.

Adres poza krzywąAdres poza krzywą

PDA kontra konta keypair

WłaściwośćKonto keypairKonto PDA
Typ adresuNa krzywej Ed25519Poza krzywą Ed25519
Ma klucz prywatnyTakNie
Może podpisywać transakcjeTak (za pomocą klucza prywatnego)Nie
Może podpisywać podczas CPINie (chyba że podpis dołączony do transakcji)Tak (przez invoke_signed)
WyprowadzanieGenerowanie keypair Ed25519Deterministycznie z seedów + program ID
Typowe zastosowaniePortfele użytkowników, Program IDKonta danych należące do programu

Opcjonalne seedy

Opcjonalne seedy to zdefiniowane przez użytkownika ciągi bajtów, które służą jako dane wejściowe do wyznaczania PDA. Pozwalają one tworzyć unikalne, deterministyczne adresy przypisane do konkretnego programu. Na przykład użycie ["user", user_pubkey] jako seedów powoduje wyznaczenie innego PDA dla każdego użytkownika.

Seed musi spełniać następujące ograniczenia:

  • Maksymalnie 16 seedów na wyznaczenie (MAX_SEEDS)
  • Maksymalnie 32 bajty na jeden seed (MAX_SEED_LEN)

Bump seed

Bump seed to pojedynczy bajt (0-255) dołączany do opcjonalnych seedów podczas wyznaczania. find_program_address przeszukuje wartości od 255 do 0, wywołując create_program_address z każdą z nich, dopóki wynik nie wypadnie poza krzywą Ed25519. Pierwsza wartość, która się powiedzie, to kanoniczny bump.

Programy powinny zawsze używać kanonicznego bumpa, aby zapewnić unikalne i deterministyczne mapowanie seedów na adres.

Zawsze używaj kanonicznego bumpa podczas wyznaczania PDA. Użycie niekanonicznego bumpa tworzy drugi poprawny adres dla tych samych seedów, co może prowadzić do podatności, w których atakujący podmieni konto na inne niż oczekiwane.

Wyznaczanie PDAWyznaczanie PDA

Algorytm wyznaczania

Wyznaczanie PDA jest zaimplementowane w funkcji SDK create_program_address. Algorytm działa następująco:

  1. Sprawdź, czy liczba seedów nie przekracza MAX_SEEDS (16) i czy żaden pojedynczy seed nie przekracza MAX_SEED_LEN (32 bajtów). Jeśli którykolwiek z tych warunków nie jest spełniony, zwróć PubkeyError::MaxSeedLengthExceeded.
  2. Wykonaj haszowanie SHA-256 wszystkich seedów, identyfikatora programu oraz ciągu "ProgramDerivedAddress", aby uzyskać 32-bajtowy wynik.
  3. Sprawdź, czy wynik jest poprawnym punktem na krzywej Ed25519.
  4. Jeśli wynik JEST na krzywej, zwróć PubkeyError::InvalidSeeds (adres miałby odpowiadający klucz prywatny, co narusza bezpieczeństwo PDA).
  5. Jeśli wynik NIE jest na krzywej, zwróć go jako PDA.

Koszty jednostek obliczeniowych

Wywołanie systemowe on-chain dla create_program_address pobiera 1 500 CU za każde wywołanie.

try_find_program_address syscall pobiera 1 500 CU przy wejściu (przed pętlą), a następnie dodatkowe 1 500 CU za każdą nieudaną próbę bumpowania w pętli.

Typowe wzorce seedów

Seedy są specyficzne dla aplikacji. Typowe wzorce to:

WzorzecSeedyPrzypadek użycia
Globalny singleton["global"]Globalne konto konfiguracyjne programu
Konto użytkownika["user", user_pubkey]Jedno konto na użytkownika na program
Użytkownik-podmiot["vault", user_pubkey, mint_pubkey]Skarbce tokenów, użytkownik-token
Licznik / sekwencyjne["order", user_pubkey, &order_id.to_le_bytes()]Sekwencyjne rekordy na użytkownika

Seedy są łączone przed haszowaniem, więc ["ab", "cd"] i ["abcd"] generują to samo PDA. Aby uniknąć kolizji, używaj seedów o stałej długości lub separatora. Na przykład, ["ab", "-", "cd"] jest jednoznaczny.

Przykłady: wyprowadzenie PDA

Wyprowadzenie PDA oblicza tylko adres. Nie tworzy konta on-chain pod tym adresem. Konto musi zostać utworzone jawnie przez osobną instrukcję (zwykle create_account przez CPI).

SDK Solana udostępniają funkcje do wyprowadzania PDA. Każda funkcja przyjmuje:

  • Program ID: Adres programu używanego do wyprowadzenia PDA. Ten program może podpisywać w imieniu PDA.
  • Opcjonalne seedy: Predefiniowane dane wejściowe, takie jak stringi, liczby lub inne adresy kont.
SDKFunkcja
@solana/kit (TypeScript)getProgramDerivedAddress
@solana/web3.js (TypeScript)findProgramAddressSync
solana_sdk (Rust)find_program_address

Poniższe przykłady pokazują, jak wyprowadzić PDA za pomocą SDK Solana. Kliknij ▷ Uruchom, aby wykonać kod.

Wyprowadzenie PDA z seed w postaci ciągu znaków

Poniższy przykład pokazuje, jak wyprowadzić PDA przy użyciu program ID oraz opcjonalnego seed w postaci ciągu znaków.

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.

Wyprowadzenie PDA z seed w postaci adresu

Poniższy przykład pokazuje, jak wyprowadzić PDA przy użyciu program ID oraz opcjonalnego seed w postaci adresu.

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.

Wyprowadzenie PDA z wieloma seedami

Poniższy przykład pokazuje, jak wyprowadzić PDA przy użyciu program ID oraz wielu opcjonalnych seed.

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.

Iteracja po wszystkich bumpach

Poniższe przykłady pokazują wyprowadzanie PDA przy użyciu wszystkich możliwych bump seedów (od 255 do 0), ilustrując jak find_program_address zwraca kanoniczny bump:

Przykład dla Kit nie jest dołączony, ponieważ funkcja createProgramDerivedAddress nie jest eksportowana.

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

W tym przykładzie bump 255 generuje adres na krzywej i kończy się niepowodzeniem. Pierwszy poprawny bump to 254, co czyni go kanonicznym bumpem.

Is this page helpful?

Zarządzane przez

© 2026 Solana Foundation.
Wszelkie prawa zastrzeżone.
Bądź na bieżąco