Виведення PDA

Підсумок

PDA виводяться шляхом хешування seeds + program ID + bump через SHA-256, доки результат не опиниться поза кривою Ed25519. Канонічний bump — це перше значення, яке створює адресу поза кривою. Максимум 16 seeds, максимум 32 байти на один seed.

Передумови

Значення Keypair у Solana є точками на кривій Ed25519. Keypair складається з публічного ключа (використовується як адреса акаунта) та секретного ключа (використовується для створення підписів). Будь-хто, хто має секретний ключ, може підписувати транзакції для цієї адреси.

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

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

Адреса поза кривоюАдреса поза кривою

PDA проти keypair-акаунтів

ВластивістьKeypair-акаунтPDA-акаунт
Тип адресиНа кривій Ed25519Поза кривою Ed25519
Має приватний ключТакНі
Може підписувати транзакціїТак (з приватним ключем)Ні
Може підписувати під час CPIНі (якщо підпис не включено в транзакцію)Так (через invoke_signed)
ВиведенняГенерація keypair Ed25519Детерміноване з seeds + program ID
Типове використанняГаманці користувачів, program IDАкаунти даних, що належать програмі

Опціональні seeds

Опціональні seeds — це визначені користувачем байтові рядки, які служать вхідними даними для виведення PDA. Вони створюють унікальні детерміновані адреси в межах програми. Наприклад, використання ["user", user_pubkey] як seeds виводить різні PDA для кожного користувача.

Seeds повинні відповідати таким обмеженням:

  • Максимум 16 seeds на виведення (MAX_SEEDS)
  • Максимум 32 байти на seed (MAX_SEED_LEN)

Bump seed

Bump seed — це один байт (0-255), який додається до опціональних seeds під час виведення. find_program_address шукає від 255 до 0, викликаючи create_program_address з кожним значенням, доки результат не вийде за межі кривої Ed25519. Перше значення, яке спрацює, є канонічним bump.

Програми завжди повинні використовувати канонічний bump, щоб забезпечити унікальне детерміноване відображення від seeds до адреси.

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

Виведення PDAВиведення PDA

Алгоритм виведення

Виведення PDA реалізовано у функції SDK create_program_address. Алгоритм працює наступним чином:

  1. Перевірити, що кількість seeds не перевищує MAX_SEEDS (16) і жоден окремий seed не перевищує MAX_SEED_LEN (32 байти). Якщо будь-яка перевірка не пройдена, повернути PubkeyError::MaxSeedLengthExceeded.
  2. Хешувати SHA-256 усі seeds, ID програми та рядок "ProgramDerivedAddress" разом, щоб отримати 32-байтовий результат.
  3. Перевірити, чи є результат валідною точкою на кривій Ed25519.
  4. Якщо результат Є на кривій, повернути PubkeyError::InvalidSeeds (адреса матиме відповідний приватний ключ, що порушує властивість безпеки PDA).
  5. Якщо результат НЕ на кривій, повернути його як PDA.

Вартість обчислювальних одиниць

Системний виклик на ланцюзі для create_program_address стягує 1 500 CU за кожен виклик.

Системний виклик try_find_program_address стягує 1 500 CU на вході (перед циклом), а потім додаткові 1 500 CU за кожну невдалу спробу збільшення bump у циклі.

Поширені шаблони seed

Seed є специфічними для застосунку. Поширені шаблони включають:

ШаблонSeedsВипадок використання
Глобальний singleton["global"]Єдиний обліковий запис конфігурації програми
Обліковий запис на користувача["user", user_pubkey]Один обліковий запис на користувача на програму
На користувача на сутність["vault", user_pubkey, mint_pubkey]Токен-сховища, на користувача на токен
Лічильник / послідовний["order", user_pubkey, &order_id.to_le_bytes()]Послідовні записи на користувача

Seeds конкатенуються перед хешуванням, тому ["ab", "cd"] і ["abcd"] створюють однаковий PDA. Використовуйте seed фіксованої довжини або роздільник, щоб уникнути колізій. Наприклад, ["ab", "-", "cd"] є однозначним.

Приклади: виведення PDA

Виведення PDA обчислює лише адресу. Воно не створює обліковий запис на ланцюзі за цією адресою. Обліковий запис має бути явно створений через окрему інструкцію (зазвичай create_account через CPI).

SDK Solana надають функції для виведення PDA. Кожна функція приймає:

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

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

Виведення PDA з рядковим seed

Приклад нижче виводить PDA, використовуючи ідентифікатор програми та опціональний рядковий 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.

Ітерація всіх bumps

Наступні приклади показують виведення PDA з використанням усіх можливих bump seeds (від 255 до 0), ілюструючи, як find_program_address повертає канонічний bump:

Приклад 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 255 створює адресу на кривій і завершується невдачею. Перший валідний bump — 254, що робить його канонічним bump.

Is this page helpful?

Керується

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