Verificações de Signatário e Owner
Validação de signatário e owner ausente causa mais exploits de programas Solana que qualquer outra classe de bug. Toda instrução deve provar que as contas certas assinaram e pertencem aos programas esperados.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
#[derive(Accounts)]
pub struct TransferOut<'info> {
#[account(mut, has_one = authority)]
pub vault: Account<'info, Vault>,
pub authority: Signer<'info>,
}
// Manual check for UncheckedAccount
require!(ctx.accounts.mint.is_signer, ErrorCode::MissingSigner);
require_keys_eq!(*ctx.accounts.mint.owner, anchor_spl::token::ID, ErrorCode::InvalidOwner);Quando usar isso:
- Qualquer instrução movendo lamports ou tokens.
- Mudanças de autoridade e atualizações de config.
- Aceitar
AccountInfode metas fornecidas pelo usuário. - Revisar achados de auditoria em controle de acesso.
Exemplo de Trabalho
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(())
}O que isso demonstra:
Signeremauthorityaplica aprovação humana.has_one = authorityvincula estado do cofre à pubkey do signatário.- PDA assina transferência de token via sementes
invoke_signed.
Mergulho Profundo
Como Funciona
- Transação inclui assinatura Ed25519 por chave assinante.
- Runtime define
is_signerem metas de conta correspondentes. - Campo
ownerem contas restringe qual programa pode escrever dados. - Macros Anchor codificam padrões comuns de signatário/owner; código nativo usa verificações
AccountInfo.
Matriz de Verificação
| Tipo de conta | Verificação de signatário | Verificação de owner |
|---|---|---|
Signer<'info> | Automática | N/A |
Account<'info, T> | Manual se necessário | Automática (seu programa) |
Account<'info, TokenAccount> | Manual | Programa Token |
UncheckedAccount | Manual obrigatória | Manual obrigatória |
Notas de Rust
// Native program equivalent
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
if vault.owner != program_id {
return Err(ProgramError::IncorrectProgramId);
}Armadilhas
- Signatário na conta errada - Pagador de taxa assina mas autoridade não. Correção:
has_oneouconstraint = authority.key() == vault.authorityexplícito. - PDA como autoridade sem invoke_signed - CPI falha ou usa signatário errado. Correção: Passe sementes para
CpiContext::new_with_signer. - Owner de conta de token - Token do cofre pertence ao programa Token, não ao seu. Correção: Valide
vault_token.owner == token_program::IDvia tipos SPL do Anchor. - Sysvar como UncheckedAccount - Atacante passa sysvar falso. Correção: Use contas tipadas
Sysvar<'info, Clock>. - Multisig - Multisig SPL requer metas de signatário extras. Correção: Corresponda layout multisig do programa Token.
- Revival de conta fechada - Conta fechada pode ser realocada na mesma tx em alguns padrões. Correção: Veja guardas de reinicialização.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
Constraint has_one | Campo pubkey único no estado | Lógica de autoridade composta |
| PDA de autoridade customizada | Custódia controlada pelo programa | Usuário deve assinar diretamente |
| CPI de programa de governança | Upgrades controlados por DAO | Auth simples de carteira |
FAQs
O pagador de taxa conta como autoridade?
Somente se sua instrução permitir explicitamente - nunca assuma payer == authority.
Uma PDA pode ser Signer na macro de conta?
Não para txs de usuário - PDAs assinam somente via invoke_signed dentro do programa.
O que has_one verifica?
Igualdade entre um campo na conta (ex.: vault.authority) e a chave de outra conta.
AccountInfo é seguro sem verificações?
Raramente - somente para ler IDs de programa imutavelmente verificados que você depois require_keys_eq!.
Como audito cobertura de signatário?
Mapeie cada mutação de estado para signatários obrigatórios; grep por UncheckedAccount sem constraints.
Verificações de owner impedem leitura de dados?
Qualquer programa pode ler qualquer conta - verificações de owner impedem escritas não autorizadas e confiança em deserialização de tipo.
E signatários delegate de token?
Delegate pode mover tokens até delegated_amount - valide signatário delegate em transferências delegadas.
Como o Anchor 0.32.1 difere?
Sintaxe de constraint é estável - sempre fixe anchor-lang 0.32.1 para corresponder ao manifest para auditorias reproduzíveis.
Posso pular signatário para ix somente leitura?
Sim se instrução verdadeiramente read-only e não pode prejudicar outros - ainda valide propriedade de conta para parsing.
Como testo signatário ausente?
Testes LiteSVM 0.6.x com is_signer: false na meta authority - espere MissingRequiredSignature.
Relacionados
- Noções Básicas de Segurança - intro ao modelo de ameaça
- Ataques de Validação de Conta - confusão de tipo
- Ataques de PDA e Semente - autoridade PDA
- Catálogo de Ataques Sealevel - checklist completo
Versões da stack: Esta página foi 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 e LiteSVM 0.6.x.