Riepilogo
I PDA vengono derivati tramite hashing di seed + program ID + bump via SHA-256 fino a quando il risultato non è fuori dalla curva Ed25519. Il bump canonico è il primo valore che produce un indirizzo fuori curva. Massimo 16 seed, massimo 32 byte per seed.
Contesto
I valori
Keypair
di Solana sono punti sulla curva Ed25519. Una
keypair consiste di una chiave pubblica (utilizzata come indirizzo dell'account)
e una chiave segreta (utilizzata per produrre firme). Chiunque possieda la
chiave segreta può firmare transazioni per quell'indirizzo.
Due account con indirizzi sulla curva
Un PDA viene intenzionalmente derivato per cadere fuori dalla curva Ed25519.
Poiché non è un punto valido sulla curva, non esiste alcuna chiave segreta e
nessuna parte esterna può produrre una firma. Solo il programma derivante può
autorizzare operazioni sul PDA attraverso invoke_signed.
Indirizzo fuori curva
Account PDA vs keypair
| Proprietà | Account keypair | Account PDA |
|---|---|---|
| Tipo di indirizzo | Sulla curva Ed25519 | Fuori dalla curva Ed25519 |
| Ha chiave privata | Sì | No |
| Può firmare transazioni | Sì (con chiave privata) | No |
| Può firmare durante CPI | No (a meno che la firma non sia inclusa nella transazione) | Sì (via invoke_signed) |
| Derivazione | Genera keypair Ed25519 | Deterministica da seed + program ID |
| Uso tipico | Wallet utente, program ID | Account dati di proprietà del programma |
Semi opzionali
I semi opzionali sono stringhe di byte definite dall'utente che fungono da input
per la derivazione del PDA. Creano indirizzi univoci e deterministici associati
a un programma. Ad esempio, utilizzare ["user", user_pubkey] come semi deriva
un PDA diverso per ogni utente.
I semi devono rispettare questi vincoli:
- Massimo 16 semi per derivazione (
MAX_SEEDS) - Massimo 32 byte per seme (
MAX_SEED_LEN)
Bump seed
Il bump seed è un singolo byte (0-255) aggiunto ai semi opzionali durante la
derivazione.
find_program_address
effettua una ricerca da 255 a 0, chiamando create_program_address con
ciascun valore finché il risultato non esce dalla curva Ed25519. Il primo valore
che ha successo è il bump canonico.
I programmi dovrebbero sempre utilizzare il bump canonico per garantire una mappatura univoca e deterministica dai semi all'indirizzo.
Utilizza sempre il bump canonico quando derivi i PDA. L'utilizzo di un bump non canonico crea un secondo indirizzo valido per gli stessi semi, il che può portare a vulnerabilità in cui un attaccante sostituisce un account diverso da quello previsto.
Derivazione PDA
Algoritmo di derivazione
La derivazione del PDA è implementata nella funzione
create_program_address
dell'SDK. L'algoritmo funziona come segue:
- Verifica che il numero di semi non superi
MAX_SEEDS(16) e che nessun singolo seme superiMAX_SEED_LEN(32 byte). Se uno dei due controlli fallisce, restituiscePubkeyError::MaxSeedLengthExceeded. - Esegue l'hash SHA-256 di tutti i semi, dell'ID del programma e della stringa
"ProgramDerivedAddress"insieme per produrre un risultato di 32 byte. - Verifica se il risultato è un punto valido sulla curva Ed25519.
- Se il risultato È sulla curva, restituisce
PubkeyError::InvalidSeeds(l'indirizzo avrebbe una chiave privata corrispondente, il che viola la proprietà di sicurezza del PDA). - Se il risultato NON è sulla curva, lo restituisce come PDA.
Costi delle unità di calcolo
La
syscall on-chain
per create_program_address addebita
1.500 CU
per chiamata.
La
syscall try_find_program_address
addebita 1.500 CU all'ingresso (prima del loop), quindi ulteriori 1.500 CU per
ogni tentativo di bump fallito all'interno del loop.
Pattern comuni per i seed
I seed sono specifici dell'applicazione. I pattern comuni includono:
| Pattern | Seed | Caso d'uso |
|---|---|---|
| Singleton globale | ["global"] | Singolo account di configurazione a livello di programma |
| Account per utente | ["user", user_pubkey] | Un account per utente per programma |
| Per utente per entità | ["vault", user_pubkey, mint_pubkey] | Vault di token, per utente per token |
| Contatore / sequenziale | ["order", user_pubkey, &order_id.to_le_bytes()] | Record sequenziali per utente |
I seed vengono concatenati prima dell'hashing, quindi ["ab", "cd"] e
["abcd"] producono lo stesso PDA. Usa seed di lunghezza fissa o un
separatore per evitare collisioni. Ad esempio, ["ab", "-", "cd"] è
inequivocabile.
Esempi: derivare un PDA
Derivare un PDA calcola solo un indirizzo. Non crea un account on-chain a
quell'indirizzo. L'account deve essere creato esplicitamente tramite
un'istruzione separata (tipicamente create_account tramite CPI).
Gli SDK di Solana forniscono funzioni per la derivazione dei PDA. Ogni funzione richiede:
- Program ID: l'indirizzo del programma utilizzato per derivare il PDA. Questo programma può firmare per conto del PDA.
- Seed opzionali: input predefiniti come stringhe, numeri o altri indirizzi di account.
| SDK | Funzione |
|---|---|
@solana/kit (TypeScript) | getProgramDerivedAddress |
@solana/web3.js (TypeScript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
Gli esempi seguenti derivano un PDA utilizzando gli SDK di Solana. Clicca su ▷ Esegui per eseguire il codice.
Derivare un PDA con un seed stringa
L'esempio seguente deriva un PDA utilizzando un ID programma e un seed stringa opzionale.
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}`);
Derivare un PDA con un seed indirizzo
L'esempio seguente deriva un PDA utilizzando un ID programma e un seed indirizzo opzionale.
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}`);
Derivare un PDA con seed multipli
L'esempio seguente deriva un PDA utilizzando un ID programma e seed opzionali multipli.
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}`);
Iterare tutti i bump
Gli esempi seguenti mostrano la derivazione PDA utilizzando tutti i possibili
seed bump (da 255 a 0), illustrando come find_program_address restituisce
il bump canonico:
L'esempio Kit non è incluso perché la funzione
createProgramDerivedAddress
non è esportata.
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 questo esempio, il bump 255 produce un indirizzo on-curve e fallisce. Il primo bump valido è 254, rendendolo il bump canonico.
Is this page helpful?