Program Derived Address (PDA)

توفر عناوين Program Derived Addresses (PDAs) للمطورين على سولانا حالتي استخدام رئيسيتين:

  • عناوين حسابات محددة: توفر PDAs آلية لإنشاء عنوان بشكل محدد باستخدام مجموعة من "seeds" الاختيارية (مدخلات محددة مسبقًا) ومعرف برنامج معين.
  • تمكين توقيع البرنامج: يمكّن وقت تشغيل سولانا البرامج من "التوقيع" لـ PDAs المشتقة من عنوان البرنامج.

يمكنك التفكير في PDAs كطريقة لإنشاء هياكل شبيهة بخرائط التجزئة على السلسلة من مجموعة محددة مسبقًا من المدخلات (مثل السلاسل النصية والأرقام وعناوين الحسابات الأخرى).

ميزة هذا النهج هي أنه يلغي الحاجة إلى تتبع عنوان محدد. بدلاً من ذلك، تحتاج فقط إلى تذكر المدخلات المحددة المستخدمة في اشتقاقه.

Program Derived AddressProgram Derived Address

من المهم فهم أن مجرد اشتقاق عنوان Program Derived Address (PDA) لا ينشئ تلقائيًا حسابًا على السلسلة بهذا العنوان. يجب إنشاء الحسابات التي تستخدم PDA كعنوان على السلسلة بشكل صريح من خلال البرنامج المستخدم لاشتقاق العنوان. يمكنك التفكير في اشتقاق PDA كالعثور على عنوان على خريطة. مجرد وجود عنوان لا يعني أن هناك شيئًا مبنيًا في ذلك الموقع.

يغطي هذا القسم تفاصيل اشتقاق PDAs. يشرح قسم Cross Program Invocations (CPIs) كيف تستخدم البرامج PDAs للتوقيع.

النقاط الرئيسية

  • PDAs هي عناوين مشتقة بشكل محدد باستخدام مجموعة من seeds المحددة مسبقًا، وbump seed، ومعرف البرنامج.
  • PDAs هي عناوين تقع خارج منحنى Ed25519 وليس لها مفتاح خاص مقابل.
  • يمكن لبرامج سولانا التوقيع نيابة عن PDAs المشتقة من معرف البرنامج الخاص بها.
  • اشتقاق PDA لا ينشئ تلقائيًا حسابًا على السلسلة.
  • يجب إنشاء حساب يستخدم PDA كعنوانه من خلال تعليمات داخل برنامج سولانا.

ما هو PDA

PDAs هي عناوين مشتقة بشكل حتمي تشبه المفاتيح العامة، ولكن ليس لها مفاتيح خاصة. هذا يعني أنه من غير الممكن إنشاء توقيع صالح للعنوان. ومع ذلك، تتيح بيئة تشغيل سولانا للبرامج "التوقيع" نيابة عن PDAs دون الحاجة إلى مفتاح خاص.

للسياق، فإن أزواج المفاتيح في سولانا هي نقاط على منحنى Ed25519 (تشفير المنحنى الإهليلجي) مع مفتاح عام ومفتاح خاص مقابل. تُستخدم المفاتيح العامة كعناوين (معرّف فريد) للحسابات على السلسلة.

عنوان على المنحنىعنوان على المنحنى

PDA هو نقطة مشتقة عمداً لتقع خارج منحنى Ed25519 باستخدام مجموعة محددة مسبقاً من المدخلات. النقطة التي ليست على منحنى Ed25519 ليس لها مفتاح خاص مقابل صالح ولا يمكنها تنفيذ عمليات التشفير (التوقيع).

يمكن أن يعمل PDA كعنوان (معرّف فريد) لحساب على السلسلة، مما يوفر طريقة سهلة لتخزين وتعيين واسترجاع حالة البرنامج.

عنوان خارج المنحنىعنوان خارج المنحنى

كيفية اشتقاق PDA

يتطلب اشتقاق PDA ثلاثة مدخلات:

  • seed الاختيارية: مدخلات محددة مسبقاً (مثل السلاسل النصية، الأرقام، عناوين الحسابات الأخرى) لاشتقاق PDA.
  • bump seed: بايت إضافي يُضاف إلى seed الاختيارية لضمان إنشاء PDA صالح (خارج المنحنى). يبدأ bump seed من 255 وينقص بمقدار 1 حتى يتم العثور على PDA صالح.
  • معرّف البرنامج: عنوان البرنامج الذي يتم اشتقاق PDA منه. يمكن لهذا البرنامج التوقيع نيابة عن PDA.

اشتقاق PDAاشتقاق PDA

استخدم الدوال التالية من مجموعات تطوير البرمجيات المعنية لاشتقاق PDA.

مكتبة التطوير البرمجيالدالة
@solana/kit (تايب سكريبت)getProgramDerivedAddress
@solana/web3.js (تايب سكريبت)findProgramAddressSync
solana_sdk (رست)find_program_address

لاشتقاق عنوان مشتق من البرنامج (PDA)، قم بتوفير المدخلات التالية لدالة مكتبة التطوير البرمجي:

  • الـ seeds الاختيارية المحددة مسبقًا محولة إلى بايتات
  • معرف البرنامج (العنوان) المستخدم للاشتقاق

بمجرد العثور على عنوان PDA صالح، تقوم الدالة بإرجاع كل من العنوان (PDA) والـ bump seed المستخدم للاشتقاق.

أمثلة

توضح الأمثلة التالية كيفية اشتقاق PDA باستخدام مكتبات التطوير البرمجي المختلفة.

انقر على زر "تشغيل" لتنفيذ الكود.

اشتقاق 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 عنوان اختياري

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 متعددة اختيارية

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.

الزيادة القانونية

يتطلب اشتقاق عنوان PDA "bump seed"، وهو بايت إضافي يُضاف إلى البذور الاختيارية. تقوم دالة الاشتقاق بالتكرار خلال قيم الزيادة، بدءًا من 255 وتنقص بمقدار 1، حتى تنتج قيمة عنوانًا صالحًا خارج المنحنى. أول قيمة زيادة تنتج عنوانًا صالحًا خارج المنحنى هي "الزيادة القانونية".

توضح الأمثلة التالية اشتقاق PDA باستخدام جميع قيم bump seed الممكنة (من 255 إلى 0):

لم يتم تضمين مثال 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 seed 255 إلى خطأ وأول قيمة bump seed تشتق PDA صالحًا هي 254.

لاحظ أن قيم bump seed 253-251 كلها تشتق عناوين PDA صالحة بعناوين مختلفة. هذا يعني أنه مع نفس البذور الاختيارية وprogramId، يمكن لـ bump seed بقيمة مختلفة أن يشتق PDA صالحًا أيضًا.

عند بناء برامج سولانا، قم دائمًا بتضمين فحوصات أمنية للتأكد من أن PDA الذي تم تمريره إلى البرنامج مشتق من الزيادة القانونية. قد يؤدي عدم تضمين هذه الفحوصات إلى إدخال ثغرات تسمح باستخدام حسابات غير متوقعة في تعليمات البرنامج. من أفضل الممارسات استخدام الزيادة القانونية فقط عند اشتقاق عناوين PDA.

إنشاء حسابات PDA

يوضح البرنامج المثال أدناه كيفية إنشاء حساب باستخدام PDA كعنوان للحساب الجديد. يستخدم البرنامج المثال إطار عمل Anchor.

يتضمن البرنامج تعليمة initialize واحدة لإنشاء حساب جديد باستخدام PDA كعنوان للحساب. يخزن الحساب الجديد عنوان user وbump المستخدمة لاشتقاق PDA.

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

في هذا المثال، تتضمن seeds لاشتقاق PDA السلسلة الثابتة data وعنوان حساب user المقدم في التعليمات. يقوم إطار عمل Anchor تلقائيًا بإيجاد seed bump القانونية.

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 إطار Anchor لاستدعاء System Program لإنشاء حساب جديد باستخدام PDA كعنوان. يقوم Anchor بذلك من خلال CPI.

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

يحتوي ملف الاختبار على كود Typescript لاشتقاق PDA.

Derive PDA
const [PDA] = PublicKey.findProgramAddressSync(
[Buffer.from("data"), user.publicKey.toBuffer()],
program.programId
);

تستدعي المعاملة في ملف الاختبار تعليمات initialize لإنشاء حساب جديد على السلسلة باستخدام PDA كعنوان. في هذا المثال، يمكن لـ Anchor استنتاج عنوان PDA في حسابات التعليمات، لذلك لا يلزم توفيره بشكل صريح.

Invoke Initialize Instruction
it("Is initialized!", async () => {
const transactionSignature = await program.methods
.initialize()
.accounts({
user: user.publicKey
})
.rpc();
console.log("Transaction Signature:", transactionSignature);
});

يوضح ملف الاختبار أيضًا كيفية جلب الحساب الموجود على السلسلة الذي تم إنشاؤه في ذلك العنوان بمجرد إرسال المعاملة.

Fetch Account
it("Fetch Account", async () => {
const pdaAccount = await program.account.dataAccount.fetch(PDA);
console.log(JSON.stringify(pdaAccount, null, 2));
});

لاحظ أنه في هذا المثال، إذا قمت باستدعاء تعليمات initialize أكثر من مرة واحدة باستخدام نفس عنوان user كـ seed، فستفشل المعاملة. يحدث هذا لأن الحساب موجود بالفعل في العنوان المشتق.

Is this page helpful?