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 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 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 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.
| SDK | Function |
|---|---|
@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 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);}}
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 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}`);
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 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}`);
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.
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 bumpdaccount_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 PDAseeds = [b"data", user.key().as_ref()],// use the canonical bumpbump,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.
#[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.
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 programconst [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?