Cách rút token từ số dư khả dụng bảo mật
Để rút token từ số dư khả dụng bảo mật sang số dư công khai:
-
Tạo hai bằng chứng ở phía client:
Bằng chứng bằng nhau (CiphertextCommitmentEqualityProofData): Xác minh rằng văn bản mã hóa số dư khả dụng còn lại sau khi rút khớp với cam kết Pedersen tương ứng, đảm bảo số dư khả dụng mới của tài khoản được tính đúng là
remaining_balance = current_balance - withdraw_amount.Bằng chứng phạm vi (BatchedRangeProofU64Data): Xác minh rằng số dư khả dụng còn lại sau khi rút là không âm và nằm trong một phạm vi xác định.
-
Đối với mỗi bằng chứng:
- Gọi chương trình bằng chứng ZK ElGamal để xác minh dữ liệu bằng chứng.
- Lưu trữ siêu dữ liệu đặc thù của bằng chứng trong tài khoản "context state" của bằng chứng để sử dụng trong các lệnh khác.
-
Gọi lệnh ConfidentialTransferInstruction::Withdraw cung cấp hai tài khoản bằng chứng.
-
Đóng các tài khoản bằng chứng để thu hồi SOL đã dùng để tạo chúng.
Sơ đồ sau đây mô tả các bước liên quan đến việc rút token từ số dư khả dụng bảo mật sang số dư công khai:
Các lệnh bắt buộc
Để rút token từ số dư khả dụng bảo mật sang số dư công khai, bạn phải:
- Tạo bằng chứng bằng nhau và bằng chứng phạm vi ở phía client
- Gọi chương trình bằng chứng Zk ElGamal để xác minh các bằng chứng và khởi tạo các tài khoản "context state"
- Gọi lệnh ConfidentialTransferInstruction::Withdraw cung cấp hai tài khoản bằng chứng.
- Đóng hai tài khoản bằng chứng để thu hồi rent.
Ví dụ Rust bên dưới tạo các bằng chứng bằng crate
spl-token-confidential-transfer-proof-generation, xác minh từng bằng chứng vào
một tài khoản context state thông qua chương trình ZK ElGamal Proof, tham chiếu
cả hai tài khoản trong lệnh rút, và đóng chúng sau đó. Ví dụ TypeScript sử dụng
hàm trợ giúp getConfidentialWithdrawInstructionPlan từ
@solana-program/token-2022/confidential, giúp tập hợp cùng các tài khoản bằng
chứng, lệnh rút và đóng thành một kế hoạch lệnh đa giao dịch.
Ví Dụ Mã
Ví dụ sau đây rút token từ số dư khả dụng bảo mật trở lại số dư công khai. Tài khoản phải được cấu hình sẵn cho chuyển khoản bảo mật và có số dư bảo mật khả dụng.
Chuyển khoản bảo mật phụ thuộc vào chương trình ZK ElGamal Proof, được kích hoạt
trên mainnet và devnet. Một solana-test-validator thông thường không kích hoạt
nó, nhưng một validator cục bộ forking mainnet như
Surfpool thì có. Chạy ví dụ với một trong số đó (mã sử
dụng devnet) với một payer được nạp tiền, và thay thế các placeholder bằng mint
và token account của bạn.
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?