Program Derived Address (PDA)
توفر عناوين Program Derived Addresses (PDAs) للمطورين على سولانا حالتي استخدام رئيسيتين:
- عناوين حسابات محددة: توفر PDAs آلية لإنشاء عنوان بشكل محدد باستخدام مجموعة من "seeds" الاختيارية (مدخلات محددة مسبقًا) ومعرف برنامج معين.
- تمكين توقيع البرنامج: يمكّن وقت تشغيل سولانا البرامج من "التوقيع" لـ PDAs المشتقة من عنوان البرنامج.
يمكنك التفكير في PDAs كطريقة لإنشاء هياكل شبيهة بخرائط التجزئة على السلسلة من مجموعة محددة مسبقًا من المدخلات (مثل السلاسل النصية والأرقام وعناوين الحسابات الأخرى).
ميزة هذا النهج هي أنه يلغي الحاجة إلى تتبع عنوان محدد. بدلاً من ذلك، تحتاج فقط إلى تذكر المدخلات المحددة المستخدمة في اشتقاقه.
Program 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.
مكتبة التطوير البرمجي | الدالة |
---|---|
@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}`);
اشتقاق 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}`);
الزيادة القانونية
يتطلب اشتقاق عنوان 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);}}
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 إلى خطأ وأول قيمة 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 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 لاستدعاء System Program لإنشاء حساب جديد باستخدام
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?