Dirección derivada de programa

Una dirección de cuenta de Solana apunta a la ubicación de la cuenta en la blockchain. Muchas direcciones de cuentas son la clave pública de un keypair, en cuyo caso la clave privada correspondiente se utiliza para firmar transacciones que involucran la cuenta.

Una alternativa útil a una dirección de clave pública es una dirección derivada de programa (PDA). Las PDAs proporcionan un método sencillo para almacenar, mapear y recuperar el estado del programa. Una PDA es una dirección que se crea de manera determinista utilizando un ID de programa y una combinación de entradas predefinidas opcionales. Las PDAs se parecen a las direcciones de clave pública, pero no tienen una clave privada correspondiente.

El tiempo de ejecución de Solana permite a los programas firmar PDAs sin necesidad de una clave privada. El uso de una PDA elimina la necesidad de realizar un seguimiento de la dirección de la cuenta. En su lugar, puedes recordar las entradas específicas utilizadas para la derivación de la PDA. (Para aprender cómo los programas utilizan PDAs para firmar, consulta la sección Invocaciones entre programas.)

Antecedentes

Los keypairs de Solana son puntos en la curva Ed25519 (criptografía de curva elíptica). Consisten en una clave pública y una clave privada. La clave pública se convierte en la dirección de la cuenta, y la clave privada se utiliza para generar firmas válidas para la cuenta.

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

Una PDA se deriva intencionalmente para que quede fuera de la curva Ed25519. Esto significa que no tiene una clave privada correspondiente válida y no puede realizar operaciones criptográficas. (Como proporcionar una firma.) Sin embargo, Solana permite a los programas firmar PDAs sin necesidad de una clave privada.

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

Puedes pensar en los PDAs como una forma de crear estructuras similares a hashmaps en la cadena utilizando un conjunto predefinido de entradas. (Por ejemplo, strings, números y otras direcciones de cuentas.)

Program Derived AddressProgram Derived Address

Derivar un PDA

Antes de crear una cuenta con un PDA, primero debes derivar la dirección. Derivar un PDA no crea automáticamente una cuenta en la cadena en esa dirección— la cuenta debe ser creada explícitamente a través del programa utilizado para derivar el PDA. Puedes pensar en un PDA como una dirección en un mapa: el hecho de que exista una dirección no significa que haya algo construido allí.

Los SDK de Solana admiten la creación de PDA con las funciones mostradas en la tabla a continuación. Cada función recibe la siguiente entrada:

  • Program ID: La dirección del programa que se utiliza para derivar el PDA. Este programa puede firmar en nombre del PDA.
  • Optional seeds: Entradas predefinidas, como strings, números u otras direcciones de cuentas.
SDKFunción
@solana/kit (Typescript)getProgramDerivedAddress
@solana/web3.js (Typescript)findProgramAddressSync
solana_sdk (Rust)find_program_address

La función utiliza el Program ID y los optional seeds, luego itera a través de valores de bump para intentar crear una dirección de programa válida. La iteración de valores de bump comienza en 255 y disminuye en 1 hasta que se encuentra un PDA válido. Después de encontrar un PDA válido, la función devuelve el PDA y el bump seed.

El bump seed es un byte adicional que se añade a los optional seeds para garantizar que se genere una dirección válida fuera de la curva.

Derivación de PDADerivación de PDA

Bump canónico

Un bump seed es un byte adicional añadido a las seeds opcionales. La función de derivación itera a través de los valores de bump, comenzando en 255 y disminuyendo en 1, hasta que un valor produce una dirección válida fuera de la curva. El primer valor que produce una dirección válida fuera de la curva se denomina "bump canónico".

Los siguientes ejemplos muestran la derivación de PDA utilizando todos los posibles bump seeds (de 255 a 0):

Ejemplo de Kit no 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 primer bump seed genera un error. El primer bump seed que deriva un PDA válido es 254. Los bump seeds 253-251 también derivan PDAs válidos y únicos.

Esto significa que dadas las mismas seeds opcionales y programId, un bump seed con un valor diferente todavía puede derivar un PDA válido.

Siempre incluye comprobaciones de seguridad para asegurar que un PDA pasado al programa se deriva del bump canónico. No hacerlo puede introducir vulnerabilidades que permitan el uso de cuentas inesperadas en las instrucciones del programa. Es una buena práctica utilizar solo el bump canónico al derivar PDAs.

Ejemplos

Los ejemplos a continuación derivan un PDA utilizando los SDKs de Solana. Haz clic en ▷ Ejecutar para ejecutar el código.

Derivar un PDA con una seed de tipo string

El ejemplo a continuación deriva un PDA utilizando un ID de programa y una seed opcional de tipo string.

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 un PDA con una seed de dirección

El ejemplo a continuación deriva un PDA utilizando un ID de programa y una 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 un PDA con múltiples seeds

El ejemplo a continuación deriva un PDA utilizando 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.

Crear una cuenta PDA

El ejemplo a continuación utiliza el framework Anchor para crear una nueva cuenta con una dirección derivada del programa. El programa incluye una única instrucción initialize para crear la nueva cuenta, que almacenará la dirección del usuario y el bump seed utilizados para derivar el 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 restricción init le indica a Anchor que invoque al System Program para crear una nueva cuenta utilizando el PDA como dirección. Las seeds utilizadas para crear el PDA son:

  • La dirección de la cuenta de usuario proporcionada en la instrucción
  • La cadena fija: "data"
  • El bump seed canónico

En este ejemplo, la restricción de bump no tiene asignado un valor, por lo que Anchor utilizará find_program_address para derivar el PDA y encontrar el 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>,

El archivo de prueba a continuación contiene una transacción que invoca la instrucción initialize para crear una nueva cuenta con una dirección derivada del programa. El archivo contiene código para derivar el PDA.

El ejemplo también muestra cómo obtener la nueva cuenta que será creada.

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 invocas la instrucción initialize nuevamente con la misma seed de dirección user, la transacción fallará. Esto ocurre porque ya existe una cuenta en la dirección derivada.

Is this page helpful?

Tabla de Contenidos

Editar Página

Gestionado por

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