Ataques de PDA e Semente
Program Derived Addresses (PDAs) ancoram estado de protocolo a sementes determinísticas. Erros de semente e bump permitem que atacantes impersonem cofres, contornem verificações de autoridade ou colidam com PDAs de outros programas.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
#[account(
init,
payer = payer,
space = 8 + State::INIT_SPACE,
seeds = [b"state", user.key().as_ref()],
bump,
)]
pub state: Account<'info, State>,
// Store bump on account for later invokes
state.bump = ctx.bumps.state;Quando usar isso:
- Criar PDAs de cofre ou escrow.
- Assinar CPIs com
invoke_signed. - Auditar constraints
seedsem#[account]. - Migrar esquemas de semente sem órfãos de fundos.
Exemplo de Trabalho
use anchor_lang::prelude::*;
#[account]
pub struct Escrow {
pub maker: Pubkey,
pub bump: u8,
}
#[derive(Accounts)]
pub struct InitEscrow<'info> {
#[account(mut)]
pub maker: Signer<'info>,
#[account(
init,
payer = maker,
space = 8 + Escrow::INIT_SPACE,
seeds = [b"escrow", maker.key().as_ref()],
bump,
)]
pub escrow: Account<'info, Escrow>,
pub system_program: Program<'info, System>,
}
pub fn init_escrow(ctx: Context<InitEscrow>) -> Result<()> {
ctx.accounts.escrow.maker = ctx.accounts.maker.key();
ctx.accounts.escrow.bump = ctx.bumps.escrow;
Ok(())
}O que isso demonstra:
- Sementes incluem pubkey do usuário - cada usuário recebe PDA de escrow única.
bumpno init armazena bump canônico parainvoke_signedposterior.seeds+bumpem instruções subsequentes previnem substituição PDA.
Mergulho Profundo
Como Funciona
- PDAs são pubkeys off-curve derivadas de
(seeds, program_id). - Runtime encontra bump canônico (255 até 0) onde endereço é off-curve.
- Somente o programa proprietário pode assinar para PDA via
invoke_signedcom sementes exatas. - Conjuntos de semente errados derivam endereços diferentes - atacante fornece contas parecidas.
Padrões de Ataque
| Padrão | Risco | Mitigação |
|---|---|---|
| Bump ausente em sementes | PDA errada aceita | bump ou bump = state.bump |
| Somente sementes controladas pelo usuário | Colisões previsíveis | Inclua prefixos de bytes escolhidos pelo programa |
| Canonicalização de semente | Múltiplos bumps | Sempre use bump canônico de find_program_address |
| PDA cross-program | Mesmas sementes, programa diferente | Nunca reutilize strings de semente entre programas |
Notas de Rust
let seeds = &[b"escrow", maker.as_ref(), &[escrow.bump]];
let signer_seeds = &[&seeds[..]];
// Pass signer_seeds to CpiContext::new_with_signerArmadilhas
- Bump não armazenado - Rebuscar bump a cada tx desperdiça CU e arrisca mismatch. Correção: Persista bump no init.
- Sementes opcionais do usuário - Atacante escolhe sementes mapeando para sua conta. Correção: Fixe bytes de prefixo de protocolo; limite entrada do usuário a pubkeys.
- init_if_needed em PDA - Reinit redefine estado. Correção: Use
inituma vez + flagis_initialized. - Confusão UTF-8 de semente -
"escrow"vsb"escrow". Correção: Sempre literais de bytesb"...". - Comprimento máximo de semente - Sementes totais > 32 bytes por limite de componente. Correção: Hash entradas longas com
hashvem 32 bytes. - PDA como autoridade de token - Deve assinar CPI de token com sementes corretas. Correção: Corresponda owner da conta de token à PDA.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| PDA global única | Config singleton | Estado por usuário |
| Conta de propriedade de keypair | Signatário externo necessário | Custódia deve permanecer no programa |
| Registro de semente nomeada | Muitos tipos PDA | Escrow simples único |
FAQs
Usuários podem derivar minha PDA no cliente?
Sim - PDAs são públicas. Segurança está em validação de sementes e regras de signatário, não em sigilo.
O que é canonicalização de bump seed?
Solana escolhe o bump válido mais alto (geralmente 255) - sempre use esse bump em invoke_signed.
Dois programas podem compartilhar endereço PDA?
Não - program_id faz parte da derivação; mesmas sementes + programa diferente = endereço diferente.
Como migro sementes PDA?
Dual-write, migre fundos, deprecie caminho de semente antigo - nunca mude sementes com fundos ativos sem plano.
O que é ataque de colisão de semente?
Atacante passa conta não-PDA correspondendo à chave esperada sem suas sementes - prevenido por constraint seeds.
Quantas sementes posso usar?
Até 16 componentes de semente, máx 32 bytes cada - veja limites do Solana SDK.
Anchor encontra bump automaticamente?
bump sem valor encontra e armazena em ctx.bumps durante init; use bump = account.bump depois.
Como testo bump errado?
Testes LiteSVM com bump incorreto em invoke_signed - espere falha de verificação de assinatura.
PDAs podem possuir contas de token?
Sim - owner da conta de token é a PDA; programa assina transferências com invoke_signed.
Sementes de endereço de programa são seguras?
Incluir crate::ID em sementes previne impersonação cross-deployment quando program id muda por cluster.
Relacionados
- Verificações de Signatário e Owner - assinatura PDA
- Ataques de Reinicialização - reinit PDA
- Testando CPIs e PDAs - testes PDA
- Program Derived Addresses - fundamentos PDA
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.