كيفية إنشاء token account مع امتداد النقل السري
يُتيح امتداد النقل السري إجراء تحويلات رمزية خاصة عن طريق إضافة حالة إضافية إلى token account. يشرح هذا القسم كيفية إنشاء token account مع تفعيل هذا الامتداد.
يوضح المخطط التالي الخطوات المتبعة في إنشاء token account مع امتداد النقل السري:
حالة token account للنقل السري
يُضيف الامتداد حالة ConfidentialTransferAccount إلى token account:
#[repr(C)]#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]pub struct ConfidentialTransferAccount {/// `true` if this account has been approved for use. All confidential/// transfer operations for the account will fail until approval is/// granted.pub approved: PodBool,/// The public key associated with ElGamal encryptionpub elgamal_pubkey: PodElGamalPubkey,/// The low 16 bits of the pending balance (encrypted by `elgamal_pubkey`)pub pending_balance_lo: EncryptedBalance,/// The high 48 bits of the pending balance (encrypted by `elgamal_pubkey`)pub pending_balance_hi: EncryptedBalance,/// The available balance (encrypted by `encryption_pubkey`)pub available_balance: EncryptedBalance,/// The decryptable available balancepub decryptable_available_balance: DecryptableBalance,/// If `false`, the extended account rejects any incoming confidential/// transferspub allow_confidential_credits: PodBool,/// If `false`, the base account rejects any incoming transferspub allow_non_confidential_credits: PodBool,/// The total number of `Deposit` and `Transfer` instructions that have/// credited `pending_balance`pub pending_balance_credit_counter: PodU64,/// The maximum number of `Deposit` and `Transfer` instructions that can/// credit `pending_balance` before the `ApplyPendingBalance`/// instruction is executedpub maximum_pending_balance_credit_counter: PodU64,/// The `expected_pending_balance_credit_counter` value that was included in/// the last `ApplyPendingBalance` instructionpub expected_pending_balance_credit_counter: PodU64,/// The actual `pending_balance_credit_counter` when the last/// `ApplyPendingBalance` instruction was executedpub actual_pending_balance_credit_counter: PodU64,}
يحتوي ConfidentialTransferAccount على عدة حقول لإدارة النقل السري:
-
approved: حالة الموافقة على الحساب لإجراء النقل السري. إذا كان إعداد
auto_approve_new_accountsالخاص بـ mint account مضبوطاً علىtrue، فسيتم اعتماد جميع token accounts تلقائياً لإجراء النقل السري. -
elgamal_pubkey: مفتاح ElGamal العام المستخدم لتشفير الأرصدة ومبالغ التحويل.
-
pending_balance_lo: الـ 16 بت السفلية المشفرة من الرصيد المعلق. يتم تقسيم الرصيد إلى أجزاء عليا وسفلى لتسهيل فك التشفير.
-
pending_balance_hi: الـ 48 بت العلوية المشفرة من الرصيد المعلق. يتم تقسيم الرصيد إلى أجزاء عليا وسفلى لتسهيل فك التشفير.
-
available_balance: الرصيد المشفر المتاح للتحويلات.
-
decryptable_available_balance: الرصيد المتاح مشفراً بمفتاح معيار التشفير المتقدم (AES) لتسهيل فك التشفير من قِبل مالك الحساب.
-
allow_confidential_credits: إذا كانت القيمة true، فإنه يسمح بالنقل السري الوارد.
-
allow_non_confidential_credits: إذا كانت القيمة true، فإنه يسمح بالنقل غير السري الوارد.
-
pending_balance_credit_counter: يحسب ائتمانات الرصيد المعلق الواردة من تعليمات الإيداع والتحويل.
-
maximum_pending_balance_credit_counter: حد العدد للائتمانات المعلقة قبل الاشتراط بتعليمة
ApplyPendingBalanceلتحويل الرصيد المعلق إلى الرصيد المتاح. -
expected_pending_balance_credit_counter: قيمة
pending_balance_credit_counterالتي يوفرها العميل من خلال instruction data في آخر مرة تمت فيها معالجة تعليمةApplyPendingBalance. -
actual_pending_balance_credit_counter: قيمة
pending_balance_credit_counterعلى token account في وقت معالجة آخر تعليمةApplyPendingBalance.
الرصيد المعلق مقابل الرصيد المتاح
تنقسم الأرصدة السرية إلى أرصدة معلقة وأرصدة متاحة للحماية من هجمات حجب الخدمة (DoS). بدون هذا الفصل، يمكن للمهاجم إرسال الرموز المميزة بشكل متكرر إلى token account، مما يعيق قدرة مالك token account على تحويل الرموز المميزة. لن يتمكن مالك token account من تحويل الرموز المميزة لأن الرصيد المشفر سيتغير بين وقت إرسال المعاملة ووقت معالجتها، مما يؤدي إلى فشل المعاملة.
تُضاف جميع مبالغ الإيداع والتحويل في البداية إلى الرصيد المعلق. يجب على أصحاب
token account استخدام تعليمة ApplyPendingBalance لتحويل الرصيد المعلق إلى
الرصيد المتاح. لا تؤثر التحويلات أو الإيداعات الواردة على الرصيد المتاح لـ token
account.
عدادات ائتمان الرصيد المعلق
عند استدعاء تعليمة ApplyPendingBalance لتحويل الرصيد المعلق إلى الرصيد
المتاح:
-
يبحث العميل عن الأرصدة المعلقة والمتاحة الحالية، ويشفّر مجموعها، ويوفر
decryptable_available_balanceمشفرًا باستخدام مفتاح AES الخاص بمالك token account. -
يتتبع عدادا الائتمان المعلق المتوقع والفعلي التغييرات في قيمة العداد بين وقت إنشاء تعليمة
ApplyPendingBalanceووقت معالجتها:expected_pending_balance_credit_counter: قيمةpending_balance_credit_counterعندما ينشئ العميل تعليمةApplyPendingBalanceactual_pending_balance_credit_counter: قيمةpending_balance_credit_counterعلى token account في وقت معالجة تعليمةApplyPendingBalance
تشير قيم العدادات المتطابقة (المتوقعة/الفعلية) إلى أن
decryptable_available_balance يتطابق مع available_balance.
عند جلب حالة token account لقراءة decryptable_available_balance، تستلزم القيم
المختلفة للعدادات المتوقعة/الفعلية من العميل البحث عن تعليمات الإيداع/التحويل
الأخيرة المقابلة لفارق العداد لحساب الرصيد الصحيح.
عملية تسوية الرصيد
عندما تختلف عدادات الرصيد المعلق المتوقعة والفعلية، اتبع هذه الخطوات لتسوية
decryptable_available_balance:
- ابدأ بـ
decryptable_available_balanceمن token account - اجلب أحدث المعاملات المتضمنة لتعليمات الإيداع والتحويل حتى فارق العداد
(الفعلي - المتوقع):
- أضف المبالغ العلنية من تعليمات الإيداع
- فكّ تشفير وأضف مبالغ النص المشفر للوجهة من تعليمات التحويل
التعليمات المطلوبة
يستخدم إنشاء token account وتهيئته للتحويلات السرية التعليمات التالية، والتي تتناسب جميعها في معاملة واحدة:
-
إنشاء Token Account: استدعاء تعليمة
AssociatedTokenAccountInstruction::Createالخاصة بـ Associated Token Program لإنشاء token account على عنوانه الحتمي. -
إعادة تخصيص مساحة الحساب: استدعاء تعليمة
TokenInstruction::Reallocateالخاصة بـ Token Extensions Program لإضافة مساحة لحالةConfidentialTransferAccount. -
التحقق من صحة إثبات pubkey: إنشاء حساب مملوك لبرنامج ZK ElGamal Proof، ثم استدعاء تعليمة
VerifyPubkeyValidityالخاصة به للتحقق من الإثبات وتخزين النتيجة المتحقق منها في حساب حالة السياق ذلك. -
تهيئة التحويلات السرية: استدعاء تعليمة ConfidentialTransferInstruction::ConfigureAccount الخاصة بـ Token Extensions Program، مع الإشارة إلى حساب حالة سياق الإثبات عبر
ProofLocation::ContextStateAccount، لتهيئة حالةConfidentialTransferAccount.
يمكن لمالك حساب الرمز المميز فقط تكوين حساب الرمز المميز للتحويلات السرية.
تستلزم تعليمة ConfigureAccount توليد مفاتيح التشفير وإثبات من جانب العميل،
ولا يمكن توليده إلا من قِبَل مالك token account.
يتحقق إثبات صحة pubkey من أن مفتاح ElGamal العام للحساب صالح. يتم توليده
باستخدام build_pubkey_validity_proof_data، والتحقق منه على السلسلة بواسطة
برنامج ZK ElGamal Proof في حساب حالة السياق، ثم الإشارة إليه من
ConfigureAccount عبر ProofLocation::ContextStateAccount، بحيث لا
تنتقل أي بيانات إثبات في تعليمة التوكن نفسها. للاطلاع على تفاصيل التنفيذ، راجع:
مثال على الكود
الكود التالي ينشئ associated token account ويُهيّئه لعمليات النقل السرية مقابل mint سري موجود مسبقاً.
تعتمد عمليات النقل السرية على برنامج ZK ElGamal Proof، المُفعَّل على mainnet
وdevnet. لا يُفعِّله solana-test-validator الافتراضي، لكن validator محلي يعمل
بنسخة متفرّعة من mainnet مثل Surfpool يُفعِّله. قم
بتشغيل المثال على أحد هذه البيئات (يستخدم الكود devnet) مع حساب دافع ممول،
واستبدل العنصر النائب للـ mint بـ mint تم إنشاؤه وفق
إنشاء Mint.
Rust
// The native ZK ElGamal Proof program verifies the proof on chain.const ZK_PROOF_PROGRAM_ID: Pubkey =solana_pubkey::pubkey!("ZkE1Gama1Proof11111111111111111111111111111");fn main() -> Result<()> {// Use a cluster whose ZK ElGamal Proof program is enabled (mainnet, devnet).let rpc_client = RpcClient::new_with_commitment(String::from("https://api.devnet.solana.com"),CommitmentConfig::confirmed(),);// The Solana CLI default keypair, used as fee payer, mint authority, and// token account owner.let payer = load_keypair()?;let decimals: u8 = 2;// Setup: create a confidential mint for the token account.let mint = create_confidential_mint(&rpc_client, &payer, decimals)?;let token_account = get_associated_token_address_with_program_id(&payer.pubkey(),&mint,&spl_token_2022::id(),);// 1. Create the associated token account.let create_ata_ix = create_associated_token_account(&payer.pubkey(), // funding account&payer.pubkey(), // token account owner&mint,&spl_token_2022::id(),);// 2. Add space for the ConfidentialTransferAccount extension.let realloc_ix = reallocate(&spl_token_2022::id(),&token_account,&payer.pubkey(), // payer&payer.pubkey(), // owner&[&payer.pubkey()],&[ExtensionType::ConfidentialTransferAccount],)?;// 3. Derive the owner's ElGamal keypair and AES key from a signature over// the token account address. The same signer and address always derive// the same keys, so the owner can recover them from their wallet.let (elgamal_keypair, aes_key) = derive_confidential_keys(&payer, &token_account.to_bytes()).map_err(|e| anyhow::anyhow!("derive confidential keys: {e}"))?;// Initial decryptable available balance of 0, encrypted with the AES key.let decryptable_balance: PodAeCiphertext = aes_key.encrypt(0).into();let maximum_pending_balance_credit_counter: u64 = 65_536;// 4. Generate the pubkey-validity proof, then pre-verify it into a context// state account owned by the ZK ElGamal Proof program. configure_account// references the verified proof by account, so no proof bytes travel in// the token instruction itself.let proof_data = build_pubkey_validity_proof_data(&elgamal_keypair).map_err(|e| anyhow::anyhow!("generate pubkey validity proof: {e}"))?;let proof_account = Keypair::new();let context_state_size = size_of::<ProofContextState<PubkeyValidityProofContext>>();let context_state_rent =rpc_client.get_minimum_balance_for_rent_exemption(context_state_size)?;let create_proof_account_ix = system_instruction::create_account(&payer.pubkey(),&proof_account.pubkey(),context_state_rent,context_state_size as u64,&ZK_PROOF_PROGRAM_ID,);let proof_account_address: Address = proof_account.pubkey().to_bytes().into();let owner_address: Address = payer.pubkey().to_bytes().into();let verify_proof_ix = ProofInstruction::VerifyPubkeyValidity.encode_verify_proof(Some(ContextStateInfo {context_state_account: &proof_account_address,context_state_authority: &owner_address,}),&proof_data,);// 5. Configure the account, pointing at the pre-verified proof account.let proof_location: ProofLocation<PubkeyValidityProofData> =ProofLocation::ContextStateAccount(&proof_account.pubkey());let configure_account_ixs = configure_account(&spl_token_2022::id(),&token_account,&mint,&decryptable_balance,maximum_pending_balance_credit_counter,&payer.pubkey(), // owner&[],proof_location,)?;// Everything fits in a single transaction.let mut instructions = vec![create_ata_ix,realloc_ix,create_proof_account_ix,verify_proof_ix,];instructions.extend(configure_account_ixs);let blockhash = rpc_client.get_latest_blockhash()?;let transaction = Transaction::new_signed_with_payer(&instructions,Some(&payer.pubkey()),&[&payer, &proof_account],blockhash,);let signature = rpc_client.send_and_confirm_transaction(&transaction)?;println!("Configured token account {token_account} for confidential transfers: {signature}");Ok(())}
Typescript
const client = await createClient().use(signerFromFile(join(homedir(), ".config/solana/id.json"))).use(solanaRpc({rpcUrl: "https://api.devnet.solana.com"}));// The Solana CLI default keypair, used as fee payer, mint authority, and// token account owner.const owner = client.payer;const decimals = 2;// Setup: create a confidential mint for the token account.const mint = await createConfidentialMint(client, owner, decimals);const [tokenAccount] = await findAssociatedTokenPda({owner: owner.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS,mint});// Derive recoverable ElGamal and AES keys bound to (owner, mint). Re-deriving// from the same wallet always yields the same keys, so the owner can recover// them rather than having to back up a separate secret.const derivedElGamal = await deriveElGamalKeypairForOwnerMint({signer: owner,owner: owner.address,mint});const elgamalKeypair = ElGamalKeypair.fromSecretKey(ElGamalSecretKey.fromBytes(derivedElGamal.secretKey));const aesKey = AeKey.fromBytes(await deriveAeKeyForOwnerMint({ signer: owner, owner: owner.address, mint }));// Build the create-ATA + reallocate + verify-proof + configure plan, then send.// The helper returns an instruction plan because the steps may span more than// one transaction.const plan = await getCreateConfidentialTransferAccountInstructionPlan({rpc: client.rpc,payer: owner,owner,mint,elgamalKeypair,aesKey});const result = await client.sendTransaction(plan);console.log(`Configured token account ${tokenAccount} for confidential transfers: ${result.context.signature}`);
تتوفر مساعدات TypeScript في المسار الفرعي
@solana-program/token-2022/confidential وتعتمد على @solana/zk-sdk لعناصر
التشفير الأساسية. يأتي owner و client من إعداد @solana/kit الخاص بك؛
ويتم إرسال خطة التعليمات المُعادة مع دعم خطط التعليمات في @solana/kit، الذي
يوزّع العمل عبر معاملات متعددة عندما تكون الإثباتات أكبر من أن تتسع لمعاملة
واحدة.
Is this page helpful?