Resumo
Os PDAs são derivados através do hash de seeds + ID do programa + bump via SHA-256 até que o resultado esteja fora da curva Ed25519. O bump canônico é o primeiro valor que produz um endereço fora da curva. Máximo de 16 seeds, máximo de 32 bytes por seed.
Contexto
Os valores de
Keypair
da Solana são pontos na curva Ed25519. Um keypair
consiste numa chave pública (usada como endereço da conta) e numa chave secreta
(usada para produzir assinaturas). Qualquer pessoa com a chave secreta pode
assinar transações para esse endereço.
Duas contas com endereços na curva
Um PDA é intencionalmente derivado para ficar fora da curva Ed25519. Como não
é um ponto válido da curva, não existe chave secreta e nenhuma parte externa
pode produzir uma assinatura. Apenas o programa que deriva pode autorizar
operações no PDA através de invoke_signed.
Endereço fora da curva
Contas PDA vs keypair
| Propriedade | Conta keypair | Conta PDA |
|---|---|---|
| Tipo de endereço | Na curva Ed25519 | Fora da curva Ed25519 |
| Tem chave privada | Sim | Não |
| Pode assinar transações | Sim (com chave privada) | Não |
| Pode assinar durante CPI | Não (a menos que a assinatura esteja incluída na transação) | Sim (via invoke_signed) |
| Derivação | Gerar keypair Ed25519 | Determinística a partir de seeds + ID do programa |
| Uso típico | Carteiras de utilizador, ID de programa | Contas de dados pertencentes ao programa |
Seeds opcionais
Os seeds opcionais são strings de bytes definidas pelo utilizador que servem
como entradas para a derivação de PDA. Eles criam endereços únicos e
determinísticos com âmbito de um programa. Por exemplo, usar
["user", user_pubkey] como seeds deriva um PDA diferente para cada utilizador.
Os seeds devem seguir estas restrições:
- Máximo de 16 seeds por derivação (
MAX_SEEDS) - Máximo de 32 bytes por seed (
MAX_SEED_LEN)
Bump seed
O bump seed é um único byte (0-255) anexado aos seeds opcionais durante a
derivação.
find_program_address
pesquisa de 255 até 0, chamando create_program_address com cada valor até
que o resultado saia da curva Ed25519. O primeiro valor que tem sucesso é o
bump canónico.
Os programas devem sempre usar o bump canónico para garantir um mapeamento único e determinístico de seeds para endereço.
Use sempre o bump canónico ao derivar PDAs. Usar um bump não canónico cria um segundo endereço válido para os mesmos seeds, o que pode levar a vulnerabilidades onde um atacante substitui uma conta diferente da esperada.
Derivação de PDA
Algoritmo de derivação
A derivação de PDA é implementada na função
create_program_address
do SDK. O algoritmo funciona da seguinte forma:
- Validar que o número de seeds não excede
MAX_SEEDS(16) e nenhum seed individual excedeMAX_SEED_LEN(32 bytes). Se qualquer verificação falhar, retornarPubkeyError::MaxSeedLengthExceeded. - Hash SHA-256 de todos os seeds, o ID do programa e a string
"ProgramDerivedAddress"juntos para produzir um resultado de 32 bytes. - Verificar se o resultado é um ponto válido na curva Ed25519.
- Se o resultado ESTÁ na curva, retornar
PubkeyError::InvalidSeeds(o endereço teria uma chave privada correspondente, o que viola a propriedade de segurança do PDA). - Se o resultado NÃO está na curva, retorná-lo como o PDA.
Custos de unidade de computação
A
syscall on-chain
para create_program_address cobra
1.500 CUs
por chamada.
A
syscall try_find_program_address
cobra 1.500 CUs na entrada (antes do loop) e, em seguida, 1.500 CUs adicionais
para cada tentativa de bump falhada dentro do loop.
Padrões comuns de seed
Seeds são específicos da aplicação. Padrões comuns incluem:
| Padrão | Seeds | Caso de uso |
|---|---|---|
| Singleton global | ["global"] | Conta de configuração única para todo o programa |
| Conta por utilizador | ["user", user_pubkey] | Uma conta por utilizador por programa |
| Por utilizador por entidade | ["vault", user_pubkey, mint_pubkey] | Cofres de tokens, por utilizador por token |
| Contador / sequencial | ["order", user_pubkey, &order_id.to_le_bytes()] | Registos sequenciais por utilizador |
Seeds são concatenados antes do hashing, portanto ["ab", "cd"] e ["abcd"]
produzem o mesmo PDA. Use seeds de comprimento fixo ou um separador para
evitar colisões. Por exemplo, ["ab", "-", "cd"] é inequívoco.
Exemplos: derivar um PDA
Derivar um PDA calcula apenas um endereço. Não cria uma conta on-chain nesse
endereço. A conta deve ser explicitamente criada através de uma instrução
separada (normalmente create_account via CPI).
Os SDKs da Solana fornecem funções para derivação de PDA. Cada função recebe:
- Program ID: o endereço do programa usado para derivar o PDA. Este programa pode assinar em nome do PDA.
- Seeds opcionais: entradas predefinidas, como strings, números ou outros endereços de conta.
| SDK | Função |
|---|---|
@solana/kit (TypeScript) | getProgramDerivedAddress |
@solana/web3.js (TypeScript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
Os exemplos abaixo derivam um PDA usando os SDKs da Solana. Clique em ▷ Executar para executar o código.
Derivar um PDA com um seed de string
O exemplo abaixo deriva um PDA usando um ID de programa e um seed de string opcional.
import { Address, getProgramDerivedAddress } from "@solana/kit";const programAddress = "11111111111111111111111111111111" as Address;const seeds = ["helloWorld"];const [pda, bump] = await getProgramDerivedAddress({programAddress,seeds});console.log(`PDA: ${pda}`);console.log(`Bump: ${bump}`);
Derivar um PDA com um seed de endereço
O exemplo abaixo deriva um PDA usando um ID de programa e um seed de endereço opcional.
import {Address,getAddressEncoder,getProgramDerivedAddress} from "@solana/kit";const programAddress = "11111111111111111111111111111111" as Address;const addressEncoder = getAddressEncoder();const optionalSeedAddress = addressEncoder.encode("B9Lf9z5BfNPT4d5KMeaBFx8x1G4CULZYR1jA2kmxRDka" as Address);const seeds = [optionalSeedAddress];const [pda, bump] = await getProgramDerivedAddress({programAddress,seeds});console.log(`PDA: ${pda}`);console.log(`Bump: ${bump}`);
Derivar um PDA com múltiplos seeds
O exemplo abaixo deriva um PDA usando um ID de programa e múltiplos seeds opcionais.
import {Address,getAddressEncoder,getProgramDerivedAddress} from "@solana/kit";const programAddress = "11111111111111111111111111111111" as Address;const optionalSeedString = "helloWorld";const addressEncoder = getAddressEncoder();const optionalSeedAddress = addressEncoder.encode("B9Lf9z5BfNPT4d5KMeaBFx8x1G4CULZYR1jA2kmxRDka" as Address);const seeds = [optionalSeedString, optionalSeedAddress];const [pda, bump] = await getProgramDerivedAddress({programAddress,seeds});console.log(`PDA: ${pda}`);console.log(`Bump: ${bump}`);
Iterando todos os bumps
Os exemplos a seguir mostram a derivação de PDA usando todos os seeds de bump
possíveis (255 a 0), ilustrando como find_program_address retorna o
bump canónico:
O exemplo do Kit não está incluído porque a função
createProgramDerivedAddress
não é exportada.
import { PublicKey } from "@solana/web3.js";const programId = new PublicKey("11111111111111111111111111111111");const optionalSeed = "helloWorld";// Loop through all bump seeds (255 down to 0)for (let bump = 255; bump >= 0; bump--) {try {const PDA = PublicKey.createProgramAddressSync([Buffer.from(optionalSeed), Buffer.from([bump])],programId);console.log("bump " + bump + ": " + PDA);} catch (error) {console.log("bump " + bump + ": " + error);}}
bump 255: Error: Invalid seeds, address must fall off the curvebump 254: 46GZzzetjCURsdFPb7rcnspbEMnCBXe9kpjrsZAkKb6Xbump 253: GBNWBGxKmdcd7JrMnBdZke9Fumj9sir4rpbruwEGmR4ybump 252: THfBMgduMonjaNsCisKa7Qz2cBoG1VCUYHyso7UXYHHbump 251: EuRrNqJAofo7y3Jy6MGvF7eZAYegqYTwH2dnLCwDDGdPbump 250: Error: Invalid seeds, address must fall off the curve...// remaining bump outputs
Neste exemplo, o bump 255 produz um endereço on-curve e falha. O primeiro bump válido é 254, tornando-o o bump canónico.
Is this page helpful?