عنوان مشتق من البرنامج
يشير عنوان حساب سولانا إلى موقع الحساب على سلسلة الكتل. العديد من عناوين الحسابات هي المفتاح العام لـ keypair، وفي هذه الحالة يتم استخدام المفتاح الخاص المقابل لتوقيع المعاملات التي تتضمن الحساب.
البديل المفيد لعنوان المفتاح العام هو عنوان مشتق من البرنامج (PDA). توفر PDAs طريقة سهلة لتخزين وتعيين واسترجاع حالة البرنامج. PDA هو عنوان يتم إنشاؤه بشكل محدد باستخدام معرف البرنامج ومجموعة من المدخلات الاختيارية المحددة مسبقًا. تبدو عناوين PDA مشابهة لعناوين المفاتيح العامة، ولكن ليس لها مفتاح خاص مقابل.
يمكّن وقت تشغيل سولانا البرامج من التوقيع لـ PDAs دون الحاجة إلى مفتاح خاص. استخدام PDA يلغي الحاجة إلى تتبع عنوان الحساب. بدلاً من ذلك، يمكنك استدعاء المدخلات المحددة المستخدمة لاشتقاق PDA. (لمعرفة كيفية استخدام البرامج لـ PDAs للتوقيع، راجع قسم استدعاءات البرامج المتقاطعة.)
الخلفية
أزواج المفاتيح في سولانا هي نقاط على منحنى Ed25519 (تشفير المنحنى الإهليلجي). تتكون من مفتاح عام ومفتاح خاص. يصبح المفتاح العام عنوان الحساب، ويستخدم المفتاح الخاص لإنشاء توقيع صالح للحساب.
حسابان بعناوين على المنحنى
يتم اشتقاق PDA عمدًا ليقع خارج منحنى Ed25519. هذا يعني أنه ليس له مفتاح خاص مقابل صالح ولا يمكنه تنفيذ عمليات التشفير. (مثل توفير توقيع.) ومع ذلك، تمكّن سولانا البرامج من التوقيع لـ PDAs دون الحاجة إلى مفتاح خاص.
عنوان خارج المنحنى
يمكنك التفكير في PDAs كطريقة لإنشاء هياكل شبيهة بخرائط التجزئة على السلسلة باستخدام مجموعة محددة مسبقًا من المدخلات. (على سبيل المثال، السلاسل النصية والأرقام وعناوين الحسابات الأخرى.)
Program Derived Address
اشتقاق PDA
قبل إنشاء حساب باستخدام PDA، يجب عليك أولاً اشتقاق العنوان. اشتقاق PDA لا ينشئ تلقائيًا حسابًا على السلسلة في ذلك العنوان - يجب إنشاء الحساب بشكل صريح من خلال البرنامج المستخدم لاشتقاق PDA. يمكنك التفكير في PDA مثل عنوان على خريطة: مجرد وجود عنوان لا يعني أن هناك شيئًا مبنيًا هناك.
تدعم مجموعات تطوير البرمجيات (SDKs) الخاصة بـ سولانا إنشاء PDA باستخدام الدوال الموضحة في الجدول أدناه. تستقبل كل دالة المدخلات التالية:
- معرف البرنامج: عنوان البرنامج المستخدم لاشتقاق PDA. يمكن لهذا البرنامج التوقيع نيابة عن PDA.
- البذور الاختيارية: مدخلات محددة مسبقًا، مثل السلاسل النصية أو الأرقام أو عناوين الحسابات الأخرى.
| SDK | الدالة |
|---|---|
@solana/kit (تايبسكريبت) | getProgramDerivedAddress |
@solana/web3.js (تايبسكريبت) | findProgramAddressSync |
solana_sdk (راست) | find_program_address |
تستخدم الدالة معرف البرنامج والبذور الاختيارية، ثم تتكرر عبر قيم bump لمحاولة إنشاء عنوان برنامج صالح. يبدأ تكرار قيم bump من 255 وينقص بمقدار 1 حتى يتم العثور على PDA صالح. بعد العثور على PDA صالح، تعيد الدالة PDA و bump seed.
bump seed هو بايت إضافي يُضاف إلى البذور الاختيارية لضمان إنشاء عنوان صالح خارج المنحنى.
اشتقاق 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 إلى خطأ. أول bump seed لاشتقاق PDA صالح هي 254. قيم bump seeds 253-251 تشتق أيضًا عناوين PDA فريدة وصالحة.
هذا يعني أنه مع نفس البذور الاختيارية وprogramId، يمكن لـ bump seed بقيمة
مختلفة أن يشتق PDA صالحًا أيضًا.
احرص دائمًا على تضمين فحوصات أمنية للتأكد من أن PDA المُمرر إلى البرنامج مشتق من الزيادة القانونية. قد يؤدي عدم القيام بذلك إلى إدخال ثغرات تسمح باستخدام حسابات غير متوقعة في تعليمات البرنامج. من أفضل الممارسات استخدام الزيادة القانونية فقط عند اشتقاق PDAs.
أمثلة
تشتق الأمثلة أدناه PDA باستخدام مكتبات سولانا SDK. انقر على ▷ تشغيل لتنفيذ الكود.
اشتقاق PDA باستخدام بذرة نصية
يشتق المثال أدناه PDA باستخدام معرف البرنامج وبذرة نصية اختيارية.
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 عنوان
المثال أدناه يشتق 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 متعددة
المثال أدناه يشتق 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
يستخدم المثال أدناه إطار عمل Anchor لإنشاء
حساب جديد بعنوان مشتق من البرنامج. يتضمن البرنامج تعليمة
initialize واحدة لإنشاء الحساب الجديد، والذي سيخزن
عنوان المستخدم وbump seed المستخدم
لاشتقاق الـ 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 bumpdaccount_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,}
يخبر قيد init إطار عمل Anchor
بـاستدعاء System Program لإنشاء حساب
جديد باستخدام الـ PDA كعنوان. seeds المستخدمة لإنشاء الـ PDA
هي:
- عنوان حساب المستخدم المقدم في التعليمة
- السلسلة الثابتة: "data"
- bump seed القانوني
في هذا المثال، لم يتم تعيين قيمة لقيد الـ bump، لذلك سيستخدم Anchor
find_program_address لاشتقاق الـ PDA والعثور على الـ bump.
#[account(init,seeds = [b"data", user.key().as_ref()],bump,payer = user,space = 8 + DataAccount::INIT_SPACE)]pub pda_account: Account<'info, DataAccount>,
يحتوي ملف الاختبار أدناه على معاملة تستدعي تعليمة
initialize لإنشاء حساب جديد بعنوان مشتق من البرنامج.
يحتوي الملف على كود لـ اشتقاق PDA.
يوضح المثال أيضًا كيفية جلب الحساب الجديد الذي سيتم إنشاؤه.
import * as anchor from "@coral-xyz/anchor";import { Program } from "@coral-xyz/anchor";import { PdaAccount } from "../target/types/pda_account";import { PublicKey } from "@solana/web3.js";describe("pda-account", () => {const provider = anchor.AnchorProvider.env();anchor.setProvider(provider);const program = anchor.workspace.PdaAccount as Program<PdaAccount>;const user = provider.wallet as anchor.Wallet;// Derive the PDA address using the seeds specified on the programconst [PDA] = PublicKey.findProgramAddressSync([Buffer.from("data"), user.publicKey.toBuffer()],program.programId);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 مرة أخرى بنفس seed عنوان user، فستفشل
المعاملة. يحدث هذا لأن الحساب موجود بالفعل في العنوان المشتق.
Is this page helpful?