Dérivation de PDA

Résumé

Les PDA sont dérivés en hachant seeds + ID de programme + bump via SHA-256 jusqu'à ce que le résultat soit hors de la courbe Ed25519. Le bump canonique est la première valeur qui produit une adresse hors courbe. Maximum 16 seeds, maximum 32 octets par seed.

Contexte

Les valeurs de Keypair Solana sont des points sur la courbe Ed25519. Une paire de clés consiste en une clé publique (utilisée comme adresse de compte) et une clé secrète (utilisée pour produire des signatures). Toute personne possédant la clé secrète peut signer des transactions pour cette adresse.

Deux comptes avec des adresses sur la courbeDeux comptes avec des adresses sur la courbe

Un PDA est intentionnellement dérivé pour tomber hors de la courbe Ed25519. Parce qu'il n'est pas un point de courbe valide, aucune clé secrète n'existe, et aucune partie externe ne peut produire une signature. Seul le programme de dérivation peut autoriser des opérations sur le PDA via invoke_signed.

Adresse hors courbeAdresse hors courbe

Comptes PDA vs comptes keypair

PropriétéCompte keypairCompte PDA
Type d'adresseSur la courbe Ed25519Hors de la courbe Ed25519
Possède une clé privéeOuiNon
Peut signer des transactionsOui (avec clé privée)Non
Peut signer pendant CPINon (sauf si signature incluse dans la transaction)Oui (via invoke_signed)
DérivationGénération de paire de clés Ed25519Déterministe à partir de seeds + ID de programme
Utilisation typiquePortefeuilles utilisateur, ID de programmeComptes de données détenus par le programme

Seeds optionnels

Les seeds optionnels sont des chaînes d'octets définies par l'utilisateur qui servent d'entrées à la dérivation de PDA. Ils créent des adresses uniques et déterministes limitées à un programme. Par exemple, l'utilisation de ["user", user_pubkey] comme seeds dérive un PDA différent pour chaque utilisateur.

Les seeds doivent respecter ces contraintes :

  • Maximum 16 seeds par dérivation (MAX_SEEDS)
  • Maximum 32 octets par seed (MAX_SEED_LEN)

Bump seed

Le bump seed est un octet unique (0-255) ajouté aux seeds optionnels pendant la dérivation. find_program_address recherche de 255 à 0, en appelant create_program_address avec chaque valeur jusqu'à ce que le résultat sorte de la courbe Ed25519. La première valeur qui réussit est le bump canonique.

Les programmes doivent toujours utiliser le bump canonique pour garantir une correspondance unique et déterministe entre les seeds et l'adresse.

Utilisez toujours le bump canonique lors de la dérivation de PDA. L'utilisation d'un bump non canonique crée une seconde adresse valide pour les mêmes seeds, ce qui peut entraîner des vulnérabilités où un attaquant substitue un compte différent de celui attendu.

Dérivation de PDADérivation de PDA

Algorithme de dérivation

La dérivation de PDA est implémentée dans la fonction create_program_address du SDK. L'algorithme fonctionne comme suit :

  1. Valider que le nombre de seeds ne dépasse pas MAX_SEEDS (16) et qu'aucun seed individuel ne dépasse MAX_SEED_LEN (32 octets). Si l'une des vérifications échoue, retourner PubkeyError::MaxSeedLengthExceeded.
  2. Hacher en SHA-256 tous les seeds, l'ID du programme et la chaîne "ProgramDerivedAddress" ensemble pour produire un résultat de 32 octets.
  3. Vérifier si le résultat est un point valide sur la courbe Ed25519.
  4. Si le résultat EST sur la courbe, retourner PubkeyError::InvalidSeeds (l'adresse aurait une clé privée correspondante, ce qui viole la propriété de sécurité du PDA).
  5. Si le résultat n'est PAS sur la courbe, le retourner comme PDA.

Coûts en unités de calcul

L'appel système on-chain pour create_program_address facture 1 500 UC par appel.

L'appel système try_find_program_address facture 1 500 UC à l'entrée (avant la boucle), puis 1 500 UC supplémentaires pour chaque tentative de bump échouée dans la boucle.

Modèles de seed courants

Les seeds sont spécifiques à l'application. Les modèles courants incluent :

ModèleSeedsCas d'usage
Singleton global["global"]Compte de configuration unique pour tout le programme
Compte par utilisateur["user", user_pubkey]Un compte par utilisateur par programme
Par utilisateur par entité["vault", user_pubkey, mint_pubkey]Coffres de tokens, par utilisateur par token
Compteur / séquentiel["order", user_pubkey, &order_id.to_le_bytes()]Enregistrements séquentiels par utilisateur

Les seeds sont concaténés avant le hachage, donc ["ab", "cd"] et ["abcd"] produisent le même PDA. Utilisez des seeds de longueur fixe ou un séparateur pour éviter les collisions. Par exemple, ["ab", "-", "cd"] est sans ambiguïté.

Exemples : dériver un PDA

Dériver un PDA calcule uniquement une adresse. Cela ne crée pas de compte on-chain à cette adresse. Le compte doit être explicitement créé via une instruction séparée (généralement create_account via CPI).

Les SDK Solana fournissent des fonctions pour la dérivation de PDA. Chaque fonction prend :

  • Program ID : l'adresse du programme utilisé pour dériver le PDA. Ce programme peut signer au nom du PDA.
  • Seeds optionnels : entrées prédéfinies telles que des chaînes, des nombres ou d'autres adresses de compte.
SDKFonction
@solana/kit (TypeScript)getProgramDerivedAddress
@solana/web3.js (TypeScript)findProgramAddressSync
solana_sdk (Rust)find_program_address

Les exemples ci-dessous dérivent un PDA en utilisant les SDK Solana. Cliquez sur ▷ Exécuter pour exécuter le code.

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

L'exemple ci-dessous dérive une PDA en utilisant un ID de programme et 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}`);
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.

Itération de tous les bumps

Les exemples suivants montrent la dérivation de PDA en utilisant tous les seeds de bump possibles (255 à 0), illustrant comment find_program_address retourne le bump canonique :

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 bump 255 produit une adresse sur la courbe et échoue. Le premier bump valide est 254, ce qui en fait le bump canonique.

Is this page helpful?

Géré par

© 2026 Fondation Solana.
Tous droits réservés.
Restez connecté