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 AddressProgram 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 AdresEğ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ışı AdresEğ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üretimiPDA Türetimi

Bir PDA türetmek için ilgili SDK'lardan aşağıdaki fonksiyonları kullanın.

SDKFonksiyon
@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}`);
Click to execute the code.

İ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}`);
Click to execute the code.

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}`);
Click to execute the code.

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

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.

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>,

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.

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>,

Test dosyası, PDA'yı türetmek için gereken Typescript kodunu içerir.

Derive PDA
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.

Invoke Initialize Instruction
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.

Fetch Account
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?

İçindekiler

Sayfayı Düzenle