اشتقاق PDA

ملخص

يتم اشتقاق PDAs عن طريق تجزئة البذور + معرف البرنامج + bump عبر SHA-256 حتى تكون النتيجة خارج منحنى Ed25519. قيمة bump القانونية هي أول قيمة تنتج عنوانًا خارج المنحنى. الحد الأقصى 16 بذرة، والحد الأقصى 32 بايت لكل بذرة.

الخلفية

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

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

يتم اشتقاق PDA عمدًا ليقع خارج منحنى Ed25519. نظرًا لأنه ليس نقطة منحنى صالحة، فلا يوجد مفتاح سري، ولا يمكن لأي طرف خارجي إنتاج توقيع. يمكن فقط للبرنامج المشتق أن يصرح بالعمليات على PDA من خلال invoke_signed.

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

حسابات PDA مقابل حسابات زوج المفاتيح

الخاصيةحساب زوج المفاتيححساب PDA
نوع العنوانعلى منحنى Ed25519خارج منحنى Ed25519
يحتوي على مفتاح خاصنعملا
يمكنه التوقيع على المعاملاتنعم (باستخدام المفتاح الخاص)لا
يمكنه التوقيع أثناء CPIلا (ما لم يتم تضمين التوقيع في المعاملة)نعم (عبر invoke_signed)
الاشتقاقتوليد زوج مفاتيح Ed25519حتمي من البذور + معرف البرنامج
الاستخدام النموذجيمحافظ المستخدمين، معرف البرنامجحسابات البيانات المملوكة للبرنامج

البذور الاختيارية

البذور الاختيارية هي سلاسل بايتات يحددها المستخدم وتعمل كمدخلات لاشتقاق عنوان PDA. تنشئ عناوين فريدة وحتمية مرتبطة ببرنامج معين. على سبيل المثال، استخدام ["user", user_pubkey] كبذور يشتق عنوان PDA مختلف لكل مستخدم.

يجب أن تتبع البذور هذه القيود:

  • حد أقصى 16 بذرة لكل اشتقاق (MAX_SEEDS)
  • حد أقصى 32 بايت لكل بذرة (MAX_SEED_LEN)

بذرة Bump

بذرة bump هي بايت واحد (0-255) يُضاف إلى البذور الاختيارية أثناء الاشتقاق. find_program_address يبحث من 255 نزولاً إلى 0، مستدعياً create_program_address مع كل قيمة حتى تخرج النتيجة عن منحنى Ed25519. القيمة الأولى التي تنجح هي bump القانوني.

يجب على البرامج دائماً استخدام bump القانوني لضمان تعيين فريد وحتمي من البذور إلى العنوان.

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

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

خوارزمية الاشتقاق

يتم تنفيذ اشتقاق PDA في دالة create_program_address الخاصة بـ SDK. تعمل الخوارزمية على النحو التالي:

  1. التحقق من أن عدد البذور لا يتجاوز MAX_SEEDS (16) وأن أي بذرة فردية لا تتجاوز MAX_SEED_LEN (32 بايت). إذا فشل أي من الفحصين، يتم إرجاع PubkeyError::MaxSeedLengthExceeded.
  2. إجراء تجزئة SHA-256 لجميع البذور ومعرف البرنامج والسلسلة النصية "ProgramDerivedAddress" معاً لإنتاج نتيجة بحجم 32 بايت.
  3. التحقق مما إذا كانت النتيجة نقطة صالحة على منحنى Ed25519.
  4. إذا كانت النتيجة على المنحنى، يتم إرجاع PubkeyError::InvalidSeeds (سيكون للعنوان مفتاح خاص مقابل، مما ينتهك خاصية أمان PDA).
  5. إذا لم تكن النتيجة على المنحنى، يتم إرجاعها كعنوان PDA.

تكاليف وحدة الحوسبة

تفرض استدعاء النظام على السلسلة لـ create_program_address 1,500 وحدة حوسبة لكل استدعاء.

يفرض استدعاء النظام try_find_program_address 1,500 وحدة حوسبة عند الدخول (قبل الحلقة)، ثم 1,500 وحدة حوسبة إضافية لكل محاولة bump فاشلة داخل الحلقة.

أنماط seed الشائعة

تكون seeds خاصة بالتطبيق. تشمل الأنماط الشائعة:

النمطSeedsحالة الاستخدام
مفرد عام["global"]حساب تكوين واحد على مستوى البرنامج
حساب لكل مستخدم["user", user_pubkey]حساب واحد لكل مستخدم لكل برنامج
لكل مستخدم لكل كيان["vault", user_pubkey, mint_pubkey]خزائن الرموز، لكل مستخدم لكل رمز
عداد / تسلسلي["order", user_pubkey, &order_id.to_le_bytes()]سجلات تسلسلية لكل مستخدم

يتم دمج seeds قبل التجزئة، لذا فإن ["ab", "cd"] و ["abcd"] ينتجان نفس PDA. استخدم seeds ذات طول ثابت أو فاصل لتجنب التصادمات. على سبيل المثال، ["ab", "-", "cd"] واضح لا لبس فيه.

أمثلة: اشتقاق PDA

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

توفر مجموعات تطوير سولانا دوال لاشتقاق PDA. تأخذ كل دالة:

  • معرّف البرنامج: عنوان البرنامج المستخدم لاشتقاق PDA. يمكن لهذا البرنامج التوقيع نيابةً عن PDA.
  • seeds اختيارية: مدخلات محددة مسبقاً مثل السلاسل النصية، الأرقام، أو عناوين الحسابات الأخرى.
SDKالدالة
@solana/kit (TypeScript)getProgramDerivedAddress
@solana/web3.js (TypeScript)findProgramAddressSync
solana_sdk (Rust)find_program_address

تشتق الأمثلة أدناه PDA باستخدام مجموعات تطوير سولانا. انقر ▷ تشغيل لتنفيذ الكود.

اشتقاق PDA باستخدام seed نصي

يوضح المثال أدناه اشتقاق 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 عنوان

يوضح المثال أدناه اشتقاق 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.

التكرار عبر جميع قيم Bump

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

لم يتم تضمين مثال 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 255 عنوانًا على المنحنى ويفشل. أول bump صالح هو 254، مما يجعله الـ bump القانوني.

Is this page helpful?

تدار بواسطة

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