Як вивести токени з конфіденційного доступного балансу
Щоб вивести токени з конфіденційного доступного балансу на публічний баланс:
-
Створіть два докази на стороні клієнта:
Доказ рівності (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, посилається на
обидва облікові записи в інструкції виведення та закриває їх після цього.
Приклад на 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?