Program Derived Address (PDA)

Las Program Derived Addresses (PDAs) proporcionan a los desarrolladores en Solana dos casos de uso principales:

  • Direcciones de cuenta deterministas: Las PDAs proporcionan un mecanismo para crear determinísticamente una dirección utilizando una combinación de "seeds" opcionales (entradas predefinidas) y un ID de programa específico.
  • Habilitar la firma del programa: El tiempo de ejecución de Solana permite que los programas "firmen" por PDAs que se derivan de la dirección del programa.

Puedes pensar en las PDAs como una forma de crear estructuras similares a hashmaps en la cadena a partir de un conjunto predefinido de entradas (por ejemplo, cadenas, números y otras direcciones de cuenta).

El beneficio de este enfoque es que elimina la necesidad de realizar un seguimiento de una dirección exacta. En su lugar, simplemente necesitas recordar las entradas específicas utilizadas para su derivación.

Program Derived AddressProgram Derived Address

Es importante entender que simplemente derivar una Program Derived Address (PDA) no crea automáticamente una cuenta en la cadena en esa dirección. Las cuentas con una PDA como dirección en la cadena deben crearse explícitamente a través del programa utilizado para derivar la dirección. Puedes pensar en derivar una PDA como encontrar una dirección en un mapa. El solo hecho de tener una dirección no significa que haya algo construido en esa ubicación.

Esta sección cubre los detalles de la derivación de PDAs. La sección sobre Cross Program Invocations (CPIs) explica cómo los programas utilizan PDAs para firmar.

Puntos clave

  • Las PDAs son direcciones derivadas determinísticamente utilizando una combinación de seeds predefinidas, un bump seed y el ID de un programa.
  • Las PDAs son direcciones que están fuera de la curva Ed25519 y no tienen una clave privada correspondiente.
  • Los programas de Solana pueden firmar en nombre de PDAs derivadas de su ID de programa.
  • Derivar una PDA no crea automáticamente una cuenta en la cadena.
  • Una cuenta que utiliza una PDA como su dirección debe crearse a través de una instrucción dentro de un programa de Solana.

Qué es un PDA

Los PDAs son direcciones que se derivan de forma determinista y que parecen claves públicas, pero no tienen claves privadas. Esto significa que no es posible generar una firma válida para la dirección. Sin embargo, el entorno de ejecución de Solana permite a los programas "firmar" por los PDAs sin necesitar una clave privada.

Para contextualizar, los Keypairs de Solana son puntos en la curva Ed25519 (criptografía de curva elíptica) con una clave pública y su correspondiente clave privada. Las claves públicas se utilizan como direcciones (identificador único) para las cuentas en la cadena.

Dirección en la curvaDirección en la curva

Un PDA es un punto que se deriva intencionalmente para quedar fuera de la curva Ed25519 utilizando un conjunto predefinido de entradas. Un punto que no está en la curva Ed25519 no tiene una clave privada correspondiente válida y no puede realizar operaciones criptográficas (firma).

Un PDA puede servir como dirección (identificador único) para una cuenta en la cadena, proporcionando un método para almacenar, mapear y recuperar fácilmente el estado del programa.

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

Cómo derivar un PDA

La derivación de un PDA requiere tres entradas:

  • Seeds opcionales: Entradas predefinidas (por ejemplo, cadenas, números, otras direcciones de cuenta) para la derivación del PDA.
  • Bump seed: Un byte adicional que se añade a los seeds opcionales para garantizar que se genere un PDA válido (fuera de la curva). El bump seed comienza en 255 y disminuye en 1 hasta que se encuentra un PDA válido.
  • ID del programa: La dirección del programa desde el cual se deriva el PDA. Este programa puede firmar en nombre del PDA.

Derivación de PDADerivación de PDA

Utiliza las siguientes funciones de los respectivos SDKs para derivar un PDA.

SDKFunción
@solana/kit (Typescript)getProgramDerivedAddress
@solana/web3.js (Typescript)findProgramAddressSync
solana_sdk (Rust)find_program_address

Para derivar un PDA, proporciona las siguientes entradas a la función del SDK:

  • Los seeds opcionales predefinidos convertidos a bytes
  • El ID del programa (dirección) utilizado para la derivación

Una vez que se encuentra un PDA válido, la función devuelve tanto la dirección (PDA) como el bump seed utilizado para la derivación.

Ejemplos

Los siguientes ejemplos muestran cómo derivar un PDA utilizando los respectivos SDKs.

Haz clic en el botón "Ejecutar" para ejecutar el código.

Derivar un PDA con 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}`);
Click to execute the code.

Derivar un PDA con 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}`);
Click to execute the code.

Derivar un PDA con 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}`);
Click to execute the code.

Bump canónico

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

Los siguientes ejemplos muestran la derivación de PDA utilizando todas las posibles semillas de bump (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);
}
}
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

El bump seed 255 genera un error y el primer bump seed que deriva un PDA válido es 254.

Observa que los bump seeds 253-251 derivan PDAs válidos con direcciones diferentes. Esto significa que dadas las mismas semillas opcionales y programId, un bump seed con un valor diferente todavía puede derivar un PDA válido.

Al construir programas en Solana, siempre incluye comprobaciones de seguridad para asegurar que un PDA pasado al programa se deriva del bump canónico. No incluir estas comprobaciones 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.

Crear cuentas PDA

El programa de ejemplo a continuación muestra cómo crear una cuenta utilizando un PDA como dirección de la nueva cuenta. El programa de ejemplo utiliza el framework Anchor.

El programa incluye una única instrucción initialize para crear una nueva cuenta utilizando un PDA como dirección de la cuenta. La nueva cuenta almacena la dirección del user y el bump seed utilizado para derivación del PDA.

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

En este ejemplo, las seeds para la derivación de PDA incluyen la cadena fija data y la dirección del user proporcionada en la instrucción. El framework Anchor encuentra automáticamente la seed bump canónica.

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

La restricción init indica a Anchor que invoque el System Program para crear una nueva cuenta usando la PDA como dirección. Anchor hace esto a través de un CPI.

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 contiene el código Typescript para derivar la PDA.

Derive PDA
const [PDA] = PublicKey.findProgramAddressSync(
[Buffer.from("data"), user.publicKey.toBuffer()],
program.programId
);

La transacción en el archivo de prueba invoca la instrucción initialize para crear una nueva cuenta en la cadena usando la PDA como dirección. En este ejemplo, Anchor puede inferir la dirección PDA en las cuentas de instrucción, por lo que no necesita ser proporcionada explícitamente.

Invoke Initialize Instruction
it("Is initialized!", async () => {
const transactionSignature = await program.methods
.initialize()
.accounts({
user: user.publicKey
})
.rpc();
console.log("Transaction Signature:", transactionSignature);
});

El archivo de prueba también muestra cómo obtener la cuenta en la cadena creada en esa dirección una vez que se envía la transacción.

Fetch Account
it("Fetch Account", async () => {
const pdaAccount = await program.account.dataAccount.fetch(PDA);
console.log(JSON.stringify(pdaAccount, null, 2));
});

Ten en cuenta que en este ejemplo, si invocas la instrucción initialize más de una vez usando la misma dirección user como seed, entonces 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