Jak wypłacić tokeny z poufnego dostępnego salda
Aby wypłacić tokeny z poufnego dostępnego salda na saldo publiczne:
-
Utwórz dwa dowody po stronie klienta:
Dowód równości (CiphertextCommitmentEqualityProofData): Weryfikuje, że szyfrogram pozostałego dostępnego salda po wypłacie odpowiada swojemu zobowiązaniu Pedersena, zapewniając poprawne obliczenie nowego dostępnego salda konta jako
remaining_balance = current_balance - withdraw_amount.Dowód zakresu (BatchedRangeProofU64Data): Weryfikuje, że pozostałe dostępne saldo po wypłacie jest nieujemne i mieści się w określonym zakresie.
-
Dla każdego dowodu:
- Wywołaj program dowodów ZK ElGamal w celu weryfikacji danych dowodowych.
- Zapisz metadane specyficzne dla dowodu w koncie "stanu kontekstu" dowodu, aby użyć ich w innych instrukcjach.
-
Wywołaj instrukcję ConfidentialTransferInstruction::Withdraw podając dwa konta dowodowe.
-
Zamknij konta dowodowe, aby odzyskać SOL użyty do ich utworzenia.
Poniższy diagram przedstawia kroki związane z wypłatą tokenów z poufnego dostępnego salda na saldo publiczne:
Wymagane instrukcje
Aby wypłacić tokeny z poufnego dostępnego salda na saldo publiczne, musisz:
- Wygenerować dowód równości i dowód zakresu po stronie klienta
- Wywołać program dowodów Zk ElGamal w celu weryfikacji dowodów i zainicjowania kont "stanu kontekstu"
- Wywołać instrukcję ConfidentialTransferInstruction::Withdraw podając dwa konta dowodowe.
- Zamknąć dwa konta dowodowe, aby odzyskać rent.
Poniższy przykład w języku Rust generuje dowody przy użyciu skrzynki
spl-token-confidential-transfer-proof-generation, weryfikuje każdy z nich do
konta stanu kontekstu za pośrednictwem programu ZK ElGamal Proof, odwołuje się
do obu kont w instrukcji wypłaty i zamyka je po zakończeniu. Przykład w
TypeScript korzysta z pomocnika getConfidentialWithdrawInstructionPlan z
@solana-program/token-2022/confidential, który składa te same konta dowodowe,
wypłatę i zamknięcie jako plan instrukcji wielu transakcji.
Przykładowy kod
Poniższy przykład wypłaca tokeny z poufnego dostępnego salda z powrotem do salda publicznego. Konto musi być już skonfigurowane do poufnych transferów i posiadać dostępne poufne saldo.
Poufne transfery zależą od programu ZK ElGamal Proof, który jest włączony w
sieci mainnet i devnet. Standardowy solana-test-validator nie włącza go, ale
lokalny validator z forkiem mainnet, taki jak Surfpool,
już tak. Uruchom przykład na jednej z tych sieci (kod używa devnet) z zasilonym
płatnikiem i zastąp symbole zastępcze swoim mint oraz 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?