Gizli kullanılabilir bakiyeden token nasıl çekilir
Gizli kullanılabilir bakiyeden genel bakiyeye token çekmek için:
-
İstemci tarafında iki kanıt oluşturun:
Eşitlik Kanıtı (CiphertextCommitmentEqualityProofData): Çekimin ardından kalan kullanılabilir bakiye şifreli metninin, karşılık gelen Pedersen taahhüdüyle eşleştiğini doğrular; böylece hesabın yeni kullanılabilir bakiyesinin
remaining_balance = current_balance - withdraw_amountolarak doğru biçimde hesaplandığını güvence altına alır.Aralık Kanıtı (BatchedRangeProofU64Data): Çekimin ardından kalan kullanılabilir bakiyenin negatif olmadığını ve belirli bir aralık içinde olduğunu doğrular.
-
Her kanıt için:
- Kanıt verilerini doğrulamak üzere ZK ElGamal kanıt programını çağırın.
- Diğer talimatlarla kullanmak üzere kanıta özgü meta verileri bir kanıt "bağlam durumu" hesabında saklayın.
-
İki kanıt hesabını sağlayarak ConfidentialTransferInstruction::Withdraw talimatını çağırın.
-
Kanıt hesaplarını kapatarak bunları oluşturmak için kullanılan SOL'u geri kazanın.
Aşağıdaki şema, gizli kullanılabilir bakiyeden genel bakiyeye token çekerken gerçekleştirilen adımları göstermektedir:
Gerekli Talimatlar
Gizli kullanılabilir bakiyeden genel bakiyeye token çekmek için şunları yapmalısınız:
- İstemci tarafında bir eşitlik kanıtı ve aralık kanıtı oluşturun
- Kanıtları doğrulamak ve "bağlam durumu" hesaplarını başlatmak için Zk ElGamal kanıt programını çağırın
- İki kanıt hesabını sağlayarak ConfidentialTransferInstruction::Withdraw talimatını çağırın.
- rent'i geri kazanmak için iki kanıt hesabını kapatın.
Aşağıdaki Rust örneği, spl-token-confidential-transfer-proof-generation
crate'i ile kanıtları oluşturur, her birini ZK ElGamal Kanıt programı
aracılığıyla bir bağlam durumu hesabına doğrular, her iki hesaba da çekme
talimatında referans verir ve ardından bunları kapatır. TypeScript örneği ise
@solana-program/token-2022/confidential'den
getConfidentialWithdrawInstructionPlan yardımcısını kullanır; bu yardımcı,
aynı kanıt hesaplarını, çekme işlemini ve kapama işlemini çok işlemli bir
talimat planı olarak bir araya getirir.
Örnek Kod
Aşağıdaki örnek, token'ları gizli kullanılabilir bakiyeden geri genel bakiyeye çeker. Hesabın gizli transferler için önceden yapılandırılmış olması ve kullanılabilir bir gizli bakiye tutması gerekir.
Gizli transferler, mainnet ve devnet üzerinde etkinleştirilen ZK ElGamal Proof
programına bağlıdır. Standart bir solana-test-validator bunu etkinleştirmez;
ancak Surfpool gibi mainnet'i çatallayan yerel bir
validator etkinleştirir. Örneği, bunlardan biri üzerinde (kod devnet kullanır)
fonlanmış bir payer ile çalıştırın ve yer tutucuları kendi mint ve token account
bilgilerinizle değiştirin.
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?