Адреса, похідна від програми

Адреса рахунку Solana вказує на місцезнаходження рахунку в блокчейні. Багато адрес рахунків є публічним ключем keypair, і в такому випадку відповідний приватний ключ використовується для підписання транзакцій, пов'язаних із рахунком.

Корисною альтернативою адресі публічного ключа є адреса, похідна від програми (PDA). PDA надають простий метод для зберігання, відображення та отримання стану програми. PDA — це адреса, яка створюється детерміновано з використанням ID програми та комбінації опціональних попередньо визначених вхідних даних. PDA виглядають подібно до адрес публічних ключів, але не мають відповідного приватного ключа.

Середовище виконання Solana дозволяє програмам підписувати PDA без необхідності приватного ключа. Використання PDA усуває необхідність відстежувати адресу рахунку. Замість цього ви можете згадати конкретні вхідні дані, використані для виведення PDA. (Щоб дізнатися, як програми використовують PDA для підписання, див. розділ Міжпрограмні виклики.)

Передумови

Keypair Solana — це точки на кривій Ed25519 (криптографія на еліптичних кривих). Вони складаються з публічного ключа та приватного ключа. Публічний ключ стає адресою рахунку, а приватний ключ використовується для генерації дійсного підпису для рахунку.

Два рахунки з адресами на кривійДва рахунки з адресами на кривій

PDA навмисно виводиться так, щоб не потрапляти на криву Ed25519. Це означає, що вона не має дійсного відповідного приватного ключа і не може виконувати криптографічні операції. (Наприклад, надавати підпис.) Однак Solana дозволяє програмам підписувати PDA без необхідності приватного ключа.

Off Curve AddressOff Curve Address

Ви можете розглядати PDA як спосіб створення структур, подібних до хеш-мап, у блокчейні, використовуючи заздалегідь визначений набір вхідних даних. (Наприклад, рядки, числа та інші адреси облікових записів.)

Program Derived AddressProgram Derived Address

Отримання PDA

Перед створенням облікового запису з PDA, ви повинні спочатку отримати адресу. Отримання PDA не створює автоматично обліковий запис у блокчейні за цією адресою — обліковий запис повинен бути явно створений через програму, яка використовується для отримання PDA. Ви можете розглядати PDA як адресу на карті: лише тому, що адреса існує, не означає, що там щось побудовано.

SDK Solana підтримують створення PDA за допомогою функцій, показаних у таблиці нижче. Кожна функція отримує такі вхідні дані:

  • Program ID: Адреса програми, яка використовується для отримання PDA. Ця програма може підписуватися від імені PDA.
  • Optional seeds: Заздалегідь визначені вхідні дані, такі як рядки, числа або інші адреси облікових записів.
SDKФункція
@solana/kit (Typescript)getProgramDerivedAddress
@solana/web3.js (Typescript)findProgramAddressSync
solana_sdk (Rust)find_program_address

Функція використовує Program ID та optional seeds, а потім перебирає значення bump для спроби створити дійсну адресу програми. Перебір значень bump починається з 255 і зменшується на 1, доки не буде знайдено дійсний PDA. Після знаходження дійсного PDA функція повертає PDA та bump seed.

Bump seed — це додатковий байт, який додається до optional seeds, щоб забезпечити генерацію дійсної адреси поза кривою.

Отримання PDAОтримання PDA

Канонічний bump

Bump seed — це додатковий байт, який додається до необов'язкових seed. Функція отримання перебирає значення bump, починаючи з 255 і зменшуючи на 1, доки значення не створить дійсну адресу поза кривою. Перше значення, яке створює дійсну адресу поза кривою, називається "канонічним bump".

Наступні приклади показують отримання PDA з використанням усіх можливих bump seed (від 255 до 0):

Приклад Kit не включено, оскільки функція createProgramDerivedAddress не експортується.

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

У цьому прикладі перший bump seed викликає помилку. Перший bump seed, який отримує дійсний PDA, — 254. Bump seed 253-251 також отримують унікальні, дійсні PDA.

Це означає, що за тих самих необов'язкових seed та programId, bump seed з іншим значенням все одно може отримати дійсний PDA.

Завжди включайте перевірки безпеки, щоб переконатися, що PDA, переданий програмі, отримано з канонічного bump. Невиконання цієї вимоги може призвести до вразливостей, які дозволяють використовувати неочікувані облікові записи в інструкціях програми. Найкращою практикою є використання лише канонічного bump при отриманні PDA.

Приклади

Наведені нижче приклади отримують PDA за допомогою SDK Solana. Натисніть ▷ Run, щоб виконати код.

Отримання PDA з рядковим seed

Наведений нижче приклад отримує PDA, використовуючи ID програми та необов'язковий рядковий seed.

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.

Отримання PDA з адресою як seed

Приклад нижче отримує PDA, використовуючи ідентифікатор програми та необов'язкову адресу як seed.

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.

Отримання PDA з кількома seeds

Приклад нижче отримує PDA, використовуючи ідентифікатор програми та кілька необов'язкових seeds.

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.

Створення облікового запису PDA

Приклад нижче використовує фреймворк Anchor для створення нового облікового запису з програмно-похідною адресою. Програма містить єдину інструкцію initialize для створення нового облікового запису, який зберігатиме адресу користувача та bump seed, використаний для отримання 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,
}

Обмеження init вказує Anchor викликати System Program для створення нового облікового запису, використовуючи PDA як адресу. Seeds, використані для створення PDA, це:

  • Адреса облікового запису користувача, надана в інструкції
  • Фіксований рядок: "data"
  • Канонічний bump seed

У цьому прикладі обмеження bump не призначено значення, тому Anchor використовуватиме find_program_address для отримання PDA та знаходження 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>,

Тестовий файл нижче містить транзакцію, яка викликає інструкцію initialize для створення нового облікового запису з адресою, похідною від програми. Файл містить код для отримання PDA.

Приклад також показує, як отримати новий обліковий запис, який буде створено.

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));
});
});

Якщо ви знову викличете інструкцію initialize з тим самим seed адреси user, транзакція завершиться невдачею. Це відбувається тому, що обліковий запис уже існує за отриманою адресою.

Is this page helpful?

Керується

© 2025 Фонд Solana.
Всі права захищені.
Залишайтеся на зв'язку