Endereço Derivado de Programa

Um endereço de conta da Solana aponta para a localização da conta no blockchain. Muitos endereços de contas são a chave pública de um keypair, caso em que a chave privada correspondente é usada para assinar transações envolvendo a conta.

Uma alternativa útil a um endereço de chave pública é um endereço derivado de programa (PDA). Os PDAs fornecem um método fácil para armazenar, mapear e buscar o estado do programa. Um PDA é um endereço que é criado deterministicamente usando um ID de programa e uma combinação de entradas predefinidas opcionais. Os PDAs parecem semelhantes aos endereços de chave pública, mas não têm uma chave privada correspondente.

O runtime da Solana permite que programas assinem PDAs sem precisar de uma chave privada. Usar um PDA elimina a necessidade de rastrear o endereço da conta. Em vez disso, você pode recuperar as entradas específicas usadas para a derivação do PDA. (Para aprender como os programas usam PDAs para assinatura, consulte a seção Invocações Entre Programas.)

Contexto

Os keypairs da Solana são pontos na curva Ed25519 (criptografia de curva elíptica). Eles consistem em uma chave pública e uma chave privada. A chave pública torna-se o endereço da conta, e a chave privada é usada para gerar assinaturas válidas para a conta.

Duas contas com endereços na curvaDuas contas com endereços na curva

Um PDA é intencionalmente derivado para ficar fora da curva Ed25519. Isso significa que ele não tem uma chave privada correspondente válida e não pode realizar operações criptográficas. (Como fornecer uma assinatura.) No entanto, a Solana permite que programas assinem PDAs sem precisar de uma chave privada.

Endereço Fora da CurvaEndereço Fora da Curva

Você pode pensar nos PDAs como uma forma de criar estruturas semelhantes a hashmaps na blockchain usando um conjunto predefinido de entradas. (Por exemplo, strings, números e outros endereços de conta.)

Program Derived AddressProgram Derived Address

Derivar um PDA

Antes de criar uma conta com um PDA, você deve primeiro derivar o endereço. Derivar um PDA não cria automaticamente uma conta na blockchain nesse endereço — a conta deve ser explicitamente criada através do programa usado para derivar o PDA. Você pode pensar em um PDA como um endereço em um mapa: só porque um endereço existe não significa que há algo construído lá.

Os SDKs da Solana suportam a criação de PDA com as funções mostradas na tabela abaixo. Cada função recebe as seguintes entradas:

  • Program ID: O endereço do programa sendo 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

A função usa o Program ID e os seeds opcionais, depois itera através dos valores de bump para tentar criar um endereço de programa válido. A iteração dos valores de bump começa em 255 e diminui em 1 até que um PDA válido seja encontrado. Depois que um PDA válido é encontrado, a função retorna o PDA e o bump seed.

O bump seed é um byte extra anexado aos seeds opcionais para garantir que um endereço fora da curva válido seja gerado.

Derivação de PDADerivação de PDA

Bump canônico

Um bump seed é um byte extra anexado aos seeds opcionais. A função de derivação itera pelos valores de bump, começando em 255 e decrementando em 1, até que um valor produza um endereço válido fora da curva. O primeiro valor que produz um endereço válido fora da curva é chamado de "bump canônico".

Os exemplos a seguir mostram a derivação de PDA usando todos os possíveis bump seeds (255 a 0):

Exemplo do Kit não 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 primeiro bump seed gera um erro. O primeiro bump seed a derivar um PDA válido é 254. Os bump seeds 253-251 também derivam PDAs válidos e únicos.

Isso significa que dados os mesmos seeds opcionais e programId, um bump seed com um valor diferente ainda pode derivar um PDA válido.

Sempre inclua verificações de segurança para garantir que um PDA passado para o programa seja derivado do bump canônico. Não fazer isso pode introduzir vulnerabilidades que permitem que contas inesperadas sejam usadas nas instruções do programa. É uma boa prática usar apenas o bump canônico ao derivar PDAs.

Exemplos

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 opcional de string.

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.

Criar uma conta PDA

O exemplo abaixo usa o framework Anchor para criar uma nova conta com um endereço derivado do programa. O programa inclui uma única instrução initialize para criar a nova conta, que armazenará o endereço do usuário e o bump seed usado para derivar o PDA.

Program
use anchor_lang::prelude::*;
declare_id!("75GJVCJNhaukaa2vCCqhreY31gaphv7XTScBChmr1ueR");
#[program]
pub mod pda_account {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let account_data = &mut ctx.accounts.pda_account;
// store the address of the `user`
account_data.user = *ctx.accounts.user.key;
// store the canonical bumpd
account_data.bump = ctx.bumps.pda_account;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(
init,
// define the seeds to derive the PDA
seeds = [b"data", user.key().as_ref()],
// use the canonical bump
bump,
payer = user,
space = 8 + DataAccount::INIT_SPACE
)]
pub pda_account: Account<'info, DataAccount>,
pub system_program: Program<'info, System>,
}
#[account]
#[derive(InitSpace)]
pub struct DataAccount {
pub user: Pubkey,
pub bump: u8,
}

A restrição init instrui o Anchor a invocar o System Program para criar uma nova conta usando o PDA como endereço. Os seeds usados para criar o PDA são:

  • O endereço da conta do usuário fornecido na instrução
  • A string fixa: "data"
  • O bump seed canônico

Neste exemplo, a restrição de bump não recebe um valor, então o Anchor usará find_program_address para derivar o PDA e encontrar o bump.

pda_account
#[account(
init,
seeds = [b"data", user.key().as_ref()],
bump,
payer = user,
space = 8 + DataAccount::INIT_SPACE
)]
pub pda_account: Account<'info, DataAccount>,

O arquivo de teste abaixo contém uma transação que invoca a instrução initialize para criar uma nova conta com um endereço derivado do programa. O arquivo contém código para derivar o PDA.

O exemplo também mostra como buscar a nova conta que será criada.

Test
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { PdaAccount } from "../target/types/pda_account";
import { PublicKey } from "@solana/web3.js";
describe("pda-account", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.PdaAccount as Program<PdaAccount>;
const user = provider.wallet as anchor.Wallet;
// Derive the PDA address using the seeds specified on the program
const [PDA] = PublicKey.findProgramAddressSync(
[Buffer.from("data"), user.publicKey.toBuffer()],
program.programId
);
it("Is initialized!", async () => {
const transactionSignature = await program.methods
.initialize()
.accounts({
user: user.publicKey
})
.rpc();
console.log("Transaction Signature:", transactionSignature);
});
it("Fetch Account", async () => {
const pdaAccount = await program.account.dataAccount.fetch(PDA);
console.log(JSON.stringify(pdaAccount, null, 2));
});
});

Se você invocar a instrução initialize novamente com o mesmo seed de endereço user, a transação falhará. Isso acontece porque já existe uma conta no endereço derivado.

Is this page helpful?

Índice

Editar Página

Gerenciado por

© 2025 Fundação Solana.
Todos os direitos reservados.
Endereço Derivado de Programa | Solana