So übertragen Sie Token vertraulich von einem token account auf einen anderen
Um Token vertraulich von einem token account auf einen anderen zu übertragen, müssen sowohl der Sender als auch der Empfänger über token accounts verfügen, die mit dem Status ConfidentialTransferAccount konfiguriert und für die Verwendung von Confidential Balances genehmigt sind. Das token account des Senders muss außerdem ein verfügbares vertrauliches Guthaben zum Übertragen aufweisen.
So übertragen Sie Token vertraulich:
-
Erstellen Sie drei Nachweise clientseitig:
Gleichheitsnachweis (CiphertextCommitmentEqualityProofData): Überprüft, ob der neue verfügbare Kontostand-Chiffretext nach der Übertragung mit der entsprechenden Pedersen-Verpflichtung übereinstimmt und stellt sicher, dass der neue verfügbare Kontostand des Quellkontos korrekt berechnet wird als
new_balance = current_balance - transfer_amount.Chiffretext-Gültigkeitsnachweis (BatchedGroupedCiphertext3HandlesValidityProofData): Überprüft, ob die Übertragungs-Chiffretexte für alle drei Parteien (Quelle, Ziel und Prüfer) ordnungsgemäß erstellt wurden, und stellt sicher, dass der Übertragungsbetrag korrekt unter dem öffentlichen Schlüssel jeder Partei verschlüsselt ist.
Bereichsnachweis (BatchedRangeProofU128Data): Überprüft, ob der neue verfügbare Kontostand und der Übertragungsbetrag (aufgeteilt in niedrige/hohe Bits) alle nicht-negativ und innerhalb eines festgelegten Bereichs sind.
-
Für jeden Nachweis:
- Rufen Sie das ZK-ElGamal-Nachweisprogramm auf, um die Nachweisdaten zu verifizieren.
- Speichern Sie die nachweisbezogenen Metadaten in einem Nachweis-„Kontext-Status“-Konten zur Verwendung in anderen Anweisungen.
-
Rufen Sie die ConfidentialTransferInstruction::Transfer Anweisungen auf und geben Sie dabei die Nachweis-Kontextstatus-Konten an.
-
Schließen Sie die Nachweis-Kontextstatus-Konten, um das zum Erstellen verwendete SOL zurückzugewinnen.
Das folgende Diagramm zeigt die Schritte, die beim Übertragen von Token vom token account eines Senders auf den token account eines Empfängers erforderlich sind.
Erforderliche Anweisungen
Um Token vertraulich von einem token account auf einen anderen zu übertragen, müssen Sie:
- Clientseitig einen Gleichheitsnachweis, einen Chiffretext-Gültigkeitsnachweis und einen Bereichsnachweis generieren
- Das ZK-ElGamal-Nachweisprogramm aufrufen, um die Nachweise zu verifizieren und die „Kontext-Status“-Konten zu initialisieren
- Die ConfidentialTransferInstruction::Transfer Anweisungen aufrufen und dabei die drei Nachweiskonten angeben.
- Die drei Nachweiskonten schließen, um rent zurückzugewinnen.
Das folgende Rust-Beispiel generiert die Beweise mit der
spl-token-confidential-transfer-proof-generation-Crate, verifiziert jeden in
einen Kontextzustands-Konten über das ZK ElGamal Proof-Programm, referenziert
die drei Konten in der Transfer- Anweisungen und schließt sie anschließend. Das
TypeScript-Beispiel verwendet den
getConfidentialTransferInstructionPlan-Helfer aus
@solana-program/token-2022/confidential, der dieselben Proof- Konten, den
Transfer und das Schließen als einen Multi-Transaktions- Anweisungsplan
zusammenstellt.
Beispielcode
Das folgende Beispiel überträgt Token vertraulich von einem Konto auf ein anderes. Beide Konten müssen bereits für Confidential Balances konfiguriert sein, und der Sender muss über ein verfügbares vertrauliches Guthaben verfügen.
Confidential Balances ist auf das ZK ElGamal Proof-Programm angewiesen, das auf Mainnet und Devnet aktiviert ist. Ein standardmäßiger solana-test-validator aktiviert es nicht, aber ein Mainnet-forkender lokaler Validator wie Surfpool schon.
Führen Sie das Beispiel mit einem finanzierten Zahler gegen eines dieser Netzwerke aus (der Code verwendet Devnet), und ersetzen Sie die Platzhalter durch Ihre Mint sowie die Sender- und Empfänger-Konten.
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 Balances, 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?