Bir token account'tan diğerine token'ları gizli olarak nasıl transfer edersiniz
Token'ları bir token account'tan diğerine gizli olarak transfer etmek için hem
gönderici hem de alıcının ConfidentialTransferAccount durumuyla
yapılandırılmış ve gizli transferler için onaylanmış token account'larına sahip
olması gerekir. Göndericinin token account'ının ayrıca transfer edilebilecek
mevcut bir gizli bakiyesi olması gerekir.
Token'ları gizli olarak transfer etmek için:
-
İstemci tarafında üç kanıt oluşturun:
Eşitlik Kanıtı (CiphertextCommitmentEqualityProofData): Transfer sonrasında yeni mevcut bakiye şifreli metninin karşılık gelen Pedersen taahhüdüyle eşleştiğini doğrular; kaynak hesabın yeni mevcut bakiyesinin
new_balance = current_balance - transfer_amountolarak doğru hesaplandığını garanti eder.Şifreli Metin Geçerlilik Kanıtı (BatchedGroupedCiphertext3HandlesValidityProofData): Transfer miktarı şifreli metinlerinin üç taraf için de (kaynak, hedef ve denetçi) uygun şekilde oluşturulduğunu doğrular; transfer miktarının her tarafın genel anahtarı altında doğru şekilde şifrelendiğini garanti eder.
Aralık Kanıtı (BatchedRangeProofU128Data): Yeni mevcut bakiye ve transfer miktarının (düşük/yüksek bitlere bölünmüş) tamamının negatif olmadığını ve belirtilen bir aralık içinde olduğunu doğrular.
-
Her kanıt için:
- Kanıt verilerini doğrulamak üzere ZK ElGamal kanıt programını çağırın.
- Diğer talimatlarda kullanmak üzere kanıta özgü meta verileri bir kanıt "bağlam durumu" hesabında saklayın.
-
Kanıt bağlam durumu hesaplarını sağlayarak ConfidentialTransferInstruction::Transfer talimatını çağırın.
-
Oluşturmak için kullanılan SOL'u geri almak amacıyla kanıt bağlam durumu hesaplarını kapatın.
Aşağıdaki diyagram, token'ları göndericinin token account'ından alıcının token account'ına transfer etme adımlarını göstermektedir.
Gerekli Talimatlar
Token'ları bir token account'tan diğerine gizli olarak transfer etmek için şunları yapmanız gerekir:
- İstemci tarafında bir eşitlik kanıtı, şifreli metin geçerlilik kanıtı ve aralık kanıtı oluşturun
- Kanıtları doğrulamak ve "bağlam durumu" hesaplarını başlatmak için Zk ElGamal kanıt programını çağırın
- Üç kanıt hesabını sağlayarak ConfidentialTransferInstruction::Transfer talimatını çağırın.
- rent'i geri almak için üç kanıt hesabını kapatın.
Aşağıdaki Rust örneği, spl-token-confidential-transfer-proof-generation
crate'i ile kanıtları oluşturur, her birini ZK ElGamal Proof programı
aracılığıyla bir bağlam durumu hesabına doğrular, transfer talimatındaki üç
hesaba başvurur ve ardından bunları kapatır. TypeScript örneği,
@solana-program/token-2022/confidential'den
getConfidentialTransferInstructionPlan yardımcısını kullanır; bu yardımcı,
aynı kanıt hesaplarını, transferi ve kapatma işlemini çok işlemli bir talimat
planı olarak bir araya getirir.
Örnek Kod
Aşağıdaki örnek, token'ları bir hesaptan diğerine gizli olarak aktarır. Her iki hesabın da gizli transferler için önceden yapılandırılmış olması ve göndericinin kullanılabilir bir gizli bakiyeye sahip olması gerekir.
Gizli transferler, mainnet ve devnet'te etkinleştirilen ZK ElGamal Proof
programına bağlıdır. Standart bir solana-test-validator bunu etkinleştirmez;
ancak Surfpool gibi mainnet'i çatallayan yerel bir
validator etkinleştirir. Örneği, fonlanmış bir ödeyici ile bu ortamlardan birine
karşı çalıştırın (kod devnet kullanır) ve yer tutucuları kendi mint'iniz ile
gönderici ve alıcı hesaplarıyla değiştirin.
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?