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 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 courbe
Comptes PDA vs comptes keypair
| Propriété | Compte keypair | Compte PDA |
|---|---|---|
| Type d'adresse | Sur la courbe Ed25519 | Hors de la courbe Ed25519 |
| Possède une clé privée | Oui | Non |
| Peut signer des transactions | Oui (avec clé privée) | Non |
| Peut signer pendant CPI | Non (sauf si signature incluse dans la transaction) | Oui (via invoke_signed) |
| Dérivation | Génération de paire de clés Ed25519 | Déterministe à partir de seeds + ID de programme |
| Utilisation typique | Portefeuilles utilisateur, ID de programme | Comptes 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 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 :
- Valider que le nombre de seeds ne dépasse pas
MAX_SEEDS(16) et qu'aucun seed individuel ne dépasseMAX_SEED_LEN(32 octets). Si l'une des vérifications échoue, retournerPubkeyError::MaxSeedLengthExceeded. - 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. - Vérifier si le résultat est un point valide sur la courbe Ed25519.
- 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). - 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èle | Seeds | Cas 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.
| SDK | Fonction |
|---|---|
@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}`);
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}`);
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);}}
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 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?