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.
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.
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 (u8) aggiunto ai seed opzionali durante la
derivazione.
find_program_address
ricerca da 255 fino a 1, chiamando create_program_address con ogni valore
finché il risultato non cade fuori 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.
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 onchain
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
La derivazione di un PDA calcola solo un indirizzo. Non crea un account onchain
a quell'indirizzo. L'account deve essere creato esplicitamente tramite
un'istruzione separata (tipicamente create_account via 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}`);
Iterazione di Tutti i Bump Possibili
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:
Questo è diverso dall'attuale comportamento di ricerca canonico documentato
per sol_try_find_program_address, che itera da 255 fino a 1.
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 sulla curva e fallisce. Il primo bump valido è 254, rendendolo il bump canonico.
Is this page helpful?