Comprobaciones de Signatario y Propietario
La falta de validación de signatario y propietario causa más exploits de programas de Solana que cualquier otra clase de error. Cada instrucción debe probar que las cuentas correctas firmaron y pertenecen a los programas esperados.
Receta
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
#[derive(Accounts)]
pub struct TransferOut<'info> {
#[account(mut, has_one = authority)]
pub vault: Account<'info, Vault>,
pub authority: Signer<'info>,
}
// Comprobación manual para UncheckedAccount
require!(ctx.accounts.mint.is_signer, ErrorCode::MissingSigner);
require_keys_eq!(*ctx.accounts.mint.owner, anchor_spl::token::ID, ErrorCode::InvalidOwner);Cuándo usar esto:
- Cualquier instrucción que mueva lamports o tokens.
- Cambios de autoridad y actualizaciones de configuración.
- Aceptar
AccountInfode metadatos proporcionados por el usuario. - Revisar hallazgos de auditoría sobre control de acceso.
Ejemplo de Trabajo
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, Transfer};
#[account]
pub struct Vault {
pub authority: Pubkey,
pub bump: u8,
}
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(
mut,
seeds = [b"vault", authority.key().as_ref()],
bump = vault.bump,
has_one = authority,
)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub vault_token: Account<'info, TokenAccount>,
#[account(mut)]
pub destination: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
pub token_program: Program<'info, Token>,
}
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
let seeds = &[b"vault", ctx.accounts.authority.key().as_ref(), &[ctx.accounts.vault.bump]];
let signer = &[&seeds[..]];
let cpi = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.vault_token.to_account_info(),
to: ctx.accounts.destination.to_account_info(),
authority: ctx.accounts.vault.to_account_info(),
},
signer,
);
token::transfer(cpi, amount)?;
Ok(())
}Lo que esto demuestra:
Signerenauthorityfuerza la aprobación humana.has_one = authorityvincula el estado de la bóveda con la clave pública del signatario.- PDA firma la transferencia de tokens a través de las semillas de
invoke_signed.
Análisis Profundo
Cómo Funciona
- La transacción incluye una firma Ed25519 por cada clave firmante.
- El runtime establece
is_signeren las metadatos de cuenta coincidentes. - El campo
owneren las cuentas restringe qué programa puede escribir datos. - Las macros de Anchor codifican patrones comunes de signatario/propietario; el código nativo utiliza comprobaciones de
AccountInfo.
Matriz de Comprobación
| Tipo de cuenta | Comprobación de signatario | Comprobación de propietario |
|---|---|---|
Signer<'info> | Automática | N/A |
Account<'info, T> | Manual si es necesario | Automática (tu programa) |
Account<'info, TokenAccount> | Manual | Programa de tokens |
UncheckedAccount | Se requiere manual | Se requiere manual |
Notas de Rust
// Equivalente de programa nativo
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
if vault.owner != program_id {
return Err(ProgramError::IncorrectProgramId);
}Trampas Comunes
- Signatario en la cuenta incorrecta - El pagador de tarifas firma pero la autoridad no. Solución:
has_oneoconstraint = authority.key() == vault.authorityexplícito. - PDA como autoridad sin invoke_signed - CPI falla o usa un signatario incorrecto. Solución: Pasar semillas a
CpiContext::new_with_signer. - Propietario de la cuenta de token - El token de la bóveda es propiedad del programa de tokens, no del tuyo. Solución: Validar
vault_token.owner == token_program::IDa través de los tipos SPL de Anchor. - Sysvar como UncheckedAccount - El atacante pasa una sysvar falsa. Solución: Usar cuentas tipadas
Sysvar<'info, Clock>. - Multifirma - La multifirma SPL requiere metadatos de signatario adicionales. Solución: Coincidir con el diseño de multifirma del programa de tokens.
- Restauración de cuenta cerrada - Una cuenta cerrada puede ser reasignada en la misma transacción en algunos patrones. Solución: Ver las protecciones de reinicialización.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
Restricción has_one | Campo de clave pública única en el estado | Lógica de autoridad compuesta |
| PDA de autoridad personalizada | Custodia controlada por el programa | El usuario debe firmar directamente |
| CPI del programa de gobernanza | Actualizaciones controladas por DAO | Autenticación simple de billetera |
Preguntas Frecuentes
¿El pagador de tarifas cuenta como autoridad?
Solo si tu instrucción lo permite explícitamente; nunca asumas que el pagador es igual a la autoridad.
¿Puede un PDA ser un Signer en la macro de cuenta?
No para transacciones de usuario; los PDA firman solo a través de invoke_signed dentro del programa.
¿Qué comprueba has_one?
Igualdad entre un campo de la cuenta (por ejemplo, vault.authority) y la clave de otra cuenta.
¿Es AccountInfo alguna vez seguro sin comprobaciones?
Raramente; solo para leer de forma inmutable IDs de programa verificados que luego usas require_keys_eq!.
¿Cómo audito la cobertura de signatarios?
Mapea cada mutación de estado a los signatarios requeridos; busca UncheckedAccount sin restricciones.
¿Las comprobaciones de propietario detienen la lectura de datos?
Cualquier programa puede leer cualquier cuenta; las comprobaciones de propietario evitan escrituras no autorizadas y la confianza en la deserialización de tipos.
¿Qué pasa con los delegados de token?
El delegado puede mover tokens hasta delegated_amount; valida el delegado como signatario en transferencias delegadas.
¿Cómo difiere Anchor 0.32.1?
La sintaxis de restricciones es estable; siempre fija anchor-lang 0.32.1 para que coincida con el manifiesto para auditorías reproducibles.
¿Puedo omitir el signatario para ix de solo vista?
Sí, si la instrucción es verdaderamente de solo lectura y no puede perjudicar a otros; aún valida la propiedad de la cuenta para el análisis.
¿Cómo pruebo la falta de signatario?
Pruebas LiteSVM 0.6.x con is_signer: false en los metadatos de la autoridad; espera MissingRequiredSignature.
Relacionado
- Conceptos Básicos de Seguridad - introducción al modelo de amenazas
- Ataques de Validación de Cuentas - confusión de tipos
- Ataques de PDA y Semillas - autoridad de PDA
- Catálogo de Ataques de Sealevel - lista de verificación completa
Versiones de Stack: Esta página fue escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, y LiteSVM 0.6.x.