Puntos Clave de los PDA en Anchor
Una Dirección Derivada por Programa (PDA) es una clave pública derivada de bytes de semilla y un ID de programa que se encuentra fuera de la curva ed25519, por lo que no tiene clave privada. Tu programa Anchor es el único actor que puede autorizar acciones para esa dirección, demostrando las semillas y el bump al runtime durante una CPI.
Esta página es el mapa conceptual para PDA, Semillas de Firma y Anchor: derivación impulsada por restricciones, almacenamiento de bumps, firma de PDA, patrones de autoridad, derivación del lado del cliente y los modos de fallo que aparecen en producción en Agave 4.1.1.
Resumen
- Anchor convierte los PDA en cuentas validadas y deterministas: las restricciones
seedsybumpprueban la identidad de la dirección en cada instrucción, y la misma tupla de semillas es lo que el programa usa más tarde para firmar CPIs. - Por qué Importa: Las cuentas de depósito en garantía (escrow), bóvedas, acuñaciones (mints), cuentas de configuración y estados por usuario casi siempre residen en PDA. Semillas incorrectas, restricciones faltantes o semillas de firma rotas significan tokens atascados, estados falsificados o deriva silenciosa de direcciones entre el cliente y el programa.
- Conceptos Clave: seeds (semillas), canonical bump (bump canónico),
find_program_address/create_program_address,#[account(seeds, bump)],ctx.bumps, stored bump (bump almacenado),CpiContext::new_with_signer, PDA authority (autoridad PDA), client seed parity (paridad de semillas del cliente). - Cuándo Usar Este Modelo: Diseñar grafos de cuentas de Anchor que necesiten direcciones deterministas, custodia del programa o fragmentación por entidad; conectar clientes TypeScript con @solana/kit 7.0.0; auditar instrucciones que tocan bóvedas o autoridades de acuñación.
- Limitaciones / Compensaciones: Los PDA no pueden ser cuentas
Signernormales; las fórmulas de semillas son permanentes una vez que existen las cuentas; las matrices de semillas y la búsqueda de bump consumen CU; los clientes y el código en cadena deben ser byte a byte idénticos en semillas y ID de programa. - Temas Relacionados: PDAs en Anchor, Firmar con PDA, Almacenar Bumps, Autoridades PDA, Derivación de PDA del Lado del Cliente, Errores Comunes de PDA.
Fundamentos
Los programas de Solana no contienen el estado de la aplicación dentro de su ejecutable. Los registros duraderos residen en cuentas que el programa posee. Un PDA es un tipo especial de dirección para esas cuentas (y para roles de firma puros que nunca contienen datos): se calcula, no se genera a partir de un par de claves.
(address, bump) = find_program_address(seeds, program_id)find_program_address prueba valores de bump desde 255 hacia abajo hasta que el punto está fuera de la curva. Ese primer valor válido es el bump canónico. create_program_address verifica un candidato con un bump conocido; no busca.
Debido a que no existe una clave privada para una dirección fuera de la curva, una firma de transacción normal no puede autorizar esa clave. Solo un programa que proporcione las semillas y el bump correctos (bajo el ID de programa utilizado en la derivación) puede marcar el PDA como firmante en una instrucción anidada a través de invoke_signed / Anchor with_signer.
En Anchor 0.32.1 declaras esa verificación en lugar de implementarla manualmente:
#[account(
seeds = [b"escrow", maker.key().as_ref(), escrow_id.to_le_bytes().as_ref()],
bump,
)]
pub escrow: Account<'info, Escrow>,Anchor re-deriva el PDA a partir de esas semillas y declare_id!, luego requiere que la clave de cuenta pasada coincida. bump = account.bump opcional utiliza un byte almacenado para que la validación no vuelva a buscar. Las rutas de inicialización usan init, payer, space, y las mismas semillas para que la cuenta se cree en la dirección derivada en un solo paso.
Un PDA no es un Signer<'info> en la estructura de cuentas. Los firmantes de par de claves prueban su presencia con una firma de transacción; los PDA prueban su presencia con semillas. Usa Account, SystemAccount u otros envoltorios tipados con seeds / bump, y firma solo al emitir una CPI.
Mecánicas e Interacciones
Restricciones como la verdad en cadena
Cada instrucción que toca un PDA debe reafirmar las semillas. Los clientes eligen qué clave pública pasar; sin restricciones, un atacante puede sustituir una cuenta parecida que tu manejador trata como la bóveda canónica o el registro de usuario.
| Preocupación | Mecanismo de Anchor | Qué impone |
|---|---|---|
| Identidad de la dirección | seeds = [...], bump / bump = ... | La clave pasada es igual al PDA para el ID del programa + semillas |
| Creación | init, payer, space, seeds | Asignar y reservar espacio en la dirección derivada |
| Mutación | mut + seeds | Mismas comprobaciones de identidad, indicador de escritura |
| Cuerpo tipado | Account<'info, T> | Propietario, discriminador, deserialización |
| Firma CPI | new_with_signer + slices de semillas | El PDA aparece como firmante para los programas llamados |
Mantén los componentes de las semillas pequeños y estables: un prefijo estático (b"vault"), una clave pública distintiva y quizás un ID en little-endian. Las pilas de semillas largas o variables queman CU e invitan a errores del cliente. Documenta la tupla de semillas junto al tipo de cuenta para que Rust, las pruebas y TypeScript se mantengan alineados.
Almacenar bumps
En la inicialización inicial, Anchor expone el mapa de bump resuelto como ctx.bumps.<nombre_campo>. Escribe eso en los datos de la cuenta:
ctx.accounts.vault.bump = ctx.bumps.vault;Las instrucciones posteriores usan bump = vault.bump en la restricción y el mismo byte en las semillas de firma CPI. Beneficios:
- CU: no hay búsqueda repetida de bump en rutas críticas.
- Determinismo: la CPI siempre usa el bump que hizo que la dirección fuera válida en el momento de la creación.
- Claridad: un campo es la fuente de verdad para la firma.
No aceptes un bump proporcionado por el cliente como argumento de instrucción para comprobaciones críticas de seguridad. Prefiere el almacenamiento en cadena y las restricciones. Si las cuentas heredadas no tienen un campo de bump, agrega una instrucción de migración que recalcule con find_program_address una vez y escriba el resultado; no inventes un bump.
Consulta Almacenar Bumps para ver patrones y notas de migración.
Firmar con PDA
Cuando el PDA debe autorizar una CPI (transferencia de tokens desde un ATA de bóveda, mint_to, transferencia del sistema de lamports en posesión de un PDA, etc.), construye semillas de firma que coincidan con las semillas de la restricción, con el bump como la semilla final de un byte:
let bump = ctx.accounts.vault.bump;
let seeds: &[&[u8]] = &[b"vault", authority.key().as_ref(), &[bump]];
let signer_seeds = &[seeds];
token::transfer(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
token::Transfer {
from: ctx.accounts.vault_ata.to_account_info(),
to: ctx.accounts.user_ata.to_account_info(),
authority: ctx.accounts.vault.to_account_info(),
},
signer_seeds,
),
amount,
)?;El orden y los bytes de las semillas deben coincidir con la inicialización y las restricciones. El metadato de autoridad en la CPI debe ser la información de la cuenta PDA, no una billetera de usuario. Un PDA puede firmar múltiples CPIs en la misma instrucción con las mismas semillas. Un bump incorrecto o una semilla faltante fallan la CPI como una firma faltante (toda la transacción se revierte). Equivalente nativo: invoke_signed. Detalles en Firmar con PDA.
Autoridades PDA
Los programas usan PDA como autoridades cuando el protocolo, no un par de claves humano, debe controlar activos o suministro:
- Bóveda / PDA de depósito en garantía posee un ATA que contiene depósitos; las CPIs de retiro usan las semillas de la bóveda.
- PDA de Tesorería recibe tarifas; las rutas de gasto requieren reglas impuestas por el programa más semillas de firma.
- PDA de autoridad de acuñación acuña recompensas o elementos de juego; opcionalmente, renuncia más tarde estableciendo la autoridad de acuñación en
Nonea través de CPI cuando el diseño congela el suministro.
Siempre restringe las relaciones de acuñación y ATA (token::authority, associated_token::authority, igualdad de claves de acuñación) para que una firma PDA válida no pueda operar sobre la acuñación o cuenta de token incorrecta. Prefiere PDA divididos por mercado, pool o característica en lugar de un PDA de autoridad global: menor radio de explosión si aparece un error en una ruta, y mejor paralelismo de Sealevel cuando diferentes mercados escriben claves diferentes.
Consulta Autoridades PDA.
Derivación del lado del cliente
Los clientes deben pasar las mismas claves públicas que el programa re-derivará. Con @solana/kit 7.0.0, usa getProgramDerivedAddress (o ayudantes generados) con:
- La dirección del programa de la IDL /
declare_id!(no una clave de despliegue aleatoria de un archivo de entorno antiguo). - Bytes de semilla idénticos a los de la cadena: prefijos de cadena como bytes ASCII que coincidan con
b"...", claves públicas como 32 bytes crudos, enteros como little-endian (paridadto_le_bytes). - El mismo orden de semillas.
const [escrowPda] = await getProgramDerivedAddress({
programAddress: programId,
seeds: [
new TextEncoder().encode("escrow"),
getBytesEncoder().encode(makerAddress),
// u64 little-endian que coincide con Rust to_le_bytes
u64ToLeBytes(escrowId),
],
});Comparte constantes o genera constructores de semillas a partir de la IDL para que TypeScript no se desvíe de Rust. Las pruebas de integración deben afirmar que los PDA derivados por el cliente son iguales a las direcciones creadas en las pruebas de Anchor. Consulta Derivación de PDA del Lado del Cliente.
Consideraciones Avanzadas y Aplicaciones
PDA de programas externos. Cuando haces CPI a otro programa que posee un PDA, pasa esa dirección pero no firmes por ella a menos que seas ese programa. seeds::program de Anchor valida los PDA extranjeros contra otro ID de programa. Tu programa solo firma PDA derivados bajo tu ID de programa.
Inicialización vs. Re-inicialización. Las semillas fijan una ranura en el espacio de direcciones. Después de cerrar, las mismas semillas pueden reabrir la misma dirección; diseña las rutas de cierre cuidadosamente si la lógica asume "una vez inicializado, siempre confiable".
Fragmentación y CU. Los PDA por usuario mantienen los bloqueos de escritura disjuntos bajo Sealevel. Mantén la configuración global de solo lectura en rutas críticas. Almacena los bumps, mantén las semillas cortas y simula antes de mainnet: la búsqueda de bump y las pilas de CPI profundas cuestan CU.
Ciclo de vida de la autoridad. Mueve la autoridad de acuñación o congelación a un PDA solo en una inicialización controlada. Dejar una autoridad de acuñación de par de claves después de un despliegue de "bóveda PDA" es un error de seguridad.
Matriz de pruebas. Para cada tipo de PDA: inicialización en el camino feliz, semillas de cliente incorrectas, ID de programa incorrecto, CPI con semillas de firma buenas y malas, y desajuste de autoridad de token. Los casos de fallo se catalogan en Errores Comunes de PDA.
Conceptos Erróneos Comunes
- "Un PDA es solo una cuenta normal con un nombre elegante." Es una dirección fuera de la curva; la identidad y la firma dependen de las semillas y el ID del programa, no de una clave privada.
- "Si paso la clave pública correcta desde el cliente, las restricciones de semillas son opcionales." Sin re-derivación en cadena, el cliente elige la confianza. Los atacantes pasan cuentas parecidas.
- "Los PDA pertenecen a
Signer<'info>porque firman CPIs." Los firmantes a nivel de transacción son pares de claves. Los PDA se convierten en firmantes solo para invocaciones anidadas a través de prueba de semillas. - "Puedo cambiar las fórmulas de semillas en una actualización y mantener las mismas cuentas." Las direcciones están fijadas por las semillas y el ID del programa. Las nuevas fórmulas son nuevas direcciones; planifica la migración.
- "El bump es un secreto." El bump es público y recuperable; la seguridad proviene de la lógica del programa y las restricciones, no de la confidencialidad del bump. Aun así, almacena y reutiliza el bump canónico para CU y consistencia.
- "La endianidad del cliente no importa para las semillas numéricas."
to_le_bytesde Rust y los buffers big-endian de JS producen diferentes PDA. Coincide explícitamente con LE. - "Un PDA de autoridad compartido es más simple y igual de seguro." Más simple hasta que un error agota todos los mercados. Prefiere PDA con ámbito cuando la escala o el aislamiento importan.
- "
with_signeres solo para Token." Cualquier CPI que requiera el PDA como firmante (Programa del Sistema, Token, Token-2022, llamados personalizados) necesita semillas de firma. - "UncheckedAccount está bien para los PDA durante el desarrollo." Es la ruta predeterminada a la falta de comprobaciones de propiedad y semillas. Usa cuentas tipadas con semillas desde el primer borrador.
- "find_program_address en cada CPI es gratis." Es correcto pero cuesta CU; almacena el bump después de la inicialización para rutas críticas.
Preguntas Frecuentes
¿Qué es un PDA en una oración?
Una dirección determinista fuera de la curva derivada de semillas y un ID de programa, que no tiene clave privada y solo puede ser firmada por ese programa a través de invoke_signed / Anchor with_signer.
¿Qué versión de Anchor y pila de cliente asume esta sección?
Anchor 0.32.1 en cadena, Rust 1.91.1, herramientas Agave 4.1.1 / Solana CLI 3.0.10, y @solana/kit 7.0.0 para derivación del cliente y construcción de transacciones.
¿Cómo funcionan las restricciones de semillas y bump de Anchor?
Re-derivan la dirección del programa a partir de las expresiones de semillas listadas y el bump esperado, luego requieren que la clave pública de la cuenta coincida. La inicialización crea la cuenta en esa dirección; las instrucciones posteriores re-verifican la identidad cada vez.
¿Se puede declarar un PDA como Signer en una estructura de Cuentas?
No. Usa una cuenta tipada (o cuenta del sistema) con seeds y bump. Proporciona semillas de firma solo al construir una CPI que necesite el PDA como autoridad.
¿Dónde debe vivir el bump?
En los datos de la cuenta, establecido una vez en la inicialización desde ctx.bumps, luego referenciado con bump = account.bump y en las matrices de semillas de CPI. Ver Almacenar Bumps.
¿Cómo mueve tokens un PDA?
El PDA (o un ATA propiedad del PDA) contiene los tokens; el programa hace CPI al programa Token con CpiContext::new_with_signer usando las semillas de la bóveda para que el PDA sea la autoridad. Ver Firmar con PDA y Autoridades PDA.
¿Qué ID de programa se usa al derivar PDA para mi programa Anchor?
El ID del programa de declare_id! / la dirección del programa desplegado en la IDL. El código del cliente debe usar esa misma ID, no la clave de otro programa.
¿Las matrices de semillas incluyen el bump?
En la sintaxis de restricciones de Anchor, el bump está separado (bump o bump = ...). Para create_program_address y las semillas de firma CPI, el bump es la semilla final de un byte añadida a la lista de semillas.
¿Cómo depuro fallos de `ConstraintSeeds` o de firma CPI?
Registra o imprime la dirección derivada por el cliente, las semillas esperadas en cadena, el ID del programa y el bump. Compara el orden de las semillas, la codificación de cadenas, la endianidad de enteros y si la cuenta de autoridad de la CPI es el PDA. Ver Errores Comunes de PDA.
¿Son las cuentas PDA exentas de alquiler (rent-exempt)?
Los PDA que contienen datos necesitan lamports exentos de alquiler para su space, financiados por el payer en init. Los PDA de solo firma aún necesitan una planificación deliberada de lamports dependiendo del patrón.
¿Puede un PDA firmar múltiples CPIs en una sola instrucción?
Sí. Reutiliza las mismas porciones de semillas de firma para cada llamada a new_with_signer.
¿Cómo debe TypeScript derivar el mismo PDA que Rust?
Usa @solana/kit 7.0.0 getProgramDerivedAddress con bytes de semilla idénticos, orden, enteros little-endian y dirección del programa. Prefiere constantes compartidas o generación de código. Ver Derivación de PDA del Lado del Cliente.
¿Cuándo debería dividir las autoridades PDA en lugar de una autoridad global?
Cuando tienes múltiples mercados o dominios de riesgo, o quieres que Sealevel ejecute flujos no superpuestos en paralelo. Una ruta de autoridad global escribible serializa el tráfico y concentra el impacto de la explotación.
¿Cambia la actualización del programa las direcciones de los PDA?
No, si el ID del programa y las fórmulas de semillas permanecen iguales. Las actualizaciones solo cambian el bytecode de ProgramData. Nuevas semillas o un nuevo ID de programa generan nuevos PDA.
Relacionados
- PDAs en Anchor - semillas, bump y restricciones de inicialización
- Firmar con PDA -
with_signery porciones de semillas CPI - Almacenar Bumps - persistencia y reutilización del bump canónico
- Autoridades PDA - bóvedas, tesorerías y autoridades de acuñación
- Derivación de PDA del Lado del Cliente - coincidencia de semillas en TypeScript con kit
- Errores Comunes de PDA - desajustes de semillas, restricciones faltantes, errores de firma
Versiones de la pila: 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.