Derivazione PDA

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 curvaDue 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 curvaIndirizzo fuori curva

Account PDA vs keypair

ProprietàAccount keypairAccount PDA
Tipo di indirizzoSulla curva Ed25519Fuori dalla curva Ed25519
Ha chiave privataNo
Può firmare transazioniSì (con chiave privata)No
Può firmare durante CPINo (a meno che la firma non sia inclusa nella transazione)Sì (via invoke_signed)
DerivazioneGenera keypair Ed25519Deterministica da seed + program ID
Uso tipicoWallet utente, program IDAccount 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 PDADerivazione PDA

Algoritmo di derivazione

La derivazione del PDA è implementata nella funzione create_program_address dell'SDK. L'algoritmo funziona come segue:

  1. Verifica che il numero di semi non superi MAX_SEEDS (16) e che nessun singolo seme superi MAX_SEED_LEN (32 byte). Se uno dei due controlli fallisce, restituisce PubkeyError::MaxSeedLengthExceeded.
  2. 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.
  3. Verifica se il risultato è un punto valido sulla curva Ed25519.
  4. Se il risultato È sulla curva, restituisce PubkeyError::InvalidSeeds (l'indirizzo avrebbe una chiave privata corrispondente, il che viola la proprietà di sicurezza del PDA).
  5. 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:

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

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

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

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

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?

Indice dei contenuti

Modifica pagina

Gestito da

© 2026 Solana Foundation.
Tutti i diritti riservati.
Resta connesso