El Plano de las Direcciones Derivadas por Programa (PDA)
Una dirección derivada por programa (PDA) es una dirección de cuenta de Solana que un programa puede controlar sin poseer una clave privada. Las semillas y un ID de programa fijan la dirección; un bump la hace aterrizar fuera de la curva ed25519 para que ninguna clave normal pueda firmarla. Una vez que veas la derivación, la creación y invoke_signed como un solo pipeline, los depósitos, los escrows y las autoridades de acuñación dejarán de parecer tipos de cuenta especiales y comenzarán a parecer el mismo modelo de cuenta con una ruta de firma propiedad del programa.
Conceptos básicos de PDA es la entrada práctica; Derivación de PDAs, Bumps Canónicos, Creación de Cuentas PDA, PDAs como Autoridades y Trampas de Seguridad de PDAs se centran en un mecanismo cada uno. Esta página se sitúa debajo: cómo la derivación, los bumps, la creación, la firma y los patrones de autoridad forman un solo plano.
Resumen
- Una PDA es una dirección determinista y fuera de la curva producida a partir de semillas + ID de programa + bump; solo el programa propietario puede autorizarla proporcionando esas semillas a
invoke_signed. - Por Qué Importa: Los programas de Solana son ejecutables sin estado. Las PDAs les dan direcciones duraderas y controladas por el programa para configuración, depósitos, escrows y autoridad de acuñación sin incrustar claves secretas en la cadena.
- Conceptos Clave: semillas,
find_program_address, bump / bump canónico, fuera de la curva, creación de cuenta PDA,invoke_signed, PDA como autoridad, re-derivación en cadena. - Cuándo Usar: Diseño de estado por usuario o por acuñación, tesorerías propiedad del programa, autoridad de acuñación/congelación de tokens, flujos de liberación de escrow, o descubrimiento de direcciones del cliente antes de construir transacciones.
- Limitaciones / Compensaciones: El diseño de semillas es permanente para un ID de programa dado; la creación aún necesita alquiler y CPI del Programa del Sistema; semillas o bumps incorrectos fallan de forma oscura; un error en las rutas de firma es equivalente a regalar la clave del depósito.
- Temas Relacionados: Conceptos básicos de PDA, derivación de PDAs, bumps canónicos, creación de cuentas, autoridades, trampas de seguridad.
Fundamentos
Las cuentas de Solana viven en claves públicas. La mayoría de las direcciones están en la curva: corresponden a un par de claves ed25519 que alguien puede poseer. Una PDA está deliberadamente fuera de la curva: el runtime nunca la tratará como un firmante normal de billetera. Ese es el propósito. El programa que posee la derivación (el ID del programa en find_program_address) puede demostrar más tarde el control reproduciendo los bytes de la semilla en la verificación del firmante de PDA del runtime.
Nada sobre el registro de la cuenta en sí es especial. Después de la creación, una cuenta PDA todavía tiene lamports, data, owner, executable y rent_epoch. Lo especial es cómo se eligió la dirección y cómo se producen las firmas.
Piensa en una ecuación de derivación:
semillas: [ "vault", pubkey_del_usuario, ... ]
program_id: TuPrograma
bump: u8 (prueba 255, 254, ... hasta estar fuera de la curva)
|
v
dirección PDA ----sin clave privada----> no puede firmar desde billetera
|
+-- tu programa puede invoke_signed(semillas + bump)Las semillas son fragmentos de bytes ordenados (como máximo 16 semillas, cada una de hasta 32 bytes). Piezas típicas: un espacio de nombres constante (b"vault"), una clave pública de usuario o acuñación (32 bytes), y enteros de codificación fija (id.to_le_bytes()). El orden y la codificación son parte de la dirección. Cambia cualquiera de ellos y obtendrás una cuenta diferente.
El ID del programa es la segunda mitad de la identidad. Las mismas semillas bajo un programa diferente derivan una PDA diferente. Los clientes deben usar el ID del programa desplegado, no una suposición codificada en la fuente de otro entorno.
El bump es la semilla final de un byte que la aplicación de búsqueda añade mientras busca un punto fuera de la curva. La búsqueda comienza en 255 y avanza hacia abajo. El primer bump válido es el bump canónico. Los programas de producción almacenan ese valor y rechazan bumps no canónicos para que las direcciones alternativas bajo las mismas semillas lógicas no puedan usarse para causar molestias.
Tanto los clientes fuera de la cadena como los programas en cadena llaman a la misma idea: hashear semillas, ID de programa y bump hasta que el resultado sea una PDA válida. Fuera de la cadena, así es como se rellena la lista de cuentas de la transacción. En la cadena, así es como verificas que una cuenta suministrada por el cliente sea realmente la PDA esperada antes de escribir datos o firmar CPIs.
Mecánicas e Interacciones
find_program_address
Pubkey::find_program_address(seeds, program_id) devuelve (pda, bump) para la dirección canónica. Ayudantes relacionados (create_program_address) toman un bump explícito y solo tienen éxito si ese bump produce una dirección fuera de la curva.
Conceptualmente:
- Para el bump de 255 hacia abajo hasta 0, hashea semillas + bump + ID de programa bajo el separador de dominio de PDA.
- Detente en el primer resultado fuera de la curva.
- Devuelve esa dirección y el bump utilizado.
Nota de coste: en cadena, una búsqueda completa puede probar muchos hashes. Prefiere almacenar el bump canónico al inicializar y luego pasar &[bump_almacenado] a las semillas firmantes, o usar create_program_address cuando ya conozcas el bump.
Anchor 0.32.1 expone esto con restricciones de cuenta seeds / bump para que el framework lo re-derive y verifique por ti. Los programas nativos deberían centralizar una pequeña ayuda assert_pda.
Creación de una cuenta PDA
La derivación por sí sola no crea estado. Una dirección PDA no utilizada no tiene cuenta hasta que algo la financia, asigna y asigna su propietario. La ruta habitual es una CPI create_account del Programa del Sistema donde:
- Un pagador de billetera firma y paga lamports exentos de alquiler para el tamaño de datos previsto.
- La nueva cuenta clave es la PDA (listada en la transacción; no es un firmante de billetera).
- El programa suministra semillas firmantes para que la PDA pueda "firmar" la creación.
- El propietario asignado suele ser tu ID de programa para que solo tú escribas
data.
pagador (billetera) --lamports--> cuenta PDA
programa --invoke_signed(semillas+bump)--> Programa del Sistema create_account
resultado: PDA financiada, dimensionada, propietario = tu programaLos clientes pasan la clave pública de la PDA que derivaron fuera de la cadena. En la cadena, re-derivas y comparas claves (y generalmente el bump canónico) antes de crear o inicializar. Después de la creación, escribe discriminadores y campos para que las instrucciones posteriores puedan rechazar intentos vacíos o de reinicialización.
Firma con invoke_signed
invoke reenvía una instrucción usando solo firmantes externos ya presentes en la transacción. invoke_signed además le dice al runtime: considera la PDA de este programa como firmante si los grupos de semillas proporcionados hashean a la clave de esa cuenta.
// Tupla conceptual de semillas para firmar
let seeds: &[&[u8]] = &[b"vault", owner.key.as_ref(), &[bump]];
invoke_signed(&ix, account_infos, &[seeds])?;Reglas que nunca se relajan:
- El orden, los bytes y el bump de las semillas deben coincidir exactamente con la derivación.
- Solo el programa cuyo ID se usó en la derivación puede firmar exitosamente esa PDA.
- Las metadatos de cuenta de la CPI deben marcar la PDA como firmante cuando el llamado requiera una firma (transferencia del Sistema, transferencia de token con autoridad PDA, etc.).
Usos comunes: mover SOL de una PDA de depósito, autorizar transferencias de SPL Token cuando la cuenta de token propietaria o delegada es una PDA, establecer o ejercer la autoridad de acuñación mantenida por una PDA, y crear cuentas anidadas bajo control del programa.
PDAs como autoridades
Una autoridad es quien un programa (Token, tu aplicación, un mercado) verifica antes de una acción privilegiada. Con las PDAs, ese "quién" es lógica del programa, no una billetera fría:
| Rol | Patrón |
|---|---|
| Configuración / estado global | Semillas como [b"config"] o [b"config", version] |
| Perfil por usuario | [b"user", pubkey_del_usuario] |
| Depósito / escrow | [b"escrow", order_id] o par comprador/vendedor |
| Autoridad de acuñación | [b"mint_auth", mint] luego mint_to de Token vía invoke_signed |
El programa debe controlar cada invoke_signed con verificaciones de negocio (máquina de estados, caducidad, firmante correcto del usuario, cuentas restantes correctas). La PDA no decide; tu manejador de instrucciones lo hace. Una utilidad pública que firma para semillas arbitrarias es efectivamente una clave maestra.
Cómo interactúan las piezas en un flujo
Una liberación de escrow típica:
- El cliente deriva la PDA de escrow + bump con las mismas semillas que documenta el programa.
- La transacción lista al usuario, la PDA de escrow (escribible), las cuentas de token, el Programa de Token, tu programa.
- Tu programa re-deriva la PDA, verifica el propietario y los datos, valida las condiciones de liberación.
- Construye una instrucción de transferencia de token con la PDA como autoridad.
invoke_signedcon el bump almacenado completa la transferencia; el fallo revierte toda la transacción.
La derivación sin verificación está incompleta. La firma sin verificación de condiciones está incompleta. La creación sin alquiler y asignación de propietario está incompleta. El plano es el bucle cerrado.
Consideraciones Avanzadas y Aplicaciones
El diseño de semillas es diseño de API. Los espacios de nombres (b"vault" vs b"meta") evitan colisiones entre roles de cuenta bajo un mismo programa. Incluye la identidad de enlace (usuario, acuñación, ID de mercado) para que la PDA de un usuario no pueda ser sustituida por la de otro. Prefiere codificaciones de ancho fijo para enteros. Documenta las tuplas de semillas para cada lenguaje de cliente que envíes (Rust, TypeScript con @solana/kit 7.0.0, etc.).
Bumps canónicos y CU. Persiste el bump en los datos de la cuenta al inicializar. Las rutas críticas no deben volver a ejecutar un find_program_address completo a menos que sea necesario. Rechaza los bumps del cliente que son válidos pero no canónicos para que los atacantes no puedan mantener cuentas paralelas que hashean bajo las mismas semillas lógicas con un bump más bajo.
Propiedad vs. autoridad. El campo owner de la cuenta sigue siendo un ID de programa (a menudo el tuyo, o Token para cuentas de token). Una PDA puede ser la dirección de una cuenta de datos que tu programa posee, o la clave pública de autoridad almacenada dentro de la cuenta de otro programa (por ejemplo, autoridad de acuñación). No confundas "la PDA posee la cuenta" con "la PDA aparece como autoridad en el estado del Token"; ambos aparecen en diseños reales, y los objetivos de CPI difieren.
Paralelismo. Las PDAs por entidad (un depósito por usuario) mantienen los bloqueos de escritura estrechos para que Sealevel pueda ejecutar usuarios no superpuestos juntos. Una única PDA global para todos los saldos serializa el protocolo.
Actualizaciones y ID de programa. Las PDAs están ligadas al ID del programa. Los cargadores actualizables mantienen la identidad del programa estable a través de actualizaciones de bytecode cuando la identidad es la cuenta del programa, pero redesplegar bajo un nuevo ID de programa deja huérfanas las PDAs antiguas. Trata los esquemas de semillas y la identidad del programa como contratos de larga duración.
| Patrón | Fortaleza | Debilidad | Mejor ajuste |
|---|---|---|---|
| PDA de configuración global | Una dirección conocida | Bloqueo crítico; punto único de contención | Parámetros del protocolo |
| PDA por usuario | Paralelismo; aislamiento claro | Más alquiler; listas de cuentas más largas | Perfiles, posiciones |
| PDA de Escrow | Liberación controlada por programa | Fondos atascados si la lógica se congela | Operaciones condicionales |
| PDA de autoridad de acuñación | Reglas de suministro automatizadas | Errores de acuñación perpetuos | Suministro de tokens gestionado |
Conceptos Erróneos Comunes
- "Una PDA es un tipo de cuenta diferente en cadena." Es una cuenta normal en una dirección especial. Mismos campos, mismas reglas de alquiler, mismo modelo de escritura del propietario.
- "
find_program_addresscrea la cuenta." Solo calcula la dirección y el bump. La creación todavía necesita financiación y asignación/asignación del Programa del Sistema (o equivalente). - "Cualquier bump que funcione está bien." Teóricamente, varios bumps pueden ser válidos; la producción usa el bump canónico (el más alto) y lo almacena para prevenir ataques de direcciones alternativas y desperdicio de CU.
- "El cliente puede pasar cualquier semilla y el programa debería firmar." Las semillas deben estar vinculadas por prefijos elegidos por el programa y verificaciones en cadena. La firma solo de semillas controlada por el usuario es una clase clásica de explotación.
- "Las PDAs firman como billeteras en el mensaje de transacción." Los usuarios no adjuntan una firma de PDA. El programa suministra semillas a
invoke_signed; el runtime marca la PDA como firmante para esa CPI. - "Si verifico la dirección de la PDA, he terminado." También verifica el propietario, el discriminador/layout y las cuentas relacionadas (acuñación de token, ATAs). La igualdad de direcciones por sí sola no prueba que el gráfico completo de cuentas sea seguro.
Preguntas Frecuentes
¿Qué es una dirección derivada por programa en una oración?
Una PDA es una dirección de cuenta determinista y fuera de la curva derivada de semillas y un ID de programa para que el programa pueda controlarla sin una clave privada.
¿Por qué las PDAs deben estar fuera de la curva ed25519?
Para que ninguna clave normal pueda producir firmas para esa dirección. El control se reserva para el programa derivador a través de invoke_signed.
¿Qué devuelve find_program_address?
La clave pública PDA canónica y la semilla bump (la más alta válida de 255 hacia abajo) que hace que la dirección esté fuera de la curva para esas semillas e ID de programa.
¿Cuáles son los límites de las semillas?
Como máximo 16 semillas, cada una de hasta 32 bytes. Mantén los espacios de nombres cortos y empaqueta enteros con codificación fija.
¿Qué es el bump canónico?
El primer bump (el más alto) que find_program_address acepta. Almacénalo al inicializar la cuenta y exígilo para verificaciones y firmas posteriores.
¿Debo llamar a find_program_address en cada instrucción?
Prefiere almacenar el bump y verificar con create_program_address o una comparación de clave esperada única. Las búsquedas completas cuestan más unidades de cómputo en rutas críticas.
¿Cómo creo una cuenta en una PDA?
Deriva y verifica la dirección, luego haz una CPI a create_account (o init de Anchor) con invoke_signed usando la tupla de semillas y el bump, financiada por un pagador al mínimo exento de alquiler para el tamaño de los datos.
¿Por qué invoke_signed falla con MissingRequiredSignature?
Generalmente semillas incorrectas, orden incorrecto, byte de bump faltante, ID de programa incorrecto, o la metadato de CPI no marcó la PDA como firmante cuando era requerido.
¿Puede una instrucción firmar para múltiples PDAs?
Sí. Pasa múltiples grupos de semillas a invoke_signed, un grupo por cada PDA que necesites marcada como firmante.
¿Quién "posee" un depósito PDA de tokens SPL?
La cuenta de token es propiedad del Programa de Token; la PDA del depósito es típicamente la autoridad propietaria de la cuenta de token. Tu programa firma transferencias demostrando las semillas de la PDA.
¿Es una PDA lo mismo que un campo de propietario de cuenta?
No. El propietario es siempre un ID de programa en los metadatos de la cuenta. Una PDA es una dirección que puede identificar una cuenta de datos o aparecer como una clave pública de autoridad dentro del estado de otro programa.
¿Qué es la búsqueda de bump?
Usar un bump no canónico pero válido para abrir una cuenta paralela bajo la misma historia de semillas lógica cuando el programa acepta cualquier bump. Solución: imponer el bump canónico.
¿Cómo descubren los clientes las direcciones de las PDAs?
Ejecutan la misma receta de semillas fuera de la cadena (Rust, o TypeScript a través de ayudantes de @solana/kit 7.0.0) con el ID de programa desplegado, y luego incluyen la clave pública resultante en la lista de cuentas de la transacción.
¿Puede otro programa firmar mi PDA?
No. Solo el ID de programa utilizado en la derivación puede proporcionar semillas firmantes exitosas para esa PDA. Otros programas deben hacer CPI a tu programa si necesitan la autoridad de tu PDA.
¿Qué debe verificar cada instrucción de PDA?
La dirección re-derivada coincide con la clave de la cuenta, el bump es canónico (o coincide con el almacenado), el propietario y el layout/discriminador son los esperados, y las autoridades o cuentas vinculadas en el gráfico son correctas antes de invoke_signed.
Relacionados
- Conceptos básicos de PDA - introducción práctica a las PDAs y semillas
- Derivación de PDAs - semillas,
find_program_addressy mecánicas de bump - Bumps Canónicos - almacenar y hacer cumplir el bump válido más alto
- Creación de Cuentas PDA - asignar, financiar e inicializar estado PDA
- PDAs como Autoridades - depósitos, autoridad de acuñación y patrones de escrow
- Trampas de Seguridad de PDAs - brechas de verificación, búsqueda de bump y riesgos de firma
Versiones de Stack: Esta página fue escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1 y @solana/kit 7.0.0.