O Blueprint de PDA
Uma program-derived address (PDA) é um endereço de conta Solana que um programa pode controlar sem guardar chave privada. Seeds e program ID fixam o endereço; um bump faz ele cair off-curve ed25519 para que nenhum keypair comum possa assinar por ele. Quando você vê derivação, criação e invoke_signed como um pipeline, vaults, escrows e autoridades de mint deixam de parecer tipos especiais de conta e passam a parecer o mesmo modelo de conta com caminho de assinatura controlado pelo programa.
Noções Básicas de PDA é a entrada prática; Derivando PDAs, Bumps Canônicos, Criação de Conta PDA, PDAs como Autoridades e Armadilhas de Segurança de PDA cada um aprofunda um mecanismo. Esta página fica por baixo: como derivação, bumps, criação, assinatura e padrões de autoridade formam um blueprint.
Resumo
- Uma PDA é um endereço determinístico off-curve produzido de seeds + program ID + bump; só o programa dono pode autorizá-la fornecendo essas seeds a
invoke_signed. - Por Que Importa: Programas Solana são executáveis sem estado. PDAs dão endereços duráveis controlados por programa para config, vaults, escrows e autoridade de mint sem embutir chaves secretas on-chain.
- Conceitos-Chave: seeds,
find_program_address, bump / bump canônico, off-curve, criação de conta PDA,invoke_signed, PDA como autoridade, re-derivação on-chain. - Quando Usar: Projetar estado por usuário ou por mint, tesourarias de programa, autoridade mint/freeze de token, fluxos de liberação de escrow, ou descoberta de endereço no cliente antes de montar transações.
- Limitações / Trade-offs: Design de seeds é permanente para um program ID dado; criação ainda precisa de rent e CPI do System Program; seeds ou bumps errados falham de forma obscura; bug em caminhos de assinatura equivale a dar a chave do vault.
- Tópicos Relacionados: Noções básicas de PDA, derivando PDAs, bumps canônicos, criação de conta, autoridades, armadilhas de segurança.
Fundamentos
Contas Solana vivem em chaves públicas. A maioria dos endereços está on-curve: correspondem a um keypair ed25519 que alguém pode guardar. Uma PDA é deliberadamente off-curve: o runtime nunca a tratará como signatário de carteira normal. Esse é o ponto inteiro. O programa que possui a derivação (o program ID em find_program_address) pode depois provar controle reexecutando os bytes de seed na verificação de signatário PDA do runtime.
Nada no registro da conta em si é especial. Após criação, conta PDA ainda tem lamports, data, owner, executable e rent_epoch. O que é especial é como o endereço foi escolhido e como assinaturas são produzidas.
Pense em uma equação de derivação:
seeds: [ "vault", user_pubkey, ... ]
program_id: YourProgram
bump: u8 (try 255, 254, ... until off-curve)
|
v
PDA address ----no private key----> cannot wallet-sign
|
+-- your program may invoke_signed(seeds + bump)Seeds são fatias de bytes ordenadas (no máximo 16 seeds, cada uma no máximo 32 bytes). Peças típicas: namespace constante (b"vault"), pubkey de usuário ou mint (32 bytes) e inteiros com endian fixo (id.to_le_bytes()). Ordem e encoding fazem parte do endereço. Mude qualquer um e você obtém conta diferente.
Program ID é a segunda metade da identidade. As mesmas seeds sob programa diferente derivam PDA diferente. Clientes devem usar o program id implantado, não chute hard-coded de outro ambiente.
Bump é o seed final de um byte que o finder acrescenta enquanto busca ponto off-curve. A busca começa em 255 e desce. O primeiro bump válido é o bump canônico. Programas de produção armazenam esse valor e recusam bumps não canônicos para que endereços alternativos sob as mesmas seeds lógicas não possam ser usados para griefing.
Clientes off-chain e programas on-chain chamam a mesma ideia: hash de seeds, program id e bump até o resultado ser PDA válida. Off-chain, é assim que você preenche a lista de contas da transação. On-chain, é assim que você verifica que conta fornecida pelo cliente é realmente a PDA esperada antes de escrever dados ou assinar CPIs.
Mecânica e Interações
find_program_address
Pubkey::find_program_address(seeds, program_id) retorna (pda, bump) para o endereço canônico. Helpers relacionados (create_program_address) recebem bump explícito e só têm sucesso se aquele bump produzir endereço off-curve.
Conceitualmente:
- Para bump de 255 até 0, hash seeds + bump + program id sob o separador de domínio PDA.
- Pare no primeiro resultado off-curve.
- Retorne aquele endereço e o bump usado.
Nota de custo: on-chain, um find completo pode tentar muitos hashes. Prefira armazenar o bump canônico no init e depois passar &[stored_bump] em signer seeds, ou use create_program_address quando já souber o bump.
Anchor 0.32.1 expõe isso com constraints de conta seeds / bump para o framework re-derivar e verificar por você. Programas nativos devem centralizar um helper assert_pda pequeno.
Criando uma conta PDA
Derivação sozinha não cria estado. Endereço PDA não usado não tem conta até algo financiar, alocar e atribuir. O caminho usual é CPI create_account do System Program onde:
- Um payer de carteira assina e paga lamports isentos de rent para o tamanho de data pretendido.
- A chave da nova conta é a PDA (listada na transação; não é signatário de carteira).
- O programa fornece signer seeds para a PDA "assinar" a criação.
- O owner atribuído é tipicamente seu program id para só você escrever
data.
payer (wallet) --lamports--> PDA account
program --invoke_signed(seeds+bump)--> System Program create_account
result: PDA funded, sized, owner = your programClientes passam a pubkey PDA que derivaram off-chain. On-chain você re-deriva e compara chaves (e geralmente o bump canônico) antes de criar ou inicializar. Após create, escreva discriminators e campos para instruções posteriores rejeitarem tentativas vazias ou de reinit.
Assinando com invoke_signed
invoke encaminha instrução usando só signatários externos já presentes na transação. invoke_signed adicionalmente diz ao runtime: trate a PDA deste programa como signatário se os grupos de seeds fornecidos fizerem hash para a chave daquela conta.
// Conceptual seed tuple for signing
let seeds: &[&[u8]] = &[b"vault", owner.key.as_ref(), &[bump]];
invoke_signed(&ix, account_infos, &[seeds])?;Regras que nunca relaxam:
- Ordem, bytes e bump das seeds devem corresponder exatamente à derivação.
- Só o programa cujo id foi usado na derivação pode assinar com sucesso aquela PDA.
- Os metas de conta do CPI devem marcar a PDA como signatário quando o callee exige assinatura (transferência System, transferência token com autoridade PDA, etc.).
Usos comuns: mover SOL de vault PDA, autorizar transferências SPL Token quando owner ou delegate da conta token é PDA, definir ou exercer autoridade de mint mantida por PDA, e criar contas aninhadas sob controle do programa.
PDAs como autoridades
Autoridade é quem um programa (Token, seu app, marketplace) verifica antes de ação privilegiada. Com PDAs, esse "quem" é lógica de programa, não carteira fria:
| Papel | Padrão |
|---|---|
| Config / estado global | Seeds como [b"config"] ou [b"config", version] |
| Perfil por usuário | [b"user", user_pubkey] |
| Vault / escrow | [b"escrow", order_id] ou par comprador/vendedor |
| Autoridade de mint | [b"mint_auth", mint] depois Token mint_to via invoke_signed |
O programa deve proteger todo invoke_signed com checagens de negócio (máquina de estado, expiração, signatário correto do usuário, remaining accounts corretas). A PDA não decide; seu handler de instrução decide. Helper público que assina para seeds arbitrárias é efetivamente chave mestra.
Como as peças interagem em um fluxo
Liberação típica de escrow:
- Cliente deriva escrow PDA + bump com as mesmas seeds que o programa documenta.
- Transação lista usuário, escrow PDA (writable), contas token, Token Program, seu programa.
- Seu programa re-deriva a PDA, verifica owner e data, valida condições de liberação.
- Monta instrução de transferência token com PDA como autoridade.
invoke_signedcom bump armazenado completa a transferência; falha reverte a transação inteira.
Derivação sem verificação é incompleta. Assinatura sem checagens de condição é incompleta. Criação sem rent e assign de owner é incompleta. O blueprint é o loop fechado.
Considerações Avançadas e Aplicações
Design de seeds é design de API. Namespaces (b"vault" vs b"meta") previnem colisões entre papéis de conta sob um programa. Inclua identidade vinculante (usuário, mint, id de mercado) para PDA de um usuário não ser substituída por outra. Prefira encodings de largura fixa para inteiros. Documente tuplas de seeds para cada linguagem de cliente que você entrega (Rust, TypeScript com @solana/kit 7.0.0, etc.).
Bumps canônicos e CU. Persista o bump nos dados da conta no init. Caminhos quentes não devem reexecutar find_program_address completo a menos que necessário. Rejeite bumps de cliente válidos mas não canônicos para atacantes não manterem contas paralelas que fazem hash sob as mesmas seeds lógicas com bump menor.
Ownership vs autoridade. O campo owner da conta ainda é program id (geralmente o seu, ou Token para contas token). Uma PDA pode ser o endereço de conta de dados que seu programa possui, ou a pubkey de autoridade armazenada dentro da conta de outro programa (ex.: autoridade de mint). Não confunda "PDA possui a conta" com "PDA está listada como autoridade no estado Token"; ambos aparecem em designs reais, e os alvos de CPI diferem.
Paralelismo. PDAs por entidade (um vault por usuário) mantêm locks de escrita estreitos para Sealevel executar usuários não sobrepostos juntos. Uma PDA global única para todos os saldos serializa o protocolo.
Upgrades e program id. PDAs estão amarradas ao program id. Loaders upgradeáveis mantêm identidade de programa estável entre upgrades de bytecode quando identidade é a conta de programa, mas redeploy sob novo program id orfana PDAs antigas. Trate esquemas de seeds e identidade de programa como contratos de longa duração.
| Padrão | Força | Fraqueza | Melhor encaixe |
|---|---|---|---|
| PDA de config global | Um endereço conhecido | Lock quente; ponto único de contenção | Parâmetros de protocolo |
| PDA por usuário | Paralelismo; isolamento claro | Mais rent; listas de conta maiores | Perfis, posições |
| PDA de escrow | Liberação com gate de programa | Fundos presos se lógica congela | Trades condicionais |
| PDA autoridade de mint | Regras automatizadas de supply | Bug minta para sempre | Supply de token gerenciado |
Conceitos Errados Comuns
- "Uma PDA é tipo de conta diferente on-chain." É conta normal em endereço especial. Mesmos campos, mesmas regras de rent, mesmo modelo de escrita por owner.
- "
find_program_addresscria a conta." Só calcula endereço e bump. Criação ainda precisa de System Program (ou equivalente) financiando e allocate/assign. - "Qualquer bump que funciona serve." Múltiplos bumps podem ser teoricamente válidos; produção usa o bump canônico (mais alto) e o armazena para prevenir ataques de endereço alternativo e desperdício de CU.
- "O cliente pode passar qualquer seed e o programa deve assinar." Seeds devem ser vinculadas por prefixos escolhidos pelo programa e checagens on-chain. Assinatura só com seeds controladas pelo usuário é classe clássica de exploit.
- "PDAs assinam como carteiras na mensagem de transação." Usuários não anexam assinatura PDA. O programa fornece seeds a
invoke_signed; o runtime marca a PDA como signatário para aquele CPI. - "Se verifico o endereço PDA, já terminei." Também verifique owner, discriminator/layout e contas relacionadas (mint token, ATAs). Igualdade de endereço sozinha não prova que o grafo completo de contas é seguro.
FAQs
O que é program-derived address em uma frase?
PDA é endereço de conta determinístico off-curve derivado de seeds e program ID para o programa controlá-lo sem chave privada.
Por que PDAs devem estar off-curve ed25519?
Para nenhum keypair comum produzir assinaturas para aquele endereço. Controle fica reservado ao programa derivador via invoke_signed.
O que find_program_address retorna?
A pubkey PDA canônica e o seed bump (mais alto válido de 255 para baixo) que torna o endereço off-curve para aquelas seeds e program id.
Quais são os limites de seeds?
No máximo 16 seeds, cada uma no máximo 32 bytes. Mantenha namespaces curtos e empacote inteiros com endian fixo.
O que é bump canônico?
O primeiro (mais alto) bump que find_program_address aceita. Armazene no init da conta e exija para verificação e assinatura posteriores.
Devo chamar find_program_address em toda instrução?
Prefira armazenar o bump e verificar com create_program_address ou comparação única de chave esperada. Finds completos custam mais compute units em caminhos quentes.
Como crio conta em uma PDA?
Derive e verifique o endereço, depois CPI create_account (ou Anchor init) com invoke_signed usando tupla de seeds e bump, financiado por payer até mínimo isento de rent para o tamanho de data.
Por que invoke_signed falha com MissingRequiredSignature?
Geralmente seeds erradas, ordem errada, byte bump ausente, program id errado, ou meta CPI não marcou PDA como signatário quando exigido.
Uma instrução pode assinar por múltiplas PDAs?
Sim. Passe múltiplos grupos de seeds a invoke_signed, um grupo por PDA que precisa ser marcada como signatário.
Quem "possui" um vault PDA de tokens SPL?
A conta token é owned pelo Token Program; o vault PDA é tipicamente a autoridade owner da conta token. Seu programa assina transferências provando seeds PDA.
PDA é o mesmo que campo owner de conta?
Não. Owner é sempre program id nos metadados da conta. PDA é endereço que pode identificar conta de dados ou aparecer como pubkey de autoridade dentro do estado de outro programa.
O que é bump hunting?
Usar bump não canônico mas válido para abrir conta paralela sob a mesma história lógica de seeds quando o programa aceita qualquer bump. Corrija exigindo bump canônico.
Como clientes descobrem endereços PDA?
Executam a mesma receita de seeds off-chain (Rust, ou TypeScript via helpers @solana/kit 7.0.0) com o program id implantado, depois incluem a pubkey resultante na lista de contas da transação.
Outro programa pode assinar minha PDA?
Não. Só o program id usado na derivação pode fornecer signer seeds com sucesso para aquela PDA. Outros programas devem fazer CPI no seu se precisarem da autoridade da sua PDA.
O que toda instrução PDA deve verificar?
Endereço re-derivado corresponde à chave da conta, bump é canônico (ou corresponde ao armazenado), owner e layout/discriminator são esperados, e quaisquer autoridades ou contas vinculadas no grafo estão corretas antes de invoke_signed.
Related
- Noções Básicas de PDA - introdução prática a PDAs e seeds
- Derivando PDAs - seeds,
find_program_addresse mecânica de bump - Bumps Canônicos - armazene e exija o bump válido mais alto
- Criação de Conta PDA - aloque, financie e inicialize estado PDA
- PDAs como Autoridades - vaults, autoridade de mint e padrões de escrow
- Armadilhas de Segurança de PDA - lacunas de verificação, bump hunting e riscos de assinatura
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1, and @solana/kit 7.0.0.