Πώς να μεταφέρετε εμπιστευτικά tokens από ένα token account σε άλλο
Για να μεταφέρετε εμπιστευτικά tokens από ένα token account σε άλλο, τόσο ο
αποστολέας όσο και ο παραλήπτης πρέπει να έχουν token accounts διαμορφωμένα με
την κατάσταση ConfidentialTransferAccount και εγκεκριμένα για
εμπιστευτικές μεταφορές. Το token account του αποστολέα πρέπει επίσης να
διαθέτει διαθέσιμο εμπιστευτικό υπόλοιπο για μεταφορά.
Για να μεταφέρετε tokens εμπιστευτικά:
-
Δημιουργήστε τρεις αποδείξεις από την πλευρά του πελάτη:
Απόδειξη Ισότητας (CiphertextCommitmentEqualityProofData): Επαληθεύει ότι το νέο διαθέσιμο ciphertext υπολοίπου μετά τη μεταφορά αντιστοιχεί στην αντίστοιχη δέσμευση Pedersen, διασφαλίζοντας ότι το νέο διαθέσιμο υπόλοιπο του λογαριασμού πηγής υπολογίζεται σωστά ως
new_balance = current_balance - transfer_amount.Απόδειξη Εγκυρότητας Ciphertext (BatchedGroupedCiphertext3HandlesValidityProofData): Επαληθεύει ότι τα ciphertexts του ποσού μεταφοράς δημιουργούνται σωστά για όλα τα τρία μέρη (πηγή, προορισμός και ελεγκτής), διασφαλίζοντας ότι το ποσό μεταφοράς είναι σωστά κρυπτογραφημένο με το δημόσιο κλειδί κάθε μέρους.
Απόδειξη Εύρους (BatchedRangeProofU128Data): Επαληθεύει ότι το νέο διαθέσιμο υπόλοιπο και το ποσό μεταφοράς (χωρισμένο σε χαμηλά/υψηλά bits) είναι όλα μη αρνητικά και εντός ενός καθορισμένου εύρους.
-
Για κάθε απόδειξη:
- Εκτελέστε το πρόγραμμα ZK ElGamal proof για να επαληθεύσετε τα δεδομένα της απόδειξης.
- Αποθηκεύστε τα μεταδεδομένα της απόδειξης σε έναν λογαριασμό "context state" για χρήση σε άλλες εντολές.
-
Εκτελέστε την εντολή ConfidentialTransferInstruction::Transfer, παρέχοντας τους λογαριασμούς context state της απόδειξης.
-
Κλείστε τους λογαριασμούς context state της απόδειξης για να ανακτήσετε το SOL που χρησιμοποιήθηκε για τη δημιουργία τους.
Το παρακάτω διάγραμμα απεικονίζει τα βήματα που απαιτούνται για τη μεταφορά tokens από το token account του αποστολέα στο token account του παραλήπτη.
Απαιτούμενες Εντολές
Για να μεταφέρετε εμπιστευτικά tokens από ένα token account σε άλλο, πρέπει να:
- Δημιουργήσετε μια απόδειξη ισότητας, μια απόδειξη εγκυρότητας ciphertext και μια απόδειξη εύρους από την πλευρά του πελάτη
- Εκτελέσετε το πρόγραμμα Zk ElGamal proof για να επαληθεύσετε τις αποδείξεις και να αρχικοποιήσετε τους λογαριασμούς "context state"
- Εκτελέσετε την εντολή ConfidentialTransferInstruction::Transfer παρέχοντας τους τρεις λογαριασμούς απόδειξης.
- Κλείσετε τους τρεις λογαριασμούς απόδειξης για να ανακτήσετε το rent.
Το παρακάτω παράδειγμα Rust δημιουργεί τις αποδείξεις με το crate
spl-token-confidential-transfer-proof-generation, επαληθεύει κάθε μία σε έναν
λογαριασμό κατάστασης context μέσω του προγράμματος ZK ElGamal Proof, αναφέρει
τους τρεις λογαριασμούς στην εντολή μεταφοράς και τους κλείνει στη συνέχεια. Το
παράδειγμα TypeScript χρησιμοποιεί τον βοηθό
getConfidentialTransferInstructionPlan από το
@solana-program/token-2022/confidential, ο οποίος συναρμολογεί τους ίδιους
λογαριασμούς αποδείξεων, τη μεταφορά και τις κλεισίματα ως πλάνο εντολών
πολλαπλών συναλλαγών.
Παράδειγμα Κώδικα
Το παρακάτω παράδειγμα μεταφέρει εμπιστευτικά tokens από έναν λογαριασμό σε έναν άλλο. Και οι δύο λογαριασμοί πρέπει να έχουν ήδη διαμορφωθεί για εμπιστευτικές μεταφορές, και ο αποστολέας πρέπει να διαθέτει διαθέσιμο εμπιστευτικό υπόλοιπο.
Οι εμπιστευτικές μεταφορές βασίζονται στο πρόγραμμα ZK ElGamal Proof, το οποίο
είναι ενεργοποιημένο στο mainnet και στο devnet. Ένας τυπικός
solana-test-validator δεν το ενεργοποιεί, αλλά ένας τοπικός validator που
κάνει fork από το mainnet, όπως το Surfpool, το κάνει.
Εκτελέστε το παράδειγμα σε έναν από αυτούς (ο κώδικας χρησιμοποιεί devnet) με
έναν χρηματοδοτημένο payer, και αντικαταστήστε τα placeholders με το mint σας
και τους λογαριασμούς αποστολέα και παραλήπτη.
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?