Derivação de PDA

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 curvaDuas 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 curvaEndereço fora da curva

Contas PDA vs keypair

PropriedadeConta keypairConta PDA
Tipo de endereçoNa curva Ed25519Fora da curva Ed25519
Tem chave privadaSimNão
Pode assinar transaçõesSim (com chave privada)Não
Pode assinar durante CPINão (a menos que a assinatura esteja incluída na transação)Sim (via invoke_signed)
DerivaçãoGerar keypair Ed25519Determinística a partir de seeds + ID do programa
Uso típicoCarteiras de utilizador, ID de programaContas 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 PDADerivaçã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:

  1. Validar que o número de seeds não excede MAX_SEEDS (16) e nenhum seed individual excede MAX_SEED_LEN (32 bytes). Se qualquer verificação falhar, retornar PubkeyError::MaxSeedLengthExceeded.
  2. Hash SHA-256 de todos os seeds, o ID do programa e a string "ProgramDerivedAddress" juntos para produzir um resultado de 32 bytes.
  3. Verificar se o resultado é um ponto válido na curva Ed25519.
  4. 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).
  5. 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ãoSeedsCaso 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.
SDKFunçã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}`);
Console
Click to execute the code.

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}`);
Console
Click to execute the code.

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}`);
Console
Click to execute the code.

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);
}
}
Console
Click to execute the code.
bump 255: Error: Invalid seeds, address must fall off the curve
bump 254: 46GZzzetjCURsdFPb7rcnspbEMnCBXe9kpjrsZAkKb6X
bump 253: GBNWBGxKmdcd7JrMnBdZke9Fumj9sir4rpbruwEGmR4y
bump 252: THfBMgduMonjaNsCisKa7Qz2cBoG1VCUYHyso7UXYHH
bump 251: EuRrNqJAofo7y3Jy6MGvF7eZAYegqYTwH2dnLCwDDGdP
bump 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?

Gerenciado por

© 2026 Fundação Solana.
Todos os direitos reservados.
Conecte-se
  • Blog