Program Derived Address (PDA)
Program Derived Address'ler (PDA'lar) Solana'daki geliştiricilere iki ana kullanım senaryosu sunar:
- Deterministik Hesap Adresleri: PDA'lar, isteğe bağlı "seed'ler" (önceden tanımlanmış girdiler) ve belirli bir program kimliği kombinasyonu kullanarak deterministik olarak bir adres oluşturmak için bir mekanizma sağlar.
- Program İmzalamayı Etkinleştirme: Solana çalışma zamanı, programların kendi adresinden türetilen PDA'lar için "imzalamasına" olanak tanır.
PDA'ları, önceden tanımlanmış bir girdi kümesinden (örneğin dizeler, sayılar ve diğer hesap adresleri) zincir üzerinde hashmap benzeri yapılar oluşturmanın bir yolu olarak düşünebilirsiniz.
Bu yaklaşımın avantajı, tam bir adresi takip etme ihtiyacını ortadan kaldırmasıdır. Bunun yerine, sadece türetme için kullanılan belirli girdileri hatırlamanız yeterlidir.
Program Derived Address
Bir Program Derived Address (PDA) türetmenin, otomatik olarak o adresteki zincir üzerinde bir hesap oluşturmadığını anlamak önemlidir. PDA'yı zincir üzerindeki adres olarak kullanan hesaplar, adresi türetmek için kullanılan program aracılığıyla açıkça oluşturulmalıdır. Bir PDA türetmeyi, haritada bir adres bulmak gibi düşünebilirsiniz. Sadece bir adrese sahip olmak, o konumda herhangi bir şeyin inşa edildiği anlamına gelmez.
Bu bölüm, PDA'ların türetilmesi hakkındaki ayrıntıları kapsar. Cross Program Invocation (CPI) bölümü, programların imzalama için PDA'ları nasıl kullandığını açıklar.
Önemli Noktalar
- PDA'lar, önceden tanımlanmış seed'ler, bir bump seed ve bir programın kimliği kombinasyonu kullanılarak deterministik olarak türetilen adreslerdir.
- PDA'lar, Ed25519 eğrisinin dışında kalan ve karşılık gelen özel anahtarı olmayan adreslerdir.
- Solana programları, kendi program kimliğinden türetilen PDA'lar adına imza atabilir.
- Bir PDA türetmek, otomatik olarak zincir üzerinde bir hesap oluşturmaz.
- PDA'yı adres olarak kullanan bir hesap, bir Solana programı içindeki bir talimat aracılığıyla oluşturulmalıdır.
PDA nedir
PDA'lar, deterministik olarak türetilen ve genel anahtarlara benzeyen, ancak özel anahtarları olmayan adreslerdir. Bu, adres için geçerli bir imza oluşturmanın mümkün olmadığı anlamına gelir. Bununla birlikte, Solana çalışma zamanı, programların özel bir anahtar gerektirmeden PDA'lar için "imza" atmasını sağlar.
Bağlam olarak, Solana Keypair'leri, bir genel anahtar ve karşılık gelen özel anahtara sahip Ed25519 eğrisindeki (eliptik eğri kriptografisi) noktalardır. Genel anahtarlar, zincir üstü hesaplar için adresler (benzersiz tanımlayıcı) olarak kullanılır.
Eğri Üzerindeki Adres
PDA, önceden tanımlanmış bir girdi seti kullanılarak kasıtlı olarak Ed25519 eğrisinin dışına düşecek şekilde türetilen bir noktadır. Ed25519 eğrisi üzerinde olmayan bir nokta, geçerli bir karşılık gelen özel anahtara sahip değildir ve kriptografik işlemler (imzalama) gerçekleştiremez.
PDA, zincir üstü bir hesap için adres (benzersiz tanımlayıcı) görevi görebilir ve program durumunu kolayca depolamak, eşlemek ve getirmek için bir yöntem sağlar.
Eğri Dışı Adres
PDA nasıl türetilir
Bir PDA'nın türetilmesi üç girdi gerektirir:
- İsteğe bağlı seed'ler: PDA türetimi için önceden tanımlanmış girdiler (örneğin dizeler, sayılar, diğer hesap adresleri).
- Bump seed: Geçerli bir PDA'nın (eğri dışı) oluşturulmasını sağlamak için isteğe bağlı seed'lere eklenen ekstra bayt. Bump seed 255'ten başlar ve geçerli bir PDA bulunana kadar 1 azalır.
- Program ID: PDA'nın türetildiği programın adresi. Bu program, PDA adına imza atabilir.
PDA Türetimi
Bir PDA türetmek için ilgili SDK'lardan aşağıdaki fonksiyonları kullanın.
SDK | Fonksiyon |
---|---|
@solana/kit (Typescript) | getProgramDerivedAddress |
@solana/web3.js (Typescript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
PDA türetmek için, SDK fonksiyonuna aşağıdaki girdileri sağlayın:
- Bayt'a dönüştürülmüş önceden tanımlanmış isteğe bağlı seed'ler
- Türetme için kullanılan program kimliği (adresi)
Geçerli bir PDA bulunduğunda, fonksiyon hem adresi (PDA) hem de türetme için kullanılan bump seed'i döndürür.
Örnekler
Aşağıdaki örnekler, ilgili SDK'ları kullanarak bir PDA'nın nasıl türetileceğini göstermektedir.
Kodu çalıştırmak için "Çalıştır" düğmesine tıklayın.
İsteğe bağlı dize seed ile bir PDA türetme
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}`);
İsteğe bağlı adres seed ile bir PDA türetme
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 isteğe bağlı seed ile bir PDA türetme
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}`);
Kanonik Bump
PDA türetimi, isteğe bağlı seed'lere eklenen ekstra bir bayt olan "bump seed" gerektirir. Türetme fonksiyonu, 255'ten başlayarak ve 1 azaltarak, geçerli bir eğri dışı adres üreten bir değer bulana kadar bump değerlerini yineler. Geçerli bir eğri dışı adres üreten ilk bump değeri "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
Bump seed 255 bir hata fırlatır ve geçerli bir PDA türeten ilk bump seed 254'tür.
253-251 bump seed'lerinin tümünün farklı adreslerle geçerli PDA'lar türettiğine
dikkat edin. 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.
Solana programları oluştururken, programa iletilen bir PDA'nın kanonik bump'tan türetildiğinden emin olmak için her zaman güvenlik kontrolleri ekleyin. Bu kontrolleri eklememeniz, program talimatlarında beklenmeyen hesapların kullanılmasına izin veren güvenlik açıklarına neden olabilir. PDA'ları türetirken yalnızca kanonik bump'ı kullanmak en iyi uygulamadır.
PDA Hesapları Oluşturma
Aşağıdaki örnek program, yeni hesabın adresi olarak bir PDA kullanarak nasıl hesap oluşturulacağını göstermektedir. Örnek program Anchor framework kullanmaktadır.
Program, hesabın adresi olarak bir PDA kullanarak yeni bir hesap oluşturmak için
tek bir initialize
talimatı içerir. Yeni hesap, user
adresini ve PDA'yı
türetmek için kullanılan bump
seed'ini saklar.
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 bumpaccount_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,}
Bu örnekte, PDA türetimi için kullanılan seed'ler sabit bir dizi olan data
ve
talimatta sağlanan user
hesabının adresini içerir. Anchor çerçevesi otomatik
olarak kanonik bump
seed'ini bulur.
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,
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. Anchor bunu bir
CPI aracılığıyla yapar.
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,
Test dosyası, PDA'yı türetmek için gereken Typescript kodunu içerir.
const [PDA] = PublicKey.findProgramAddressSync([Buffer.from("data"), user.publicKey.toBuffer()],program.programId);
Test dosyasındaki işlem, PDA'yı adres olarak kullanarak yeni bir zincir üstü
hesap oluşturmak için initialize
talimatını çağırır. Bu örnekte, Anchor
talimat hesaplarındaki PDA adresini çıkarabildiği için, açıkça belirtilmesine
gerek yoktur.
it("Is initialized!", async () => {const transactionSignature = await program.methods.initialize().accounts({user: user.publicKey}).rpc();console.log("Transaction Signature:", transactionSignature);});
Test dosyası ayrıca işlem gönderildikten sonra o adresteki zincir üstü hesabı nasıl alacağınızı da gösterir.
it("Fetch Account", async () => {const pdaAccount = await program.account.dataAccount.fetch(PDA);console.log(JSON.stringify(pdaAccount, null, 2));});
Bu örnekte, aynı user
adresini seed olarak kullanarak initialize
talimatını
birden fazla kez çağırırsanız, işlemin başarısız olacağını unutmayın. Bu,
türetilen adreste zaten bir hesap var olduğu için gerçekleşir.
Is this page helpful?