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 courbeDeux 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 courbeAdresse 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 AddressProgram 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.
SDKFonction
@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 PDADé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);
}
}
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

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}`);
Console
Click to execute the code.

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}`);
Console
Click to execute the code.

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}`);
Console
Click to execute the code.

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.

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

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.

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 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éé.

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

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?

Table des matières

Modifier la page

Géré par

© 2025 Fondation Solana.
Tous droits réservés.
Restez connecté
Adresse dérivée de programme | Solana