Verificações de Signatário e Owner
Validação de signatário e owner ausente causa mais exploits de programas Solana do que qualquer outra classe de bug. Toda instrução deve provar que as contas corretas assinaram e pertencem aos programas esperados.
Busque em todas as páginas da documentação
Validação de signatário e owner ausente causa mais exploits de programas Solana do que qualquer outra classe de bug. Toda instrução deve provar que as contas corretas assinaram e pertencem aos programas esperados.
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>,
}
// Verificação manual de signatário na autoridade de uma UncheckedAccount
require!(ctx.accounts.authority.is_signer, ErrorCode::MissingSigner);
// Verificação manual de owner em um mint UncheckedAccount (um mint não assina)
require_keys_eq!(*ctx.accounts.mint.owner, anchor_spl::token::ID, ErrorCode::InvalidOwner);Quando usar isso:
AccountInfo de metadados fornecidos pelo usuário.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<()> {
// Vincula a Pubkey a uma variável local primeiro - `authority.key()` retorna por valor e
// a temporária seria descartada enquanto `seeds` ainda a empresta (E0716).
let authority_key = ctx.accounts.authority.key();
let bump = ctx.accounts.vault.bump;
let seeds: &[&[u8]] = &[b"vault", authority_key.as_ref(), &[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:
Signer em authority impõe aprovação humana.has_one = authority vincula o estado do cofre à pubkey do signatário.invoke_signed.is_signer nas metadados de conta correspondentes.owner nas contas restringe qual programa pode escrever dados.AccountInfo.| 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 |
// Equivalente em programa nativo
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
if vault.owner != program_id {
return Err(ProgramError::IncorrectProgramId);
}has_one ou constraint = authority.key() == vault.authority explícito.CpiContext::new_with_signer.Account<'info, TokenAccount> já verifica se a conta é propriedade do programa SPL Token, e .owner nesse tipo é o campo autoridade do token, não o owner do programa. Correção: Adicione uma verificação na autoridade do token: constraint = vault_token.owner == vault.key().Sysvar<'info, Clock>.| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
Restrição 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 | Autenticação simples de carteira |
Somente se sua instrução permitir explicitamente - nunca assuma que o pagador é igual à autoridade.
Não para transações de usuário - PDAs assinam apenas através de invoke_signed dentro do programa.
Igualdade entre um campo na conta (ex.: vault.authority) e a chave de outra conta.
Raramente - apenas para ler IDs de programa imutavelmente verificados que você então usa require_keys_eq!.
Mapeie cada mutação de estado para os signatários necessários; use grep para UncheckedAccount sem restrições.
Qualquer programa pode ler qualquer conta - verificações de owner impedem escritas não autorizadas e a confiança na desserialização de tipos.
O delegado pode mover tokens até delegated_amount - valide o signatário delegado em transferências delegadas.
A sintaxe de restrição é estável - sempre fixe anchor-lang 0.32.1 para corresponder ao manifest para auditorias reproduzíveis.
Sim, se a instrução for verdadeiramente somente leitura e não puder prejudicar outros - ainda assim valide a propriedade da conta para parsing.
Testes LiteSVM 0.6.x com is_signer: false na metadada de autoridade - espere MissingRequiredSignature.
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.
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026