So werden Token aus dem vertraulichen verfügbaren Guthaben abgehoben
So werden Token vom vertraulichen verfügbaren Guthaben auf das öffentliche Guthaben übertragen:
-
Erstellen Sie zwei Beweise auf der Client-Seite:
Gleichheitsnachweis (CiphertextCommitmentEqualityProofData): Verifiziert, dass der verbleibende verfügbare Guthaben-Chiffretext nach der Auszahlung mit dem entsprechenden Pedersen-Commitment übereinstimmt und sicherstellt, dass das neue verfügbare Guthaben der Konten korrekt als
remaining_balance = current_balance - withdraw_amountberechnet wird.Bereichsnachweis (BatchedRangeProofU64Data): Verifiziert, dass das verbleibende verfügbare Guthaben nach der Auszahlung nicht negativ und innerhalb eines festgelegten Bereichs liegt.
-
Für jeden Beweis:
- Rufen Sie das ZK-ElGamal-Beweisprogramm auf, um die Beweisdaten zu verifizieren.
- Speichern Sie die beweisspezifischen Metadaten in einem "Kontext-Zustand"-Konten, um sie in anderen Anweisungen zu verwenden.
-
Rufen Sie die ConfidentialTransferInstruction::Withdraw Anweisung auf und geben Sie die beiden Beweis-Konten an.
-
Schließen Sie die Beweis-Konten, um die zur Erstellung verwendeten SOL zurückzuerhalten.
Das folgende Diagramm zeigt die Schritte zum Abheben von Token aus dem vertraulichen verfügbaren Guthaben auf das öffentliche Guthaben:
Erforderliche Anweisungen
Um Token vom vertraulichen verfügbaren Guthaben auf das öffentliche Guthaben zu übertragen, müssen Sie:
- Einen Gleichheitsnachweis und Bereichsnachweis auf der Client-Seite generieren
- Das ZK-ElGamal-Beweisprogramm aufrufen, um die Beweise zu verifizieren und die "Kontext-Zustand"-Konten zu initialisieren
- Die ConfidentialTransferInstruction::Withdraw Anweisung aufrufen und die beiden Beweis-Konten angeben.
- Die beiden Beweis-Konten schließen, um rent zurückzuerhalten.
Das folgende Rust-Beispiel generiert die Beweise mit der
spl-token-confidential-transfer-proof-generation-Bibliothek, verifiziert jeden
in einem Kontext-Zustand-Konten über das ZK-ElGamal-Beweisprogramm, referenziert
beide Konten in der Auszahlungs Anweisungen und schließt sie anschließend. Das
TypeScript- Beispiel verwendet den
getConfidentialWithdrawInstructionPlan-Helfer aus
@solana-program/token-2022/confidential, der dieselben Beweis-Konten, die
Auszahlung und die Schließungen als Multi-Transaktions- Anweisungen-Plan
zusammenstellt.
Beispielcode
Das folgende Beispiel hebt Token aus dem vertraulichen verfügbaren Guthaben zurück auf das öffentliche Guthaben ab. Die Konten müssen bereits für vertrauliche Transfers konfiguriert sein und ein verfügbares vertrauliches Guthaben aufweisen.
Vertrauliche Transfers sind vom ZK ElGamal Proof-Programm abhängig, das auf
Mainnet und Devnet aktiviert ist. Ein standardmäßiges solana-test-validator
aktiviert es nicht, aber ein Mainnet-forkendes lokales validator wie
Surfpool schon. Führe das Beispiel gegen eines davon aus
(der Code verwendet Devnet) mit einem finanzierten Zahler und ersetze die
Platzhalter durch deine Mint und dein 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?