Adresse dérivée de programme
Une adresse de compte Solana pointe vers l'emplacement du compte sur la blockchain. De nombreuses adresses de compte sont la clé publique d'un keypair, auquel cas la clé privée correspondante est utilisée pour signer les transactions impliquant le compte.
Une alternative utile à une adresse de clé publique est une adresse dérivée de programme (PDA). Les PDAs fournissent une méthode simple pour stocker, mapper et récupérer l'état du programme. Une PDA est une adresse créée de manière déterministe en utilisant un ID de programme et une combinaison d'entrées prédéfinies optionnelles. Les PDAs ressemblent aux adresses de clé publique, mais n'ont pas de clé privée correspondante.
L'environnement d'exécution de Solana permet aux programmes de signer pour les PDAs sans avoir besoin d'une clé privée. L'utilisation d'une PDA élimine la nécessité de suivre l'adresse du compte. Au lieu de cela, vous pouvez rappeler les entrées spécifiques utilisées pour la dérivation de la PDA. (Pour apprendre comment les programmes utilisent les PDAs pour la signature, consultez la section Invocations entre programmes.)
Contexte
Les keypairs Solana sont des points sur la courbe Ed25519 (cryptographie à courbe elliptique). Ils se composent d'une clé publique et d'une clé privée. La clé publique devient l'adresse du compte, et la clé privée est utilisée pour générer une signature valide pour le compte.
Deux comptes avec des adresses sur la courbe
Une PDA est intentionnellement dérivée pour se situer en dehors de la courbe Ed25519. Cela signifie qu'elle n'a pas de clé privée correspondante valide et ne peut pas effectuer d'opérations cryptographiques. (Comme fournir une signature.) Cependant, Solana permet aux programmes de signer pour les PDAs sans avoir besoin d'une clé privée.
Adresse hors courbe
Vous pouvez considérer les PDAs comme un moyen de créer des structures similaires à des tables de hachage sur la blockchain en utilisant un ensemble prédéfini d'entrées. (Par exemple, des chaînes de caractères, des nombres et d'autres adresses de compte.)
Program Derived Address
Dériver une PDA
Avant de créer un compte avec une PDA, vous devez d'abord dériver l'adresse. Dériver une PDA ne crée pas automatiquement un compte sur la blockchain à cette adresse — le compte doit être explicitement créé via le programme utilisé pour dériver la PDA. Vous pouvez considérer une PDA comme une adresse sur une carte : ce n'est pas parce qu'une adresse existe qu'il y a nécessairement quelque chose de construit à cet endroit.
Les SDK Solana prennent en charge la création de PDA avec les fonctions présentées dans le tableau ci-dessous. Chaque fonction reçoit les entrées suivantes :
- Program ID : L'adresse du programme utilisé pour dériver la PDA. Ce programme peut signer au nom de la PDA.
- Seeds optionnels : Entrées prédéfinies, telles que des chaînes de caractères, des nombres ou d'autres adresses de compte.
| SDK | Fonction |
|---|---|
@solana/kit (Typescript) | getProgramDerivedAddress |
@solana/web3.js (Typescript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
La fonction utilise le Program ID et les seeds optionnels, puis itère à travers les valeurs de bump pour tenter de créer une adresse de programme valide. L'itération des valeurs de bump commence à 255 et décrémente de 1 jusqu'à ce qu'une PDA valide soit trouvée. Une fois qu'une PDA valide est trouvée, la fonction renvoie la PDA et le bump seed.
Le bump seed est un octet supplémentaire ajouté aux seeds optionnels pour garantir qu'une adresse hors courbe valide soit générée.
Dérivation PDA
Bump canonique
Un bump seed est un octet supplémentaire ajouté aux seeds optionnels. La fonction de dérivation itère à travers les valeurs de bump, en commençant à 255 et en décrémentant de 1, jusqu'à ce qu'une valeur produise une adresse valide hors courbe. La première valeur qui produit une adresse valide hors courbe est appelée le "bump canonique".
Les exemples suivants montrent la dérivation PDA en utilisant tous les bump seeds possibles (de 255 à 0) :
L'exemple Kit n'est pas inclus car la fonction createProgramDerivedAddress n'est pas exportée.
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
Dans cet exemple, le premier bump seed génère une erreur. Le premier bump seed à dériver un PDA valide est 254. Les bump seeds 253-251 dérivent également des PDA valides et uniques.
Cela signifie qu'avec les mêmes seeds optionnels et programId, un bump seed
avec une valeur différente peut toujours dériver un PDA valide.
Incluez toujours des vérifications de sécurité pour vous assurer qu'un PDA transmis au programme est dérivé du bump canonique. Ne pas le faire peut introduire des vulnérabilités permettant l'utilisation de comptes inattendus dans les instructions du programme. Il est recommandé d'utiliser uniquement le bump canonique lors de la dérivation des PDA.
Exemples
Les exemples ci-dessous dérivent un PDA en utilisant les SDK Solana. Cliquez sur ▷ Run pour exécuter le code.
Dériver un PDA avec un seed de type chaîne
L'exemple ci-dessous dérive un PDA en utilisant un ID de programme et un seed optionnel de type chaîne.
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}`);
Dériver une PDA avec un seed d'adresse
L'exemple ci-dessous dérive une PDA en utilisant un ID de programme et un seed d'adresse optionnel.
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}`);
Dériver une PDA avec plusieurs seeds
L'exemple ci-dessous dérive une PDA en utilisant un ID de programme et plusieurs seeds optionnels.
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}`);
Créer un compte PDA
L'exemple ci-dessous utilise le
framework Anchor pour créer un nouveau
compte avec une adresse dérivée du programme. Le programme inclut une seule
instruction initialize pour créer le nouveau compte, qui
stockera l'adresse de l'utilisateur et le
bump seed utilisé pour dériver la 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,}
La contrainte init indique à Anchor
d'invoquer le System Program pour
créer un nouveau compte en utilisant la PDA comme adresse. Les
seeds utilisés pour créer la PDA sont :
- L'adresse du compte utilisateur fournie dans l'instruction
- La chaîne fixe : "data"
- Le bump seed canonique
Dans cet exemple, la contrainte bump n'est pas assignée à une valeur, donc
Anchor utilisera find_program_address pour dériver la PDA et trouver le bump.
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,
Le fichier de test ci-dessous contient une transaction qui invoque l'instruction
initialize pour créer un nouveau compte avec une adresse
dérivée du programme. Le fichier contient du code pour
dériver la PDA.
L'exemple montre également comment récupérer le nouveau compte qui sera créé.
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));});});
Si vous invoquez à nouveau l'instruction initialize avec le même seed
d'adresse user, la transaction échouera. Cela se produit parce qu'un compte
existe déjà à l'adresse dérivée.
Is this page helpful?