عنوان مشتق من البرنامج

يشير عنوان حساب سولانا إلى موقع الحساب على سلسلة الكتل. العديد من عناوين الحسابات هي المفتاح العام لـ keypair، وفي هذه الحالة يتم استخدام المفتاح الخاص المقابل لتوقيع المعاملات التي تتضمن الحساب.

البديل المفيد لعنوان المفتاح العام هو عنوان مشتق من البرنامج (PDA). توفر PDAs طريقة سهلة لتخزين وتعيين واسترجاع حالة البرنامج. PDA هو عنوان يتم إنشاؤه بشكل محدد باستخدام معرف البرنامج ومجموعة من المدخلات الاختيارية المحددة مسبقًا. تبدو عناوين PDA مشابهة لعناوين المفاتيح العامة، ولكن ليس لها مفتاح خاص مقابل.

يمكّن وقت تشغيل سولانا البرامج من التوقيع لـ PDAs دون الحاجة إلى مفتاح خاص. استخدام PDA يلغي الحاجة إلى تتبع عنوان الحساب. بدلاً من ذلك، يمكنك استدعاء المدخلات المحددة المستخدمة لاشتقاق PDA. (لمعرفة كيفية استخدام البرامج لـ PDAs للتوقيع، راجع قسم استدعاءات البرامج المتقاطعة.)

الخلفية

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

حسابان بعناوين على المنحنىحسابان بعناوين على المنحنى

يتم اشتقاق PDA عمدًا ليقع خارج منحنى Ed25519. هذا يعني أنه ليس له مفتاح خاص مقابل صالح ولا يمكنه تنفيذ عمليات التشفير. (مثل توفير توقيع.) ومع ذلك، تمكّن سولانا البرامج من التوقيع لـ PDAs دون الحاجة إلى مفتاح خاص.

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

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

Program Derived AddressProgram 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اشتقاق 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 إلى خطأ. أول 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}`);
Console
Click to execute the code.

اشتقاق 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}`);
Console
Click to execute the code.

اشتقاق 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}`);
Console
Click to execute the code.

إنشاء حساب PDA

يستخدم المثال أدناه إطار عمل Anchor لإنشاء حساب جديد بعنوان مشتق من البرنامج. يتضمن البرنامج تعليمة initialize واحدة لإنشاء الحساب الجديد، والذي سيخزن عنوان المستخدم وbump seed المستخدم لاشتقاق الـ PDA.

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

يخبر قيد init إطار عمل Anchor بـاستدعاء System Program لإنشاء حساب جديد باستخدام الـ PDA كعنوان. seeds المستخدمة لإنشاء الـ PDA هي:

  • عنوان حساب المستخدم المقدم في التعليمة
  • السلسلة الثابتة: "data"
  • bump seed القانوني

في هذا المثال، لم يتم تعيين قيمة لقيد الـ bump، لذلك سيستخدم Anchor find_program_address لاشتقاق الـ PDA والعثور على الـ 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>,

يحتوي ملف الاختبار أدناه على معاملة تستدعي تعليمة initialize لإنشاء حساب جديد بعنوان مشتق من البرنامج. يحتوي الملف على كود لـ اشتقاق PDA.

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

Test
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 program
const [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?

جدول المحتويات

تعديل الصفحة

تدار بواسطة

© 2025 مؤسسة سولانا.
جميع الحقوق محفوظة.