Program Derived Address (PDA)

I Program Derived Addresses (PDA) offrono agli sviluppatori su Solana due casi d'uso principali:

  • Indirizzi di account deterministici: i PDA forniscono un meccanismo per creare deterministicamente un indirizzo utilizzando una combinazione di "seed" opzionali (input predefiniti) e uno specifico ID del programma.
  • Abilitazione della firma del programma: il runtime di Solana consente ai programmi di "firmare" per i PDA che sono derivati dall'indirizzo del programma.

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

Il vantaggio di questo approccio è che elimina la necessità di tenere traccia di un indirizzo esatto. Invece, devi semplicemente ricordare gli input specifici utilizzati per la sua derivazione.

Program Derived AddressProgram Derived Address

È importante capire che derivare semplicemente un Program Derived Address (PDA) non crea automaticamente un account on-chain a quell'indirizzo. Gli account con un PDA come indirizzo on-chain devono essere creati esplicitamente attraverso il programma utilizzato per derivare l'indirizzo. Puoi pensare alla derivazione di un PDA come alla ricerca di un indirizzo su una mappa. Avere solo un indirizzo non significa che ci sia qualcosa costruito in quella posizione.

Questa sezione copre i dettagli della derivazione dei PDA. La sezione sulle Cross Program Invocations (CPI) spiega come i programmi utilizzano i PDA per la firma.

Punti chiave

  • I PDA sono indirizzi derivati deterministicamente utilizzando una combinazione di seed predefiniti, un bump seed e l'ID di un programma.
  • I PDA sono indirizzi che cadono fuori dalla curva Ed25519 e non hanno una chiave privata corrispondente.
  • I programmi Solana possono firmare per conto dei PDA derivati dal proprio ID programma.
  • Derivare un PDA non crea automaticamente un account on-chain.
  • Un account che utilizza un PDA come indirizzo deve essere creato tramite un'istruzione all'interno di un programma Solana.

Cos'è un PDA

I PDA sono indirizzi che derivano in modo deterministico e sembrano chiavi pubbliche, ma non hanno chiavi private. Questo significa che non è possibile generare una firma valida per l'indirizzo. Tuttavia, il runtime di Solana consente ai programmi di "firmare" per i PDA senza bisogno di una chiave privata.

Per contesto, le Keypair di Solana sono punti sulla curva Ed25519 (crittografia a curva ellittica) con una chiave pubblica e la corrispondente chiave privata. Le chiavi pubbliche sono utilizzate come indirizzi (identificatori unici) per gli account on-chain.

Indirizzo sulla curvaIndirizzo sulla curva

Un PDA è un punto che viene intenzionalmente derivato per cadere fuori dalla curva Ed25519 utilizzando un insieme predefinito di input. Un punto che non si trova sulla curva Ed25519 non ha una chiave privata corrispondente valida e non può eseguire operazioni crittografiche (firma).

Un PDA può servire come indirizzo (identificatore unico) per un account on-chain, fornendo un metodo per memorizzare, mappare e recuperare facilmente lo stato del programma.

Indirizzo fuori dalla curvaIndirizzo fuori dalla curva

Come derivare un PDA

La derivazione di un PDA richiede tre input:

  • seed opzionali: Input predefiniti (ad es. stringhe, numeri, altri indirizzi di account) per la derivazione del PDA.
  • bump seed: Un byte aggiuntivo aggiunto ai seed opzionali per garantire che venga generato un PDA valido (fuori dalla curva). Il bump seed inizia da 255 e diminuisce di 1 fino a quando non viene trovato un PDA valido.
  • Program ID: L'indirizzo del programma da cui deriva il PDA. Questo programma può firmare per conto del PDA.

Derivazione PDADerivazione PDA

Usa le seguenti funzioni dai rispettivi SDK per derivare un PDA.

SDKFunzione
@solana/kit (Typescript)getProgramDerivedAddress
@solana/web3.js (Typescript)findProgramAddressSync
solana_sdk (Rust)find_program_address

Per derivare un PDA, fornisci i seguenti input alla funzione SDK:

  • I seed opzionali predefiniti convertiti in byte
  • L'ID del programma (indirizzo) utilizzato per la derivazione

Una volta trovato un PDA valido, la funzione restituisce sia l'indirizzo (PDA) che il bump seed utilizzato per la derivazione.

Esempi

I seguenti esempi mostrano come derivare un PDA utilizzando i rispettivi SDK.

Clicca il pulsante "Run" per eseguire il codice.

Derivare un PDA con 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}`);
Click to execute the code.

Derivare un PDA con 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}`);
Click to execute the code.

Derivare un PDA con molteplici 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}`);
Click to execute the code.

Canonical Bump

La derivazione PDA richiede 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 di bump che produce un indirizzo valido fuori dalla curva è il "canonical bump."

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

Il bump seed 255 genera un errore e il primo bump seed a derivare un PDA valido è 254.

Nota che i bump seed 253-251 derivano tutti PDA validi con indirizzi diversi. Questo significa che dati gli stessi seed opzionali e programId, un bump seed con un valore diverso può comunque derivare un PDA valido.

Quando si sviluppano programmi Solana, includi sempre controlli di sicurezza per garantire che un PDA passato al programma sia derivato dal canonical bump. Non includere questi controlli potrebbe introdurre vulnerabilità che consentono l'utilizzo di account imprevisti nelle istruzioni del programma. È buona pratica utilizzare solo il canonical bump quando si derivano i PDA.

Creare account PDA

Il programma di esempio seguente mostra come creare un account utilizzando un PDA come indirizzo del nuovo account. Il programma di esempio utilizza il framework Anchor.

Il programma include una singola istruzione initialize per creare un nuovo account utilizzando un PDA come indirizzo dell'account. Il nuovo account memorizza l'indirizzo del user e il seed bump 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 bump
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,
}

In questo esempio, i seed per la derivazione PDA includono la stringa fissa data e l'indirizzo dell'account user fornito nell'istruzione. Il framework Anchor trovà automaticamente il seed canonico 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 vincolo init indica ad Anchor di invocare il System Program per creare un nuovo account utilizzando il PDA come indirizzo. Anchor lo fa attraverso una CPI.

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 contiene il codice Typescript per derivare il PDA.

Derive PDA
const [PDA] = PublicKey.findProgramAddressSync(
[Buffer.from("data"), user.publicKey.toBuffer()],
program.programId
);

La transazione nel file di test invoca l'istruzione initialize per creare un nuovo account on-chain utilizzando il PDA come indirizzo. In questo esempio, Anchor può dedurre l'indirizzo PDA negli account dell'istruzione, quindi non è necessario fornirlo esplicitamente.

Invoke Initialize Instruction
it("Is initialized!", async () => {
const transactionSignature = await program.methods
.initialize()
.accounts({
user: user.publicKey
})
.rpc();
console.log("Transaction Signature:", transactionSignature);
});

Il file di test mostra anche come recuperare l'account on-chain creato a quell'indirizzo una volta inviata la transazione.

Fetch Account
it("Fetch Account", async () => {
const pdaAccount = await program.account.dataAccount.fetch(PDA);
console.log(JSON.stringify(pdaAccount, null, 2));
});

Nota che in questo esempio, se invochi l'istruzione initialize più di una volta utilizzando lo stesso indirizzo user come seed, la transazione fallirà. Questo accade perché esiste già un account all'indirizzo derivato.

Is this page helpful?

Indice

Modifica Pagina