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 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 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 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.
| SDK | Funçã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 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);}}
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 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}`);
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}`);
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.
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 bumpdaccount_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 PDAseeds = [b"data", user.key().as_ref()],// use the canonical bumpbump,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.
#[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.
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 programconst [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?