Derivación de PDA

Resumen

Los PDA se derivan mediante el hash de seeds + ID de programa + bump vía SHA-256 hasta que el resultado esté fuera de la curva Ed25519. El bump canónico es el primer valor que produce una dirección fuera de la curva. Máximo 16 seeds, máximo 32 bytes por seed.

Contexto

Los valores de Keypair de Solana son puntos en la curva Ed25519. Un keypair consiste en una clave pública (usada como dirección de cuenta) y una clave secreta (usada para producir firmas). Cualquiera con la clave secreta puede firmar transacciones para esa dirección.

Dos cuentas con direcciones en la curvaDos cuentas con direcciones en la curva

Un PDA se deriva intencionalmente para caer fuera de la curva Ed25519. Como no es un punto válido de la curva, no existe clave secreta, y ninguna parte externa puede producir una firma. Solo el programa que lo deriva puede autorizar operaciones en el PDA mediante invoke_signed.

Dirección fuera de la curvaDirección fuera de la curva

Cuentas PDA vs keypair

PropiedadCuenta keypairCuenta PDA
Tipo de direcciónEn la curva Ed25519Fuera de la curva Ed25519
Tiene clave privadaNo
Puede firmar transaccionesSí (con clave privada)No
Puede firmar durante CPINo (a menos que la firma esté incluida en la transacción)Sí (vía invoke_signed)
DerivaciónGenerar keypair Ed25519Determinista desde seeds + ID de programa
Uso típicoWallets de usuario, ID de programaCuentas de datos propiedad del programa

Seeds opcionales

Los seeds opcionales son cadenas de bytes definidas por el usuario que sirven como entradas para la derivación de PDA. Crean direcciones únicas y deterministas con alcance a un programa. Por ejemplo, usar ["user", user_pubkey] como seeds deriva un PDA diferente para cada usuario.

Los seeds deben seguir estas restricciones:

  • Máximo 16 seeds por derivación (MAX_SEEDS)
  • Máximo 32 bytes por seed (MAX_SEED_LEN)

Bump seed

El bump seed es un solo byte (0-255) que se añade a los seeds opcionales durante la derivación. find_program_address busca desde 255 hacia abajo hasta 0, llamando a create_program_address con cada valor hasta que el resultado quede fuera de la curva Ed25519. El primer valor que tiene éxito es el bump canónico.

Los programas siempre deben usar el bump canónico para garantizar un mapeo único y determinista de seeds a dirección.

Siempre usa el bump canónico al derivar PDAs. Usar un bump no canónico crea una segunda dirección válida para los mismos seeds, lo que puede generar vulnerabilidades donde un atacante sustituye una cuenta diferente a la esperada.

PDA DerivationPDA Derivation

Algoritmo de derivación

La derivación de PDA se implementa en la función create_program_address del SDK. El algoritmo funciona de la siguiente manera:

  1. Validar que el número de seeds no exceda MAX_SEEDS (16) y que ningún seed individual exceda MAX_SEED_LEN (32 bytes). Si alguna verificación falla, devolver PubkeyError::MaxSeedLengthExceeded.
  2. Aplicar hash SHA-256 a todos los seeds, el ID del programa y la cadena "ProgramDerivedAddress" juntos para producir un resultado de 32 bytes.
  3. Verificar si el resultado es un punto válido en la curva Ed25519.
  4. Si el resultado ESTÁ en la curva, devolver PubkeyError::InvalidSeeds (la dirección tendría una clave privada correspondiente, lo que viola la propiedad de seguridad de PDA).
  5. Si el resultado NO está en la curva, devolverlo como el PDA.

Costos de unidades de cómputo

La syscall en cadena para create_program_address cobra 1.500 CU por llamada.

La syscall try_find_program_address cobra 1.500 CU al inicio (antes del bucle), luego 1.500 CU adicionales por cada intento de bump fallido dentro del bucle.

Patrones comunes de seed

Los seeds son específicos de la aplicación. Los patrones comunes incluyen:

PatrónSeedsCaso de uso
Singleton global["global"]Cuenta de configuración única para todo el programa
Cuenta por usuario["user", user_pubkey]Una cuenta por usuario por programa
Por usuario por entidad["vault", user_pubkey, mint_pubkey]Bóvedas de tokens, por usuario por token
Contador / secuencial["order", user_pubkey, &order_id.to_le_bytes()]Registros secuenciales por usuario

Los seeds se concatenan antes del hash, por lo que ["ab", "cd"] e ["abcd"] producen la misma PDA. Usa seeds de longitud fija o un separador para evitar colisiones. Por ejemplo, ["ab", "-", "cd"] no es ambiguo.

Ejemplos: derivar una PDA

Derivar una PDA solo calcula una dirección. No crea una cuenta en cadena en esa dirección. La cuenta debe crearse explícitamente mediante una instrucción separada (típicamente create_account vía CPI).

Los SDK de Solana proporcionan funciones para la derivación de PDA. Cada función toma:

  • ID del programa: la dirección del programa utilizado para derivar la PDA. Este programa puede firmar en nombre de la PDA.
  • Seeds opcionales: entradas predefinidas como cadenas, números u otras direcciones de cuentas.
SDKFunción
@solana/kit (TypeScript)getProgramDerivedAddress
@solana/web3.js (TypeScript)findProgramAddressSync
solana_sdk (Rust)find_program_address

Los ejemplos a continuación derivan una PDA usando los SDK de Solana. Haz clic en ▷ Ejecutar para ejecutar el código.

Derivar una PDA con un seed de cadena

El ejemplo a continuación deriva una PDA usando un ID de programa y un seed de cadena opcional.

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.

Derivar una PDA con un seed de dirección

El ejemplo a continuación deriva una PDA usando un ID de programa y un seed de dirección opcional.

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.

Derivar una PDA con múltiples seeds

El ejemplo a continuación deriva una PDA usando un ID de programa y múltiples seeds opcionales.

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.

Iterando todos los bumps

Los siguientes ejemplos muestran la derivación de PDA usando todos los posibles bump seeds (255 a 0), ilustrando cómo find_program_address devuelve el bump canónico:

El ejemplo de Kit no está incluido porque la función createProgramDerivedAddress no está exportada.

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

En este ejemplo, el bump 255 produce una dirección en la curva y falla. El primer bump válido es 254, lo que lo convierte en el bump canónico.

Is this page helpful?

Gestionado por

© 2026 Fundación Solana.
Todos los derechos reservados.
Conéctate