Program-Derived Address
Bir Solana hesap adresi, hesabın blok zincirindeki konumunu gösterir. Birçok hesap adresi bir keypair'in açık anahtarıdır; bu durumda, ilgili özel anahtar hesabı içeren işlemleri imzalamak için kullanılır.
Açık anahtar adresine kullanışlı bir alternatif, program-derived address (PDA)'dir. PDA'lar, program durumunu depolamak, eşleştirmek ve getirmek için kolay bir yöntem sağlar. Bir PDA, bir program kimliği ve isteğe bağlı önceden tanımlanmış girdilerin bir kombinasyonu kullanılarak deterministik olarak oluşturulan bir adrestir. PDA'lar açık anahtar adreslerine benzer, ancak karşılık gelen bir özel anahtarları yoktur.
Solana çalışma zamanı, programların özel bir anahtar gerektirmeden PDA'lar için imza atmasını sağlar. Bir PDA kullanmak, hesabın adresini takip etme ihtiyacını ortadan kaldırır. Bunun yerine, PDA'nın türetilmesi için kullanılan belirli girdileri hatırlayabilirsiniz. (Programların imzalama için PDA'ları nasıl kullandığını öğrenmek için Programlar Arası Çağrılar bölümüne bakın.)
Arka plan
Solana keypair'leri, Ed25519 eğrisi (eliptik eğri kriptografisi) üzerindeki noktalardır. Bunlar bir açık anahtar ve bir özel anahtardan oluşur. Açık anahtar hesap adresi olur ve özel anahtar hesap için geçerli bir imza oluşturmak için kullanılır.
Eğri üzerinde adresleri olan iki hesap
Bir PDA, kasıtlı olarak Ed25519 eğrisinin dışına düşecek şekilde türetilir. Bu, geçerli bir karşılık gelen özel anahtarı olmadığı ve kriptografik işlemler gerçekleştiremeyeceği anlamına gelir. (İmza sağlamak gibi.) Ancak, Solana, programların özel bir anahtar gerektirmeden PDA'lar için imza atmasını sağlar.
Eğri Dışı Adres
PDA'ları, önceden tanımlanmış bir girdi kümesi kullanarak zincir üzerinde hashmap benzeri yapılar oluşturmanın bir yolu olarak düşünebilirsiniz. (Örneğin, dizeler, sayılar ve diğer hesap adresleri.)
Program Derived Address
Bir PDA türet
Bir PDA ile hesap oluşturmadan önce, öncelikle adresi türetmeniz gerekir. Bir PDA türetmek, otomatik olarak o adresteki zincir üzerinde bir hesap oluşturmaz - hesap, PDA'yı türetmek için kullanılan program aracılığıyla açıkça oluşturulmalıdır. Bir PDA'yı haritadaki bir adres gibi düşünebilirsiniz: bir adresin var olması, orada bir şeyin inşa edildiği anlamına gelmez.
Solana SDK'ları, aşağıdaki tabloda gösterilen fonksiyonlarla PDA oluşturulmasını destekler. Her fonksiyon şu girdileri alır:
- Program ID: PDA'yı türetmek için kullanılan programın adresi. Bu program, PDA adına imza atabilir.
- İsteğe bağlı seed'ler: Dizeler, sayılar veya diğer hesap adresleri gibi önceden tanımlanmış girdiler.
| SDK | Fonksiyon |
|---|---|
@solana/kit (Typescript) | getProgramDerivedAddress |
@solana/web3.js (Typescript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
Fonksiyon, program ID'sini ve isteğe bağlı seed'leri kullanır, ardından geçerli bir program adresi oluşturmaya çalışmak için bump değerlerini yineler. Bump değerlerinin yinelenmesi 255'ten başlar ve geçerli bir PDA bulunana kadar 1'er azalır. Geçerli bir PDA bulunduktan sonra, fonksiyon PDA'yı ve bump seed'i döndürür.
Bump seed, geçerli bir eğri dışı adresin oluşturulmasını sağlamak için isteğe bağlı seed'lere eklenen ekstra bir bayttır.
PDA Türetimi
Kanonik bump
Bump seed, isteğe bağlı seed'lere eklenen ekstra bir bayttır. Türetme fonksiyonu, 255'ten başlayarak ve 1 azaltarak, geçerli bir eğri dışı adres üretene kadar bump değerlerini yineler. Geçerli bir eğri dışı adres üreten ilk değer "kanonik bump" olarak adlandırılır.
Aşağıdaki örnekler, tüm olası bump seed'leri (255'ten 0'a) kullanarak PDA türetimini göstermektedir:
Kit örneği dahil edilmemiştir çünkü createProgramDerivedAddress fonksiyonu dışa aktarılmamıştır.
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
Bu örnekte, ilk bump seed bir hata fırlatır. Geçerli bir PDA türeten ilk bump seed 254'tür. 253-251 bump seed'leri de benzersiz, geçerli PDA'lar türetir.
Bu, aynı isteğe bağlı seed'ler ve programId verildiğinde, farklı bir değere
sahip bir bump seed'in hala geçerli bir PDA türetebileceği anlamına gelir.
Programa iletilen bir PDA'nın kanonik bump'tan türetildiğinden emin olmak için her zaman güvenlik kontrolleri ekleyin. Bunu yapmamak, programın talimatlarında beklenmeyen hesapların kullanılmasına izin veren güvenlik açıklarına yol açabilir. PDA'ları türetirken yalnızca kanonik bump'ı kullanmak en iyi uygulamadır.
Örnekler
Aşağıdaki örnekler, Solana SDK'larını kullanarak bir PDA türetmektedir. Kodu çalıştırmak için ▷ Çalıştır'a tıklayın.
Bir string seed ile PDA türetme
Aşağıdaki örnek, bir program kimliği ve isteğe bağlı bir string seed kullanarak bir PDA türetir.
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}`);
Adres seed ile bir PDA türetin
Aşağıdaki örnek, bir program kimliği ve isteğe bağlı bir adres seed kullanarak bir PDA türetir.
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}`);
Birden fazla seed ile bir PDA türetin
Aşağıdaki örnek, bir program kimliği ve birden fazla isteğe bağlı seed kullanarak bir PDA türetir.
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}`);
Bir PDA hesabı oluşturun
Aşağıdaki örnek, program türetilmiş bir adrese sahip yeni bir hesap oluşturmak
için Anchor framework kullanır. Program,
PDA'yı türetmek için kullanılan kullanıcı adresini ve
bump seed depolayacak yeni hesabı oluşturmak için tek bir
initialize talimatı içerir.
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,}
init kısıtlaması, Anchor'a PDA'yı adres olarak kullanarak yeni
bir hesap oluşturmak için
System Program'ı çağırmasını söyler.
PDA'yı oluşturmak için kullanılan seeds şunlardır:
- Talimatta sağlanan kullanıcı hesabının adresi
- Sabit dize: "data"
- Kanonik bump seed
Bu örnekte, bump kısıtlamasına bir değer atanmamıştır, bu nedenle Anchor, PDA'yı
türetmek ve bump'ı bulmak için find_program_address kullanacaktır.
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,
Aşağıdaki test dosyası, program türetilmiş bir adresle yeni bir hesap oluşturmak
için initialize talimatını çağıran bir işlem içerir.
Dosya, PDA'yı türetmek için kod içerir.
Örnek ayrıca oluşturulacak yeni hesabı nasıl alacağınızı gösterir.
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));});});
Eğer aynı user adres seed'i ile initialize talimatını tekrar çağırırsanız,
işlem başarısız olacaktır. Bu, türetilen adreste zaten bir hesap var olduğu
için gerçekleşir.
Is this page helpful?