Program Derived Address (PDA)

Program Derived Addresses (PDAs) oferecem aos desenvolvedores na Solana dois casos de uso principais:

  • Endereços de conta determinísticos: PDAs fornecem um mecanismo para criar deterministicamente um endereço usando uma combinação de "seeds" opcionais (entradas predefinidas) e um ID de programa específico.
  • Permitir assinatura de programa: O runtime da Solana permite que programas "assinem" por PDAs que são derivados do endereço do programa.

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

O benefício dessa abordagem é que ela elimina a necessidade de acompanhar um endereço exato. Em vez disso, você simplesmente precisa lembrar as entradas específicas usadas para sua derivação.

Program Derived AddressProgram Derived Address

É importante entender que simplesmente derivar um Program Derived Address (PDA) não cria automaticamente uma conta na blockchain nesse endereço. Contas com um PDA como endereço na blockchain devem ser explicitamente criadas através do programa usado para derivar o endereço. Você pode pensar na derivação de um PDA como encontrar um endereço em um mapa. Apenas ter um endereço não significa que existe algo construído naquele local.

Esta seção cobre os detalhes da derivação de PDAs. A seção sobre Cross Program Invocations (CPIs) explica como os programas usam PDAs para assinatura.

Pontos-chave

  • PDAs são endereços derivados deterministicamente usando uma combinação de seeds predefinidos, um bump seed e o ID de um programa.
  • PDAs são endereços que estão fora da curva Ed25519 e não têm chave privada correspondente.
  • Programas Solana podem assinar em nome de PDAs derivados do ID do seu programa.
  • Derivar um PDA não cria automaticamente uma conta na blockchain.
  • Uma conta usando um PDA como seu endereço deve ser criada através de uma instrução dentro de um programa Solana.

O que é um PDA

PDAs são endereços que derivam deterministicamente e se parecem com chaves públicas, mas não possuem chaves privadas. Isso significa que não é possível gerar uma assinatura válida para o endereço. No entanto, o runtime da Solana permite que programas "assinem" pelos PDAs sem precisar de uma chave privada.

Para contextualizar, os Keypairs da Solana são pontos na curva Ed25519 (criptografia de curva elíptica) com uma chave pública e uma chave privada correspondente. As chaves públicas são usadas como endereços (identificadores únicos) para contas on-chain.

Endereço Na CurvaEndereço Na Curva

Um PDA é um ponto que é intencionalmente derivado para ficar fora da curva Ed25519 usando um conjunto predefinido de entradas. Um ponto que não está na curva Ed25519 não tem uma chave privada correspondente válida e não pode realizar operações criptográficas (assinatura).

Um PDA pode servir como o endereço (identificador único) para uma conta on-chain, fornecendo um método para facilmente armazenar, mapear e buscar o estado do programa.

Endereço Fora da CurvaEndereço Fora da Curva

Como derivar um PDA

A derivação de um PDA requer três entradas:

  • Seeds opcionais: Entradas predefinidas (por exemplo, strings, números, outros endereços de contas) para derivação do PDA.
  • Bump seed: Um byte extra anexado aos seeds opcionais para garantir que um PDA válido (fora da curva) seja gerado. O bump seed começa em 255 e é decrementado em 1 até que um PDA válido seja encontrado.
  • Program ID: O endereço do programa do qual o PDA é derivado. Este programa pode assinar em nome do PDA.

Derivação de PDADerivação de PDA

Use as seguintes funções dos respectivos SDKs para derivar um PDA.

SDKFunção
@solana/kit (Typescript)getProgramDerivedAddress
@solana/web3.js (Typescript)findProgramAddressSync
solana_sdk (Rust)find_program_address

Para derivar um PDA, forneça os seguintes inputs para a função do SDK:

  • Os seeds opcionais predefinidos convertidos em bytes
  • O ID do programa (endereço) usado para derivação

Uma vez que um PDA válido é encontrado, a função retorna tanto o endereço (PDA) quanto o bump seed usado para derivação.

Exemplos

Os exemplos a seguir mostram como derivar um PDA usando os respectivos SDKs.

Clique no botão "Executar" para executar o código.

Derivar um PDA com 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}`);
Click to execute the code.

Derivar um PDA com 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}`);
Click to execute the code.

Derivar um PDA com 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}`);
Click to execute the code.

Bump canônico

A derivação de PDA requer um "bump seed", um byte extra anexado às 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 de bump que produz um endereço válido fora da curva é o "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);
}
}
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

O bump seed 255 gera um erro e o primeiro bump seed a derivar um PDA válido é 254.

Observe que os bump seeds 253-251 derivam PDAs válidos com endereços diferentes. Isso significa que, dados os mesmos seeds opcionais e programId, um bump seed com um valor diferente ainda pode derivar um PDA válido.

Ao construir programas Solana, sempre inclua verificações de segurança para garantir que um PDA passado para o programa seja derivado do bump canônico. Não incluir essas verificações 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.

Criar contas PDA

O programa de exemplo abaixo mostra como criar uma conta usando um PDA como o endereço da nova conta. O programa de exemplo usa o framework Anchor.

O programa inclui uma única instrução initialize para criar uma nova conta usando um PDA como o endereço da conta. A nova conta armazena o endereço do user e o seed bump usado para derivar o PDA.

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 bump
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,
}

Neste exemplo, os seeds para derivação do PDA incluem a string fixa data e o endereço da conta user fornecida na instrução. O framework Anchor automaticamente encontra o seed canônico 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>,

A restrição init instrui o Anchor a invocar o System Program para criar uma nova conta usando o PDA como endereço. O Anchor faz isso através de um CPI.

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 contém o código Typescript para derivar o PDA.

Derive PDA
const [PDA] = PublicKey.findProgramAddressSync(
[Buffer.from("data"), user.publicKey.toBuffer()],
program.programId
);

A transação no arquivo de teste invoca a instrução initialize para criar uma nova conta on-chain usando o PDA como endereço. Neste exemplo, o Anchor pode inferir o endereço PDA nas contas de instrução, então não precisa ser explicitamente fornecido.

Invoke Initialize Instruction
it("Is initialized!", async () => {
const transactionSignature = await program.methods
.initialize()
.accounts({
user: user.publicKey
})
.rpc();
console.log("Transaction Signature:", transactionSignature);
});

O arquivo de teste também mostra como buscar a conta on-chain criada nesse endereço depois que a transação é enviada.

Fetch Account
it("Fetch Account", async () => {
const pdaAccount = await program.account.dataAccount.fetch(PDA);
console.log(JSON.stringify(pdaAccount, null, 2));
});

Observe que neste exemplo, se você invocar a instrução initialize mais de uma vez usando o mesmo endereço user como seed, a transação falha. Isso acontece porque já existe uma conta no endereço derivado.

Is this page helpful?

Índice

Editar Página