Adresse dérivée de programme (PDA)

Les adresses dérivées de programme (PDAs) offrent aux développeurs sur Solana deux cas d'utilisation principaux :

  • Adresses de compte déterministes : Les PDAs fournissent un mécanisme pour créer de façon déterministe une adresse en utilisant une combinaison de "seeds" optionnels (entrées prédéfinies) et un ID de programme spécifique.
  • Permettre la signature par programme : L'environnement d'exécution de Solana permet aux programmes de "signer" pour les PDAs qui sont dérivés de l'adresse du programme.

Vous pouvez considérer les PDAs comme un moyen de créer des structures similaires à des tables de hachage sur la blockchain à partir d'un ensemble prédéfini d'entrées (par exemple, des chaînes de caractères, des nombres et d'autres adresses de compte).

L'avantage de cette approche est qu'elle élimine la nécessité de suivre une adresse exacte. Au lieu de cela, vous avez simplement besoin de vous rappeler les entrées spécifiques utilisées pour sa dérivation.

Adresse dérivée de programmeAdresse dérivée de programme

Il est important de comprendre que le simple fait de dériver une adresse dérivée de programme (PDA) ne crée pas automatiquement un compte sur la blockchain à cette adresse. Les comptes avec une PDA comme adresse sur la blockchain doivent être explicitement créés via le programme utilisé pour dériver l'adresse. Vous pouvez considérer la dérivation d'une PDA comme la recherche d'une adresse sur une carte. Le simple fait d'avoir une adresse ne signifie pas qu'il y a quelque chose de construit à cet emplacement.

Cette section couvre les détails de la dérivation des PDAs. La section sur les invocations inter-programmes (CPIs) explique comment les programmes utilisent les PDAs pour la signature.

Points clés

  • Les PDAs sont des adresses dérivées de manière déterministe en utilisant une combinaison de seeds prédéfinis, un bump seed, et l'ID d'un programme.
  • Les PDAs sont des adresses qui se situent en dehors de la courbe Ed25519 et n'ont pas de clé privée correspondante.
  • Les programmes Solana peuvent signer au nom des PDAs dérivés de leur ID de programme.
  • Dériver une PDA ne crée pas automatiquement un compte sur la blockchain.
  • Un compte utilisant une PDA comme adresse doit être créé via une instruction dans un programme Solana.

Qu'est-ce qu'une PDA

Les PDA sont des adresses qui dérivent de manière déterministe et ressemblent à des clés publiques, mais n'ont pas de clés privées. Cela signifie qu'il n'est pas possible de générer une signature valide pour l'adresse. Cependant, le runtime de Solana permet aux programmes de "signer" pour les PDA sans avoir besoin d'une clé privée.

Pour contextualiser, les Keypairs de Solana sont des points sur la courbe Ed25519 (cryptographie à courbe elliptique) avec une clé publique et une clé privée correspondante. Les clés publiques sont utilisées comme adresses (identifiant unique) pour les comptes on-chain.

Adresse sur la courbeAdresse sur la courbe

Une PDA est un point intentionnellement dérivé pour se situer en dehors de la courbe Ed25519 en utilisant un ensemble prédéfini d'entrées. Un point qui n'est pas sur la courbe Ed25519 n'a pas de clé privée correspondante valide et ne peut pas effectuer d'opérations cryptographiques (signature).

Une PDA peut servir d'adresse (identifiant unique) pour un compte on-chain, fournissant une méthode pour facilement stocker, mapper et récupérer l'état du programme.

Adresse hors courbeAdresse hors courbe

Comment dériver une PDA

La dérivation d'une PDA nécessite trois entrées:

  • Seeds optionnels: Entrées prédéfinies (par ex. chaînes de caractères, nombres, autres adresses de compte) pour la dérivation de PDA.
  • Bump seed: Un octet supplémentaire ajouté aux seeds optionnels pour garantir qu'une PDA valide (hors courbe) est générée. Le bump seed commence à 255 et décrémente de 1 jusqu'à ce qu'une PDA valide soit trouvée.
  • ID du programme: L'adresse du programme à partir duquel la PDA est dérivée. Ce programme peut signer au nom de la PDA.

Dérivation PDADérivation PDA

Utilisez les fonctions suivantes des SDK respectifs pour dériver une PDA.

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

Pour dériver une PDA, fournissez les entrées suivantes à la fonction SDK :

  • Les seeds prédéfinies optionnelles converties en octets
  • L'ID du programme (adresse) utilisé pour la dérivation

Une fois qu'une PDA valide est trouvée, la fonction renvoie à la fois l'adresse (PDA) et le bump seed utilisé pour la dérivation.

Exemples

Les exemples suivants montrent comment dériver une PDA en utilisant les SDK respectifs.

Cliquez sur le bouton "Run" pour exécuter le code.

Dériver une PDA avec un seed de chaîne optionnel

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.

Dériver une PDA avec 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}`);
Click to execute the code.

Dériver une PDA avec 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}`);
Click to execute the code.

Bump canonique

La dérivation PDA nécessite un "bump seed", 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 hors-courbe valide. La première valeur de bump qui produit une adresse hors-courbe valide est le "bump canonique".

Les exemples suivants montrent la dérivation PDA 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);
}
}
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

Le bump seed 255 génère une erreur et le premier bump seed à dériver un PDA valide est 254.

Notez que les bump seeds 253-251 dérivent tous des PDA valides avec des adresses différentes. 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.

Lors de la création de programmes Solana, incluez toujours des vérifications de sécurité pour garantir qu'un PDA transmis au programme est dérivé du bump canonique. Ne pas inclure ces vérifications peut introduire des vulnérabilités permettant l'utilisation de comptes inattendus dans les instructions du programme. Il est recommandé de n'utiliser que le bump canonique lors de la dérivation des PDA.

Créer des comptes PDA

Le programme exemple ci-dessous montre comment créer un compte en utilisant un PDA comme adresse du nouveau compte. Le programme exemple utilise le framework Anchor.

Le programme inclut une seule instruction initialize pour créer un nouveau compte en utilisant un PDA comme adresse du compte. Le nouveau compte stocke l'adresse du user et le seed bump utilisé pour dériver le 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,
}

Dans cet exemple, les seeds pour la dérivation PDA incluent la chaîne fixe data et l'adresse du compte user fournie dans l'instruction. Le framework Anchor trouve automatiquement le seed bump canonique.

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>,

La contrainte init indique à Anchor d'invoquer le System Program pour créer un nouveau compte en utilisant le PDA comme adresse. Anchor fait cela via un 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>,

Le fichier de test contient le code Typescript pour dériver le PDA.

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

La transaction dans le fichier de test invoque l'instruction initialize pour créer un nouveau compte on-chain en utilisant le PDA comme adresse. Dans cet exemple, Anchor peut inférer l'adresse PDA dans les comptes d'instruction, donc elle n'a pas besoin d'être explicitement fournie.

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

Le fichier de test montre également comment récupérer le compte on-chain créé à cette adresse une fois que la transaction est envoyée.

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

Notez que dans cet exemple, si vous invoquez l'instruction initialize plus d'une fois en utilisant la même adresse user comme seed, la transaction échouera. Cela se produit car un compte existe déjà à l'adresse dérivée.

Is this page helpful?

Table des matières

Modifier la page