Program Derived Address (PDA)
توفر العناوين المشتقة من البرامج (PDAs) للمطورين على سولانا حالتي استخدام رئيسيتين:
- عناوين حسابات محددة: توفر PDAs آلية لإنشاء عنوان بشكل محدد باستخدام مجموعة من "seeds" الاختيارية (مدخلات محددة مسبقًا) ومعرف برنامج معين.
- تمكين توقيع البرنامج: يمكّن وقت تشغيل سولانا البرامج من "التوقيع" لـ PDAs المشتقة من عنوان البرنامج.
يمكنك التفكير في PDAs كطريقة لإنشاء هياكل شبيهة بخرائط التجزئة على السلسلة من مجموعة محددة مسبقًا من المدخلات (مثل السلاسل النصية والأرقام وعناوين الحسابات الأخرى).
ميزة هذا النهج هي أنه يلغي الحاجة إلى تتبع عنوان محدد. بدلاً من ذلك، تحتاج فقط إلى تذكر المدخلات المحددة المستخدمة في اشتقاقه.
Program Derived Address
من المهم فهم أن مجرد اشتقاق عنوان مشتق من البرنامج (PDA) لا ينشئ تلقائيًا حسابًا على السلسلة في ذلك العنوان. يجب إنشاء الحسابات التي تستخدم PDA كعنوان على السلسلة بشكل صريح من خلال البرنامج المستخدم لاشتقاق العنوان. يمكنك التفكير في اشتقاق PDA كالعثور على عنوان على خريطة. مجرد وجود عنوان لا يعني أن هناك شيئًا مبنيًا في ذلك الموقع.
يغطي هذا القسم تفاصيل اشتقاق PDAs. يشرح قسم Cross Program Invocations (CPIs) كيف تستخدم البرامج PDAs للتوقيع.
النقاط الرئيسية
- PDAs هي عناوين مشتقة بشكل محدد باستخدام مجموعة من seeds المحددة مسبقًا، وbump seed، ومعرف البرنامج.
- PDAs هي عناوين تقع خارج منحنى Ed25519 وليس لها مفتاح خاص مقابل.
- يمكن لبرامج سولانا التوقيع نيابة عن PDAs المشتقة من معرف البرنامج الخاص بها.
- اشتقاق PDA لا ينشئ تلقائيًا حسابًا على السلسلة.
- يجب إنشاء حساب يستخدم PDA كعنوانه من خلال تعليمات داخل برنامج سولانا.
ما هو عنوان PDA
عناوين PDA هي عناوين مشتقة بشكل حتمي تشبه المفاتيح العامة، ولكن ليس لها مفاتيح خاصة. وهذا يعني أنه من غير الممكن إنشاء توقيع صالح لهذا العنوان. ومع ذلك، تتيح بيئة تشغيل Solana للبرامج "التوقيع" نيابة عن عناوين PDA دون الحاجة إلى مفتاح خاص.
للسياق، فإن أزواج المفاتيح في Solana هي نقاط على منحنى Ed25519 (تشفير المنحنى الإهليلجي) مع مفتاح عام ومفتاح خاص مقابل. تُستخدم المفاتيح العامة كعناوين (معرّف فريد) للحسابات على السلسلة.
عنوان على المنحنى
عنوان PDA هو نقطة مشتقة عمداً لتقع خارج منحنى Ed25519 باستخدام مجموعة محددة مسبقاً من المدخلات. النقطة التي ليست على منحنى Ed25519 ليس لها مفتاح خاص مقابل صالح ولا يمكنها تنفيذ عمليات التشفير (التوقيع).
يمكن أن يعمل عنوان PDA كعنوان (معرّف فريد) لحساب على السلسلة، مما يوفر طريقة سهلة لتخزين وتعيين واسترجاع حالة البرنامج.
عنوان خارج المنحنى
كيفية اشتقاق عنوان PDA
يتطلب اشتقاق عنوان PDA ثلاثة مدخلات:
- البذور الاختيارية (seeds): مدخلات محددة مسبقاً (مثل السلاسل النصية، الأرقام، عناوين الحسابات الأخرى) لاشتقاق عنوان PDA.
- بذرة الإزاحة (bump seed): بايت إضافي يُضاف إلى البذور الاختيارية لضمان إنشاء عنوان PDA صالح (خارج المنحنى). تبدأ بذرة الإزاحة من 255 وتنقص بمقدار 1 حتى يتم العثور على عنوان PDA صالح.
- معرّف البرنامج (Program ID): عنوان البرنامج الذي يتم اشتقاق عنوان PDA منه. يمكن لهذا البرنامج التوقيع نيابة عن عنوان PDA.
اشتقاق عنوان PDA
استخدم الدوال التالية من مجموعات تطوير البرمجيات (SDKs) المعنية لاشتقاق عنوان PDA.
SDK | الوظيفة |
---|---|
@solana/kit (Typescript) | getProgramDerivedAddress |
@solana/web3.js (Typescript) | findProgramAddressSync |
solana_sdk (Rust) | find_program_address |
لاشتقاق عنوان PDA، قم بتوفير المدخلات التالية لوظيفة SDK:
- الـ seeds الاختيارية المحددة مسبقًا محولة إلى بايتات
- معرّف البرنامج (العنوان) المستخدم للاشتقاق
بمجرد العثور على عنوان PDA صالح، تقوم الوظيفة بإرجاع كل من العنوان (PDA) والـ bump seed المستخدم للاشتقاق.
أمثلة
توضح الأمثلة التالية كيفية اشتقاق عنوان PDA باستخدام مجموعات SDK المختلفة.
انقر على زر "تشغيل" لتنفيذ الكود.
اشتقاق عنوان 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 عنوان اختياري
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 متعددة اختيارية
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}`);
البامب القانوني (Canonical Bump)
يتطلب اشتقاق عنوان PDA "بامب سيد (bump seed)"، وهو بايت إضافي يُضاف إلى البذور (seeds) الاختيارية. تقوم دالة الاشتقاق بالتكرار خلال قيم البامب، بدءًا من 255 وتنقص بمقدار 1، حتى تنتج قيمة عنوانًا صالحًا خارج المنحنى. أول قيمة بامب تنتج عنوانًا صالحًا خارج المنحنى هي "البامب القانوني".
توضح الأمثلة التالية اشتقاق PDA باستخدام جميع قيم البامب سيد الممكنة (من 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);}}
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
قيمة البامب سيد 255 تؤدي إلى خطأ وأول قيمة بامب سيد تشتق عنوان PDA صالح هي 254.
لاحظ أن قيم البامب سيد 253-251 كلها تشتق عناوين PDA صالحة بعناوين مختلفة. هذا
يعني أنه مع نفس البذور الاختيارية وprogramId
، يمكن لقيمة بامب سيد مختلفة أن
تشتق عنوان PDA صالح أيضًا.
عند بناء برامج Solana، قم دائمًا بتضمين فحوصات أمان للتأكد من أن عنوان 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 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,}
في هذا المثال، تتضمن بذور (seeds) اشتقاق PDA السلسلة الثابتة data
وعنوان حساب
user
المقدم في التعليمات. يقوم إطار عمل Anchor تلقائيًا بإيجاد بذرة (seed)
bump
القانونية.
#[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 لاستدعاء برنامج النظام لإنشاء حساب جديد باستخدام
PDA كعنوان. يقوم Anchor بذلك من خلال CPI.
#[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.
const [PDA] = PublicKey.findProgramAddressSync([Buffer.from("data"), user.publicKey.toBuffer()],program.programId);
تستدعي المعاملة في ملف الاختبار تعليمات initialize
لإنشاء حساب جديد على
السلسلة باستخدام PDA كعنوان. في هذا المثال، يمكن لـ Anchor استنتاج عنوان PDA في
حسابات التعليمات، لذلك لا يلزم توفيره بشكل صريح.
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));});
لاحظ أنه في هذا المثال، إذا قمت باستدعاء تعليمات initialize
أكثر من مرة واحدة
باستخدام نفس عنوان user
كبذرة (seed)، فستفشل المعاملة. يحدث هذا لأن الحساب
موجود بالفعل في العنوان المشتق.
Is this page helpful?