Підсумок
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 реалізовано у функції SDK
create_program_address.
Алгоритм працює наступним чином:
- Перевірити, що кількість seeds не перевищує
MAX_SEEDS(16) і жоден окремий seed не перевищуєMAX_SEED_LEN(32 байти). Якщо будь-яка перевірка не пройдена, повернутиPubkeyError::MaxSeedLengthExceeded. - Хешувати SHA-256 усі seeds, ID програми та рядок
"ProgramDerivedAddress"разом, щоб отримати 32-байтовий результат. - Перевірити, чи є результат валідною точкою на кривій Ed25519.
- Якщо результат Є на кривій, повернути
PubkeyError::InvalidSeeds(адреса матиме відповідний приватний ключ, що порушує властивість безпеки PDA). - Якщо результат НЕ на кривій, повернути його як 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}`);
Виведення 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}`);
Виведення 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}`);
Ітерація всіх 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);}}
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
У цьому прикладі bump 255 створює адресу на кривій і завершується невдачею. Перший валідний bump — 254, що робить його канонічним bump.
Is this page helpful?