Kuinka nostaa tokeneita luottamuksellisesta käytettävissä olevasta saldosta
Tokeneiden nostaminen luottamuksellisesta käytettävissä olevasta saldosta julkiseen saldoon:
-
Luo kaksi todistetta asiakaspuolella:
Yhdenvertaisuustodiste (CiphertextCommitmentEqualityProofData): Varmistaa, että noston jälkeen jäljellä oleva käytettävissä olevan saldon salateksti vastaa sitä vastaavaa Pedersen-sitoumusta, varmistaen, että tilin uusi käytettävissä oleva saldo lasketaan oikein muodossa
remaining_balance = current_balance - withdraw_amount.Alueentodiste (BatchedRangeProofU64Data): Varmistaa, että noston jälkeen jäljellä oleva käytettävissä oleva saldo on ei-negatiivinen ja määritetyn alueen sisällä.
-
Kullekin todisteelle:
- Kutsu ZK ElGamal -todisteohjelmaa todistetietojen verifioimiseksi.
- Tallenna todiste-kohtainen metatiedot todisteen "kontekstitila"-tilille käytettäväksi muissa ohjeissa.
-
Kutsu ConfidentialTransferInstruction::Withdraw -ohjetta antaen kaksi todistustiliä.
-
Sulje todistustilit ja palauta niiden luomiseen käytetty SOL.
Seuraava kaavio näyttää vaiheet, jotka liittyvät tokeneiden nostamiseen luottamuksellisesta käytettävissä olevasta saldosta julkiseen saldoon:
Vaaditut ohjeet
Tokeneiden nostamiseksi luottamuksellisesta käytettävissä olevasta saldosta julkiseen saldoon sinun tulee:
- Luo yhdenvertaisuustodiste ja alueentodiste asiakaspuolella
- Kutsu Zk ElGamal -todisteohjelmaa todisteiden verifioimiseksi ja "kontekstitila"-tilien alustamiseksi
- Kutsu ConfidentialTransferInstruction::Withdraw -ohjetta antaen kaksi todistustiliä.
- Sulje kaksi todistustiliä rent-maksujen palauttamiseksi.
Alla oleva Rust-esimerkki luo todisteet
spl-token-confidential-transfer-proof-generation-paketilla, verifioi jokaisen
kontekstitila-tilille ZK ElGamal Proof -ohjelman kautta, viittaa molempiin
tileihin nostamisohjeessa ja sulkee ne sen jälkeen. TypeScript-esimerkki käyttää
getConfidentialWithdrawInstructionPlan-apufunktiota kohteesta
@solana-program/token-2022/confidential, joka kokoaa samat todistustilit,
noston ja sulkemiset monivaiheiseksi transaktio-ohjesuunnitelmaksi.
Esimerkkikoodi
Seuraava esimerkki nostaa tokenit luottamuksellisesta käytettävissä olevasta saldosta takaisin julkiseen saldoon. Tilin täytyy olla jo määritetty luottamuksellisia siirtoja varten ja sen täytyy sisältää käytettävissä oleva luottamuksellinen saldo.
Luottamukselliset siirrot ovat riippuvaisia ZK ElGamal Proof -ohjelmasta, joka
on käytössä mainnetissä ja devnetissä. Tavallinen solana-test-validator ei ota
sitä käyttöön, mutta mainnettia haaruttava paikallinen validator, kuten
Surfpool, ottaa. Aja esimerkki jotakin näistä vasten
(koodi käyttää devnettiä) rahoitetulla maksajalla ja korvaa paikanvaraukset
omalla mintilläsi ja token account -tililläsi.
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?