Hoe tokens vertrouwelijk over te dragen van het ene token account naar het andere
Om tokens vertrouwelijk over te dragen van het ene token account naar het
andere, moeten zowel de afzender als de ontvanger token accounts hebben
geconfigureerd met de ConfidentialTransferAccount status en goedgekeurd
voor vertrouwelijke overdrachten. Het token account van de afzender moet ook een
beschikbaar vertrouwelijk saldo hebben om van over te dragen.
Om tokens vertrouwelijk over te dragen:
-
Maak drie bewijzen aan de clientzijde:
Gelijkheidsbewijs (CiphertextCommitmentEqualityProofData): Verifieert dat de nieuwe beschikbare saldo-ciphertext na de overdracht overeenkomt met de bijbehorende Pedersen commitment, en zorgt ervoor dat het nieuwe beschikbare saldo van de bronaccount correct wordt berekend als
new_balance = current_balance - transfer_amount.Ciphertext-geldigheidsbewijs (BatchedGroupedCiphertext3HandlesValidityProofData): Verifieert dat de ciphertexts van het overdrachtsbedrag correct zijn gegenereerd voor alle drie de partijen (bron, bestemming en controleur), en zorgt ervoor dat het overdrachtsbedrag correct is versleuteld onder de publieke sleutel van elke partij.
Bereikbewijs (BatchedRangeProofU128Data): Verifieert dat het nieuwe beschikbare saldo en het overdrachtsbedrag (opgesplitst in lage/hoge bits) allemaal niet-negatief zijn en binnen een opgegeven bereik vallen.
-
Voor elk bewijs:
- Roep het ZK ElGamal-bewijsprogramma aan om de bewijsgegevens te verifiëren.
- Sla de bewijsspecifieke metadata op in een bewijs-"contextstate"-account voor gebruik in andere instructies.
-
Roep de ConfidentialTransferInstruction::Transfer instructie aan en geef de context-state-accounts van de bewijzen op.
-
Sluit de context-state-accounts van de bewijzen om de gebruikte SOL voor het aanmaken ervan terug te vorderen.
Het volgende diagram toont de stappen voor het overdragen van tokens van het token account van een afzender naar het token account van een ontvanger.
Vereiste instructies
Om tokens vertrouwelijk over te dragen van het ene token account naar het andere, moet u:
- Genereer een gelijkheidsbewijs, ciphertext-geldigheidsbewijs en bereikbewijs aan de clientzijde
- Roep het Zk ElGamal-bewijsprogramma aan om de bewijzen te verifiëren en de "contextstate"-accounts te initialiseren
- Roep de ConfidentialTransferInstruction::Transfer instructie aan en geef de drie bewijsaccounts op.
- Sluit de drie bewijsaccounts om rent terug te vorderen.
Het onderstaande Rust-voorbeeld genereert de bewijzen met de
spl-token-confidential-transfer-proof-generation crate, verifieert elk bewijs
in een context-statusaccount via het ZK ElGamal Proof-programma, verwijst naar
de drie accounts in de overdrachtsinstructie en sluit ze daarna. Het
TypeScript-voorbeeld gebruikt de getConfidentialTransferInstructionPlan helper
van @solana-program/token-2022/confidential, die dezelfde bewijsaccounts,
overdracht en sluitingen samenstelt als een instructieplan met meerdere
transacties.
Voorbeeldcode
Het volgende voorbeeld draagt tokens vertrouwelijk over van het ene account naar het andere. Beide accounts moeten al geconfigureerd zijn voor vertrouwelijke overdrachten, en de verzender moet een beschikbaar vertrouwelijk saldo hebben.
Vertrouwelijke overdrachten zijn afhankelijk van het ZK ElGamal Proof-programma,
dat is ingeschakeld op mainnet en devnet. Een standaard solana-test-validator
schakelt het niet in, maar een lokale validator die mainnet forkt, zoals
Surfpool, wel. Voer het voorbeeld uit tegen een van deze
omgevingen (de code gebruikt devnet) met een gefinancierde betaler, en vervang
de tijdelijke aanduidingen door uw mint en de verzender- en ontvangersaccounts.
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(),);// Sender = fee payer = token account owner. Both the sender and recipient// accounts must already be configured for confidential transfers, and the// sender must have an available confidential balance (deposit then apply// pending balance beforehand).let sender = load_keypair()?;let amount: u64 = 100;// Setup: create confidential accounts and fund the sender.let recipient_keypair = Keypair::new();let (mint, sender_token_account, recipient_token_account) =setup_transfer_accounts(&rpc_client, &sender, &recipient_keypair, amount)?;// Read the recipient's ElGamal public key from their confidential account.let recipient_acc = rpc_client.get_account(&recipient_token_account)?;let recipient_state = StateWithExtensions::<TokenAccount>::unpack(&recipient_acc.data)?;let recipient_elgamal_pubkey: ElGamalPubkey = recipient_state.get_extension::<ConfidentialTransferAccount>()?.elgamal_pubkey.try_into().map_err(|e| anyhow::anyhow!("recipient ElGamal pubkey: {e:?}"))?;// Read the optional auditor ElGamal public key from the mint.let mint_acc = rpc_client.get_account(&mint)?;let mint_state = StateWithExtensions::<Mint>::unpack(&mint_acc.data)?;let mint_ext = mint_state.get_extension::<ConfidentialTransferMint>()?;let auditor_elgamal_pubkey: Option<ElGamalPubkey> =Option::<PodElGamalPubkey>::from(mint_ext.auditor_elgamal_pubkey).map(|pod| {ElGamalPubkey::try_from(pod).map_err(|e| anyhow::anyhow!("auditor pubkey: {e:?}"))}).transpose()?;// Derive the sender's keys and read their current confidential balance.let (sender_elgamal, sender_aes) =derive_confidential_keys(&sender, &sender_token_account.to_bytes()).map_err(|e| anyhow::anyhow!("derive confidential keys: {e}"))?;let sender_acc = rpc_client.get_account(&sender_token_account)?;let sender_state = StateWithExtensions::<TokenAccount>::unpack(&sender_acc.data)?;let sender_ext = sender_state.get_extension::<ConfidentialTransferAccount>()?;let current_available: ElGamalCiphertext = sender_ext.available_balance.try_into().map_err(|e| anyhow::anyhow!("available balance: {e:?}"))?;let current_decryptable: AeCiphertext = sender_ext.decryptable_available_balance.try_into().map_err(|e| anyhow::anyhow!("decryptable balance: {e:?}"))?;// Generate the three transfer proofs (equality, ciphertext-validity, range).let proof_data = transfer_split_proof_data(¤t_available,¤t_decryptable,amount,&sender_elgamal,&sender_aes,&recipient_elgamal_pubkey,auditor_elgamal_pubkey.as_ref(),).map_err(|e| anyhow::anyhow!("transfer_split_proof_data: {e}"))?;// Create one context state account per proof, owned by the ZK program.let equality_account = Keypair::new();let validity_account = Keypair::new();let range_account = Keypair::new();let equality_size = size_of::<ProofContextState<CiphertextCommitmentEqualityProofContext>>();let validity_size =size_of::<ProofContextState<BatchedGroupedCiphertext3HandlesValidityProofContext>>();let range_size = size_of::<ProofContextState<BatchedRangeProofContext>>();let create = |account: &Keypair, space: usize| -> Result<Instruction> {Ok(system_instruction::create_account(&sender.pubkey(),&account.pubkey(),rpc_client.get_minimum_balance_for_rent_exemption(space)?,space as u64,&ZK_PROOF_PROGRAM_ID,))};let equality_create_ix = create(&equality_account, equality_size)?;let validity_create_ix = create(&validity_account, validity_size)?;let range_create_ix = create(&range_account, range_size)?;// The sender is the context-state authority for all three proof accounts.let authority: Address = sender.pubkey().to_bytes().into();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,);let validity_verify_ix = ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity.encode_verify_proof(Some(ContextStateInfo {context_state_account: &Address::from(validity_account.pubkey().to_bytes()),context_state_authority: &authority,}),&proof_data.ciphertext_validity_proof_data_with_ciphertext.proof_data,);let range_verify_ix = ProofInstruction::VerifyBatchedRangeProofU128.encode_verify_proof(Some(ContextStateInfo {context_state_account: &Address::from(range_account.pubkey().to_bytes()),context_state_authority: &authority,}),&proof_data.range_proof_data,);// Transaction 1: create all three accounts and verify the validity proof.send_tx(&rpc_client,&[equality_create_ix,validity_create_ix,range_create_ix,validity_verify_ix,],&[&sender, &equality_account, &validity_account, &range_account],)?;// Transaction 2: verify the range proof (the largest, on its own).send_tx(&rpc_client, &[range_verify_ix], &[&sender])?;// Compute the sender's new decryptable available balance after the transfer.let current_plaintext = current_decryptable.decrypt(&sender_aes).context("decrypt available balance")?;let new_plaintext = current_plaintext.checked_sub(amount).context("insufficient available balance")?;let new_decryptable: PodAeCiphertext = sender_aes.encrypt(new_plaintext).into();let auditor_lo = proof_data.ciphertext_validity_proof_data_with_ciphertext.ciphertext_lo;let auditor_hi = proof_data.ciphertext_validity_proof_data_with_ciphertext.ciphertext_hi;let transfer_ix = inner_transfer(&spl_token_2022::id(),&sender_token_account,&mint,&recipient_token_account,&new_decryptable,&auditor_lo,&auditor_hi,&sender.pubkey(),&[],ProofLocation::ContextStateAccount(&equality_account.pubkey()),ProofLocation::ContextStateAccount(&validity_account.pubkey()),ProofLocation::ContextStateAccount(&range_account.pubkey()),)?;// Transaction 3: verify the equality proof, run the transfer, and close the// three proof accounts to reclaim their rent.let close = |account: &Keypair| {close_context_state(ContextStateInfo {context_state_account: &Address::from(account.pubkey().to_bytes()),context_state_authority: &authority,},&authority,)};let instructions = [equality_verify_ix,transfer_ix,close(&equality_account),close(&validity_account),close(&range_account),];let blockhash = rpc_client.get_latest_blockhash()?;let transaction = Transaction::new_signed_with_payer(&instructions,Some(&sender.pubkey()),&[&sender],blockhash,);let signature = rpc_client.send_and_confirm_transaction(&transaction)?;println!("Transferred {amount} tokens confidentially: {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}))// Temporary custom plugin to skip the default compute-budget estimate// so proof instructions fit within the transaction message cap.// The Solana CLI default keypair, used as fee payer, mint authority, and sender.const owner = client.payer;const recipient = await generateKeyPairSigner();const depositAmount = 100n;const amount = 25n;const decimals = 2;// Setup: create source and destination confidential accounts, then fund source.const mint = await createConfidentialMint(client, owner, decimals);const auditorElgamalPubkey = await getAuditorElgamalPubkey(client, mint);const sourceToken = await createConfidentialTokenAccount(client, owner, mint);const destinationToken = await createConfidentialTokenAccount(client,recipient,mint);await mintPublicTokens(client, owner, mint, sourceToken, depositAmount);await depositTokens(client, owner, mint, sourceToken, depositAmount, decimals);await applyPendingBalance(client, owner, mint, sourceToken);// Derive the sender's recoverable ElGamal and AES keys, bound to (owner, mint).const { elgamalKeypair: sourceElgamalKeypair, aesKey } =await deriveConfidentialKeys(owner, mint);// The helper reads the recipient key from the destination account; pass the// configured auditor key so the proof matches the mint configuration.const sourceTokenAccount = (await fetchToken(client.rpc, sourceToken)).data;const destinationTokenAccount = (await fetchToken(client.rpc, destinationToken)).data;// Builds the proof context-state accounts, the transfer, and the closes as a// multi-transaction plan (the three proofs are too large for one transaction).const plan = await getConfidentialTransferInstructionPlan({rpc: client.rpc,payer: owner,authority: owner,mint,sourceToken,sourceTokenAccount,destinationToken,destinationTokenAccount,auditorElgamalPubkey,amount,sourceElgamalKeypair,aesKey});const result = await client.sendTransactions(plan);const summary = summarizeTransactionPlanResult(result);const signature =summary.successfulTransactions[summary.successfulTransactions.length - 1].context.signature;console.log(`Transferred ${amount} tokens confidentially: ${signature}`);
Is this page helpful?