كيفية سحب الرموز من الرصيد المتاح السري
لسحب الرموز من الرصيد المتاح السري إلى الرصيد العام:
-
أنشئ إثباتين على جانب العميل:
إثبات المساواة (CiphertextCommitmentEqualityProofData): يتحقق من أن نص التشفير للرصيد المتاح المتبقي بعد السحب يطابق التزام بيدرسن المقابل له، مما يضمن احتساب الرصيد المتاح الجديد للحساب بشكل صحيح على أنه
remaining_balance = current_balance - withdraw_amount.إثبات النطاق (BatchedRangeProofU64Data): يتحقق من أن الرصيد المتاح المتبقي بعد السحب غير سالب وضمن نطاق محدد.
-
لكل إثبات:
- استدعِ برنامج إثبات ZK ElGamal للتحقق من بيانات الإثبات.
- خزِّن البيانات الوصفية الخاصة بالإثبات في حساب "حالة السياق" للإثبات لاستخدامها في تعليمات أخرى.
-
استدعِ تعليمة ConfidentialTransferInstruction::Withdraw مع توفير حسابَي الإثبات.
-
أغلق حسابات الإثبات لاسترداد SOL المستخدم لإنشائها.
يوضح المخطط التالي الخطوات المتبعة في سحب الرموز من الرصيد المتاح السري إلى الرصيد العام:
التعليمات المطلوبة
لسحب الرموز من الرصيد المتاح السري إلى الرصيد العام، يجب عليك:
- توليد إثبات مساواة وإثبات نطاق على جانب العميل
- استدعاء برنامج إثبات Zk ElGamal للتحقق من الإثباتات وتهيئة حسابات "حالة السياق"
- استدعاء تعليمة ConfidentialTransferInstruction::Withdraw مع توفير حسابَي الإثبات.
- إغلاق حسابَي الإثبات لاسترداد rent.
يقوم مثال Rust أدناه بتوليد الإثباتات باستخدام حزمة
spl-token-confidential-transfer-proof-generation، والتحقق من كل منها في حساب
حالة سياق عبر برنامج ZK ElGamal Proof، مع الإشارة إلى كلا الحسابين في تعليمة
السحب وإغلاقهما بعد ذلك. يستخدم مثال TypeScript المساعد
getConfidentialWithdrawInstructionPlan من
@solana-program/token-2022/confidential، الذي يُجمِّع نفس حسابات الإثبات
والسحب والإغلاق في خطة تعليمات متعددة المعاملات.
مثال على الكود
يسحب المثال التالي الرموز المميزة من الرصيد المتاح السري إلى الرصيد العام. يجب أن يكون الحساب مُهيَّأً مسبقاً لإجراء التحويلات السرية وأن يحتوي على رصيد سري متاح.
تعتمد التحويلات السرية على برنامج ZK ElGamal Proof، المُفعَّل على mainnet
وdevnet. لا يُفعِّل solana-test-validator الافتراضي هذا البرنامج، غير أن
validator محلياً يعمل بالتشعيب من mainnet مثل Surfpool
يدعمه. شغِّل المثال على أحد تلك البيئات (يستخدم الكود devnet) مع حساب مموَّل،
واستبدل العناصر النائبة بعنوان mint الخاص بك وtoken account.
Rust
const ZK_PROOF_PROGRAM_ID: Pubkey =solana_pubkey::pubkey!("ZkE1Gama1Proof11111111111111111111111111111");fn main() -> Result<()> {let rpc_client = RpcClient::new_with_commitment(String::from("https://api.devnet.solana.com"),CommitmentConfig::confirmed(),);// Owner = fee payer = token account owner. The account must already be// configured for confidential transfers with an available confidential// balance to withdraw from.let owner = load_keypair()?;let amount: u64 = 100;let decimals: u8 = 2;// Setup: create an available confidential balance to withdraw.let (mint, token_account) = setup_withdrawable_account(&rpc_client, &owner, amount, decimals)?;// Derive the owner's keys and read the current confidential available balance.let (elgamal_keypair, aes_key) = derive_confidential_keys(&owner, &token_account.to_bytes()).map_err(|e| anyhow::anyhow!("derive confidential keys: {e}"))?;let account_data = rpc_client.get_account(&token_account)?;let account = StateWithExtensions::<TokenAccount>::unpack(&account_data.data)?;let ct_extension = account.get_extension::<ConfidentialTransferAccount>()?;// The ElGamal available-balance ciphertext is required to build the proof.let available_balance: ElGamalCiphertext = ct_extension.available_balance.try_into().map_err(|e| anyhow::anyhow!("decode available balance: {e:?}"))?;// Read the plaintext balance from the AES-encrypted decryptable balance.// ElGamal's decrypt_u32 only recovers values up to 2^32 raw units, so it// fails for realistic balances; the AES field has no such limit.let decryptable_balance: AeCiphertext = ct_extension.decryptable_available_balance.try_into().map_err(|e| anyhow::anyhow!("decode decryptable balance: {e:?}"))?;let current_available = decryptable_balance.decrypt(&aes_key).context("decrypt available balance")?;// Generate the equality and range proofs for the withdrawal.let proof_data = withdraw_proof_data(&available_balance, current_available, amount, &elgamal_keypair).map_err(|e| anyhow::anyhow!("withdraw_proof_data: {e}"))?;let new_available = current_available.checked_sub(amount).context("insufficient confidential balance")?;let new_decryptable: PodAeCiphertext = aes_key.encrypt(new_available).into();// The owner is the context-state authority for both proof accounts.let authority: Address = owner.pubkey().to_bytes().into();// Equality proof context state account.let equality_account = Keypair::new();let equality_size = size_of::<ProofContextState<CiphertextCommitmentEqualityProofContext>>();let equality_create_ix = system_instruction::create_account(&owner.pubkey(),&equality_account.pubkey(),rpc_client.get_minimum_balance_for_rent_exemption(equality_size)?,equality_size as u64,&ZK_PROOF_PROGRAM_ID,);let equality_verify_ix = ProofInstruction::VerifyCiphertextCommitmentEquality.encode_verify_proof(Some(ContextStateInfo {context_state_account: &Address::from(equality_account.pubkey().to_bytes()),context_state_authority: &authority,}),&proof_data.equality_proof_data,);send_tx(&rpc_client, &[equality_create_ix], &[&owner, &equality_account])?;send_tx(&rpc_client, &[equality_verify_ix], &[&owner])?;// Range proof context state account.let range_account = Keypair::new();let range_size = size_of::<ProofContextState<BatchedRangeProofContext>>();let range_create_ix = system_instruction::create_account(&owner.pubkey(),&range_account.pubkey(),rpc_client.get_minimum_balance_for_rent_exemption(range_size)?,range_size as u64,&ZK_PROOF_PROGRAM_ID,);let range_verify_ix = ProofInstruction::VerifyBatchedRangeProofU64.encode_verify_proof(Some(ContextStateInfo {context_state_account: &Address::from(range_account.pubkey().to_bytes()),context_state_authority: &authority,}),&proof_data.range_proof_data,);send_tx(&rpc_client, &[range_create_ix], &[&owner, &range_account])?;send_tx(&rpc_client, &[range_verify_ix], &[&owner])?;// Withdraw, referencing both pre-verified proof accounts.let withdraw_ixs = withdraw(&spl_token_2022::id(),&token_account,&mint,amount,decimals,&new_decryptable,&owner.pubkey(),&[&owner.pubkey()],ProofLocation::ContextStateAccount(&equality_account.pubkey()),ProofLocation::ContextStateAccount(&range_account.pubkey()),)?;let blockhash = rpc_client.get_latest_blockhash()?;let transaction =Transaction::new_signed_with_payer(&withdraw_ixs, Some(&owner.pubkey()), &[&owner], blockhash);let signature = rpc_client.send_and_confirm_transaction(&transaction)?;// Close both proof accounts to reclaim their rent.for account in [&equality_account, &range_account] {let close_ix = close_context_state(ContextStateInfo {context_state_account: &Address::from(account.pubkey().to_bytes()),context_state_authority: &authority,},&authority,);send_tx(&rpc_client, &[close_ix], &[&owner])?;}println!("Withdrew {amount} tokens to the public balance: {signature}");Ok(())}
Typescript
const client = await createClient().use(signerFromFile(join(homedir(), ".config/solana/id.json"))).use(solanaRpc({rpcUrl: "https://api.devnet.solana.com",maxConcurrency: 1}));// The Solana CLI default keypair, used as fee payer, mint authority, and// token account owner.const owner = client.payer;const depositAmount = 100n;const amount = 25n;const decimals = 2;// Setup: create a confidential account with an available confidential balance.const mint = await createConfidentialMint(client, owner, decimals);const token = await createConfidentialTokenAccount(client, owner, mint);await mintPublicTokens(client, owner, mint, token, depositAmount);await depositTokens(client, owner, mint, token, depositAmount, decimals);await applyPendingBalance(client, owner, mint, token);// Derive the owner's recoverable ElGamal and AES keys, bound to (owner, mint).const { elgamalKeypair, aesKey } = await deriveConfidentialKeys(owner, mint);const tokenAccount = (await fetchToken(client.rpc, token)).data;// Builds the equality + range proof accounts, the withdraw, and the closes as a// multi-transaction instruction plan.const plan = await getConfidentialWithdrawInstructionPlan({rpc: client.rpc,payer: owner,authority: owner,token,mint,tokenAccount,amount,decimals,elgamalKeypair,aesKey});const result = await client.sendTransactions(plan);const summary = summarizeTransactionPlanResult(result);const signature =summary.successfulTransactions[summary.successfulTransactions.length - 1].context.signature;console.log(`Withdrew ${amount} tokens to the public balance: ${signature}`);
Is this page helpful?