Indirizzo derivato dal programma

Un indirizzo di account Solana indica la posizione dell'account sulla blockchain. Molti indirizzi di account sono la chiave pubblica di un keypair, nel qual caso la chiave privata corrispondente viene utilizzata per firmare transazioni che coinvolgono l'account.

Un'alternativa utile a un indirizzo di chiave pubblica è un indirizzo derivato dal programma (PDA). I PDA forniscono un metodo semplice per memorizzare, mappare e recuperare lo stato del programma. Un PDA è un indirizzo creato deterministicamente utilizzando un ID programma e una combinazione di input predefiniti opzionali. I PDA sembrano simili agli indirizzi di chiave pubblica, ma non hanno una chiave privata corrispondente.

Il runtime di Solana consente ai programmi di firmare per i PDA senza necessità di una chiave privata. L'utilizzo di un PDA elimina la necessità di tenere traccia dell'indirizzo dell'account. Invece, puoi richiamare gli input specifici utilizzati per la derivazione del PDA. (Per sapere come i programmi utilizzano i PDA per la firma, consulta la sezione Invocazioni tra programmi.)

Contesto

I keypair di Solana sono punti sulla curva Ed25519 (crittografia a curva ellittica). Consistono in una chiave pubblica e una chiave privata. La chiave pubblica diventa l'indirizzo dell'account, e la chiave privata viene utilizzata per generare firme valide per l'account.

Due account con indirizzi sulla curvaDue account con indirizzi sulla curva

Un PDA è intenzionalmente derivato per cadere fuori dalla curva Ed25519. Questo significa che non ha una chiave privata corrispondente valida e non può eseguire operazioni crittografiche. (Come fornire una firma.) Tuttavia, Solana consente ai programmi di firmare per i PDA senza necessità di una chiave privata.

Off Curve AddressOff Curve Address

Puoi pensare ai PDA come a un modo per creare strutture simili a hashmap on-chain utilizzando un insieme predefinito di input. (Per esempio, stringhe, numeri e altri indirizzi di account.)

Program Derived AddressProgram Derived Address

Derivare un PDA

Prima di creare un account con un PDA, devi prima derivare l'indirizzo. Derivare un PDA non crea automaticamente un account on-chain a quell'indirizzo— l'account deve essere esplicitamente creato attraverso il programma utilizzato per derivare il PDA. Puoi pensare a un PDA come a un indirizzo su una mappa: il fatto che un indirizzo esista non significa che ci sia qualcosa costruito lì.

Gli SDK di Solana supportano la creazione di PDA con le funzioni mostrate nella tabella sottostante. Ogni funzione riceve i seguenti input:

  • Program ID: L'indirizzo del programma utilizzato per derivare il PDA. Questo programma può firmare per conto del PDA.
  • Optional seeds: Input predefiniti, come stringhe, numeri o altri indirizzi di account.
SDKFunction
@solana/kit (Typescript)getProgramDerivedAddress
@solana/web3.js (Typescript)findProgramAddressSync
solana_sdk (Rust)find_program_address

La funzione utilizza il Program ID e i seed opzionali, poi itera attraverso i valori di bump per tentare di creare un indirizzo di programma valido. L'iterazione dei valori di bump inizia da 255 e decrementa di 1 fino a quando non viene trovato un PDA valido. Dopo che un PDA valido è stato trovato, la funzione restituisce il PDA e il bump seed.

Il bump seed è un byte extra aggiunto ai seed opzionali per garantire che venga generato un indirizzo off-curve valido.

Derivazione PDADerivazione PDA

Bump canonico

Un bump seed è un byte aggiuntivo aggiunto ai seed opzionali. La funzione di derivazione itera attraverso i valori di bump, partendo da 255 e decrementando di 1, fino a quando un valore produce un indirizzo valido fuori dalla curva. Il primo valore che produce un indirizzo valido fuori dalla curva è chiamato "bump canonico".

I seguenti esempi mostrano la derivazione PDA utilizzando tutti i possibili bump seed (da 255 a 0):

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 primo bump seed genera un errore. Il primo bump seed a derivare un PDA valido è 254. Anche i bump seed 253-251 derivano PDA validi e unici.

Questo significa che dati gli stessi seed opzionali e programId, un bump seed con un valore diverso può comunque derivare un PDA valido.

Includi sempre controlli di sicurezza per assicurarti che un PDA passato al programma sia derivato dal bump canonico. Non farlo potrebbe introdurre vulnerabilità che permettono l'uso di account inaspettati nelle istruzioni del programma. È buona pratica utilizzare solo il bump canonico quando si derivano i PDA.

Esempi

Gli esempi seguenti derivano un PDA utilizzando gli SDK di Solana. Clicca su ▷ Run 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 più seed opzionali.

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.

Creare un account PDA

L'esempio seguente utilizza il framework Anchor per creare un nuovo account con un indirizzo derivato dal programma. Il programma include una singola istruzione initialize per creare il nuovo account, che memorizzerà l'indirizzo utente e il bump seed utilizzato per derivare il PDA.

Program
use anchor_lang::prelude::*;
declare_id!("75GJVCJNhaukaa2vCCqhreY31gaphv7XTScBChmr1ueR");
#[program]
pub mod pda_account {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let account_data = &mut ctx.accounts.pda_account;
// store the address of the `user`
account_data.user = *ctx.accounts.user.key;
// store the canonical bumpd
account_data.bump = ctx.bumps.pda_account;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(
init,
// define the seeds to derive the PDA
seeds = [b"data", user.key().as_ref()],
// use the canonical bump
bump,
payer = user,
space = 8 + DataAccount::INIT_SPACE
)]
pub pda_account: Account<'info, DataAccount>,
pub system_program: Program<'info, System>,
}
#[account]
#[derive(InitSpace)]
pub struct DataAccount {
pub user: Pubkey,
pub bump: u8,
}

Il vincolo init indica ad Anchor di invocare il System Program per creare un nuovo account utilizzando il PDA come indirizzo. I seed utilizzati per creare il PDA sono:

  • L'indirizzo dell'account utente fornito nell'istruzione
  • La stringa fissa: "data"
  • Il bump seed canonico

In questo esempio, al vincolo bump non viene assegnato un valore, quindi Anchor utilizzerà find_program_address per derivare il PDA e trovare il bump.

pda_account
#[account(
init,
seeds = [b"data", user.key().as_ref()],
bump,
payer = user,
space = 8 + DataAccount::INIT_SPACE
)]
pub pda_account: Account<'info, DataAccount>,

Il file di test qui sotto contiene una transazione che invoca l'istruzione initialize per creare un nuovo account con un indirizzo derivato dal programma. Il file contiene il codice per derivare il PDA.

L'esempio mostra anche come recuperare il nuovo account che verrà creato.

Test
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { PdaAccount } from "../target/types/pda_account";
import { PublicKey } from "@solana/web3.js";
describe("pda-account", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.PdaAccount as Program<PdaAccount>;
const user = provider.wallet as anchor.Wallet;
// Derive the PDA address using the seeds specified on the program
const [PDA] = PublicKey.findProgramAddressSync(
[Buffer.from("data"), user.publicKey.toBuffer()],
program.programId
);
it("Is initialized!", async () => {
const transactionSignature = await program.methods
.initialize()
.accounts({
user: user.publicKey
})
.rpc();
console.log("Transaction Signature:", transactionSignature);
});
it("Fetch Account", async () => {
const pdaAccount = await program.account.dataAccount.fetch(PDA);
console.log(JSON.stringify(pdaAccount, null, 2));
});
});

Se invochi nuovamente l'istruzione initialize con lo stesso seed di indirizzo user, la transazione fallirà. Questo accade perché esiste già un account all'indirizzo derivato.

Is this page helpful?

Indice

Modifica Pagina

Gestito da

© 2025 Solana Foundation.
Tutti i diritti riservati.
Rimani Connesso