From 3dc64b0e27b8601ccce92585a9ebe4d2fe00c0ad Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Sat, 3 Aug 2024 18:09:27 +0900 Subject: [PATCH 1/9] refactor out verify proof logic into a separate function --- .../confidential_transfer/verify_proof.rs | 97 +++---------------- .../confidential_transfer_fee/processor.rs | 43 ++------ token/program-2022/src/proof.rs | 72 +++++++++++++- 3 files changed, 92 insertions(+), 120 deletions(-) diff --git a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs index dc234d65bff..d142ad032e5 100644 --- a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs +++ b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs @@ -5,7 +5,7 @@ use { confidential_transfer::{ciphertext_extraction::*, instruction::*, *}, transfer_fee::TransferFee, }, - proof::decode_proof_instruction_context, + proof::{decode_proof_instruction_context, verify_and_extract_context}, }, solana_program::{ account_info::{next_account_info, AccountInfo}, @@ -24,34 +24,10 @@ pub fn verify_configure_account_proof( account_info_iter: &mut Iter<'_, AccountInfo<'_>>, proof_instruction_offset: i64, ) -> Result { - if proof_instruction_offset == 0 { - // interpret `account_info` as a context state account - let context_state_account_info = next_account_info(account_info_iter)?; - check_zk_token_proof_program_account(context_state_account_info.owner)?; - let context_state_account_data = context_state_account_info.data.borrow(); - let context_state = pod_from_bytes::>( - &context_state_account_data, - )?; - - if context_state.proof_type != ProofType::PubkeyValidity.into() { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(context_state.proof_context) - } else { - // interpret `account_info` as a sysvar - let sysvar_account_info = next_account_info(account_info_iter)?; - let zkp_instruction = - get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; - Ok(decode_proof_instruction_context::< - PubkeyValidityData, - PubkeyValidityProofContext, - >( - account_info_iter, - ProofInstruction::VerifyPubkeyValidity, - &zkp_instruction, - )?) - } + verify_and_extract_context::( + account_info_iter, + proof_instruction_offset, + ) } /// Verify zero-knowledge proof needed for a [EmptyAccount] instruction and @@ -60,34 +36,10 @@ pub fn verify_empty_account_proof( account_info_iter: &mut Iter<'_, AccountInfo<'_>>, proof_instruction_offset: i64, ) -> Result { - if proof_instruction_offset == 0 { - // interpret `account_info` as a context state account - let context_state_account_info = next_account_info(account_info_iter)?; - check_zk_token_proof_program_account(context_state_account_info.owner)?; - let context_state_account_data = context_state_account_info.data.borrow(); - let context_state = pod_from_bytes::>( - &context_state_account_data, - )?; - - if context_state.proof_type != ProofType::ZeroBalance.into() { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(context_state.proof_context) - } else { - // interpret `account_info` as a sysvar - let sysvar_account_info = next_account_info(account_info_iter)?; - let zkp_instruction = - get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; - Ok(decode_proof_instruction_context::< - ZeroBalanceProofData, - ZeroBalanceProofContext, - >( - account_info_iter, - ProofInstruction::VerifyZeroBalance, - &zkp_instruction, - )?) - } + verify_and_extract_context::( + account_info_iter, + proof_instruction_offset, + ) } /// Verify zero-knowledge proof needed for a [Withdraw] instruction and return @@ -96,33 +48,10 @@ pub fn verify_withdraw_proof( account_info_iter: &mut Iter<'_, AccountInfo<'_>>, proof_instruction_offset: i64, ) -> Result { - if proof_instruction_offset == 0 { - // interpret `account_info` as a context state account - let context_state_account_info = next_account_info(account_info_iter)?; - check_zk_token_proof_program_account(context_state_account_info.owner)?; - let context_state_account_data = context_state_account_info.data.borrow(); - let context_state = - pod_from_bytes::>(&context_state_account_data)?; - - if context_state.proof_type != ProofType::Withdraw.into() { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(context_state.proof_context) - } else { - // interpret `account_info` as a sysvar - let sysvar_account_info = next_account_info(account_info_iter)?; - let zkp_instruction = - get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; - Ok(decode_proof_instruction_context::< - WithdrawData, - WithdrawProofContext, - >( - account_info_iter, - ProofInstruction::VerifyWithdraw, - &zkp_instruction, - )?) - } + verify_and_extract_context::( + account_info_iter, + proof_instruction_offset, + ) } /// Verify zero-knowledge proof needed for a [Transfer] instruction without fee diff --git a/token/program-2022/src/extension/confidential_transfer_fee/processor.rs b/token/program-2022/src/extension/confidential_transfer_fee/processor.rs index 26ee3a9c235..63db39cab8e 100644 --- a/token/program-2022/src/extension/confidential_transfer_fee/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer_fee/processor.rs @@ -3,14 +3,12 @@ use solana_zk_token_sdk::zk_token_elgamal::ops as syscall; use { crate::{ - check_program_account, check_zk_token_proof_program_account, + check_program_account, error::TokenError, extension::{ confidential_transfer::{ instruction::{ - CiphertextCiphertextEqualityProofContext, - CiphertextCiphertextEqualityProofData, ProofContextState, ProofInstruction, - ProofType, + CiphertextCiphertextEqualityProofContext, CiphertextCiphertextEqualityProofData, }, ConfidentialTransferAccount, DecryptableBalance, }, @@ -29,7 +27,7 @@ use { instruction::{decode_instruction_data, decode_instruction_type}, pod::{PodAccount, PodMint}, processor::Processor, - proof::decode_proof_instruction_context, + proof::verify_and_extract_context, solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalPubkey, }, bytemuck::Zeroable, @@ -39,9 +37,8 @@ use { msg, program_error::ProgramError, pubkey::Pubkey, - sysvar::instructions::get_instruction_relative, }, - spl_pod::{bytemuck::pod_from_bytes, optional_keys::OptionalNonZeroPubkey}, + spl_pod::optional_keys::OptionalNonZeroPubkey, std::slice::Iter, }; @@ -177,34 +174,10 @@ fn verify_ciphertext_ciphertext_equality_proof( account_info_iter: &mut Iter<'_, AccountInfo<'_>>, proof_instruction_offset: i64, ) -> Result { - if proof_instruction_offset == 0 { - let context_account_info = next_account_info(account_info_iter)?; - // interpret `account_info` as a context state account - check_zk_token_proof_program_account(context_account_info.owner)?; - let context_state_account_data = context_account_info.data.borrow(); - let context_state = pod_from_bytes::< - ProofContextState, - >(&context_state_account_data)?; - - if context_state.proof_type != ProofType::CiphertextCiphertextEquality.into() { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(context_state.proof_context) - } else { - let sysvar_account_info = next_account_info(account_info_iter)?; - // interpret `account_info` as a sysvar - let zkp_instruction = - get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; - Ok(decode_proof_instruction_context::< - CiphertextCiphertextEqualityProofData, - CiphertextCiphertextEqualityProofContext, - >( - account_info_iter, - ProofInstruction::VerifyCiphertextCiphertextEquality, - &zkp_instruction, - )?) - } + verify_and_extract_context::< + CiphertextCiphertextEqualityProofData, + CiphertextCiphertextEqualityProofContext, + >(account_info_iter, proof_instruction_offset) } /// Processes a [WithdrawWithheldTokensFromAccounts] instruction. diff --git a/token/program-2022/src/proof.rs b/token/program-2022/src/proof.rs index d0dcf9efc2d..d26f23f044e 100644 --- a/token/program-2022/src/proof.rs +++ b/token/program-2022/src/proof.rs @@ -1,6 +1,7 @@ //! Helper for processing instruction data from ZK Token proof program use { + crate::check_zk_token_proof_program_account, bytemuck::Pod, solana_program::{ account_info::{next_account_info, AccountInfo}, @@ -8,11 +9,15 @@ use { msg, program_error::ProgramError, pubkey::Pubkey, + sysvar::instructions::get_instruction_relative, }, solana_zk_token_sdk::{ - instruction::ZkProofData, zk_token_proof_instruction::ProofInstruction, + instruction::{ProofType, ZkProofData}, + zk_token_proof_instruction::ProofInstruction, zk_token_proof_program, + zk_token_proof_state::ProofContextState, }, + spl_pod::bytemuck::pod_from_bytes, std::{num::NonZeroI8, slice::Iter}, }; @@ -83,6 +88,71 @@ pub enum ProofData<'a, T> { RecordAccount(&'a Pubkey, u32), } +/// Verify zero-knowledge proof and return the corresponding proof context. +pub fn verify_and_extract_context, U: Pod>( + account_info_iter: &mut Iter<'_, AccountInfo<'_>>, + proof_instruction_offset: i64, +) -> Result { + if proof_instruction_offset == 0 { + // interpret `account_info` as a context state account + let context_state_account_info = next_account_info(account_info_iter)?; + check_zk_token_proof_program_account(context_state_account_info.owner)?; + let context_state_account_data = context_state_account_info.data.borrow(); + let context_state = pod_from_bytes::>(&context_state_account_data)?; + + if context_state.proof_type != T::PROOF_TYPE.into() { + return Err(ProgramError::InvalidInstructionData); + } + + Ok(context_state.proof_context) + } else { + // interpret `account_info` as a sysvar + let sysvar_account_info = next_account_info(account_info_iter)?; + let zkp_instruction = + get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; + let expected_proof_type = zk_proof_type_to_instruction(T::PROOF_TYPE)?; + Ok(decode_proof_instruction_context::( + account_info_iter, + expected_proof_type, + &zkp_instruction, + )?) + } +} + +fn zk_proof_type_to_instruction(proof_type: ProofType) -> Result { + match proof_type { + ProofType::ZeroBalance => Ok(ProofInstruction::VerifyZeroBalance), + ProofType::Withdraw => Ok(ProofInstruction::VerifyWithdraw), + ProofType::CiphertextCiphertextEquality => { + Ok(ProofInstruction::VerifyCiphertextCiphertextEquality) + } + ProofType::Transfer => Ok(ProofInstruction::VerifyTransfer), + ProofType::TransferWithFee => Ok(ProofInstruction::VerifyTransferWithFee), + ProofType::PubkeyValidity => Ok(ProofInstruction::VerifyPubkeyValidity), + ProofType::RangeProofU64 => Ok(ProofInstruction::VerifyRangeProofU64), + ProofType::BatchedRangeProofU64 => Ok(ProofInstruction::VerifyBatchedRangeProofU64), + ProofType::BatchedRangeProofU128 => Ok(ProofInstruction::VerifyBatchedRangeProofU128), + ProofType::BatchedRangeProofU256 => Ok(ProofInstruction::VerifyBatchedRangeProofU256), + ProofType::CiphertextCommitmentEquality => { + Ok(ProofInstruction::VerifyCiphertextCommitmentEquality) + } + ProofType::GroupedCiphertext2HandlesValidity => { + Ok(ProofInstruction::VerifyGroupedCiphertext2HandlesValidity) + } + ProofType::BatchedGroupedCiphertext2HandlesValidity => { + Ok(ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity) + } + ProofType::FeeSigma => Ok(ProofInstruction::VerifyFeeSigma), + ProofType::GroupedCiphertext3HandlesValidity => { + Ok(ProofInstruction::VerifyGroupedCiphertext3HandlesValidity) + } + ProofType::BatchedGroupedCiphertext3HandlesValidity => { + Ok(ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity) + } + ProofType::Uninitialized => Err(ProgramError::InvalidInstructionData), + } +} + /// Instruction options for when using split context state accounts #[derive(Clone, Copy)] pub struct SplitContextStateAccountsConfig { From c4cbfa92333b8e5b06079e8c21c9d2e95b15c179 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Tue, 6 Aug 2024 11:03:32 +0900 Subject: [PATCH 2/9] clean up transfer proof verification --- .../confidential_transfer/verify_proof.rs | 607 ++++-------------- token/program-2022/src/proof.rs | 15 +- 2 files changed, 141 insertions(+), 481 deletions(-) diff --git a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs index d142ad032e5..652adf63904 100644 --- a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs +++ b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs @@ -1,539 +1,194 @@ use { crate::{ - check_system_program_account, check_zk_token_proof_program_account, extension::{ - confidential_transfer::{ciphertext_extraction::*, instruction::*, *}, + confidential_transfer::{ciphertext_extraction::*, instruction::*}, transfer_fee::TransferFee, }, - proof::{decode_proof_instruction_context, verify_and_extract_context}, + proof::verify_and_extract_context, }, solana_program::{ account_info::{next_account_info, AccountInfo}, - msg, - program::invoke, program_error::ProgramError, - sysvar::instructions::get_instruction_relative, }, - solana_zk_token_sdk::zk_token_proof_instruction::{self, ContextStateInfo}, std::slice::Iter, }; /// Verify zero-knowledge proof needed for a [ConfigureAccount] instruction and /// return the corresponding proof context. -pub fn verify_configure_account_proof( - account_info_iter: &mut Iter<'_, AccountInfo<'_>>, +pub fn verify_configure_account_proof<'a>( + account_info_iter: &mut Iter<'a, AccountInfo<'a>>, proof_instruction_offset: i64, ) -> Result { verify_and_extract_context::( account_info_iter, proof_instruction_offset, + None, ) } /// Verify zero-knowledge proof needed for a [EmptyAccount] instruction and /// return the corresponding proof context. -pub fn verify_empty_account_proof( - account_info_iter: &mut Iter<'_, AccountInfo<'_>>, +pub fn verify_empty_account_proof<'a>( + account_info_iter: &mut Iter<'a, AccountInfo<'a>>, proof_instruction_offset: i64, ) -> Result { verify_and_extract_context::( account_info_iter, proof_instruction_offset, + None, ) } /// Verify zero-knowledge proof needed for a [Withdraw] instruction and return /// the corresponding proof context. -pub fn verify_withdraw_proof( - account_info_iter: &mut Iter<'_, AccountInfo<'_>>, +pub fn verify_withdraw_proof<'a>( + account_info_iter: &mut Iter<'a, AccountInfo<'a>>, proof_instruction_offset: i64, ) -> Result { verify_and_extract_context::( account_info_iter, proof_instruction_offset, + None, ) } /// Verify zero-knowledge proof needed for a [Transfer] instruction without fee /// and return the corresponding proof context. -/// -/// This returns a `Result` type for an `Option` type. -/// If the proof verification fails, then the function returns a suitable error -/// variant. If the proof succeeds to verify, then the function returns a -/// `TransferProofContextInfo` that is wrapped inside -/// `Ok(Some(TransferProofContextInfo))`. If -/// `no_op_on_split_proof_context_state` is `true` and some a split context -/// state account is not initialized, then it returns `Ok(None)`. #[cfg(feature = "zk-ops")] -pub fn verify_transfer_proof( - account_info_iter: &mut Iter<'_, AccountInfo<'_>>, - proof_instruction_offset: i64, - split_proof_context_state_accounts: bool, - no_op_on_split_proof_context_state: bool, - close_split_context_state_on_execution: bool, +pub fn verify_transfer_proof<'a>( + account_info_iter: &mut Iter<'a, AccountInfo<'a>>, + equality_proof_instruction_offset: i64, + ciphertext_validity_proof_instruction_offset: i64, + range_proof_instruction_offset: i64, source_decrypt_handles: &SourceDecryptHandles, -) -> Result, ProgramError> { - if proof_instruction_offset == 0 && split_proof_context_state_accounts { - let equality_proof_context_state_account_info = next_account_info(account_info_iter)?; - let ciphertext_validity_proof_context_state_account_info = - next_account_info(account_info_iter)?; - let range_proof_context_state_account_info = next_account_info(account_info_iter)?; - - if no_op_on_split_proof_context_state - && check_system_program_account(equality_proof_context_state_account_info.owner).is_ok() - { - msg!("Equality proof context state account not initialized"); - return Ok(None); - } - - if no_op_on_split_proof_context_state - && check_system_program_account( - ciphertext_validity_proof_context_state_account_info.owner, - ) - .is_ok() - { - msg!("Ciphertext validity proof context state account not initialized"); - return Ok(None); - } - - if no_op_on_split_proof_context_state - && check_system_program_account(range_proof_context_state_account_info.owner).is_ok() - { - msg!("Range proof context state account not initialized"); - return Ok(None); - } - - let equality_proof_context = - verify_equality_proof(equality_proof_context_state_account_info)?; - let ciphertext_validity_proof_context = - verify_ciphertext_validity_proof(ciphertext_validity_proof_context_state_account_info)?; - let range_proof_context = - verify_transfer_range_proof(range_proof_context_state_account_info)?; +) -> Result { + let sysvar_account_info = if equality_proof_instruction_offset != 0 + || ciphertext_validity_proof_instruction_offset != 0 + || range_proof_instruction_offset != 0 + { + Some(next_account_info(account_info_iter)?) + } else { + None + }; - // The `TransferProofContextInfo` constructor verifies the consistency of the - // individual proof context and generates a `TransferWithFeeProofInfo` struct - // that is used to process the rest of the token-2022 logic. - let transfer_proof_context = TransferProofContextInfo::verify_and_extract( - &equality_proof_context, - &ciphertext_validity_proof_context, - &range_proof_context, - source_decrypt_handles, + let equality_proof_context = verify_and_extract_context::< + CiphertextCommitmentEqualityProofData, + CiphertextCommitmentEqualityProofContext, + >( + account_info_iter, + equality_proof_instruction_offset, + sysvar_account_info, + )?; + + let ciphertext_validity_proof_context = verify_and_extract_context::< + BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedGroupedCiphertext2HandlesValidityProofContext, + >( + account_info_iter, + equality_proof_instruction_offset, + sysvar_account_info, + )?; + + let range_proof_context = + verify_and_extract_context::( + account_info_iter, + equality_proof_instruction_offset, + sysvar_account_info, )?; - if close_split_context_state_on_execution { - let lamport_destination_account_info = next_account_info(account_info_iter)?; - let context_state_account_authority_info = next_account_info(account_info_iter)?; - let _zk_token_proof_program = next_account_info(account_info_iter)?; - - msg!("Closing equality proof context state account"); - invoke( - &zk_token_proof_instruction::close_context_state( - ContextStateInfo { - context_state_account: equality_proof_context_state_account_info.key, - context_state_authority: context_state_account_authority_info.key, - }, - lamport_destination_account_info.key, - ), - &[ - equality_proof_context_state_account_info.clone(), - lamport_destination_account_info.clone(), - context_state_account_authority_info.clone(), - ], - )?; - - msg!("Closing ciphertext validity proof context state account"); - invoke( - &zk_token_proof_instruction::close_context_state( - ContextStateInfo { - context_state_account: ciphertext_validity_proof_context_state_account_info - .key, - context_state_authority: context_state_account_authority_info.key, - }, - lamport_destination_account_info.key, - ), - &[ - ciphertext_validity_proof_context_state_account_info.clone(), - lamport_destination_account_info.clone(), - context_state_account_authority_info.clone(), - ], - )?; - - msg!("Closing range proof context state account"); - invoke( - &zk_token_proof_instruction::close_context_state( - ContextStateInfo { - context_state_account: range_proof_context_state_account_info.key, - context_state_authority: context_state_account_authority_info.key, - }, - lamport_destination_account_info.key, - ), - &[ - range_proof_context_state_account_info.clone(), - lamport_destination_account_info.clone(), - context_state_account_authority_info.clone(), - ], - )?; - } - - Ok(Some(transfer_proof_context)) - } else if proof_instruction_offset == 0 && !split_proof_context_state_accounts { - // interpret `account_info` as a context state account - let context_state_account_info = next_account_info(account_info_iter)?; - check_zk_token_proof_program_account(context_state_account_info.owner)?; - let context_state_account_data = context_state_account_info.data.borrow(); - let context_state = - pod_from_bytes::>(&context_state_account_data)?; - - if context_state.proof_type != ProofType::Transfer.into() { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(Some(context_state.proof_context.into())) - } else { - // interpret `account_info` as sysvar - let sysvar_account_info = next_account_info(account_info_iter)?; - let zkp_instruction = - get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; - let proof_context = - (decode_proof_instruction_context::( - account_info_iter, - ProofInstruction::VerifyTransfer, - &zkp_instruction, - )?) - .into(); - - Ok(Some(proof_context)) - } + // The `TransferProofContextInfo` constructor verifies the consistency of the + // individual proof context and generates a `TransferWithFeeProofInfo` struct + // that is used to process the rest of the token-2022 logic. + let transfer_proof_context = TransferProofContextInfo::verify_and_extract( + &equality_proof_context, + &ciphertext_validity_proof_context, + &range_proof_context, + source_decrypt_handles, + )?; + + Ok(transfer_proof_context) } /// Verify zero-knowledge proof needed for a [Transfer] instruction with fee and /// return the corresponding proof context. #[cfg(feature = "zk-ops")] -pub fn verify_transfer_with_fee_proof( - account_info_iter: &mut Iter<'_, AccountInfo<'_>>, - proof_instruction_offset: i64, - split_proof_context_state_accounts: bool, - no_op_on_split_proof_context_state: bool, - close_split_context_state_on_execution: bool, +pub fn verify_transfer_with_fee_proof<'a>( + account_info_iter: &mut Iter<'a, AccountInfo<'a>>, + equality_proof_instruction_offset: i64, + transfer_amount_ciphertext_validity_proof_instruction_offset: i64, + fee_sigma_proof_instruction_offset: i64, + fee_ciphertext_validity_proof_instruction_offset: i64, + range_proof_instruction_offset: i64, source_decrypt_handles: &SourceDecryptHandles, fee_parameters: &TransferFee, -) -> Result, ProgramError> { - if proof_instruction_offset == 0 && split_proof_context_state_accounts { - let equality_proof_context_state_account_info = next_account_info(account_info_iter)?; - let transfer_amount_ciphertext_validity_proof_context_state_account_info = - next_account_info(account_info_iter)?; - let fee_sigma_proof_context_state_account_info = next_account_info(account_info_iter)?; - let fee_ciphertext_validity_proof_context_state_account_info = - next_account_info(account_info_iter)?; - let range_proof_context_state_account_info = next_account_info(account_info_iter)?; - - if no_op_on_split_proof_context_state - && check_system_program_account(equality_proof_context_state_account_info.owner).is_ok() - { - msg!("Equality proof context state account not initialized"); - return Ok(None); - } - - if no_op_on_split_proof_context_state - && check_system_program_account( - transfer_amount_ciphertext_validity_proof_context_state_account_info.owner, - ) - .is_ok() - { - msg!("Transfer amount ciphertext validity proof context state account not initialized"); - return Ok(None); - } - - if no_op_on_split_proof_context_state - && check_system_program_account(fee_sigma_proof_context_state_account_info.owner) - .is_ok() - { - msg!("Fee sigma proof context state account not initialized"); - return Ok(None); - } - - if no_op_on_split_proof_context_state - && check_system_program_account( - fee_ciphertext_validity_proof_context_state_account_info.owner, - ) - .is_ok() - { - msg!("Fee ciphertext validity proof context state account not initialized"); - return Ok(None); - } - - if no_op_on_split_proof_context_state - && check_system_program_account(range_proof_context_state_account_info.owner).is_ok() - { - msg!("Range proof context state account not initialized"); - return Ok(None); - } - - let equality_proof_context = - verify_equality_proof(equality_proof_context_state_account_info)?; - let transfer_amount_ciphertext_validity_proof_context = verify_ciphertext_validity_proof( - transfer_amount_ciphertext_validity_proof_context_state_account_info, - )?; - let fee_sigma_proof_context = - verify_fee_sigma_proof(fee_sigma_proof_context_state_account_info)?; - let fee_ciphertext_validity_proof_context = verify_ciphertext_validity_proof( - fee_ciphertext_validity_proof_context_state_account_info, - )?; - let range_proof_context = - verify_transfer_with_fee_range_proof(range_proof_context_state_account_info)?; +) -> Result { + let sysvar_account_info = if equality_proof_instruction_offset != 0 + || transfer_amount_ciphertext_validity_proof_instruction_offset != 0 + || fee_sigma_proof_instruction_offset != 0 + || fee_ciphertext_validity_proof_instruction_offset != 0 + || range_proof_instruction_offset != 0 + { + Some(next_account_info(account_info_iter)?) + } else { + None + }; - // The `TransferWithFeeProofContextInfo` constructor verifies the consistency of - // the individual proof context and generates a - // `TransferWithFeeProofInfo` struct that is used to process the rest of - // the token-2022 logic. The consistency check includes verifying - // whether the fee-related zkps were generated with respect to the correct fee - // parameter that is stored in the mint extension. - let transfer_with_fee_proof_context = TransferWithFeeProofContextInfo::verify_and_extract( - &equality_proof_context, - &transfer_amount_ciphertext_validity_proof_context, - &fee_sigma_proof_context, - &fee_ciphertext_validity_proof_context, - &range_proof_context, - source_decrypt_handles, - fee_parameters, + let equality_proof_context = verify_and_extract_context::< + CiphertextCommitmentEqualityProofData, + CiphertextCommitmentEqualityProofContext, + >( + account_info_iter, + equality_proof_instruction_offset, + sysvar_account_info, + )?; + + let transfer_amount_ciphertext_validity_proof_context = verify_and_extract_context::< + BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedGroupedCiphertext2HandlesValidityProofContext, + >( + account_info_iter, + equality_proof_instruction_offset, + sysvar_account_info, + )?; + + let fee_sigma_proof_context = + verify_and_extract_context::( + account_info_iter, + equality_proof_instruction_offset, + sysvar_account_info, )?; - if close_split_context_state_on_execution { - let lamport_destination_account_info = next_account_info(account_info_iter)?; - let context_state_account_authority_info = next_account_info(account_info_iter)?; - let _zk_token_proof_program = next_account_info(account_info_iter)?; - - msg!("Closing equality proof context state account"); - invoke( - &zk_token_proof_instruction::close_context_state( - ContextStateInfo { - context_state_account: equality_proof_context_state_account_info.key, - context_state_authority: context_state_account_authority_info.key, - }, - lamport_destination_account_info.key, - ), - &[ - equality_proof_context_state_account_info.clone(), - lamport_destination_account_info.clone(), - context_state_account_authority_info.clone(), - ], - )?; - - msg!("Closing transfer amount ciphertext validity proof context state account"); - invoke( - &zk_token_proof_instruction::close_context_state( - ContextStateInfo { - context_state_account: - transfer_amount_ciphertext_validity_proof_context_state_account_info.key, - context_state_authority: context_state_account_authority_info.key, - }, - lamport_destination_account_info.key, - ), - &[ - transfer_amount_ciphertext_validity_proof_context_state_account_info.clone(), - lamport_destination_account_info.clone(), - context_state_account_authority_info.clone(), - ], - )?; - - msg!("Closing fee sigma proof context state account"); - invoke( - &zk_token_proof_instruction::close_context_state( - ContextStateInfo { - context_state_account: fee_sigma_proof_context_state_account_info.key, - context_state_authority: context_state_account_authority_info.key, - }, - lamport_destination_account_info.key, - ), - &[ - fee_sigma_proof_context_state_account_info.clone(), - lamport_destination_account_info.clone(), - context_state_account_authority_info.clone(), - ], - )?; - - msg!("Closing fee ciphertext validity proof context state account"); - invoke( - &zk_token_proof_instruction::close_context_state( - ContextStateInfo { - context_state_account: - fee_ciphertext_validity_proof_context_state_account_info.key, - context_state_authority: context_state_account_authority_info.key, - }, - lamport_destination_account_info.key, - ), - &[ - fee_ciphertext_validity_proof_context_state_account_info.clone(), - lamport_destination_account_info.clone(), - context_state_account_authority_info.clone(), - ], - )?; - - msg!("Closing range proof context state account"); - invoke( - &zk_token_proof_instruction::close_context_state( - ContextStateInfo { - context_state_account: range_proof_context_state_account_info.key, - context_state_authority: context_state_account_authority_info.key, - }, - lamport_destination_account_info.key, - ), - &[ - range_proof_context_state_account_info.clone(), - lamport_destination_account_info.clone(), - context_state_account_authority_info.clone(), - ], - )?; - } - - Ok(Some(transfer_with_fee_proof_context)) - } else if proof_instruction_offset == 0 && !split_proof_context_state_accounts { - // interpret `account_info` as a context state account - let context_state_account_info = next_account_info(account_info_iter)?; - check_zk_token_proof_program_account(context_state_account_info.owner)?; - let context_state_account_data = context_state_account_info.data.borrow(); - let context_state = pod_from_bytes::>( - &context_state_account_data, + let fee_ciphertext_validity_proof_context = verify_and_extract_context::< + BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedGroupedCiphertext2HandlesValidityProofContext, + >( + account_info_iter, + equality_proof_instruction_offset, + sysvar_account_info, + )?; + + let range_proof_context = + verify_and_extract_context::( + account_info_iter, + equality_proof_instruction_offset, + sysvar_account_info, )?; - if context_state.proof_type != ProofType::TransferWithFee.into() { - return Err(ProgramError::InvalidInstructionData); - } - - let proof_tranfer_fee_basis_points: u16 = context_state - .proof_context - .fee_parameters - .fee_rate_basis_points - .into(); - let proof_maximum_fee: u64 = context_state - .proof_context - .fee_parameters - .maximum_fee - .into(); - - // check consistency of the transfer fee parameters in the mint extension with - // what were used to generate the zkp, which is not checked in the - // `From` implementation for - // `TransferWithFeeProofContextInfo`. - if u16::from(fee_parameters.transfer_fee_basis_points) != proof_tranfer_fee_basis_points - || u64::from(fee_parameters.maximum_fee) != proof_maximum_fee - { - return Err(TokenError::FeeParametersMismatch.into()); - } - - Ok(Some(context_state.proof_context.into())) - } else { - // interpret `account_info` as sysvar - let sysvar_account_info = next_account_info(account_info_iter)?; - let zkp_instruction = - get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; - let proof_context = - decode_proof_instruction_context::( - account_info_iter, - ProofInstruction::VerifyTransferWithFee, - &zkp_instruction, - )?; - - let proof_tranfer_fee_basis_points: u16 = - proof_context.fee_parameters.fee_rate_basis_points.into(); - let proof_maximum_fee: u64 = proof_context.fee_parameters.maximum_fee.into(); - - // check consistency of the transfer fee parameters in the mint extension with - // what were used to generate the zkp, which is not checked in the - // `From` implementation for - // `TransferWithFeeProofContextInfo`. - if u16::from(fee_parameters.transfer_fee_basis_points) != proof_tranfer_fee_basis_points - || u64::from(fee_parameters.maximum_fee) != proof_maximum_fee - { - return Err(TokenError::FeeParametersMismatch.into()); - } - - Ok(Some(proof_context.into())) - } -} - -/// Verify and process equality proof for [Transfer] and [TransferWithFee] -/// instructions. -fn verify_equality_proof( - account_info: &AccountInfo<'_>, -) -> Result { - check_zk_token_proof_program_account(account_info.owner)?; - let context_state_account_data = account_info.data.borrow(); - let equality_proof_context_state = pod_from_bytes::< - ProofContextState, - >(&context_state_account_data)?; - - if equality_proof_context_state.proof_type != ProofType::CiphertextCommitmentEquality.into() { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(equality_proof_context_state.proof_context) -} - -/// Verify and process ciphertext validity proof for [Transfer] and -/// [TransferWithFee] instructions. -fn verify_ciphertext_validity_proof( - account_info: &AccountInfo<'_>, -) -> Result { - check_zk_token_proof_program_account(account_info.owner)?; - let context_state_account_data = account_info.data.borrow(); - let ciphertext_validity_proof_context_state = pod_from_bytes::< - ProofContextState, - >(&context_state_account_data)?; - - if ciphertext_validity_proof_context_state.proof_type - != ProofType::BatchedGroupedCiphertext2HandlesValidity.into() - { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(ciphertext_validity_proof_context_state.proof_context) -} - -/// Verify and process range proof for [Transfer] instruction. -fn verify_transfer_range_proof( - account_info: &AccountInfo<'_>, -) -> Result { - check_zk_token_proof_program_account(account_info.owner)?; - let context_state_account_data = account_info.data.borrow(); - let range_proof_context_state = - pod_from_bytes::>(&context_state_account_data)?; - - if range_proof_context_state.proof_type != ProofType::BatchedRangeProofU128.into() { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(range_proof_context_state.proof_context) -} - -/// Verify and process range proof for [Transfer] instruction with fee. -fn verify_transfer_with_fee_range_proof( - account_info: &AccountInfo<'_>, -) -> Result { - check_zk_token_proof_program_account(account_info.owner)?; - let context_state_account_data = account_info.data.borrow(); - let range_proof_context_state = - pod_from_bytes::>(&context_state_account_data)?; - - if range_proof_context_state.proof_type != ProofType::BatchedRangeProofU256.into() { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(range_proof_context_state.proof_context) -} - -/// Verify and process fee sigma proof for [TransferWithFee] instruction. -fn verify_fee_sigma_proof( - account_info: &AccountInfo<'_>, -) -> Result { - check_zk_token_proof_program_account(account_info.owner)?; - let context_state_account_data = account_info.data.borrow(); - let fee_sigma_proof_context_state = - pod_from_bytes::>(&context_state_account_data)?; - - if fee_sigma_proof_context_state.proof_type != ProofType::FeeSigma.into() { - return Err(ProgramError::InvalidInstructionData); - } - - Ok(fee_sigma_proof_context_state.proof_context) + // The `TransferWithFeeProofContextInfo` constructor verifies the consistency of + // the individual proof context and generates a + // `TransferWithFeeProofInfo` struct that is used to process the rest of + // the token-2022 logic. The consistency check includes verifying + // whether the fee-related zkps were generated with respect to the correct fee + // parameter that is stored in the mint extension. + let transfer_with_fee_proof_context = TransferWithFeeProofContextInfo::verify_and_extract( + &equality_proof_context, + &transfer_amount_ciphertext_validity_proof_context, + &fee_sigma_proof_context, + &fee_ciphertext_validity_proof_context, + &range_proof_context, + source_decrypt_handles, + fee_parameters, + )?; + + Ok(transfer_with_fee_proof_context) } diff --git a/token/program-2022/src/proof.rs b/token/program-2022/src/proof.rs index d26f23f044e..d3ffb66678f 100644 --- a/token/program-2022/src/proof.rs +++ b/token/program-2022/src/proof.rs @@ -89,9 +89,10 @@ pub enum ProofData<'a, T> { } /// Verify zero-knowledge proof and return the corresponding proof context. -pub fn verify_and_extract_context, U: Pod>( - account_info_iter: &mut Iter<'_, AccountInfo<'_>>, +pub fn verify_and_extract_context<'a, T: Pod + ZkProofData, U: Pod>( + account_info_iter: &mut Iter<'a, AccountInfo<'a>>, proof_instruction_offset: i64, + sysvar_account_info: Option<&'a AccountInfo<'a>>, ) -> Result { if proof_instruction_offset == 0 { // interpret `account_info` as a context state account @@ -106,10 +107,14 @@ pub fn verify_and_extract_context, U: Pod>( Ok(context_state.proof_context) } else { - // interpret `account_info` as a sysvar - let sysvar_account_info = next_account_info(account_info_iter)?; + // if sysvar account is not provided, then get the sysvar account + let sysvar_account_info = if let Some(sysvar_account_info) = sysvar_account_info { + sysvar_account_info + } else { + next_account_info(account_info_iter)? + }; let zkp_instruction = - get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; + get_instruction_relative(proof_instruction_offset, &sysvar_account_info)?; let expected_proof_type = zk_proof_type_to_instruction(T::PROOF_TYPE)?; Ok(decode_proof_instruction_context::( account_info_iter, From e7a08d7bbf103bf2040efa6baa85182e9133866f Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 9 Aug 2024 11:46:01 +0900 Subject: [PATCH 3/9] re-organize confidential transfer instruction into `Transfer` and `TransferWithFee` --- .../confidential_transfer/account_info.rs | 6 +- .../ciphertext_extraction.rs | 119 ++- .../confidential_transfer/instruction.rs | 772 +++++++++--------- .../extension/confidential_transfer/mod.rs | 1 - .../confidential_transfer/processor.rs | 419 ++++------ .../split_proof_generation.rs | 39 +- .../confidential_transfer/verify_proof.rs | 45 +- .../confidential_transfer_fee/processor.rs | 4 +- token/program-2022/src/proof.rs | 16 +- 9 files changed, 683 insertions(+), 738 deletions(-) diff --git a/token/program-2022/src/extension/confidential_transfer/account_info.rs b/token/program-2022/src/extension/confidential_transfer/account_info.rs index bfa88b73b3e..eebc84e9238 100644 --- a/token/program-2022/src/extension/confidential_transfer/account_info.rs +++ b/token/program-2022/src/extension/confidential_transfer/account_info.rs @@ -2,7 +2,6 @@ use { crate::{ error::TokenError, extension::confidential_transfer::{ - ciphertext_extraction::SourceDecryptHandles, split_proof_generation::transfer_split_proof_data, ConfidentialTransferAccount, DecryptableBalance, EncryptedBalance, PENDING_BALANCE_LO_BIT_LENGTH, }, @@ -17,7 +16,7 @@ use { transfer::{FeeParameters, TransferData, TransferWithFeeData}, withdraw::WithdrawData, zero_balance::ZeroBalanceProofData, - BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU128Data, + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, CiphertextCommitmentEqualityProofData, }, }, @@ -284,9 +283,8 @@ impl TransferAccountInfo { ) -> Result< ( CiphertextCommitmentEqualityProofData, - BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, - SourceDecryptHandles, ), TokenError, > { diff --git a/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs b/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs index 85f9fd0b031..a43c50d1d77 100644 --- a/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs +++ b/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs @@ -15,7 +15,8 @@ use crate::{ }, instruction::{ transfer::{TransferProofContext, TransferWithFeeProofContext}, - BatchedGroupedCiphertext2HandlesValidityProofContext, BatchedRangeProofContext, + BatchedGroupedCiphertext2HandlesValidityProofContext, + BatchedGroupedCiphertext3HandlesValidityProofContext, BatchedRangeProofContext, CiphertextCommitmentEqualityProofContext, FeeSigmaProofContext, }, zk_token_elgamal::pod::{ @@ -35,14 +36,35 @@ use { /// A grouped ciphertext with 2 handles consists of the following 32-bytes /// components that are serialized in order: /// 1. The `commitment` component that encodes the fee amount. +/// 2. The `decryption handle` component with respect to the destination +/// public key. +/// 3. The `decryption handle` component with respect to the withdraw withheld +/// authority public key. +/// +/// The fee commitment component consists of the first 32-byte. +pub(crate) fn extract_commitment_from_grouped_ciphertext_2_handles( + transfer_amount_ciphertext: &GroupedElGamalCiphertext2Handles, +) -> PedersenCommitment { + let transfer_amount_ciphertext_bytes = bytemuck::bytes_of(transfer_amount_ciphertext); + let transfer_amount_commitment_bytes = + transfer_amount_ciphertext_bytes[..32].try_into().unwrap(); + PedersenCommitment(transfer_amount_commitment_bytes) +} + +/// Extract the commitment component from a grouped ciphertext with 3 handles. +/// +/// A grouped ciphertext with 3 handles consists of the following 32-bytes +/// components that are serialized in order: +/// 1. The `commitment` component that encodes the fee amount. +/// 2. The `source handle` component with respect to the source public key. /// 3. The `decryption handle` component with respect to the destination /// public key. /// 4. The `decryption handle` component with respect to the withdraw withheld /// authority public key. /// /// The fee commitment component consists of the first 32-byte. -pub(crate) fn extract_commitment_from_grouped_ciphertext( - transfer_amount_ciphertext: &GroupedElGamalCiphertext2Handles, +pub(crate) fn extract_commitment_from_grouped_ciphertext_3_handles( + transfer_amount_ciphertext: &GroupedElGamalCiphertext3Handles, ) -> PedersenCommitment { let transfer_amount_ciphertext_bytes = bytemuck::bytes_of(transfer_amount_ciphertext); let transfer_amount_commitment_bytes = @@ -160,24 +182,6 @@ pub(crate) fn fee_amount_withdraw_withheld_authority_ciphertext( ElGamalCiphertext(destination_ciphertext_bytes) } -#[cfg(feature = "zk-ops")] -pub(crate) fn transfer_amount_encryption_from_decrypt_handle( - source_decrypt_handle: &DecryptHandle, - grouped_ciphertext: &GroupedElGamalCiphertext2Handles, -) -> TransferAmountCiphertext { - let source_decrypt_handle_bytes = bytemuck::bytes_of(source_decrypt_handle); - let grouped_ciphertext_bytes = bytemuck::bytes_of(grouped_ciphertext); - - let mut transfer_amount_ciphertext_bytes = [0u8; 128]; - transfer_amount_ciphertext_bytes[..32].copy_from_slice(&grouped_ciphertext_bytes[..32]); - transfer_amount_ciphertext_bytes[32..64].copy_from_slice(source_decrypt_handle_bytes); - transfer_amount_ciphertext_bytes[64..128].copy_from_slice(&grouped_ciphertext_bytes[32..96]); - - TransferAmountCiphertext(GroupedElGamalCiphertext3Handles( - transfer_amount_ciphertext_bytes, - )) -} - /// The transfer public keys associated with a transfer. #[cfg(feature = "zk-ops")] pub struct TransferPubkeysInfo { @@ -227,9 +231,8 @@ impl TransferProofContextInfo { /// their consistency. pub fn verify_and_extract( equality_proof_context: &CiphertextCommitmentEqualityProofContext, - ciphertext_validity_proof_context: &BatchedGroupedCiphertext2HandlesValidityProofContext, + ciphertext_validity_proof_context: &BatchedGroupedCiphertext3HandlesValidityProofContext, range_proof_context: &BatchedRangeProofContext, - source_decrypt_handles: &SourceDecryptHandles, ) -> Result { // The equality proof context consists of the source ElGamal public key, the new // source available balance ciphertext, and the new source available @@ -237,24 +240,30 @@ impl TransferProofContextInfo { // of `TransferProofContextInfo` and the commitment should be checked // with range proof for consistency. let CiphertextCommitmentEqualityProofContext { - pubkey: source_pubkey, + pubkey: source_pubkey_from_equality_proof, ciphertext: new_source_ciphertext, commitment: new_source_commitment, } = equality_proof_context; - // The ciphertext validity proof context consists of the destination ElGamal + // The ciphertext validity proof context consists of the source ElGamal public key, + // destination ElGamal // public key, auditor ElGamal public key, and the transfer amount // ciphertexts. All of these fields should be returned as part of // `TransferProofContextInfo`. In addition, the commitments pertaining // to the transfer amount ciphertexts should be checked with range proof for // consistency. - let BatchedGroupedCiphertext2HandlesValidityProofContext { + let BatchedGroupedCiphertext3HandlesValidityProofContext { + source_pubkey: source_pubkey_from_ciphertext_validity_proof, destination_pubkey, auditor_pubkey, grouped_ciphertext_lo: transfer_amount_ciphertext_lo, grouped_ciphertext_hi: transfer_amount_ciphertext_hi, } = ciphertext_validity_proof_context; + if source_pubkey_from_equality_proof != source_pubkey_from_ciphertext_validity_proof { + return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); + } + // The range proof context consists of the Pedersen commitments and bit-lengths // for which the range proof is proved. The commitments must consist of // three commitments pertaining to the new source available balance, the @@ -269,9 +278,9 @@ impl TransferProofContextInfo { // check that the range proof was created for the correct set of Pedersen // commitments let transfer_amount_commitment_lo = - extract_commitment_from_grouped_ciphertext(transfer_amount_ciphertext_lo); + extract_commitment_from_grouped_ciphertext_3_handles(transfer_amount_ciphertext_lo); let transfer_amount_commitment_hi = - extract_commitment_from_grouped_ciphertext(transfer_amount_ciphertext_hi); + extract_commitment_from_grouped_ciphertext_3_handles(transfer_amount_ciphertext_hi); let expected_commitments = [ *new_source_commitment, @@ -310,24 +319,14 @@ impl TransferProofContextInfo { } let transfer_pubkeys = TransferPubkeysInfo { - source: *source_pubkey, + source: *source_pubkey_from_equality_proof, destination: *destination_pubkey, auditor: *auditor_pubkey, }; - let transfer_amount_ciphertext_lo = transfer_amount_encryption_from_decrypt_handle( - &source_decrypt_handles.lo, - transfer_amount_ciphertext_lo, - ); - - let transfer_amount_ciphertext_hi = transfer_amount_encryption_from_decrypt_handle( - &source_decrypt_handles.hi, - transfer_amount_ciphertext_hi, - ); - Ok(Self { - ciphertext_lo: transfer_amount_ciphertext_lo, - ciphertext_hi: transfer_amount_ciphertext_hi, + ciphertext_lo: TransferAmountCiphertext(*transfer_amount_ciphertext_lo), + ciphertext_hi: TransferAmountCiphertext(*transfer_amount_ciphertext_hi), transfer_pubkeys, new_source_ciphertext: *new_source_ciphertext, }) @@ -397,11 +396,10 @@ impl TransferWithFeeProofContextInfo { /// their consistency. pub fn verify_and_extract( equality_proof_context: &CiphertextCommitmentEqualityProofContext, - transfer_amount_ciphertext_validity_proof_context: &BatchedGroupedCiphertext2HandlesValidityProofContext, + transfer_amount_ciphertext_validity_proof_context: &BatchedGroupedCiphertext3HandlesValidityProofContext, fee_sigma_proof_context: &FeeSigmaProofContext, fee_ciphertext_validity_proof_context: &BatchedGroupedCiphertext2HandlesValidityProofContext, range_proof_context: &BatchedRangeProofContext, - source_decrypt_handles: &SourceDecryptHandles, fee_parameters: &TransferFee, ) -> Result { // The equality proof context consists of the source ElGamal public key, the new @@ -410,7 +408,7 @@ impl TransferWithFeeProofContextInfo { // of `TransferWithFeeProofContextInfo` and the commitment should be // checked with range proof for consistency. let CiphertextCommitmentEqualityProofContext { - pubkey: source_pubkey, + pubkey: source_pubkey_from_equality_proof, ciphertext: new_source_ciphertext, commitment: new_source_commitment, } = equality_proof_context; @@ -421,13 +419,18 @@ impl TransferWithFeeProofContextInfo { // as part of `TransferWithFeeProofContextInfo`. In addition, the // commitments pertaining to the transfer amount ciphertexts should be // checked with range proof for consistency. - let BatchedGroupedCiphertext2HandlesValidityProofContext { + let BatchedGroupedCiphertext3HandlesValidityProofContext { + source_pubkey: source_pubkey_from_ciphertext_validity_proof, destination_pubkey, auditor_pubkey, grouped_ciphertext_lo: transfer_amount_ciphertext_lo, grouped_ciphertext_hi: transfer_amount_ciphertext_hi, } = transfer_amount_ciphertext_validity_proof_context; + if source_pubkey_from_equality_proof != source_pubkey_from_ciphertext_validity_proof { + return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); + } + // The fee sigma proof context consists of the fee commitment, delta commitment, // claimed commitment, and max fee. The fee and claimed commitment // should be checked with range proof for consistency. The delta @@ -485,12 +488,14 @@ impl TransferWithFeeProofContextInfo { // check that the range proof was created for the correct set of Pedersen // commitments let transfer_amount_commitment_lo = - extract_commitment_from_grouped_ciphertext(transfer_amount_ciphertext_lo); + extract_commitment_from_grouped_ciphertext_3_handles(transfer_amount_ciphertext_lo); let transfer_amount_commitment_hi = - extract_commitment_from_grouped_ciphertext(transfer_amount_ciphertext_hi); + extract_commitment_from_grouped_ciphertext_3_handles(transfer_amount_ciphertext_hi); - let fee_commitment_lo = extract_commitment_from_grouped_ciphertext(fee_ciphertext_lo); - let fee_commitment_hi = extract_commitment_from_grouped_ciphertext(fee_ciphertext_hi); + let fee_commitment_lo = + extract_commitment_from_grouped_ciphertext_2_handles(fee_ciphertext_lo); + let fee_commitment_hi = + extract_commitment_from_grouped_ciphertext_2_handles(fee_ciphertext_hi); const MAX_FEE_BASIS_POINTS: u64 = 10_000; let max_fee_basis_points_scalar = u64_to_scalar(MAX_FEE_BASIS_POINTS); @@ -567,25 +572,15 @@ impl TransferWithFeeProofContextInfo { // create transfer with fee proof context info and return let transfer_with_fee_pubkeys = TransferWithFeePubkeysInfo { - source: *source_pubkey, + source: *source_pubkey_from_equality_proof, destination: *destination_pubkey, auditor: *auditor_pubkey, withdraw_withheld_authority: *withdraw_withheld_authority_pubkey, }; - let transfer_amount_ciphertext_lo = transfer_amount_encryption_from_decrypt_handle( - &source_decrypt_handles.lo, - transfer_amount_ciphertext_lo, - ); - - let transfer_amount_ciphertext_hi = transfer_amount_encryption_from_decrypt_handle( - &source_decrypt_handles.hi, - transfer_amount_ciphertext_hi, - ); - Ok(Self { - ciphertext_lo: transfer_amount_ciphertext_lo, - ciphertext_hi: transfer_amount_ciphertext_hi, + ciphertext_lo: TransferAmountCiphertext(*transfer_amount_ciphertext_lo), + ciphertext_hi: TransferAmountCiphertext(*transfer_amount_ciphertext_hi), transfer_with_fee_pubkeys, new_source_ciphertext: *new_source_ciphertext, fee_ciphertext_lo: FeeEncryption(*fee_ciphertext_lo), diff --git a/token/program-2022/src/extension/confidential_transfer/instruction.rs b/token/program-2022/src/extension/confidential_transfer/instruction.rs index d8e857c3817..27cd136b936 100644 --- a/token/program-2022/src/extension/confidential_transfer/instruction.rs +++ b/token/program-2022/src/extension/confidential_transfer/instruction.rs @@ -11,7 +11,7 @@ use { use { crate::{ check_program_account, - extension::confidential_transfer::{ciphertext_extraction::SourceDecryptHandles, *}, + extension::confidential_transfer::*, instruction::{encode_instruction, TokenInstruction}, proof::{ProofData, ProofLocation}, }, @@ -244,11 +244,14 @@ pub enum ConfidentialTransferInstruction { /// Transfer tokens confidentially. /// - /// In order for this instruction to be successfully processed, it must be - /// accompanied by either the `VerifyTransfer` or - /// `VerifyTransferWithFee` instruction of the `zk_token_proof` - /// program in the same transaction or the address of a context state - /// account for the proof must be provided. + /// In order for this instruction to be successfully processed, it must be accompanied by + /// the following list of `zk_token_proof` program instructions: + /// - `VerifyCiphertextCommitmentEqualityProof` + /// - `VerifyBatchedGroupedCiphertext3HandlesValidityProof` + /// - `VerifyBatchedRangeProofU128` + /// These instructions can be accompanied in the same transaction or can be pre-verified into a + /// context state account, in which case, only their context state account addresses need to be + /// provided. /// /// Fails if the associated mint is extended as `NonTransferable`. /// @@ -256,26 +259,24 @@ pub enum ConfidentialTransferInstruction { /// 1. `[writable]` The source SPL Token account. /// 2. `[]` The token mint. /// 3. `[writable]` The destination SPL Token account. - /// 4. `[]` Instructions sysvar if `VerifyTransfer` or - /// `VerifyTransferWithFee` is included in the same transaction or - /// context state account if these proofs are pre-verified into a - /// context state account. - /// 5. `[]` (Optional) Record account if the accompanying proof is to be - /// read from a record account. - /// 6. `[signer]` The single source account owner. + /// 4. `[]` (Optional) Instructions sysvar if at least one of the `zk_token_proof` + /// instructions are included in the same transaction. + /// 5. `[]` (Optional) Equality proof record account or context state account. + /// 6. `[]` (Optional) Ciphertext validity proof record account or context state account. + /// 7. `[]` (Optional) Range proof record account or context state account. + /// 8. `[signer]` The single source account owner. /// /// * Multisignature owner/delegate /// 1. `[writable]` The source SPL Token account. /// 2. `[]` The token mint. /// 3. `[writable]` The destination SPL Token account. - /// 4. `[]` Instructions sysvar if `VerifyTransfer` or - /// `VerifyTransferWithFee` is included in the same transaction or - /// context state account if these proofs are pre-verified into a - /// context state account. - /// 5. `[]` (Optional) Record account if the accompanying proof is to be - /// read from a record account. - /// 6. `[]` The multisig source account owner. - /// 7.. `[signer]` Required M signer accounts for the SPL Token Multisig + /// 4. `[]` (Optional) Instructions sysvar if at least one of the `zk_token_proof` + /// instructions are included in the same transaction. + /// 5. `[]` (Optional) Equality proof record account or context state account. + /// 6. `[]` (Optional) Ciphertext validity proof record account or context state account. + /// 7. `[]` (Optional) Range proof record account or context state account. + /// 8. `[]` The multisig source account owner. + /// 9.. `[signer]` Required M signer accounts for the SPL Token Multisig /// account. /// /// Data expected by this instruction: @@ -395,59 +396,53 @@ pub enum ConfidentialTransferInstruction { /// None DisableNonConfidentialCredits, - /// Transfer tokens confidentially with zero-knowledge proofs that are split - /// into smaller components. + /// Transfer tokens confidentially with fee. /// - /// In order for this instruction to be successfully processed, it must be - /// accompanied by suitable zero-knowledge proof context accounts listed - /// below. + /// In order for this instruction to be successfully processed, it must be accompanied by the + /// following list of `zk_token_proof` program instructions: + /// - `VerifyCiphertextCommitmentEqualityProof` + /// - `VerifyBatchedGroupedCiphertext3HandlesValidityProof` (transfer amount ciphertext) + /// - `FeeSigmaProof` + /// - `VerifyBatchedGroupedCiphertext2HandlesValidityProof` (fee ciphertext) + /// - `VerifyBatchedRangeProofU256` + /// These instructions can be accompanied in the same transaction or can be pre-verified into a + /// context state account, in which case, only their context state account addresses need to be + /// provided. /// /// The same restrictions for the `Transfer` applies to - /// `TransferWithSplitProofs`. Namely, the instruction fails if the + /// `TransferWithFee`. Namely, the instruction fails if the /// associated mint is extended as `NonTransferable`. /// /// * Transfer without fee /// 1. `[writable]` The source SPL Token account. /// 2. `[]` The token mint. /// 3. `[writable]` The destination SPL Token account. - /// 4. `[]` Context state account for - /// `VerifyCiphertextCommitmentEqualityProof`. - /// 5. `[]` Context state account for - /// `VerifyBatchedGroupedCiphertext2HandlesValidityProof`. - /// 6. `[]` Context state account for `VerifyBatchedRangeProofU128`. - /// If `close_split_context_state_on_execution` is set, all context state - /// accounts must be `writable` and the following sequence - /// of accounts that are marked with asterisk are needed: - /// 7*. `[]` The destination account for lamports from the context state - /// accounts. - /// 8*. `[signer]` The context state account owner. - /// 9*. `[]` The zk token proof program. + /// 4. `[]` (Optional) Instructions sysvar if at least one of the `zk_token_proof` + /// instructions are included in the same transaction. + /// 5. `[]` (Optional) Equality proof record account or context state account. + /// 6. `[]` (Optional) Transfer amount ciphertext validity proof record account or context state account. + /// 7. `[]` (Optional) Fee sigma proof record account or context state account. + /// 8. `[]` (Optional) Fee ciphertext validity proof record account or context state account. + /// 9. `[]` (Optional) Range proof record account or context state account. /// 10. `[signer]` The source account owner. /// /// * Transfer with fee /// 1. `[writable]` The source SPL Token account. /// 2. `[]` The token mint. /// 3. `[writable]` The destination SPL Token account. - /// 4. `[]` Context state account for - /// `VerifyCiphertextCommitmentEqualityProof`. - /// 5. `[]` Context state account for - /// `VerifyBatchedGroupedCiphertext2HandlesValidityProof`. - /// 6. `[]` Context state account for `VerifyFeeSigmaProof`. - /// 7. `[]` Context state account for - /// `VerifyBatchedGroupedCiphertext2HandlesValidityProof`. - /// 8. `[]` Context state account for `VerifyBatchedRangeProofU256`. - /// If `close_split_context_state_on_execution` is set, all context state - /// accounts must be `writable` and the following sequence - /// of accounts that are marked with asterisk are needed: - /// 9*. `[]` The destination account for lamports from the context state - /// accounts. - /// 10*. `[signer]` The context state account owner. - /// 11*. `[]` The zk token proof program. - /// 12. `[signer]` The source account owner. + /// 4. `[]` (Optional) Instructions sysvar if at least one of the `zk_token_proof` + /// instructions are included in the same transaction. + /// 5. `[]` (Optional) Equality proof record account or context state account. + /// 6. `[]` (Optional) Transfer amount ciphertext validity proof record account or context state account. + /// 7. `[]` (Optional) Fee sigma proof record account or context state account. + /// 8. `[]` (Optional) Fee ciphertext validity proof record account or context state account. + /// 9. `[]` (Optional) Range proof record account or context state account. + /// 10. `[]` The multisig source account owner. + /// 11.. `[signer]` Required M signer accounts for the SPL Token Multisig /// /// Data expected by this instruction: - /// `TransferWithSplitProofsInstructionData` - TransferWithSplitProofs, + /// `TransferWithFeeProofsInstructionData` + TransferWithFee, } /// Data expected by `ConfidentialTransferInstruction::InitializeMint` @@ -550,10 +545,18 @@ pub struct TransferInstructionData { /// The new source decryptable balance if the transfer succeeds #[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))] pub new_source_decryptable_available_balance: DecryptableBalance, - /// Relative location of the `ProofInstruction::VerifyTransfer` instruction + /// Relative location of the `ProofInstruction::VerifyCiphertextCommitmentEqualityProof` instruction /// to the `Transfer` instruction in the transaction. If the offset is /// `0`, then use a context state account for the proof. - pub proof_instruction_offset: i8, + pub equality_proof_instruction_offset: i8, + /// Relative location of the `ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidityProof` instruction + /// to the `Transfer` instruction in the transaction. If the offset is + /// `0`, then use a context state account for the proof. + pub ciphertext_validity_proof_instruction_offset: i8, + /// Relative location of the `ProofInstruction::BatchedRangeProofU128Data` instruction + /// to the `Transfer` instruction in the transaction. If the offset is + /// `0`, then use a context state account for the proof. + pub range_proof_instruction_offset: i8, } /// Data expected by `ConfidentialTransferInstruction::ApplyPendingBalance` @@ -571,89 +574,34 @@ pub struct ApplyPendingBalanceData { pub new_decryptable_available_balance: DecryptableBalance, } -/// Data expected by `ConfidentialTransferInstruction::TransferWithSplitProofs` +/// Data expected by `ConfidentialTransferInstruction::TransferWithFee` #[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))] #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] #[repr(C)] -pub struct TransferWithSplitProofsInstructionData { +pub struct TransferWithFeeInstructionData { /// The new source decryptable balance if the transfer succeeds #[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))] pub new_source_decryptable_available_balance: DecryptableBalance, - /// If true, execute no op when an associated context state account is not - /// initialized. Otherwise, fail on an uninitialized context state - /// account. - pub no_op_on_uninitialized_split_context_state: PodBool, - /// Close associated context states after a complete execution of the - /// transfer instruction. - pub close_split_context_state_on_execution: PodBool, - /// The ElGamal decryption handle pertaining to the low and high bits of the - /// transfer amount. This field is used when the transfer proofs are - /// split and verified as smaller components. - /// - /// NOTE: This field is to be removed in the next Solana upgrade. - pub source_decrypt_handles: SourceDecryptHandles, -} - -/// Type for split transfer (without fee) instruction proof context state -/// account addresses intended to be used as parameters to functions. -#[derive(Clone, Copy)] -pub struct TransferSplitContextStateAccounts<'a> { - /// The context state account address for an equality proof needed for a - /// transfer. - pub equality_proof: &'a Pubkey, - /// The context state account address for a ciphertext validity proof needed - /// for a transfer. - pub ciphertext_validity_proof: &'a Pubkey, - /// The context state account address for a range proof needed for a - /// transfer. - pub range_proof: &'a Pubkey, - /// The context state accounts authority - pub authority: &'a Pubkey, - /// No op if an associated split proof context state account is not - /// initialized. - pub no_op_on_uninitialized_split_context_state: bool, - /// Accounts needed if `close_split_context_state_on_execution` flag is - /// enabled. - pub close_split_context_state_accounts: Option>, -} - -/// Type for split transfer (with fee) instruction proof context state account -/// addresses intended to be used as parameters to functions. -#[derive(Clone, Copy)] -pub struct TransferWithFeeSplitContextStateAccounts<'a> { - /// The context state account address for an equality proof needed for a - /// transfer with fee. - pub equality_proof: &'a Pubkey, - /// The context state account address for a transfer amount ciphertext - /// validity proof needed for a transfer with fee. - pub transfer_amount_ciphertext_validity_proof: &'a Pubkey, - /// The context state account address for a fee sigma proof needed for a - /// transfer with fee. - pub fee_sigma_proof: &'a Pubkey, - /// The context state account address for a fee ciphertext validity proof - /// needed for a transfer with fee. - pub fee_ciphertext_validity_proof: &'a Pubkey, - /// The context state account address for a range proof needed for a - /// transfer with fee. - pub range_proof: &'a Pubkey, - /// The context state accounts authority - pub authority: &'a Pubkey, - /// No op if an associated split proof context state account is not - /// initialized. - pub no_op_on_uninitialized_split_context_state: bool, - /// Accounts needed if `close_split_context_state_on_execution` flag is - /// enabled. - pub close_split_context_state_accounts: Option>, -} - -/// Accounts needed if `close_split_context_state_on_execution` flag is enabled -/// on a transfer. -#[derive(Clone, Copy)] -pub struct CloseSplitContextStateAccounts<'a> { - /// The lamport destination account. - pub lamport_destination: &'a Pubkey, - /// The ZK Token proof program. - pub zk_token_proof_program: &'a Pubkey, + /// Relative location of the `ProofInstruction::VerifyCiphertextCommitmentEqualityProof` instruction + /// to the `TransferWithFee` instruction in the transaction. If the offset is + /// `0`, then use a context state account for the proof. + pub equality_proof_instruction_offset: i8, + /// Relative location of the `ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidityProof` instruction + /// to the `TransferWithFee` instruction in the transaction. If the offset is + /// `0`, then use a context state account for the proof. + pub transfer_amount_ciphertext_validity_proof_instruction_offset: i8, + /// Relative location of the `ProofInstruction::VerifyFeeSigmaProof` instruction + /// to the `TransferWithFee` instruction in the transaction. If the offset is + /// `0`, then use a context state account for the proof. + pub fee_sigma_proof_instruction_offset: i8, + /// Relative location of the `ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidityProof` instruction + /// to the `TransferWithFee` instruction in the transaction. If the offset is + /// `0`, then use a context state account for the proof. + pub fee_ciphertext_validity_proof_instruction_offset: i8, + /// Relative location of the `ProofInstruction::BatchedRangeProofU256Data` instruction + /// to the `TransferWithFee` instruction in the transaction. If the offset is + /// `0`, then use a context state account for the proof. + pub range_proof_instruction_offset: i8, } /// Create a `InitializeMint` instruction @@ -1066,7 +1014,7 @@ pub fn withdraw( Ok(instructions) } -/// Create a inner `Transfer` instruction +/// Create an inner `Transfer` instruction /// /// This instruction is suitable for use with a cross-program `invoke` #[allow(clippy::too_many_arguments)] @@ -1078,7 +1026,11 @@ pub fn inner_transfer( new_source_decryptable_available_balance: DecryptableBalance, authority: &Pubkey, multisig_signers: &[&Pubkey], - proof_data_location: ProofLocation, + equality_proof_data_location: ProofLocation, + ciphertext_validity_proof_data_location: ProofLocation< + BatchedGroupedCiphertext3HandlesValidityProofData, + >, + range_proof_data_location: ProofLocation, ) -> Result { check_program_account(token_program_id)?; let mut accounts = vec![ @@ -1087,9 +1039,43 @@ pub fn inner_transfer( AccountMeta::new(*destination_token_account, false), ]; - let proof_instruction_offset = match proof_data_location { + // if at least one of the proof locations is an instruction offset, sysvar account is needed + if equality_proof_data_location.is_instruction_offset() + || ciphertext_validity_proof_data_location.is_instruction_offset() + || range_proof_data_location.is_instruction_offset() + { + accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); + } + + let equality_proof_instruction_offset = match equality_proof_data_location { + ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { + if let ProofData::RecordAccount(record_address, _) = proof_data { + accounts.push(AccountMeta::new_readonly(*record_address, false)); + } + proof_instruction_offset.into() + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + 0 + } + }; + + let ciphertext_validity_proof_instruction_offset = match ciphertext_validity_proof_data_location + { + ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { + if let ProofData::RecordAccount(record_address, _) = proof_data { + accounts.push(AccountMeta::new_readonly(*record_address, false)); + } + proof_instruction_offset.into() + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + 0 + } + }; + + let range_proof_instruction_offset = match range_proof_data_location { ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { - accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); if let ProofData::RecordAccount(record_address, _) = proof_data { accounts.push(AccountMeta::new_readonly(*record_address, false)); } @@ -1106,10 +1092,6 @@ pub fn inner_transfer( multisig_signers.is_empty(), )); - for multisig_signer in multisig_signers.iter() { - accounts.push(AccountMeta::new_readonly(**multisig_signer, true)); - } - Ok(encode_instruction( token_program_id, accounts, @@ -1117,157 +1099,97 @@ pub fn inner_transfer( ConfidentialTransferInstruction::Transfer, &TransferInstructionData { new_source_decryptable_available_balance, - proof_instruction_offset, + equality_proof_instruction_offset, + ciphertext_validity_proof_instruction_offset, + range_proof_instruction_offset, }, )) } -/// Create a `Transfer` instruction with regular (no-fee) proof +/// Create a `Transfer` instruction #[allow(clippy::too_many_arguments)] pub fn transfer( token_program_id: &Pubkey, source_token_account: &Pubkey, mint: &Pubkey, destination_token_account: &Pubkey, - new_source_decryptable_available_balance: AeCiphertext, + new_source_decryptable_available_balance: DecryptableBalance, authority: &Pubkey, multisig_signers: &[&Pubkey], - proof_data_location: ProofLocation, + equality_proof_data_location: ProofLocation, + ciphertext_validity_proof_data_location: ProofLocation< + BatchedGroupedCiphertext3HandlesValidityProofData, + >, + range_proof_data_location: ProofLocation, ) -> Result, ProgramError> { let mut instructions = vec![inner_transfer( token_program_id, source_token_account, mint, destination_token_account, - new_source_decryptable_available_balance.into(), + new_source_decryptable_available_balance, authority, multisig_signers, - proof_data_location, + equality_proof_data_location, + ciphertext_validity_proof_data_location, + range_proof_data_location, )?]; if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) = - proof_data_location + equality_proof_data_location { - // This constructor appends the proof instruction right after the `Transfer` - // instruction. This means that the proof instruction offset must be - // always be 1. To use an arbitrary proof instruction offset, use the - // `inner_transfer` constructor. let proof_instruction_offset: i8 = proof_instruction_offset.into(); if proof_instruction_offset != 1 { return Err(TokenError::InvalidProofInstructionOffset.into()); } match proof_data { - ProofData::InstructionData(data) => instructions.push(verify_transfer(None, data)), + ProofData::InstructionData(data) => instructions.push( + ProofInstruction::VerifyCiphertextCommitmentEquality + .encode_verify_proof(None, data), + ), ProofData::RecordAccount(address, offset) => instructions.push( - ProofInstruction::VerifyTransfer + ProofInstruction::VerifyCiphertextCommitmentEquality .encode_verify_proof_from_account(None, address, offset), ), }; - }; - - Ok(instructions) -} - -/// Create a inner `Transfer` instruction with fee -/// -/// This instruction is suitable for use with a cross-program `invoke` -#[allow(clippy::too_many_arguments)] -pub fn inner_transfer_with_fee( - token_program_id: &Pubkey, - source_token_account: &Pubkey, - mint: &Pubkey, - destination_token_account: &Pubkey, - new_source_decryptable_available_balance: DecryptableBalance, - authority: &Pubkey, - multisig_signers: &[&Pubkey], - proof_data_location: ProofLocation, -) -> Result { - check_program_account(token_program_id)?; - let mut accounts = vec![ - AccountMeta::new(*source_token_account, false), - AccountMeta::new_readonly(*mint, false), - AccountMeta::new(*destination_token_account, false), - ]; + } - let proof_instruction_offset = match proof_data_location { - ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { - accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); - if let ProofData::RecordAccount(record_address, _) = proof_data { - accounts.push(AccountMeta::new_readonly(*record_address, false)); - } - proof_instruction_offset.into() - } - ProofLocation::ContextStateAccount(context_state_account) => { - accounts.push(AccountMeta::new_readonly(*context_state_account, false)); - 0 + if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) = + ciphertext_validity_proof_data_location + { + let proof_instruction_offset: i8 = proof_instruction_offset.into(); + if proof_instruction_offset != 2 { + return Err(TokenError::InvalidProofInstructionOffset.into()); } - }; - - accounts.push(AccountMeta::new_readonly( - *authority, - multisig_signers.is_empty(), - )); - - for multisig_signer in multisig_signers.iter() { - accounts.push(AccountMeta::new_readonly(**multisig_signer, true)); + match proof_data { + ProofData::InstructionData(data) => instructions.push( + ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity + .encode_verify_proof(None, data), + ), + ProofData::RecordAccount(address, offset) => instructions.push( + ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity + .encode_verify_proof_from_account(None, address, offset), + ), + }; } - Ok(encode_instruction( - token_program_id, - accounts, - TokenInstruction::ConfidentialTransferExtension, - ConfidentialTransferInstruction::Transfer, - &TransferInstructionData { - new_source_decryptable_available_balance, - proof_instruction_offset, - }, - )) -} - -/// Create a `Transfer` instruction with fee proof -#[allow(clippy::too_many_arguments)] -pub fn transfer_with_fee( - token_program_id: &Pubkey, - source_token_account: &Pubkey, - mint: &Pubkey, - destination_token_account: &Pubkey, - new_source_decryptable_available_balance: AeCiphertext, - authority: &Pubkey, - multisig_signers: &[&Pubkey], - proof_data_location: ProofLocation, -) -> Result, ProgramError> { - let mut instructions = vec![inner_transfer_with_fee( - token_program_id, - source_token_account, - destination_token_account, - mint, - new_source_decryptable_available_balance.into(), - authority, - multisig_signers, - proof_data_location, - )?]; - if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) = - proof_data_location + range_proof_data_location { - // This constructor appends the proof instruction right after the - // `TransferWithFee` instruction. This means that the proof instruction - // offset must be always be 1. To use an arbitrary proof instruction - // offset, use the `inner_transfer_with_fee` constructor. let proof_instruction_offset: i8 = proof_instruction_offset.into(); - if proof_instruction_offset != 1 { + if proof_instruction_offset != 3 { return Err(TokenError::InvalidProofInstructionOffset.into()); } match proof_data { - ProofData::InstructionData(data) => { - instructions.push(verify_transfer_with_fee(None, data)) - } + ProofData::InstructionData(data) => instructions.push( + ProofInstruction::VerifyBatchedRangeProofU128.encode_verify_proof(None, data), + ), ProofData::RecordAccount(address, offset) => instructions.push( - ProofInstruction::VerifyTransferWithFee + ProofInstruction::VerifyBatchedRangeProofU128 .encode_verify_proof_from_account(None, address, offset), ), }; - }; + } Ok(instructions) } @@ -1414,17 +1336,27 @@ pub fn disable_non_confidential_credits( ) } -/// Create a `TransferWithSplitProof` instruction without fee +/// Create an inner `TransferWithFee` instruction +/// +/// This instruction is suitable for use with a cross-program `invoke` #[allow(clippy::too_many_arguments)] -pub fn transfer_with_split_proofs( +pub fn inner_transfer_with_fee( token_program_id: &Pubkey, source_token_account: &Pubkey, mint: &Pubkey, destination_token_account: &Pubkey, new_source_decryptable_available_balance: DecryptableBalance, - source_account_authority: &Pubkey, - context_accounts: TransferSplitContextStateAccounts, - source_decrypt_handles: &SourceDecryptHandles, + authority: &Pubkey, + multisig_signers: &[&Pubkey], + equality_proof_data_location: ProofLocation, + transfer_amount_ciphertext_validity_proof_data_location: ProofLocation< + BatchedGroupedCiphertext3HandlesValidityProofData, + >, + fee_sigma_proof_data_location: ProofLocation, + fee_ciphertext_validity_proof_data_location: ProofLocation< + BatchedGroupedCiphertext2HandlesValidityProofData, + >, + range_proof_data_location: ProofLocation, ) -> Result { check_program_account(token_program_id)?; let mut accounts = vec![ @@ -1433,151 +1365,231 @@ pub fn transfer_with_split_proofs( AccountMeta::new(*destination_token_account, false), ]; - let close_split_context_state_on_execution = - if let Some(close_split_context_state_on_execution_accounts) = - context_accounts.close_split_context_state_accounts - { - // If `close_split_context_state_accounts` is set, then all context state - // accounts must be `writable`. - accounts.push(AccountMeta::new(*context_accounts.equality_proof, false)); - accounts.push(AccountMeta::new( - *context_accounts.ciphertext_validity_proof, - false, - )); - accounts.push(AccountMeta::new(*context_accounts.range_proof, false)); - accounts.push(AccountMeta::new( - *close_split_context_state_on_execution_accounts.lamport_destination, - false, - )); - accounts.push(AccountMeta::new_readonly(*context_accounts.authority, true)); - accounts.push(AccountMeta::new_readonly( - *close_split_context_state_on_execution_accounts.zk_token_proof_program, - false, - )); - accounts.push(AccountMeta::new_readonly(*source_account_authority, true)); - true - } else { - // If `close_split_context_state_accounts` is not set, then context state - // accounts can be read-only. - accounts.push(AccountMeta::new_readonly( - *context_accounts.equality_proof, - false, - )); - accounts.push(AccountMeta::new_readonly( - *context_accounts.ciphertext_validity_proof, - false, - )); - accounts.push(AccountMeta::new_readonly( - *context_accounts.range_proof, - false, - )); - accounts.push(AccountMeta::new_readonly(*source_account_authority, true)); - - false + // if at least one of the proof locations is an instruction offset, sysvar account is needed + if equality_proof_data_location.is_instruction_offset() + || transfer_amount_ciphertext_validity_proof_data_location.is_instruction_offset() + || fee_sigma_proof_data_location.is_instruction_offset() + || fee_ciphertext_validity_proof_data_location.is_instruction_offset() + || range_proof_data_location.is_instruction_offset() + { + accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false)); + } + + let equality_proof_instruction_offset = match equality_proof_data_location { + ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { + if let ProofData::RecordAccount(record_address, _) = proof_data { + accounts.push(AccountMeta::new_readonly(*record_address, false)); + } + proof_instruction_offset.into() + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + 0 + } + }; + + let transfer_amount_ciphertext_validity_proof_instruction_offset = + match transfer_amount_ciphertext_validity_proof_data_location { + ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { + if let ProofData::RecordAccount(record_address, _) = proof_data { + accounts.push(AccountMeta::new_readonly(*record_address, false)); + } + proof_instruction_offset.into() + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + 0 + } + }; + + let fee_sigma_proof_instruction_offset = match fee_sigma_proof_data_location { + ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { + if let ProofData::RecordAccount(record_address, _) = proof_data { + accounts.push(AccountMeta::new_readonly(*record_address, false)); + } + proof_instruction_offset.into() + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + 0 + } + }; + + let fee_ciphertext_validity_proof_instruction_offset = + match fee_ciphertext_validity_proof_data_location { + ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { + if let ProofData::RecordAccount(record_address, _) = proof_data { + accounts.push(AccountMeta::new_readonly(*record_address, false)); + } + proof_instruction_offset.into() + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + 0 + } }; + let range_proof_instruction_offset = match range_proof_data_location { + ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => { + if let ProofData::RecordAccount(record_address, _) = proof_data { + accounts.push(AccountMeta::new_readonly(*record_address, false)); + } + proof_instruction_offset.into() + } + ProofLocation::ContextStateAccount(context_state_account) => { + accounts.push(AccountMeta::new_readonly(*context_state_account, false)); + 0 + } + }; + + accounts.push(AccountMeta::new_readonly( + *authority, + multisig_signers.is_empty(), + )); + Ok(encode_instruction( token_program_id, accounts, TokenInstruction::ConfidentialTransferExtension, - ConfidentialTransferInstruction::TransferWithSplitProofs, - &TransferWithSplitProofsInstructionData { + ConfidentialTransferInstruction::TransferWithFee, + &TransferWithFeeInstructionData { new_source_decryptable_available_balance, - no_op_on_uninitialized_split_context_state: context_accounts - .no_op_on_uninitialized_split_context_state - .into(), - close_split_context_state_on_execution: close_split_context_state_on_execution.into(), - source_decrypt_handles: *source_decrypt_handles, + equality_proof_instruction_offset, + transfer_amount_ciphertext_validity_proof_instruction_offset, + fee_sigma_proof_instruction_offset, + fee_ciphertext_validity_proof_instruction_offset, + range_proof_instruction_offset, }, )) } -/// Create a `TransferWithSplitProof` instruction with fee +/// Create a `TransferWithFee` instruction #[allow(clippy::too_many_arguments)] -pub fn transfer_with_fee_and_split_proofs( +pub fn transfer_with_fee( token_program_id: &Pubkey, source_token_account: &Pubkey, mint: &Pubkey, destination_token_account: &Pubkey, new_source_decryptable_available_balance: DecryptableBalance, - source_account_authority: &Pubkey, - context_accounts: TransferWithFeeSplitContextStateAccounts, - source_decrypt_handles: &SourceDecryptHandles, -) -> Result { - check_program_account(token_program_id)?; - let mut accounts = vec![ - AccountMeta::new(*source_token_account, false), - AccountMeta::new_readonly(*mint, false), - AccountMeta::new(*destination_token_account, false), - ]; + authority: &Pubkey, + multisig_signers: &[&Pubkey], + equality_proof_data_location: ProofLocation, + transfer_amount_ciphertext_validity_proof_data_location: ProofLocation< + BatchedGroupedCiphertext3HandlesValidityProofData, + >, + fee_sigma_proof_data_location: ProofLocation, + fee_ciphertext_validity_proof_data_location: ProofLocation< + BatchedGroupedCiphertext2HandlesValidityProofData, + >, + range_proof_data_location: ProofLocation, +) -> Result, ProgramError> { + let mut instructions = vec![inner_transfer_with_fee( + token_program_id, + source_token_account, + mint, + destination_token_account, + new_source_decryptable_available_balance, + authority, + multisig_signers, + equality_proof_data_location, + transfer_amount_ciphertext_validity_proof_data_location, + fee_sigma_proof_data_location, + fee_ciphertext_validity_proof_data_location, + range_proof_data_location, + )?]; - let close_split_context_state_on_execution = - if let Some(close_split_context_state_on_execution_accounts) = - context_accounts.close_split_context_state_accounts - { - // If `close_split_context_state_accounts` is set, then all context state - // accounts must be `writable`. - accounts.push(AccountMeta::new(*context_accounts.equality_proof, false)); - accounts.push(AccountMeta::new( - *context_accounts.transfer_amount_ciphertext_validity_proof, - false, - )); - accounts.push(AccountMeta::new(*context_accounts.fee_sigma_proof, false)); - accounts.push(AccountMeta::new( - *context_accounts.fee_ciphertext_validity_proof, - false, - )); - accounts.push(AccountMeta::new(*context_accounts.range_proof, false)); - accounts.push(AccountMeta::new( - *close_split_context_state_on_execution_accounts.lamport_destination, - false, - )); - accounts.push(AccountMeta::new_readonly(*context_accounts.authority, true)); - accounts.push(AccountMeta::new_readonly( - *close_split_context_state_on_execution_accounts.zk_token_proof_program, - false, - )); - accounts.push(AccountMeta::new_readonly(*source_account_authority, true)); - true - } else { - // If `close_split_context_state_accounts` is not set, then context state - // accounts can be read-only. - accounts.push(AccountMeta::new_readonly( - *context_accounts.equality_proof, - false, - )); - accounts.push(AccountMeta::new_readonly( - *context_accounts.transfer_amount_ciphertext_validity_proof, - false, - )); - accounts.push(AccountMeta::new_readonly( - *context_accounts.fee_sigma_proof, - false, - )); - accounts.push(AccountMeta::new_readonly( - *context_accounts.fee_ciphertext_validity_proof, - false, - )); - accounts.push(AccountMeta::new_readonly( - *context_accounts.range_proof, - false, - )); - accounts.push(AccountMeta::new_readonly(*source_account_authority, true)); - false + if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) = + equality_proof_data_location + { + let proof_instruction_offset: i8 = proof_instruction_offset.into(); + if proof_instruction_offset != 1 { + return Err(TokenError::InvalidProofInstructionOffset.into()); + } + match proof_data { + ProofData::InstructionData(data) => instructions.push( + ProofInstruction::VerifyCiphertextCommitmentEquality + .encode_verify_proof(None, data), + ), + ProofData::RecordAccount(address, offset) => instructions.push( + ProofInstruction::VerifyCiphertextCommitmentEquality + .encode_verify_proof_from_account(None, address, offset), + ), }; + } - Ok(encode_instruction( - token_program_id, - accounts, - TokenInstruction::ConfidentialTransferExtension, - ConfidentialTransferInstruction::TransferWithSplitProofs, - &TransferWithSplitProofsInstructionData { - new_source_decryptable_available_balance, - no_op_on_uninitialized_split_context_state: context_accounts - .no_op_on_uninitialized_split_context_state - .into(), - close_split_context_state_on_execution: close_split_context_state_on_execution.into(), - source_decrypt_handles: *source_decrypt_handles, - }, - )) + if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) = + transfer_amount_ciphertext_validity_proof_data_location + { + let proof_instruction_offset: i8 = proof_instruction_offset.into(); + if proof_instruction_offset != 2 { + return Err(TokenError::InvalidProofInstructionOffset.into()); + } + match proof_data { + ProofData::InstructionData(data) => instructions.push( + ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity + .encode_verify_proof(None, data), + ), + ProofData::RecordAccount(address, offset) => instructions.push( + ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity + .encode_verify_proof_from_account(None, address, offset), + ), + }; + } + + if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) = + fee_sigma_proof_data_location + { + let proof_instruction_offset: i8 = proof_instruction_offset.into(); + if proof_instruction_offset != 3 { + return Err(TokenError::InvalidProofInstructionOffset.into()); + } + match proof_data { + ProofData::InstructionData(data) => { + instructions.push(ProofInstruction::VerifyFeeSigma.encode_verify_proof(None, data)) + } + ProofData::RecordAccount(address, offset) => instructions.push( + ProofInstruction::VerifyFeeSigma + .encode_verify_proof_from_account(None, address, offset), + ), + }; + } + + if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) = + fee_ciphertext_validity_proof_data_location + { + let proof_instruction_offset: i8 = proof_instruction_offset.into(); + if proof_instruction_offset != 4 { + return Err(TokenError::InvalidProofInstructionOffset.into()); + } + match proof_data { + ProofData::InstructionData(data) => instructions.push( + ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity + .encode_verify_proof(None, data), + ), + ProofData::RecordAccount(address, offset) => instructions.push( + ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity + .encode_verify_proof_from_account(None, address, offset), + ), + }; + } + + if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) = + range_proof_data_location + { + let proof_instruction_offset: i8 = proof_instruction_offset.into(); + if proof_instruction_offset != 5 { + return Err(TokenError::InvalidProofInstructionOffset.into()); + } + match proof_data { + ProofData::InstructionData(data) => instructions.push( + ProofInstruction::VerifyBatchedRangeProofU256.encode_verify_proof(None, data), + ), + ProofData::RecordAccount(address, offset) => instructions.push( + ProofInstruction::VerifyBatchedRangeProofU256 + .encode_verify_proof_from_account(None, address, offset), + ), + }; + } + + Ok(instructions) } diff --git a/token/program-2022/src/extension/confidential_transfer/mod.rs b/token/program-2022/src/extension/confidential_transfer/mod.rs index c2f177d1acf..635a1c56bdc 100644 --- a/token/program-2022/src/extension/confidential_transfer/mod.rs +++ b/token/program-2022/src/extension/confidential_transfer/mod.rs @@ -7,7 +7,6 @@ use { solana_program::entrypoint::ProgramResult, solana_zk_token_sdk::zk_token_elgamal::pod::{AeCiphertext, ElGamalCiphertext, ElGamalPubkey}, spl_pod::{ - bytemuck::pod_from_bytes, optional_keys::{OptionalNonZeroElGamalPubkey, OptionalNonZeroPubkey}, primitives::{PodBool, PodU64}, }, diff --git a/token/program-2022/src/extension/confidential_transfer/processor.rs b/token/program-2022/src/extension/confidential_transfer/processor.rs index e73ed6bfec4..32a0bb0f3a5 100644 --- a/token/program-2022/src/extension/confidential_transfer/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer/processor.rs @@ -430,18 +430,18 @@ fn process_withdraw( Ok(()) } -/// Processes a [Transfer] or [TransferWithSplitProofs] instruction. +/// Processes a [Transfer] or [TransferWithFee] instruction. #[allow(clippy::too_many_arguments)] #[cfg(feature = "zk-ops")] fn process_transfer( program_id: &Pubkey, accounts: &[AccountInfo], new_source_decryptable_available_balance: DecryptableBalance, - proof_instruction_offset: i64, - split_proof_context_state_accounts: bool, - no_op_on_uninitialized_split_context_state: bool, - close_split_context_state_on_execution: bool, - source_decrypt_handles: &SourceDecryptHandles, + equality_proof_instruction_offset: i64, + transfer_amount_ciphertext_validity_proof_instruction_offset: i64, + fee_sigma_proof_instruction_offset: Option, + fee_ciphertext_validity_proof_instruction_offset: Option, + range_proof_instruction_offset: i64, ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); let source_account_info = next_account_info(account_info_iter)?; @@ -466,46 +466,27 @@ fn process_transfer( // transfer fee is required. let authority_info = if mint.get_extension::().is_err() { // Transfer fee is not required. Decode the zero-knowledge proof as - // `TransferData`. + // `TransferContext`. // // The zero-knowledge proof certifies that: // 1. the transfer amount is encrypted in the correct form // 2. the source account has enough balance to send the transfer amount - let maybe_proof_context = verify_transfer_proof( + let proof_context = verify_transfer_proof( account_info_iter, - proof_instruction_offset, - split_proof_context_state_accounts, - no_op_on_uninitialized_split_context_state, - close_split_context_state_on_execution, - source_decrypt_handles, + equality_proof_instruction_offset, + transfer_amount_ciphertext_validity_proof_instruction_offset, + range_proof_instruction_offset, )?; - // If `maybe_proof_context` is `None`, then this means that - // `no_op_on_uninitialized_split_context_state` is true and a required context - // state account is not yet initialized. Even if this is the case, we - // follow through with the rest of the transfer logic to perform all the - // necessary checks for a transfer to be safe. - - // If `close_split_context_state_on_execution` is `true`, then the source - // account authority info is located after the lamport destination, - // context state authority, and zk token proof program account infos. - // Flush out these account infos. - if close_split_context_state_on_execution && maybe_proof_context.is_none() { - let _lamport_destination_account_info = next_account_info(account_info_iter)?; - let _context_state_authority_info = next_account_info(account_info_iter)?; - let _zk_token_proof_program_info = next_account_info(account_info_iter)?; - } let authority_info = next_account_info(account_info_iter)?; // Check that the auditor encryption public key associated wth the confidential // mint is consistent with what was actually used to generate the zkp. - if let Some(ref proof_context) = maybe_proof_context { - if !confidential_transfer_mint - .auditor_elgamal_pubkey - .equals(&proof_context.transfer_pubkeys.auditor) - { - return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); - } + if !confidential_transfer_mint + .auditor_elgamal_pubkey + .equals(&proof_context.transfer_pubkeys.auditor) + { + return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); } process_source_for_transfer( @@ -514,72 +495,50 @@ fn process_transfer( mint_info, authority_info, account_info_iter.as_slice(), - maybe_proof_context.as_ref(), + &proof_context, new_source_decryptable_available_balance, )?; - process_destination_for_transfer( - destination_account_info, - mint_info, - maybe_proof_context.as_ref(), - )?; + process_destination_for_transfer(destination_account_info, mint_info, &proof_context)?; - if maybe_proof_context.is_none() { - msg!( - "Context states not fully initialized: returning with no op; transfer is NOT yet - executed" - ); - } authority_info } else { // Transfer fee is required. let transfer_fee_config = mint.get_extension::()?; let fee_parameters = transfer_fee_config.get_epoch_fee(Clock::get()?.epoch); - // Decode the zero-knowledge proof as `TransferWithFeeData`. + let fee_sigma_proof_insruction_offset = + fee_sigma_proof_instruction_offset.ok_or(ProgramError::InvalidInstructionData)?; + let fee_ciphertext_validity_proof_insruction_offset = + fee_ciphertext_validity_proof_instruction_offset + .ok_or(ProgramError::InvalidInstructionData)?; + + // Decode the zero-knowledge proof as `TransferWithFeeContext`. // // The zero-knowledge proof certifies that: // 1. the transfer amount is encrypted in the correct form // 2. the source account has enough balance to send the transfer amount // 3. the transfer fee is computed correctly and encrypted in the correct form - let maybe_proof_context = verify_transfer_with_fee_proof( + let proof_context = verify_transfer_with_fee_proof( account_info_iter, - proof_instruction_offset, - split_proof_context_state_accounts, - no_op_on_uninitialized_split_context_state, - close_split_context_state_on_execution, - source_decrypt_handles, + equality_proof_instruction_offset, + transfer_amount_ciphertext_validity_proof_instruction_offset, + fee_sigma_proof_insruction_offset, + fee_ciphertext_validity_proof_insruction_offset, + range_proof_instruction_offset, fee_parameters, )?; - // If `maybe_proof_context` is `None`, then this means that - // `no_op_on_uninitialized_split_context_state` is true and a required context - // state account is not yet initialized. Even if this is the case, we - // follow through with the rest of the transfer with fee logic to - // perform all the necessary checks to be safe. - - // If `close_split_context_state_on_execution` is `true`, then the source - // account authority info is located after the lamport destination, - // context state authority, and zk token proof program account infos. - // Flush out these account infos. - if close_split_context_state_on_execution && maybe_proof_context.is_none() { - let _lamport_destination_account_info = next_account_info(account_info_iter)?; - let _context_state_authority_info = next_account_info(account_info_iter)?; - let _zk_token_proof_program_info = next_account_info(account_info_iter)?; - } - let authority_info = next_account_info(account_info_iter)?; // Check that the encryption public keys associated with the mint confidential // transfer and confidential transfer fee extensions are consistent with // the keys that were used to generate the zkp. - if let Some(ref proof_context) = maybe_proof_context { - if !confidential_transfer_mint - .auditor_elgamal_pubkey - .equals(&proof_context.transfer_with_fee_pubkeys.auditor) - { - return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); - } + if !confidential_transfer_mint + .auditor_elgamal_pubkey + .equals(&proof_context.transfer_with_fee_pubkeys.auditor) + { + return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); } let confidential_transfer_fee_config = @@ -587,14 +546,12 @@ fn process_transfer( // Check that the withdraw withheld authority ElGamal public key in the mint is // consistent with what was used to generate the zkp. - if let Some(ref proof_context) = maybe_proof_context { - if proof_context - .transfer_with_fee_pubkeys - .withdraw_withheld_authority - != confidential_transfer_fee_config.withdraw_withheld_authority_elgamal_pubkey - { - return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); - } + if proof_context + .transfer_with_fee_pubkeys + .withdraw_withheld_authority + != confidential_transfer_fee_config.withdraw_withheld_authority_elgamal_pubkey + { + return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); } process_source_for_transfer_with_fee( @@ -603,7 +560,7 @@ fn process_transfer( mint_info, authority_info, account_info_iter.as_slice(), - maybe_proof_context.as_ref(), + &proof_context, new_source_decryptable_available_balance, )?; @@ -611,15 +568,10 @@ fn process_transfer( process_destination_for_transfer_with_fee( destination_account_info, mint_info, - maybe_proof_context.as_ref(), + &proof_context, is_self_transfer, )?; - if maybe_proof_context.is_none() { - msg!( - "Context state not fully initialized: returning with no op; transfer is NOT yet executed" - ); - } authority_info }; @@ -668,7 +620,7 @@ fn process_source_for_transfer( mint_info: &AccountInfo, authority_info: &AccountInfo, signers: &[AccountInfo], - maybe_proof_context: Option<&TransferProofContextInfo>, + proof_context: &TransferProofContextInfo, new_source_decryptable_available_balance: DecryptableBalance, ) -> ProgramResult { check_program_account(source_account_info.owner)?; @@ -702,36 +654,32 @@ fn process_source_for_transfer( token_account.get_extension_mut::()?; confidential_transfer_account.valid_as_source()?; - if let Some(proof_context) = maybe_proof_context { - // Check that the source encryption public key is consistent with what was - // actually used to generate the zkp. - if proof_context.transfer_pubkeys.source != confidential_transfer_account.elgamal_pubkey { - return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); - } - - let source_transfer_amount_lo = - transfer_amount_source_ciphertext(&proof_context.ciphertext_lo); - let source_transfer_amount_hi = - transfer_amount_source_ciphertext(&proof_context.ciphertext_hi); + // Check that the source encryption public key is consistent with what was + // actually used to generate the zkp. + if proof_context.transfer_pubkeys.source != confidential_transfer_account.elgamal_pubkey { + return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); + } - let new_source_available_balance = syscall::subtract_with_lo_hi( - &confidential_transfer_account.available_balance, - &source_transfer_amount_lo, - &source_transfer_amount_hi, - ) - .ok_or(TokenError::CiphertextArithmeticFailed)?; + let source_transfer_amount_lo = transfer_amount_source_ciphertext(&proof_context.ciphertext_lo); + let source_transfer_amount_hi = transfer_amount_source_ciphertext(&proof_context.ciphertext_hi); - // Check that the computed available balance is consistent with what was - // actually used to generate the zkp on the client side. - if new_source_available_balance != proof_context.new_source_ciphertext { - return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); - } + let new_source_available_balance = syscall::subtract_with_lo_hi( + &confidential_transfer_account.available_balance, + &source_transfer_amount_lo, + &source_transfer_amount_hi, + ) + .ok_or(TokenError::CiphertextArithmeticFailed)?; - confidential_transfer_account.available_balance = new_source_available_balance; - confidential_transfer_account.decryptable_available_balance = - new_source_decryptable_available_balance; + // Check that the computed available balance is consistent with what was + // actually used to generate the zkp on the client side. + if new_source_available_balance != proof_context.new_source_ciphertext { + return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); } + confidential_transfer_account.available_balance = new_source_available_balance; + confidential_transfer_account.decryptable_available_balance = + new_source_decryptable_available_balance; + Ok(()) } @@ -739,7 +687,7 @@ fn process_source_for_transfer( fn process_destination_for_transfer( destination_account_info: &AccountInfo, mint_info: &AccountInfo, - maybe_transfer_proof_context_info: Option<&TransferProofContextInfo>, + proof_context: &TransferProofContextInfo, ) -> ProgramResult { check_program_account(destination_account_info.owner)?; let destination_token_account_data = &mut destination_account_info.data.borrow_mut(); @@ -762,32 +710,30 @@ fn process_destination_for_transfer( destination_token_account.get_extension_mut::()?; destination_confidential_transfer_account.valid_as_destination()?; - if let Some(proof_context) = maybe_transfer_proof_context_info { - if proof_context.transfer_pubkeys.destination - != destination_confidential_transfer_account.elgamal_pubkey - { - return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); - } + if proof_context.transfer_pubkeys.destination + != destination_confidential_transfer_account.elgamal_pubkey + { + return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); + } - let destination_ciphertext_lo = - transfer_amount_destination_ciphertext(&proof_context.ciphertext_lo); - let destination_ciphertext_hi = - transfer_amount_destination_ciphertext(&proof_context.ciphertext_hi); + let destination_ciphertext_lo = + transfer_amount_destination_ciphertext(&proof_context.ciphertext_lo); + let destination_ciphertext_hi = + transfer_amount_destination_ciphertext(&proof_context.ciphertext_hi); - destination_confidential_transfer_account.pending_balance_lo = syscall::add( - &destination_confidential_transfer_account.pending_balance_lo, - &destination_ciphertext_lo, - ) - .ok_or(TokenError::CiphertextArithmeticFailed)?; + destination_confidential_transfer_account.pending_balance_lo = syscall::add( + &destination_confidential_transfer_account.pending_balance_lo, + &destination_ciphertext_lo, + ) + .ok_or(TokenError::CiphertextArithmeticFailed)?; - destination_confidential_transfer_account.pending_balance_hi = syscall::add( - &destination_confidential_transfer_account.pending_balance_hi, - &destination_ciphertext_hi, - ) - .ok_or(TokenError::CiphertextArithmeticFailed)?; + destination_confidential_transfer_account.pending_balance_hi = syscall::add( + &destination_confidential_transfer_account.pending_balance_hi, + &destination_ciphertext_hi, + ) + .ok_or(TokenError::CiphertextArithmeticFailed)?; - destination_confidential_transfer_account.increment_pending_balance_credit_counter()?; - } + destination_confidential_transfer_account.increment_pending_balance_credit_counter()?; Ok(()) } @@ -800,7 +746,7 @@ fn process_source_for_transfer_with_fee( mint_info: &AccountInfo, authority_info: &AccountInfo, signers: &[AccountInfo], - maybe_proof_context: Option<&TransferWithFeeProofContextInfo>, + proof_context: &TransferWithFeeProofContextInfo, new_source_decryptable_available_balance: DecryptableBalance, ) -> ProgramResult { check_program_account(source_account_info.owner)?; @@ -834,38 +780,34 @@ fn process_source_for_transfer_with_fee( token_account.get_extension_mut::()?; confidential_transfer_account.valid_as_source()?; - if let Some(proof_context) = maybe_proof_context { - // Check that the source encryption public key is consistent with what was - // actually used to generate the zkp. - if proof_context.transfer_with_fee_pubkeys.source - != confidential_transfer_account.elgamal_pubkey - { - return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); - } - - let source_transfer_amount_lo = - transfer_amount_source_ciphertext(&proof_context.ciphertext_lo); - let source_transfer_amount_hi = - transfer_amount_source_ciphertext(&proof_context.ciphertext_hi); + // Check that the source encryption public key is consistent with what was + // actually used to generate the zkp. + if proof_context.transfer_with_fee_pubkeys.source + != confidential_transfer_account.elgamal_pubkey + { + return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); + } - let new_source_available_balance = syscall::subtract_with_lo_hi( - &confidential_transfer_account.available_balance, - &source_transfer_amount_lo, - &source_transfer_amount_hi, - ) - .ok_or(TokenError::CiphertextArithmeticFailed)?; + let source_transfer_amount_lo = transfer_amount_source_ciphertext(&proof_context.ciphertext_lo); + let source_transfer_amount_hi = transfer_amount_source_ciphertext(&proof_context.ciphertext_hi); - // Check that the computed available balance is consistent with what was - // actually used to generate the zkp on the client side. - if new_source_available_balance != proof_context.new_source_ciphertext { - return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); - } + let new_source_available_balance = syscall::subtract_with_lo_hi( + &confidential_transfer_account.available_balance, + &source_transfer_amount_lo, + &source_transfer_amount_hi, + ) + .ok_or(TokenError::CiphertextArithmeticFailed)?; - confidential_transfer_account.available_balance = new_source_available_balance; - confidential_transfer_account.decryptable_available_balance = - new_source_decryptable_available_balance; + // Check that the computed available balance is consistent with what was + // actually used to generate the zkp on the client side. + if new_source_available_balance != proof_context.new_source_ciphertext { + return Err(TokenError::ConfidentialTransferBalanceMismatch.into()); } + confidential_transfer_account.available_balance = new_source_available_balance; + confidential_transfer_account.decryptable_available_balance = + new_source_decryptable_available_balance; + Ok(()) } @@ -873,7 +815,7 @@ fn process_source_for_transfer_with_fee( fn process_destination_for_transfer_with_fee( destination_account_info: &AccountInfo, mint_info: &AccountInfo, - maybe_proof_context: Option<&TransferWithFeeProofContextInfo>, + proof_context: &TransferWithFeeProofContextInfo, is_self_transfer: bool, ) -> ProgramResult { check_program_account(destination_account_info.owner)?; @@ -897,71 +839,69 @@ fn process_destination_for_transfer_with_fee( destination_token_account.get_extension_mut::()?; destination_confidential_transfer_account.valid_as_destination()?; - if let Some(proof_context) = maybe_proof_context { - if proof_context.transfer_with_fee_pubkeys.destination - != destination_confidential_transfer_account.elgamal_pubkey - { - return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); - } + if proof_context.transfer_with_fee_pubkeys.destination + != destination_confidential_transfer_account.elgamal_pubkey + { + return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into()); + } + + let destination_transfer_amount_lo = + transfer_amount_destination_ciphertext(&proof_context.ciphertext_lo); + let destination_transfer_amount_hi = + transfer_amount_destination_ciphertext(&proof_context.ciphertext_hi); - let destination_transfer_amount_lo = - transfer_amount_destination_ciphertext(&proof_context.ciphertext_lo); - let destination_transfer_amount_hi = - transfer_amount_destination_ciphertext(&proof_context.ciphertext_hi); + destination_confidential_transfer_account.pending_balance_lo = syscall::add( + &destination_confidential_transfer_account.pending_balance_lo, + &destination_transfer_amount_lo, + ) + .ok_or(TokenError::CiphertextArithmeticFailed)?; - destination_confidential_transfer_account.pending_balance_lo = syscall::add( + destination_confidential_transfer_account.pending_balance_hi = syscall::add( + &destination_confidential_transfer_account.pending_balance_hi, + &destination_transfer_amount_hi, + ) + .ok_or(TokenError::CiphertextArithmeticFailed)?; + + destination_confidential_transfer_account.increment_pending_balance_credit_counter()?; + + // process transfer fee + if !is_self_transfer { + // Decode lo and hi fee amounts encrypted under the destination encryption + // public key + let destination_fee_lo = + fee_amount_destination_ciphertext(&proof_context.fee_ciphertext_lo); + let destination_fee_hi = + fee_amount_destination_ciphertext(&proof_context.fee_ciphertext_hi); + + // Subtract the fee amount from the destination pending balance + destination_confidential_transfer_account.pending_balance_lo = syscall::subtract( &destination_confidential_transfer_account.pending_balance_lo, - &destination_transfer_amount_lo, + &destination_fee_lo, ) .ok_or(TokenError::CiphertextArithmeticFailed)?; - - destination_confidential_transfer_account.pending_balance_hi = syscall::add( + destination_confidential_transfer_account.pending_balance_hi = syscall::subtract( &destination_confidential_transfer_account.pending_balance_hi, - &destination_transfer_amount_hi, + &destination_fee_hi, ) .ok_or(TokenError::CiphertextArithmeticFailed)?; - destination_confidential_transfer_account.increment_pending_balance_credit_counter()?; - - // process transfer fee - if !is_self_transfer { - // Decode lo and hi fee amounts encrypted under the destination encryption - // public key - let destination_fee_lo = - fee_amount_destination_ciphertext(&proof_context.fee_ciphertext_lo); - let destination_fee_hi = - fee_amount_destination_ciphertext(&proof_context.fee_ciphertext_hi); - - // Subtract the fee amount from the destination pending balance - destination_confidential_transfer_account.pending_balance_lo = syscall::subtract( - &destination_confidential_transfer_account.pending_balance_lo, - &destination_fee_lo, - ) - .ok_or(TokenError::CiphertextArithmeticFailed)?; - destination_confidential_transfer_account.pending_balance_hi = syscall::subtract( - &destination_confidential_transfer_account.pending_balance_hi, - &destination_fee_hi, - ) - .ok_or(TokenError::CiphertextArithmeticFailed)?; - - // Decode lo and hi fee amounts encrypted under the withdraw authority - // encryption public key - let withdraw_withheld_authority_fee_lo = - fee_amount_withdraw_withheld_authority_ciphertext(&proof_context.fee_ciphertext_lo); - let withdraw_withheld_authority_fee_hi = - fee_amount_withdraw_withheld_authority_ciphertext(&proof_context.fee_ciphertext_hi); - - let destination_confidential_transfer_fee_amount = - destination_token_account.get_extension_mut::()?; - - // Add the fee amount to the destination withheld fee - destination_confidential_transfer_fee_amount.withheld_amount = syscall::add_with_lo_hi( - &destination_confidential_transfer_fee_amount.withheld_amount, - &withdraw_withheld_authority_fee_lo, - &withdraw_withheld_authority_fee_hi, - ) - .ok_or(TokenError::CiphertextArithmeticFailed)?; - } + // Decode lo and hi fee amounts encrypted under the withdraw authority + // encryption public key + let withdraw_withheld_authority_fee_lo = + fee_amount_withdraw_withheld_authority_ciphertext(&proof_context.fee_ciphertext_lo); + let withdraw_withheld_authority_fee_hi = + fee_amount_withdraw_withheld_authority_ciphertext(&proof_context.fee_ciphertext_hi); + + let destination_confidential_transfer_fee_amount = + destination_token_account.get_extension_mut::()?; + + // Add the fee amount to the destination withheld fee + destination_confidential_transfer_fee_amount.withheld_amount = syscall::add_with_lo_hi( + &destination_confidential_transfer_fee_amount.withheld_amount, + &withdraw_withheld_authority_fee_lo, + &withdraw_withheld_authority_fee_hi, + ) + .ok_or(TokenError::CiphertextArithmeticFailed)?; } Ok(()) @@ -1164,11 +1104,11 @@ pub(crate) fn process_instruction( program_id, accounts, data.new_source_decryptable_available_balance, - data.proof_instruction_offset as i64, - false, - false, - false, - &SourceDecryptHandles::zeroed(), + data.equality_proof_instruction_offset as i64, + data.ciphertext_validity_proof_instruction_offset as i64, + None, + None, + data.range_proof_instruction_offset as i64, ) } #[cfg(not(feature = "zk-ops"))] @@ -1205,21 +1145,20 @@ pub(crate) fn process_instruction( msg!("ConfidentialTransferInstruction::EnableNonConfidentialCredits"); process_allow_non_confidential_credits(program_id, accounts, true) } - ConfidentialTransferInstruction::TransferWithSplitProofs => { - msg!("ConfidentialTransferInstruction::TransferWithSplitProofs"); + ConfidentialTransferInstruction::TransferWithFee => { + msg!("ConfidentialTransferInstruction::TransferWithFee"); #[cfg(feature = "zk-ops")] { - let data = - decode_instruction_data::(input)?; + let data = decode_instruction_data::(input)?; process_transfer( program_id, accounts, data.new_source_decryptable_available_balance, - 0, - true, - data.no_op_on_uninitialized_split_context_state.into(), - data.close_split_context_state_on_execution.into(), - &data.source_decrypt_handles, + data.equality_proof_instruction_offset as i64, + data.transfer_amount_ciphertext_validity_proof_instruction_offset as i64, + Some(data.fee_sigma_proof_instruction_offset as i64), + Some(data.fee_ciphertext_validity_proof_instruction_offset as i64), + data.range_proof_instruction_offset as i64, ) } #[cfg(not(feature = "zk-ops"))] diff --git a/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs b/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs index 9d376bb0883..46be6e6701d 100644 --- a/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs +++ b/token/program-2022/src/extension/confidential_transfer/split_proof_generation.rs @@ -6,19 +6,18 @@ use crate::{ extension::confidential_transfer::{ - ciphertext_extraction::{transfer_amount_source_ciphertext, SourceDecryptHandles}, - processor::verify_and_split_deposit_amount, - *, + ciphertext_extraction::transfer_amount_source_ciphertext, + processor::verify_and_split_deposit_amount, *, }, solana_zk_token_sdk::{ encryption::{ auth_encryption::{AeCiphertext, AeKey}, - elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, + elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, grouped_elgamal::GroupedElGamal, pedersen::Pedersen, }, instruction::{ - transfer::TransferAmountCiphertext, BatchedGroupedCiphertext2HandlesValidityProofData, + transfer::TransferAmountCiphertext, BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, CiphertextCommitmentEqualityProofData, }, zk_token_elgamal::ops::subtract_with_lo_hi, @@ -37,9 +36,8 @@ pub fn transfer_split_proof_data( ) -> Result< ( CiphertextCommitmentEqualityProofData, - BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, - SourceDecryptHandles, ), TokenError, > { @@ -108,32 +106,30 @@ pub fn transfer_split_proof_data( ) .map_err(|_| TokenError::ProofGeneration)?; - // create source decrypt handle - let source_decrypt_handle_lo = - DecryptHandle::new(source_elgamal_keypair.pubkey(), &transfer_amount_opening_lo); - let source_decrypt_handle_hi = - DecryptHandle::new(source_elgamal_keypair.pubkey(), &transfer_amount_opening_hi); - - let source_decrypt_handles = SourceDecryptHandles { - lo: source_decrypt_handle_lo.into(), - hi: source_decrypt_handle_hi.into(), - }; - // encrypt the transfer amount under the destination and auditor ElGamal public // key let transfer_amount_destination_auditor_ciphertext_lo = GroupedElGamal::encrypt_with( - [destination_elgamal_pubkey, auditor_elgamal_pubkey], + [ + source_elgamal_keypair.pubkey(), + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + ], transfer_amount_lo, &transfer_amount_opening_lo, ); let transfer_amount_destination_auditor_ciphertext_hi = GroupedElGamal::encrypt_with( - [destination_elgamal_pubkey, auditor_elgamal_pubkey], + [ + source_elgamal_keypair.pubkey(), + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + ], transfer_amount_hi, &transfer_amount_opening_hi, ); // generate ciphertext validity data - let ciphertext_validity_proof_data = BatchedGroupedCiphertext2HandlesValidityProofData::new( + let ciphertext_validity_proof_data = BatchedGroupedCiphertext3HandlesValidityProofData::new( + source_elgamal_keypair.pubkey(), destination_elgamal_pubkey, auditor_elgamal_pubkey, &transfer_amount_destination_auditor_ciphertext_lo, @@ -185,6 +181,5 @@ pub fn transfer_split_proof_data( equality_proof_data, ciphertext_validity_proof_data, range_proof_data, - source_decrypt_handles, )) } diff --git a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs index 652adf63904..9160e9f1c7b 100644 --- a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs +++ b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs @@ -15,8 +15,8 @@ use { /// Verify zero-knowledge proof needed for a [ConfigureAccount] instruction and /// return the corresponding proof context. -pub fn verify_configure_account_proof<'a>( - account_info_iter: &mut Iter<'a, AccountInfo<'a>>, +pub fn verify_configure_account_proof( + account_info_iter: &mut Iter, proof_instruction_offset: i64, ) -> Result { verify_and_extract_context::( @@ -28,8 +28,8 @@ pub fn verify_configure_account_proof<'a>( /// Verify zero-knowledge proof needed for a [EmptyAccount] instruction and /// return the corresponding proof context. -pub fn verify_empty_account_proof<'a>( - account_info_iter: &mut Iter<'a, AccountInfo<'a>>, +pub fn verify_empty_account_proof( + account_info_iter: &mut Iter, proof_instruction_offset: i64, ) -> Result { verify_and_extract_context::( @@ -41,8 +41,8 @@ pub fn verify_empty_account_proof<'a>( /// Verify zero-knowledge proof needed for a [Withdraw] instruction and return /// the corresponding proof context. -pub fn verify_withdraw_proof<'a>( - account_info_iter: &mut Iter<'a, AccountInfo<'a>>, +pub fn verify_withdraw_proof( + account_info_iter: &mut Iter, proof_instruction_offset: i64, ) -> Result { verify_and_extract_context::( @@ -55,12 +55,11 @@ pub fn verify_withdraw_proof<'a>( /// Verify zero-knowledge proof needed for a [Transfer] instruction without fee /// and return the corresponding proof context. #[cfg(feature = "zk-ops")] -pub fn verify_transfer_proof<'a>( - account_info_iter: &mut Iter<'a, AccountInfo<'a>>, +pub fn verify_transfer_proof( + account_info_iter: &mut Iter, equality_proof_instruction_offset: i64, ciphertext_validity_proof_instruction_offset: i64, range_proof_instruction_offset: i64, - source_decrypt_handles: &SourceDecryptHandles, ) -> Result { let sysvar_account_info = if equality_proof_instruction_offset != 0 || ciphertext_validity_proof_instruction_offset != 0 @@ -81,18 +80,18 @@ pub fn verify_transfer_proof<'a>( )?; let ciphertext_validity_proof_context = verify_and_extract_context::< - BatchedGroupedCiphertext2HandlesValidityProofData, - BatchedGroupedCiphertext2HandlesValidityProofContext, + BatchedGroupedCiphertext3HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofContext, >( account_info_iter, - equality_proof_instruction_offset, + ciphertext_validity_proof_instruction_offset, sysvar_account_info, )?; let range_proof_context = verify_and_extract_context::( account_info_iter, - equality_proof_instruction_offset, + range_proof_instruction_offset, sysvar_account_info, )?; @@ -103,7 +102,6 @@ pub fn verify_transfer_proof<'a>( &equality_proof_context, &ciphertext_validity_proof_context, &range_proof_context, - source_decrypt_handles, )?; Ok(transfer_proof_context) @@ -112,14 +110,14 @@ pub fn verify_transfer_proof<'a>( /// Verify zero-knowledge proof needed for a [Transfer] instruction with fee and /// return the corresponding proof context. #[cfg(feature = "zk-ops")] -pub fn verify_transfer_with_fee_proof<'a>( - account_info_iter: &mut Iter<'a, AccountInfo<'a>>, +#[allow(clippy::too_many_arguments)] +pub fn verify_transfer_with_fee_proof( + account_info_iter: &mut Iter, equality_proof_instruction_offset: i64, transfer_amount_ciphertext_validity_proof_instruction_offset: i64, fee_sigma_proof_instruction_offset: i64, fee_ciphertext_validity_proof_instruction_offset: i64, range_proof_instruction_offset: i64, - source_decrypt_handles: &SourceDecryptHandles, fee_parameters: &TransferFee, ) -> Result { let sysvar_account_info = if equality_proof_instruction_offset != 0 @@ -143,18 +141,18 @@ pub fn verify_transfer_with_fee_proof<'a>( )?; let transfer_amount_ciphertext_validity_proof_context = verify_and_extract_context::< - BatchedGroupedCiphertext2HandlesValidityProofData, - BatchedGroupedCiphertext2HandlesValidityProofContext, + BatchedGroupedCiphertext3HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofContext, >( account_info_iter, - equality_proof_instruction_offset, + transfer_amount_ciphertext_validity_proof_instruction_offset, sysvar_account_info, )?; let fee_sigma_proof_context = verify_and_extract_context::( account_info_iter, - equality_proof_instruction_offset, + fee_sigma_proof_instruction_offset, sysvar_account_info, )?; @@ -163,14 +161,14 @@ pub fn verify_transfer_with_fee_proof<'a>( BatchedGroupedCiphertext2HandlesValidityProofContext, >( account_info_iter, - equality_proof_instruction_offset, + fee_ciphertext_validity_proof_instruction_offset, sysvar_account_info, )?; let range_proof_context = verify_and_extract_context::( account_info_iter, - equality_proof_instruction_offset, + range_proof_instruction_offset, sysvar_account_info, )?; @@ -186,7 +184,6 @@ pub fn verify_transfer_with_fee_proof<'a>( &fee_sigma_proof_context, &fee_ciphertext_validity_proof_context, &range_proof_context, - source_decrypt_handles, fee_parameters, )?; diff --git a/token/program-2022/src/extension/confidential_transfer_fee/processor.rs b/token/program-2022/src/extension/confidential_transfer_fee/processor.rs index 63db39cab8e..5014fb08104 100644 --- a/token/program-2022/src/extension/confidential_transfer_fee/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer_fee/processor.rs @@ -171,13 +171,13 @@ fn process_withdraw_withheld_tokens_from_mint( /// instruction or a `[WithdrawWithheldTokensFromAccounts]` and return the /// corresponding proof context. fn verify_ciphertext_ciphertext_equality_proof( - account_info_iter: &mut Iter<'_, AccountInfo<'_>>, + account_info_iter: &mut Iter, proof_instruction_offset: i64, ) -> Result { verify_and_extract_context::< CiphertextCiphertextEqualityProofData, CiphertextCiphertextEqualityProofContext, - >(account_info_iter, proof_instruction_offset) + >(account_info_iter, proof_instruction_offset, None) } /// Processes a [WithdrawWithheldTokensFromAccounts] instruction. diff --git a/token/program-2022/src/proof.rs b/token/program-2022/src/proof.rs index d3ffb66678f..e1a4ccf1b1c 100644 --- a/token/program-2022/src/proof.rs +++ b/token/program-2022/src/proof.rs @@ -77,6 +77,16 @@ pub enum ProofLocation<'a, T> { ContextStateAccount(&'a Pubkey), } +impl<'a, T> ProofLocation<'a, T> { + /// Returns true if the proof location is an instruction offset + pub fn is_instruction_offset(&self) -> bool { + match self { + Self::InstructionOffset(_, _) => true, + Self::ContextStateAccount(_) => false, + } + } +} + /// A proof data type to distinguish between proof data included as part of /// zk-token proof instruction data and proof data stored in a record account. #[derive(Clone, Copy)] @@ -90,9 +100,9 @@ pub enum ProofData<'a, T> { /// Verify zero-knowledge proof and return the corresponding proof context. pub fn verify_and_extract_context<'a, T: Pod + ZkProofData, U: Pod>( - account_info_iter: &mut Iter<'a, AccountInfo<'a>>, + account_info_iter: &mut Iter<'_, AccountInfo<'a>>, proof_instruction_offset: i64, - sysvar_account_info: Option<&'a AccountInfo<'a>>, + sysvar_account_info: Option<&'_ AccountInfo<'a>>, ) -> Result { if proof_instruction_offset == 0 { // interpret `account_info` as a context state account @@ -114,7 +124,7 @@ pub fn verify_and_extract_context<'a, T: Pod + ZkProofData, U: Pod>( next_account_info(account_info_iter)? }; let zkp_instruction = - get_instruction_relative(proof_instruction_offset, &sysvar_account_info)?; + get_instruction_relative(proof_instruction_offset, sysvar_account_info)?; let expected_proof_type = zk_proof_type_to_instruction(T::PROOF_TYPE)?; Ok(decode_proof_instruction_context::( account_info_iter, From ca8b1ad87bb4ee4bb2168003bf8b01399c544a0b Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 9 Aug 2024 11:51:53 +0900 Subject: [PATCH 4/9] use `verify_and_extract_context` function directly --- .../confidential_transfer/processor.rs | 20 ++++++++-- .../confidential_transfer/verify_proof.rs | 39 ------------------- .../confidential_transfer_fee/processor.rs | 26 ++++--------- 3 files changed, 24 insertions(+), 61 deletions(-) diff --git a/token/program-2022/src/extension/confidential_transfer/processor.rs b/token/program-2022/src/extension/confidential_transfer/processor.rs index 32a0bb0f3a5..57ccc9f28f9 100644 --- a/token/program-2022/src/extension/confidential_transfer/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer/processor.rs @@ -22,6 +22,7 @@ use { instruction::{decode_instruction_data, decode_instruction_type}, pod::{PodAccount, PodMint}, processor::Processor, + proof::verify_and_extract_context, }, solana_program::{ account_info::{next_account_info, AccountInfo}, @@ -101,8 +102,11 @@ fn process_configure_account( let mint_info = next_account_info(account_info_iter)?; // zero-knowledge proof certifies that the supplied ElGamal public key is valid - let proof_context = - verify_configure_account_proof(account_info_iter, proof_instruction_offset)?; + let proof_context = verify_and_extract_context::( + account_info_iter, + proof_instruction_offset, + None, + )?; let authority_info = next_account_info(account_info_iter)?; let authority_info_data_len = authority_info.data_len(); @@ -206,7 +210,11 @@ fn process_empty_account( // zero-knowledge proof certifies that the available balance ciphertext holds // the balance of 0. - let proof_context = verify_empty_account_proof(account_info_iter, proof_instruction_offset)?; + let proof_context = verify_and_extract_context::( + account_info_iter, + proof_instruction_offset, + None, + )?; let authority_info = next_account_info(account_info_iter)?; let authority_info_data_len = authority_info.data_len(); @@ -354,7 +362,11 @@ fn process_withdraw( // zero-knowledge proof certifies that the account has enough available balance // to withdraw the amount. - let proof_context = verify_withdraw_proof(account_info_iter, proof_instruction_offset)?; + let proof_context = verify_and_extract_context::( + account_info_iter, + proof_instruction_offset, + None, + )?; let authority_info = next_account_info(account_info_iter)?; let authority_info_data_len = authority_info.data_len(); diff --git a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs index 9160e9f1c7b..e48baba6053 100644 --- a/token/program-2022/src/extension/confidential_transfer/verify_proof.rs +++ b/token/program-2022/src/extension/confidential_transfer/verify_proof.rs @@ -13,45 +13,6 @@ use { std::slice::Iter, }; -/// Verify zero-knowledge proof needed for a [ConfigureAccount] instruction and -/// return the corresponding proof context. -pub fn verify_configure_account_proof( - account_info_iter: &mut Iter, - proof_instruction_offset: i64, -) -> Result { - verify_and_extract_context::( - account_info_iter, - proof_instruction_offset, - None, - ) -} - -/// Verify zero-knowledge proof needed for a [EmptyAccount] instruction and -/// return the corresponding proof context. -pub fn verify_empty_account_proof( - account_info_iter: &mut Iter, - proof_instruction_offset: i64, -) -> Result { - verify_and_extract_context::( - account_info_iter, - proof_instruction_offset, - None, - ) -} - -/// Verify zero-knowledge proof needed for a [Withdraw] instruction and return -/// the corresponding proof context. -pub fn verify_withdraw_proof( - account_info_iter: &mut Iter, - proof_instruction_offset: i64, -) -> Result { - verify_and_extract_context::( - account_info_iter, - proof_instruction_offset, - None, - ) -} - /// Verify zero-knowledge proof needed for a [Transfer] instruction without fee /// and return the corresponding proof context. #[cfg(feature = "zk-ops")] diff --git a/token/program-2022/src/extension/confidential_transfer_fee/processor.rs b/token/program-2022/src/extension/confidential_transfer_fee/processor.rs index 5014fb08104..b835da72f2c 100644 --- a/token/program-2022/src/extension/confidential_transfer_fee/processor.rs +++ b/token/program-2022/src/extension/confidential_transfer_fee/processor.rs @@ -39,7 +39,6 @@ use { pubkey::Pubkey, }, spl_pod::optional_keys::OptionalNonZeroPubkey, - std::slice::Iter, }; /// Processes an [InitializeConfidentialTransferFeeConfig] instruction. @@ -77,8 +76,10 @@ fn process_withdraw_withheld_tokens_from_mint( // zero-knowledge proof certifies that the exact withheld amount is credited to // the destination account. - let proof_context = - verify_ciphertext_ciphertext_equality_proof(account_info_iter, proof_instruction_offset)?; + let proof_context = verify_and_extract_context::< + CiphertextCiphertextEqualityProofData, + CiphertextCiphertextEqualityProofContext, + >(account_info_iter, proof_instruction_offset, None)?; let authority_info = next_account_info(account_info_iter)?; let authority_info_data_len = authority_info.data_len(); @@ -167,19 +168,6 @@ fn process_withdraw_withheld_tokens_from_mint( Ok(()) } -/// Verify zero-knowledge proof needed for a [WithdrawWithheldTokensFromMint] -/// instruction or a `[WithdrawWithheldTokensFromAccounts]` and return the -/// corresponding proof context. -fn verify_ciphertext_ciphertext_equality_proof( - account_info_iter: &mut Iter, - proof_instruction_offset: i64, -) -> Result { - verify_and_extract_context::< - CiphertextCiphertextEqualityProofData, - CiphertextCiphertextEqualityProofContext, - >(account_info_iter, proof_instruction_offset, None) -} - /// Processes a [WithdrawWithheldTokensFromAccounts] instruction. #[cfg(feature = "zk-ops")] fn process_withdraw_withheld_tokens_from_accounts( @@ -195,8 +183,10 @@ fn process_withdraw_withheld_tokens_from_accounts( // zero-knowledge proof certifies that the exact aggregate withheld amount is // credited to the destination account. - let proof_context = - verify_ciphertext_ciphertext_equality_proof(account_info_iter, proof_instruction_offset)?; + let proof_context = verify_and_extract_context::< + CiphertextCiphertextEqualityProofData, + CiphertextCiphertextEqualityProofContext, + >(account_info_iter, proof_instruction_offset, None)?; let authority_info = next_account_info(account_info_iter)?; let authority_info_data_len = authority_info.data_len(); From 46a180bfec048705bd16f4e331b540a85f515922 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 9 Aug 2024 12:16:57 +0900 Subject: [PATCH 5/9] update token client --- token/client/src/proof_generation.rs | 39 +- token/client/src/token.rs | 1209 +++++++------------------- 2 files changed, 350 insertions(+), 898 deletions(-) diff --git a/token/client/src/proof_generation.rs b/token/client/src/proof_generation.rs index cd5f540c7ad..588d718d8e0 100644 --- a/token/client/src/proof_generation.rs +++ b/token/client/src/proof_generation.rs @@ -10,13 +10,13 @@ use { spl_token_2022::{ error::TokenError, extension::confidential_transfer::{ - ciphertext_extraction::{transfer_amount_source_ciphertext, SourceDecryptHandles}, + ciphertext_extraction::transfer_amount_source_ciphertext, processor::verify_and_split_deposit_amount, }, solana_zk_token_sdk::{ encryption::{ auth_encryption::{AeCiphertext, AeKey}, - elgamal::{DecryptHandle, ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, + elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, grouped_elgamal::GroupedElGamal, pedersen::{Pedersen, PedersenCommitment, PedersenOpening}, }, @@ -25,7 +25,8 @@ use { try_combine_lo_hi_commitments, try_combine_lo_hi_openings, FeeEncryption, FeeParameters, TransferAmountCiphertext, }, - BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU256Data, + BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU256Data, CiphertextCommitmentEqualityProofData, FeeSigmaProofData, }, zk_token_elgamal::ops::subtract_with_lo_hi, @@ -53,11 +54,10 @@ pub fn transfer_with_fee_split_proof_data( ) -> Result< ( CiphertextCommitmentEqualityProofData, - BatchedGroupedCiphertext2HandlesValidityProofData, + BatchedGroupedCiphertext3HandlesValidityProofData, FeeSigmaProofData, BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU256Data, - SourceDecryptHandles, ), TokenError, > { @@ -126,33 +126,31 @@ pub fn transfer_with_fee_split_proof_data( ) .map_err(|_| TokenError::ProofGeneration)?; - // create source decrypt handle - let source_decrypt_handle_lo = - DecryptHandle::new(source_elgamal_keypair.pubkey(), &transfer_amount_opening_lo); - let source_decrypt_handle_hi = - DecryptHandle::new(source_elgamal_keypair.pubkey(), &transfer_amount_opening_hi); - - let source_decrypt_handles = SourceDecryptHandles { - lo: source_decrypt_handle_lo.into(), - hi: source_decrypt_handle_hi.into(), - }; - - // encrypt the transfer amount under the destination and auditor ElGamal public + // encrypt the transfer amount under the source, destination and auditor ElGamal public // key let transfer_amount_destination_auditor_ciphertext_lo = GroupedElGamal::encrypt_with( - [destination_elgamal_pubkey, auditor_elgamal_pubkey], + [ + source_elgamal_keypair.pubkey(), + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + ], transfer_amount_lo, &transfer_amount_opening_lo, ); let transfer_amount_destination_auditor_ciphertext_hi = GroupedElGamal::encrypt_with( - [destination_elgamal_pubkey, auditor_elgamal_pubkey], + [ + source_elgamal_keypair.pubkey(), + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + ], transfer_amount_hi, &transfer_amount_opening_hi, ); // generate transfer amount ciphertext validity data let transfer_amount_ciphertext_validity_proof_data = - BatchedGroupedCiphertext2HandlesValidityProofData::new( + BatchedGroupedCiphertext3HandlesValidityProofData::new( + source_elgamal_keypair.pubkey(), destination_elgamal_pubkey, auditor_elgamal_pubkey, &transfer_amount_destination_auditor_ciphertext_lo, @@ -332,7 +330,6 @@ pub fn transfer_with_fee_split_proof_data( fee_sigma_proof_data, fee_ciphertext_validity_proof_data, range_proof_data, - source_decrypt_handles, )) } diff --git a/token/client/src/token.rs b/token/client/src/token.rs index c65b2b40812..b8cef8347f4 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -7,7 +7,7 @@ use { proof_generation::{transfer_with_fee_split_proof_data, ProofAccount}, }, bytemuck::bytes_of, - futures::{future::join_all, try_join}, + futures::future::join_all, futures_util::TryFutureExt, solana_program_test::tokio::time, solana_sdk::{ @@ -40,10 +40,6 @@ use { ApplyPendingBalanceAccountInfo, EmptyAccountAccountInfo, TransferAccountInfo, WithdrawAccountInfo, }, - ciphertext_extraction::SourceDecryptHandles, - instruction::{ - TransferSplitContextStateAccounts, TransferWithFeeSplitContextStateAccounts, - }, ConfidentialTransferAccount, DecryptableBalance, }, confidential_transfer_fee::{ @@ -1921,9 +1917,12 @@ where // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`, // which is guaranteed by the previous check - let proof_location = - Self::confidential_transfer_create_proof_location(proof_data.as_ref(), proof_account) - .unwrap(); + let proof_location = Self::confidential_transfer_create_proof_location( + proof_data.as_ref(), + proof_account, + 1, + ) + .unwrap(); let decryptable_balance = aes_key.encrypt(0); @@ -2001,9 +2000,12 @@ where // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`, // which is guaranteed by the previous check - let proof_location = - Self::confidential_transfer_create_proof_location(proof_data.as_ref(), proof_account) - .unwrap(); + let proof_location = Self::confidential_transfer_create_proof_location( + proof_data.as_ref(), + proof_account, + 1, + ) + .unwrap(); self.process_ixs( &confidential_transfer::instruction::empty_account( @@ -2085,9 +2087,12 @@ where // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`, // which is guaranteed by the previous check - let proof_location = - Self::confidential_transfer_create_proof_location(proof_data.as_ref(), proof_account) - .unwrap(); + let proof_location = Self::confidential_transfer_create_proof_location( + proof_data.as_ref(), + proof_account, + 1, + ) + .unwrap(); let new_decryptable_available_balance = account_info .new_decryptable_available_balance(withdraw_amount, aes_key) @@ -2174,7 +2179,9 @@ where source_account: &Pubkey, destination_account: &Pubkey, source_authority: &Pubkey, - proof_account: Option<&ProofAccount>, + equality_proof_account: Option<&ProofAccount>, + ciphertext_validity_proof_account: Option<&ProofAccount>, + range_proof_account: Option<&ProofAccount>, transfer_amount: u64, account_info: Option, source_elgamal_keypair: &ElGamalKeypair, @@ -2195,27 +2202,45 @@ where TransferAccountInfo::new(confidential_transfer_account) }; - let proof_data = if proof_account.is_some() { - None - } else { - Some( - account_info - .generate_transfer_proof_data( - transfer_amount, - source_elgamal_keypair, - source_aes_key, - destination_elgamal_pubkey, - auditor_elgamal_pubkey, - ) - .map_err(|_| TokenError::ProofGeneration)?, + let (equality_proof_data, ciphertext_validity_proof_data, range_proof_data) = account_info + .generate_split_transfer_proof_data( + transfer_amount, + source_elgamal_keypair, + source_aes_key, + destination_elgamal_pubkey, + auditor_elgamal_pubkey, ) - }; + .map_err(|_| TokenError::ProofGeneration)?; + + // if proof accounts are none, then proof data must be included as instruction data + let equality_proof_data = equality_proof_account + .is_none() + .then_some(equality_proof_data); + let ciphertext_validity_proof_data = ciphertext_validity_proof_account + .is_none() + .then_some(ciphertext_validity_proof_data); + let range_proof_data = range_proof_account.is_none().then_some(range_proof_data); // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`, // which is guaranteed by the previous check - let proof_location = - Self::confidential_transfer_create_proof_location(proof_data.as_ref(), proof_account) - .unwrap(); + let equality_proof_location = Self::confidential_transfer_create_proof_location( + equality_proof_data.as_ref(), + equality_proof_account, + 1, + ) + .unwrap(); + let ciphertext_validity_proof_location = Self::confidential_transfer_create_proof_location( + ciphertext_validity_proof_data.as_ref(), + ciphertext_validity_proof_account, + 2, + ) + .unwrap(); + let range_proof_location = Self::confidential_transfer_create_proof_location( + range_proof_data.as_ref(), + range_proof_account, + 3, + ) + .unwrap(); let new_decryptable_available_balance = account_info .new_decryptable_available_balance(transfer_amount, source_aes_key) @@ -2226,10 +2251,12 @@ where source_account, self.get_address(), destination_account, - new_decryptable_available_balance, + new_decryptable_available_balance.into(), source_authority, &multisig_signers, - proof_location, + equality_proof_location, + ciphertext_validity_proof_location, + range_proof_location, )?; offchain::add_extra_account_metas( &mut instructions[0], @@ -2249,162 +2276,6 @@ where self.process_ixs(&instructions, signing_keypairs).await } - /// Transfer tokens confidentially using split proofs. - /// - /// This function assumes that proof context states have already been - /// created. - #[allow(clippy::too_many_arguments)] - pub async fn confidential_transfer_transfer_with_split_proofs( - &self, - source_account: &Pubkey, - destination_account: &Pubkey, - source_authority: &Pubkey, - context_state_accounts: TransferSplitContextStateAccounts<'_>, - transfer_amount: u64, - account_info: Option, - source_aes_key: &AeKey, - source_decrypt_handles: &SourceDecryptHandles, - signing_keypairs: &S, - ) -> TokenResult { - let account_info = if let Some(account_info) = account_info { - account_info - } else { - let account = self.get_account_info(source_account).await?; - let confidential_transfer_account = - account.get_extension::()?; - TransferAccountInfo::new(confidential_transfer_account) - }; - - let new_decryptable_available_balance = account_info - .new_decryptable_available_balance(transfer_amount, source_aes_key) - .map_err(|_| TokenError::AccountDecryption)?; - - let mut instruction = confidential_transfer::instruction::transfer_with_split_proofs( - &self.program_id, - source_account, - self.get_address(), - destination_account, - new_decryptable_available_balance.into(), - source_authority, - context_state_accounts, - source_decrypt_handles, - )?; - offchain::add_extra_account_metas( - &mut instruction, - source_account, - self.get_address(), - destination_account, - source_authority, - u64::MAX, - |address| { - self.client - .get_account(address) - .map_ok(|opt| opt.map(|acc| acc.data)) - }, - ) - .await - .map_err(|_| TokenError::AccountNotFound)?; - self.process_ixs(&[instruction], signing_keypairs).await - } - - /// Transfer tokens confidentially using split proofs in parallel - /// - /// This function internally generates the ZK Token proof instructions to - /// create the necessary proof context states. - #[allow(clippy::too_many_arguments)] - pub async fn confidential_transfer_transfer_with_split_proofs_in_parallel( - &self, - source_account: &Pubkey, - destination_account: &Pubkey, - source_authority: &Pubkey, - context_state_accounts: TransferSplitContextStateAccounts<'_>, - transfer_amount: u64, - account_info: Option, - source_elgamal_keypair: &ElGamalKeypair, - source_aes_key: &AeKey, - destination_elgamal_pubkey: &ElGamalPubkey, - auditor_elgamal_pubkey: Option<&ElGamalPubkey>, - equality_and_ciphertext_validity_proof_signers: &S, - range_proof_signers: &S, - ) -> TokenResult<(T::Output, T::Output)> { - let account_info = if let Some(account_info) = account_info { - account_info - } else { - let account = self.get_account_info(source_account).await?; - let confidential_transfer_account = - account.get_extension::()?; - TransferAccountInfo::new(confidential_transfer_account) - }; - - let ( - equality_proof_data, - ciphertext_validity_proof_data, - range_proof_data, - source_decrypt_handles, - ) = account_info - .generate_split_transfer_proof_data( - transfer_amount, - source_elgamal_keypair, - source_aes_key, - destination_elgamal_pubkey, - auditor_elgamal_pubkey, - ) - .map_err(|_| TokenError::ProofGeneration)?; - - let new_decryptable_available_balance = account_info - .new_decryptable_available_balance(transfer_amount, source_aes_key) - .map_err(|_| TokenError::AccountDecryption)?; - - let mut transfer_instruction = - confidential_transfer::instruction::transfer_with_split_proofs( - &self.program_id, - source_account, - self.get_address(), - destination_account, - new_decryptable_available_balance.into(), - source_authority, - context_state_accounts, - &source_decrypt_handles, - )?; - offchain::add_extra_account_metas( - &mut transfer_instruction, - source_account, - self.get_address(), - destination_account, - source_authority, - u64::MAX, - |address| { - self.client - .get_account(address) - .map_ok(|opt| opt.map(|acc| acc.data)) - }, - ) - .await - .map_err(|_| TokenError::AccountNotFound)?; - - let transfer_with_equality_and_ciphertext_validity = self - .create_equality_and_ciphertext_validity_proof_context_states_for_transfer_parallel( - context_state_accounts, - &equality_proof_data, - &ciphertext_validity_proof_data, - &transfer_instruction, - equality_and_ciphertext_validity_proof_signers, - ); - - let transfer_with_range_proof = self - .create_range_proof_context_state_for_transfer_parallel( - context_state_accounts, - &range_proof_data, - &transfer_instruction, - range_proof_signers, - ); - - try_join!( - transfer_with_equality_and_ciphertext_validity, - transfer_with_range_proof - ) - } - /// Create a record account containing zero-knowledge proof needed for a /// confidential transfer. pub async fn confidential_transfer_create_record_account< @@ -2509,11 +2380,12 @@ where .await } - /// Create equality proof context state account for a confidential transfer. + /// Create an equality proof context state account for a confidential transfer. #[allow(clippy::too_many_arguments)] pub async fn create_equality_proof_context_state_for_transfer( &self, - context_state_accounts: TransferSplitContextStateAccounts<'_>, + context_state_account: &Pubkey, + context_state_authority: &Pubkey, equality_proof_data: &CiphertextCommitmentEqualityProofData, equality_proof_signer: &S, ) -> TokenResult { @@ -2527,15 +2399,15 @@ where .map_err(TokenError::Client)?; let equality_proof_context_state_info = ContextStateInfo { - context_state_account: context_state_accounts.equality_proof, - context_state_authority: context_state_accounts.authority, + context_state_account, + context_state_authority, }; self.process_ixs( &[ system_instruction::create_account( &self.payer.pubkey(), - context_state_accounts.equality_proof, + context_state_account, rent, space as u64, &zk_token_proof_program::id(), @@ -2550,18 +2422,19 @@ where .await } - /// Create ciphertext validity proof context state account for a + /// Create a ciphertext validity proof context state account for a /// confidential transfer. pub async fn create_ciphertext_validity_proof_context_state_for_transfer( &self, - context_state_accounts: TransferSplitContextStateAccounts<'_>, - ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData, + context_state_account: &Pubkey, + context_state_authority: &Pubkey, + ciphertext_validity_proof_data: &BatchedGroupedCiphertext3HandlesValidityProofData, ciphertext_validity_proof_signer: &S, ) -> TokenResult { // create ciphertext validity proof context state - let instruction_type = ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity; + let instruction_type = ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity; let space = - size_of::>(); + size_of::>(); let rent = self .client .get_minimum_balance_for_rent_exemption(space) @@ -2569,15 +2442,15 @@ where .map_err(TokenError::Client)?; let ciphertext_validity_proof_context_state_info = ContextStateInfo { - context_state_account: context_state_accounts.ciphertext_validity_proof, - context_state_authority: context_state_accounts.authority, + context_state_account, + context_state_authority, }; self.process_ixs( &[ system_instruction::create_account( &self.payer.pubkey(), - context_state_accounts.ciphertext_validity_proof, + context_state_account, rent, space as u64, &zk_token_proof_program::id(), @@ -2592,132 +2465,11 @@ where .await } - /// Create equality and ciphertext validity proof context state accounts for - /// a confidential transfer. - #[allow(clippy::too_many_arguments)] - pub async fn create_equality_and_ciphertext_validity_proof_context_states_for_transfer< - S: Signers, - >( - &self, - context_state_accounts: TransferSplitContextStateAccounts<'_>, - equality_proof_data: &CiphertextCommitmentEqualityProofData, - ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData, - signing_keypairs: &S, - ) -> TokenResult { - self.create_equality_and_ciphertext_validity_proof_context_state_with_optional_transfer( - context_state_accounts, - equality_proof_data, - ciphertext_validity_proof_data, - None, - signing_keypairs, - ) - .await - } - - /// Create equality and ciphertext validity proof context state accounts - /// with a confidential transfer instruction. - #[allow(clippy::too_many_arguments)] - pub async fn create_equality_and_ciphertext_validity_proof_context_states_for_transfer_parallel< - S: Signers, - >( - &self, - context_state_accounts: TransferSplitContextStateAccounts<'_>, - equality_proof_data: &CiphertextCommitmentEqualityProofData, - ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData, - transfer_instruction: &Instruction, - signing_keypairs: &S, - ) -> TokenResult { - self.create_equality_and_ciphertext_validity_proof_context_state_with_optional_transfer( - context_state_accounts, - equality_proof_data, - ciphertext_validity_proof_data, - Some(transfer_instruction), - signing_keypairs, - ) - .await - } - - /// Create equality and ciphertext validity proof context states for a - /// confidential transfer. - /// - /// If an optional transfer instruction is provided, then the transfer - /// instruction is attached to the same transaction. - #[allow(clippy::too_many_arguments)] - async fn create_equality_and_ciphertext_validity_proof_context_state_with_optional_transfer< - S: Signers, - >( - &self, - context_state_accounts: TransferSplitContextStateAccounts<'_>, - equality_proof_data: &CiphertextCommitmentEqualityProofData, - ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData, - transfer_instruction: Option<&Instruction>, - signing_keypairs: &S, - ) -> TokenResult { - let mut instructions = vec![]; - - // create equality proof context state - let instruction_type = ProofInstruction::VerifyCiphertextCommitmentEquality; - let space = size_of::>(); - let rent = self - .client - .get_minimum_balance_for_rent_exemption(space) - .await - .map_err(TokenError::Client)?; - instructions.push(system_instruction::create_account( - &self.payer.pubkey(), - context_state_accounts.equality_proof, - rent, - space as u64, - &zk_token_proof_program::id(), - )); - - let equality_proof_context_state_info = ContextStateInfo { - context_state_account: context_state_accounts.equality_proof, - context_state_authority: context_state_accounts.authority, - }; - instructions.push( - instruction_type - .encode_verify_proof(Some(equality_proof_context_state_info), equality_proof_data), - ); - - // create ciphertext validity proof context state - let instruction_type = ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity; - let space = - size_of::>(); - let rent = self - .client - .get_minimum_balance_for_rent_exemption(space) - .await - .map_err(TokenError::Client)?; - instructions.push(system_instruction::create_account( - &self.payer.pubkey(), - context_state_accounts.ciphertext_validity_proof, - rent, - space as u64, - &zk_token_proof_program::id(), - )); - - let ciphertext_validity_proof_context_state_info = ContextStateInfo { - context_state_account: context_state_accounts.ciphertext_validity_proof, - context_state_authority: context_state_accounts.authority, - }; - instructions.push(instruction_type.encode_verify_proof( - Some(ciphertext_validity_proof_context_state_info), - ciphertext_validity_proof_data, - )); - - // add transfer instruction - if let Some(transfer_instruction) = transfer_instruction { - instructions.push(transfer_instruction.clone()); - } - - self.process_ixs(&instructions, signing_keypairs).await - } - /// Create a range proof context state account for a confidential transfer. pub async fn create_range_proof_context_state_for_transfer( &self, - context_state_accounts: TransferSplitContextStateAccounts<'_>, + context_state_account: &Pubkey, + context_state_authority: &Pubkey, range_proof_data: &BatchedRangeProofU128Data, range_proof_keypair: &S, ) -> TokenResult { @@ -2729,13 +2481,13 @@ where .await .map_err(TokenError::Client)?; let range_proof_context_state_info = ContextStateInfo { - context_state_account: context_state_accounts.range_proof, - context_state_authority: context_state_accounts.authority, + context_state_account, + context_state_authority, }; self.process_ixs( &[system_instruction::create_account( &self.payer.pubkey(), - context_state_accounts.range_proof, + context_state_account, rent, space as u64, &zk_token_proof_program::id(), @@ -2766,104 +2518,50 @@ where .map_err(TokenError::Client) } - /// Create a range proof context state account with a confidential transfer - /// instruction. - pub async fn create_range_proof_context_state_for_transfer_parallel( + /// Close a ZK Token proof program context state + pub async fn confidential_transfer_close_context_state( &self, - context_state_accounts: TransferSplitContextStateAccounts<'_>, - range_proof_data: &BatchedRangeProofU128Data, - transfer_instruction: &Instruction, + context_state_account: &Pubkey, + lamport_destination_account: &Pubkey, + context_state_authority: &Pubkey, signing_keypairs: &S, ) -> TokenResult { - self.create_range_proof_context_state_with_optional_transfer( - context_state_accounts, - range_proof_data, - Some(transfer_instruction), - signing_keypairs, + let context_state_info = ContextStateInfo { + context_state_account, + context_state_authority, + }; + + self.process_ixs( + &[zk_token_proof_instruction::close_context_state( + context_state_info, + lamport_destination_account, + )], + &[signing_keypairs], ) .await } - /// Create a range proof context state account and an optional confidential - /// transfer instruction. - async fn create_range_proof_context_state_with_optional_transfer( + /// Transfer tokens confidentially with fee + #[allow(clippy::too_many_arguments)] + pub async fn confidential_transfer_transfer_with_fee( &self, - context_state_accounts: TransferSplitContextStateAccounts<'_>, - range_proof_data: &BatchedRangeProofU128Data, - transfer_instruction: Option<&Instruction>, - signing_keypairs: &S, - ) -> TokenResult { - let instruction_type = ProofInstruction::VerifyBatchedRangeProofU128; - let space = size_of::>(); - let rent = self - .client - .get_minimum_balance_for_rent_exemption(space) - .await - .map_err(TokenError::Client)?; - let range_proof_context_state_info = ContextStateInfo { - context_state_account: context_state_accounts.range_proof, - context_state_authority: context_state_accounts.authority, - }; - - let mut instructions = vec![ - system_instruction::create_account( - &self.payer.pubkey(), - context_state_accounts.range_proof, - rent, - space as u64, - &zk_token_proof_program::id(), - ), - instruction_type - .encode_verify_proof(Some(range_proof_context_state_info), range_proof_data), - ]; - - if let Some(transfer_instruction) = transfer_instruction { - instructions.push(transfer_instruction.clone()); - } - - self.process_ixs(&instructions, signing_keypairs).await - } - - /// Close a ZK Token proof program context state - pub async fn confidential_transfer_close_context_state( - &self, - context_state_account: &Pubkey, - lamport_destination_account: &Pubkey, - context_state_authority: &Pubkey, - signing_keypairs: &S, - ) -> TokenResult { - let context_state_info = ContextStateInfo { - context_state_account, - context_state_authority, - }; - - self.process_ixs( - &[zk_token_proof_instruction::close_context_state( - context_state_info, - lamport_destination_account, - )], - signing_keypairs, - ) - .await - } - - /// Transfer tokens confidentially with fee - #[allow(clippy::too_many_arguments)] - pub async fn confidential_transfer_transfer_with_fee( - &self, - source_account: &Pubkey, - destination_account: &Pubkey, - source_authority: &Pubkey, - proof_account: Option<&ProofAccount>, - transfer_amount: u64, - account_info: Option, - source_elgamal_keypair: &ElGamalKeypair, - source_aes_key: &AeKey, - destination_elgamal_pubkey: &ElGamalPubkey, - auditor_elgamal_pubkey: Option<&ElGamalPubkey>, - withdraw_withheld_authority_elgamal_pubkey: &ElGamalPubkey, - fee_rate_basis_points: u16, - maximum_fee: u64, + source_account: &Pubkey, + destination_account: &Pubkey, + source_authority: &Pubkey, + equality_proof_account: Option<&ProofAccount>, + transfer_amount_ciphertext_validity_proof_account: Option<&ProofAccount>, + fee_sigma_proof_account: Option<&ProofAccount>, + fee_ciphertext_validity_proof_account: Option<&ProofAccount>, + range_proof_account: Option<&ProofAccount>, + transfer_amount: u64, + account_info: Option, + source_elgamal_keypair: &ElGamalKeypair, + source_aes_key: &AeKey, + destination_elgamal_pubkey: &ElGamalPubkey, + auditor_elgamal_pubkey: Option<&ElGamalPubkey>, + withdraw_withheld_authority_elgamal_pubkey: &ElGamalPubkey, + fee_rate_basis_points: u16, + maximum_fee: u64, signing_keypairs: &S, ) -> TokenResult { let signing_pubkeys = signing_keypairs.pubkeys(); @@ -2878,157 +2576,6 @@ where TransferAccountInfo::new(confidential_transfer_account) }; - let proof_data = if proof_account.is_some() { - None - } else { - Some( - account_info - .generate_transfer_with_fee_proof_data( - transfer_amount, - source_elgamal_keypair, - source_aes_key, - destination_elgamal_pubkey, - auditor_elgamal_pubkey, - withdraw_withheld_authority_elgamal_pubkey, - fee_rate_basis_points, - maximum_fee, - ) - .map_err(|_| TokenError::ProofGeneration)?, - ) - }; - - // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`, - // which is guaranteed by the previous check - let proof_location = - Self::confidential_transfer_create_proof_location(proof_data.as_ref(), proof_account) - .unwrap(); - - let new_decryptable_available_balance = account_info - .new_decryptable_available_balance(transfer_amount, source_aes_key) - .map_err(|_| TokenError::AccountDecryption)?; - - let mut instructions = confidential_transfer::instruction::transfer_with_fee( - &self.program_id, - source_account, - destination_account, - self.get_address(), - new_decryptable_available_balance, - source_authority, - &multisig_signers, - proof_location, - )?; - offchain::add_extra_account_metas( - &mut instructions[0], - source_account, - self.get_address(), - destination_account, - source_authority, - u64::MAX, - |address| { - self.client - .get_account(address) - .map_ok(|opt| opt.map(|acc| acc.data)) - }, - ) - .await - .map_err(|_| TokenError::AccountNotFound)?; - self.process_ixs(&instructions, signing_keypairs).await - } - - /// Transfer tokens confidentially with fee using split proofs. - /// - /// This function assumes that proof context states have already been - /// created. - #[allow(clippy::too_many_arguments)] - pub async fn confidential_transfer_transfer_with_fee_and_split_proofs( - &self, - source_account: &Pubkey, - destination_account: &Pubkey, - source_authority: &Pubkey, - context_state_accounts: TransferWithFeeSplitContextStateAccounts<'_>, - transfer_amount: u64, - account_info: Option, - source_aes_key: &AeKey, - source_decrypt_handles: &SourceDecryptHandles, - signing_keypairs: &S, - ) -> TokenResult { - let account_info = if let Some(account_info) = account_info { - account_info - } else { - let account = self.get_account_info(source_account).await?; - let confidential_transfer_account = - account.get_extension::()?; - TransferAccountInfo::new(confidential_transfer_account) - }; - - let new_decryptable_available_balance = account_info - .new_decryptable_available_balance(transfer_amount, source_aes_key) - .map_err(|_| TokenError::AccountDecryption)?; - - let mut instruction = - confidential_transfer::instruction::transfer_with_fee_and_split_proofs( - &self.program_id, - source_account, - self.get_address(), - destination_account, - new_decryptable_available_balance.into(), - source_authority, - context_state_accounts, - source_decrypt_handles, - )?; - offchain::add_extra_account_metas( - &mut instruction, - source_account, - self.get_address(), - destination_account, - source_authority, - u64::MAX, - |address| { - self.client - .get_account(address) - .map_ok(|opt| opt.map(|acc| acc.data)) - }, - ) - .await - .map_err(|_| TokenError::AccountNotFound)?; - self.process_ixs(&[instruction], signing_keypairs).await - } - - /// Transfer tokens confidentially using split proofs in parallel - /// - /// This function internally generates the ZK Token proof instructions to - /// create the necessary proof context states. - #[allow(clippy::too_many_arguments)] - pub async fn confidential_transfer_transfer_with_fee_and_split_proofs_in_parallel< - S: Signers, - >( - &self, - source_account: &Pubkey, - destination_account: &Pubkey, - source_authority: &Pubkey, - context_state_accounts: TransferWithFeeSplitContextStateAccounts<'_>, - transfer_amount: u64, - account_info: Option, - source_elgamal_keypair: &ElGamalKeypair, - source_aes_key: &AeKey, - destination_elgamal_pubkey: &ElGamalPubkey, - auditor_elgamal_pubkey: Option<&ElGamalPubkey>, - withdraw_withheld_authority_elgamal_pubkey: &ElGamalPubkey, - fee_rate_basis_points: u16, - maximum_fee: u64, - equality_and_ciphertext_validity_proof_signers: &S, - fee_sigma_proof_signers: &S, - range_proof_signers: &S, - ) -> TokenResult<(T::Output, T::Output, T::Output)> { - let account_info = if let Some(account_info) = account_info { - account_info - } else { - let account = self.get_account_info(source_account).await?; - let confidential_transfer_account = - account.get_extension::()?; - TransferAccountInfo::new(confidential_transfer_account) - }; - let current_source_available_balance = account_info .available_balance .try_into() @@ -3049,7 +2596,6 @@ where fee_sigma_proof_data, fee_ciphertext_validity_proof_data, range_proof_data, - source_decrypt_handles, ) = transfer_with_fee_split_proof_data( ¤t_source_available_balance, ¤t_decryptable_available_balance, @@ -3063,23 +2609,76 @@ where ) .map_err(|_| TokenError::ProofGeneration)?; + let equality_proof_data = equality_proof_account + .is_none() + .then_some(equality_proof_data); + let transfer_amount_ciphertext_validity_proof_data = + transfer_amount_ciphertext_validity_proof_account + .is_none() + .then_some(transfer_amount_ciphertext_validity_proof_data); + let fee_sigma_proof_data = fee_sigma_proof_account + .is_none() + .then_some(fee_sigma_proof_data); + let fee_ciphertext_validity_proof_data = fee_ciphertext_validity_proof_account + .is_none() + .then_some(fee_ciphertext_validity_proof_data); + let range_proof_data = range_proof_account.is_none().then_some(range_proof_data); + + // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`, + // which is guaranteed by the previous check + let equality_proof_location = Self::confidential_transfer_create_proof_location( + equality_proof_data.as_ref(), + equality_proof_account, + 1, + ) + .unwrap(); + let transfer_amount_ciphertext_validity_proof_location = + Self::confidential_transfer_create_proof_location( + transfer_amount_ciphertext_validity_proof_data.as_ref(), + transfer_amount_ciphertext_validity_proof_account, + 2, + ) + .unwrap(); + let fee_sigma_proof_location = Self::confidential_transfer_create_proof_location( + fee_sigma_proof_data.as_ref(), + fee_sigma_proof_account, + 3, + ) + .unwrap(); + let fee_ciphertext_validity_proof_location = + Self::confidential_transfer_create_proof_location( + fee_ciphertext_validity_proof_data.as_ref(), + fee_ciphertext_validity_proof_account, + 4, + ) + .unwrap(); + let range_proof_location = Self::confidential_transfer_create_proof_location( + range_proof_data.as_ref(), + range_proof_account, + 5, + ) + .unwrap(); + let new_decryptable_available_balance = account_info .new_decryptable_available_balance(transfer_amount, source_aes_key) .map_err(|_| TokenError::AccountDecryption)?; - let mut transfer_instruction = - confidential_transfer::instruction::transfer_with_fee_and_split_proofs( - &self.program_id, - source_account, - self.get_address(), - destination_account, - new_decryptable_available_balance.into(), - source_authority, - context_state_accounts, - &source_decrypt_handles, - )?; + let mut instructions = confidential_transfer::instruction::transfer_with_fee( + &self.program_id, + source_account, + self.get_address(), + destination_account, + new_decryptable_available_balance.into(), + source_authority, + &multisig_signers, + equality_proof_location, + transfer_amount_ciphertext_validity_proof_location, + fee_sigma_proof_location, + fee_ciphertext_validity_proof_location, + range_proof_location, + )?; offchain::add_extra_account_metas( - &mut transfer_instruction, + &mut instructions[0], source_account, self.get_address(), destination_account, @@ -3093,104 +2692,18 @@ where ) .await .map_err(|_| TokenError::AccountNotFound)?; - - let transfer_with_equality_and_ciphertext_valdity = self - .create_equality_and_ciphertext_validity_proof_context_states_for_transfer_with_fee_parallel( - context_state_accounts, - &equality_proof_data, - &transfer_amount_ciphertext_validity_proof_data, - &transfer_instruction, - equality_and_ciphertext_validity_proof_signers - ); - - let transfer_with_fee_sigma_and_ciphertext_validity = self - .create_fee_sigma_and_ciphertext_validity_proof_context_states_for_transfer_with_fee_parallel( - context_state_accounts, - &fee_sigma_proof_data, - &fee_ciphertext_validity_proof_data, - &transfer_instruction, - fee_sigma_proof_signers, - ); - - let transfer_with_range_proof = self - .create_range_proof_context_state_for_transfer_with_fee_parallel( - context_state_accounts, - &range_proof_data, - &transfer_instruction, - range_proof_signers, - ); - - try_join!( - transfer_with_equality_and_ciphertext_valdity, - transfer_with_fee_sigma_and_ciphertext_validity, - transfer_with_range_proof, - ) - } - - /// Create equality and transfer amount ciphertext validity proof context - /// state accounts for a confidential transfer with fee. - #[allow(clippy::too_many_arguments)] - pub async fn create_equality_and_ciphertext_validity_proof_context_states_for_transfer_with_fee< - S: Signers, - >( - &self, - context_state_accounts: TransferWithFeeSplitContextStateAccounts<'_>, - equality_proof_data: &CiphertextCommitmentEqualityProofData, - ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData, - signing_keypairs: &S, - ) -> TokenResult { - self.create_equality_and_ciphertext_validity_proof_context_states_with_optional_transfer_with_fee( - context_state_accounts, - equality_proof_data, - ciphertext_validity_proof_data, - None, - signing_keypairs, - ) - .await - } - - /// Create equality and transfer amount ciphertext validity proof context - /// state accounts with a confidential transfer instruction. - #[allow(clippy::too_many_arguments)] - pub async fn create_equality_and_ciphertext_validity_proof_context_states_for_transfer_with_fee_parallel< - S: Signers, - >( - &self, - context_state_accounts: TransferWithFeeSplitContextStateAccounts<'_>, - equality_proof_data: &CiphertextCommitmentEqualityProofData, - ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData, - transfer_instruction: &Instruction, - signing_keypairs: &S, - ) -> TokenResult { - self.create_equality_and_ciphertext_validity_proof_context_states_with_optional_transfer_with_fee( - context_state_accounts, - equality_proof_data, - ciphertext_validity_proof_data, - Some(transfer_instruction), - signing_keypairs, - ) - .await + self.process_ixs(&instructions, signing_keypairs).await } - /// Create equality and ciphertext validity proof context states for a - /// confidential transfer with fee. - /// - /// If an optional transfer instruction is provided, then the transfer - /// instruction is attached to the same transaction. + /// Create equality proof context state accounts for a confidential transfer with fee. #[allow(clippy::too_many_arguments)] - async fn create_equality_and_ciphertext_validity_proof_context_states_with_optional_transfer_with_fee< - S: Signers, - >( + pub async fn create_equality_proof_context_state_for_transfer_with_fee( &self, - context_state_accounts: TransferWithFeeSplitContextStateAccounts<'_>, + context_state_account: &Pubkey, + context_state_authority: &Pubkey, equality_proof_data: &CiphertextCommitmentEqualityProofData, - transfer_amount_ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData, - transfer_instruction: Option<&Instruction>, - signing_keypairs: &S, + signing_keypair: &S, ) -> TokenResult { - let mut instructions = vec![]; - - // create equality proof context state let instruction_type = ProofInstruction::VerifyCiphertextCommitmentEquality; let space = size_of::>(); let rent = self @@ -3198,121 +2711,81 @@ where .get_minimum_balance_for_rent_exemption(space) .await .map_err(TokenError::Client)?; - instructions.push(system_instruction::create_account( - &self.payer.pubkey(), - context_state_accounts.equality_proof, - rent, - space as u64, - &zk_token_proof_program::id(), - )); - let equality_proof_context_state_info = ContextStateInfo { - context_state_account: context_state_accounts.equality_proof, - context_state_authority: context_state_accounts.authority, + context_state_account, + context_state_authority, }; - instructions.push( - instruction_type - .encode_verify_proof(Some(equality_proof_context_state_info), equality_proof_data), - ); + self.process_ixs( + &[ + system_instruction::create_account( + &self.payer.pubkey(), + context_state_account, + rent, + space as u64, + &zk_token_proof_program::id(), + ), + instruction_type.encode_verify_proof( + Some(equality_proof_context_state_info), + equality_proof_data, + ), + ], + &[signing_keypair], + ) + .await + } - // create transfer amount ciphertext validity proof context state - let instruction_type = ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity; + /// Create a transfer amount ciphertext validity proof context state account + /// for a confidential transfer with fee. + #[allow(clippy::too_many_arguments)] + pub async fn create_transfer_amount_ciphertext_validity_proof_context_state_for_transfer_with_fee< + S: Signer, + >( + &self, + context_state_account: &Pubkey, + context_state_authority: &Pubkey, + transfer_amount_ciphertext_validity_proof_data: &BatchedGroupedCiphertext3HandlesValidityProofData, + signing_keypair: &S, + ) -> TokenResult { + let instruction_type = ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity; let space = - size_of::>(); + size_of::>(); let rent = self .client .get_minimum_balance_for_rent_exemption(space) .await .map_err(TokenError::Client)?; - instructions.push(system_instruction::create_account( - &self.payer.pubkey(), - context_state_accounts.transfer_amount_ciphertext_validity_proof, - rent, - space as u64, - &zk_token_proof_program::id(), - )); - let transfer_amount_ciphertext_validity_proof_context_state_info = ContextStateInfo { - context_state_account: context_state_accounts.transfer_amount_ciphertext_validity_proof, - context_state_authority: context_state_accounts.authority, + context_state_account, + context_state_authority, }; - instructions.push(instruction_type.encode_verify_proof( - Some(transfer_amount_ciphertext_validity_proof_context_state_info), - transfer_amount_ciphertext_validity_proof_data, - )); - - // add transfer instruction - if let Some(transfer_instruction) = transfer_instruction { - instructions.push(transfer_instruction.clone()); - } - - self.process_ixs(&instructions, signing_keypairs).await - } - - /// Create fee sigma and fee ciphertext validity proof context state - /// accounts for a confidential transfer with fee. - #[allow(clippy::too_many_arguments)] - pub async fn create_fee_sigma_and_ciphertext_validity_proof_context_states_for_transfer_with_fee< - S: Signers, - >( - &self, - context_state_accounts: TransferWithFeeSplitContextStateAccounts<'_>, - fee_sigma_proof_data: &FeeSigmaProofData, - fee_ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData, - signing_keypairs: &S, - ) -> TokenResult { - self.create_fee_sigma_and_ciphertext_validity_proof_context_states_with_optional_transfer_with_fee( - context_state_accounts, - fee_sigma_proof_data, - fee_ciphertext_validity_proof_data, - None, - signing_keypairs, - ) - .await - } - - /// Create fee sigma and fee ciphertext validity proof context state - /// accounts with a confidential transfer with fee. - #[allow(clippy::too_many_arguments)] - pub async fn create_fee_sigma_and_ciphertext_validity_proof_context_states_for_transfer_with_fee_parallel< - S: Signers, - >( - &self, - context_state_accounts: TransferWithFeeSplitContextStateAccounts<'_>, - fee_sigma_proof_data: &FeeSigmaProofData, - fee_ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData, - transfer_instruction: &Instruction, - signing_keypairs: &S, - ) -> TokenResult { - self.create_fee_sigma_and_ciphertext_validity_proof_context_states_with_optional_transfer_with_fee( - context_state_accounts, - fee_sigma_proof_data, - fee_ciphertext_validity_proof_data, - Some(transfer_instruction), - signing_keypairs, + self.process_ixs( + &[ + system_instruction::create_account( + &self.payer.pubkey(), + context_state_account, + rent, + space as u64, + &zk_token_proof_program::id(), + ), + instruction_type.encode_verify_proof( + Some(transfer_amount_ciphertext_validity_proof_context_state_info), + transfer_amount_ciphertext_validity_proof_data, + ), + ], + &[signing_keypair], ) .await } - /// Create fee sigma and fee ciphertext validity proof context states for a - /// confidential transfer with fee. - /// - /// If an optional transfer instruction is provided, then the transfer - /// instruction is attached to the same transaction. + /// Create a fee sigma proof context state account for a confidential transfer with fee. #[allow(clippy::too_many_arguments)] - async fn create_fee_sigma_and_ciphertext_validity_proof_context_states_with_optional_transfer_with_fee< - S: Signers, - >( + pub async fn create_fee_sigma_proof_context_state_for_transfer_with_fee( &self, - context_state_accounts: TransferWithFeeSplitContextStateAccounts<'_>, + context_state_account: &Pubkey, + context_state_authority: &Pubkey, fee_sigma_proof_data: &FeeSigmaProofData, - fee_ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData, - transfer_instruction: Option<&Instruction>, - signing_keypairs: &S, + signing_keypair: &S, ) -> TokenResult { - let mut instructions = vec![]; - - // create fee sigma proof context state let instruction_type = ProofInstruction::VerifyFeeSigma; let space = size_of::>(); let rent = self @@ -3320,24 +2793,41 @@ where .get_minimum_balance_for_rent_exemption(space) .await .map_err(TokenError::Client)?; - instructions.push(system_instruction::create_account( - &self.payer.pubkey(), - context_state_accounts.fee_sigma_proof, - rent, - space as u64, - &zk_token_proof_program::id(), - )); - let fee_sigma_proof_context_state_info = ContextStateInfo { - context_state_account: context_state_accounts.fee_sigma_proof, - context_state_authority: context_state_accounts.authority, + context_state_account, + context_state_authority, }; - instructions.push(instruction_type.encode_verify_proof( - Some(fee_sigma_proof_context_state_info), - fee_sigma_proof_data, - )); + self.process_ixs( + &[ + system_instruction::create_account( + &self.payer.pubkey(), + context_state_account, + rent, + space as u64, + &zk_token_proof_program::id(), + ), + instruction_type.encode_verify_proof( + Some(fee_sigma_proof_context_state_info), + fee_sigma_proof_data, + ), + ], + &[signing_keypair], + ) + .await + } - // create fee ciphertext validity proof context state + /// Create a fee ciphertext validity proof context state account for a confidential transfer + /// with fee. + #[allow(clippy::too_many_arguments)] + pub async fn create_fee_ciphertext_validity_proof_context_state_for_transfer_with_fee< + S: Signer, + >( + &self, + context_state_account: &Pubkey, + context_state_authority: &Pubkey, + fee_ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData, + signing_keypair: &S, + ) -> TokenResult { let instruction_type = ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity; let space = size_of::>(); @@ -3346,76 +2836,37 @@ where .get_minimum_balance_for_rent_exemption(space) .await .map_err(TokenError::Client)?; - instructions.push(system_instruction::create_account( - &self.payer.pubkey(), - context_state_accounts.fee_ciphertext_validity_proof, - rent, - space as u64, - &zk_token_proof_program::id(), - )); - let fee_ciphertext_validity_proof_context_state_info = ContextStateInfo { - context_state_account: context_state_accounts.fee_ciphertext_validity_proof, - context_state_authority: context_state_accounts.authority, + context_state_account, + context_state_authority, }; - instructions.push(instruction_type.encode_verify_proof( - Some(fee_ciphertext_validity_proof_context_state_info), - fee_ciphertext_validity_proof_data, - )); - - // add transfer instruction - if let Some(transfer_instruction) = transfer_instruction { - instructions.push(transfer_instruction.clone()); - } - - self.process_ixs(&instructions, signing_keypairs).await - } - - /// Create range proof context state account for a confidential transfer - /// with fee. - #[allow(clippy::too_many_arguments)] - pub async fn create_range_proof_context_state_for_transfer_with_fee( - &self, - context_state_accounts: TransferWithFeeSplitContextStateAccounts<'_>, - range_proof_data: &BatchedRangeProofU256Data, - signing_keypairs: &S, - ) -> TokenResult { - self.create_range_proof_context_state_with_optional_transfer_with_fee( - context_state_accounts, - range_proof_data, - None, - signing_keypairs, + self.process_ixs( + &[ + system_instruction::create_account( + &self.payer.pubkey(), + context_state_account, + rent, + space as u64, + &zk_token_proof_program::id(), + ), + instruction_type.encode_verify_proof( + Some(fee_ciphertext_validity_proof_context_state_info), + fee_ciphertext_validity_proof_data, + ), + ], + &[signing_keypair], ) .await } - /// Create range proof context state account for a confidential transfer - /// with fee. + /// Create a range proof context state for a confidential transfer with fee. #[allow(clippy::too_many_arguments)] - pub async fn create_range_proof_context_state_for_transfer_with_fee_parallel( + pub async fn create_range_proof_context_state_for_transfer_with_fee( &self, - context_state_accounts: TransferWithFeeSplitContextStateAccounts<'_>, - range_proof_data: &BatchedRangeProofU256Data, - transfer_instruction: &Instruction, - signing_keypairs: &S, - ) -> TokenResult { - self.create_range_proof_context_state_with_optional_transfer_with_fee( - context_state_accounts, - range_proof_data, - Some(transfer_instruction), - signing_keypairs, - ) - .await - } - - /// Create a range proof context state account and an optional confidential - /// transfer instruction. - async fn create_range_proof_context_state_with_optional_transfer_with_fee( - &self, - context_state_accounts: TransferWithFeeSplitContextStateAccounts<'_>, + context_state_account: &Pubkey, + context_state_authority: &Pubkey, range_proof_data: &BatchedRangeProofU256Data, - transfer_instruction: Option<&Instruction>, - signing_keypairs: &S, + signing_keypair: &S, ) -> TokenResult { let instruction_type = ProofInstruction::VerifyBatchedRangeProofU256; let space = size_of::>(); @@ -3425,27 +2876,24 @@ where .await .map_err(TokenError::Client)?; let range_proof_context_state_info = ContextStateInfo { - context_state_account: context_state_accounts.range_proof, - context_state_authority: context_state_accounts.authority, + context_state_account, + context_state_authority, }; - - let mut instructions = vec![ - system_instruction::create_account( - &self.payer.pubkey(), - context_state_accounts.range_proof, - rent, - space as u64, - &zk_token_proof_program::id(), - ), - instruction_type - .encode_verify_proof(Some(range_proof_context_state_info), range_proof_data), - ]; - - if let Some(transfer_instruction) = transfer_instruction { - instructions.push(transfer_instruction.clone()); - } - - self.process_ixs(&instructions, signing_keypairs).await + self.process_ixs( + &[ + system_instruction::create_account( + &self.payer.pubkey(), + context_state_account, + rent, + space as u64, + &zk_token_proof_program::id(), + ), + instruction_type + .encode_verify_proof(Some(range_proof_context_state_info), range_proof_data), + ], + &[signing_keypair], + ) + .await } /// Applies the confidential transfer pending balance to the available @@ -3631,9 +3079,12 @@ where // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`, // which is guaranteed by the previous check - let proof_location = - Self::confidential_transfer_create_proof_location(proof_data.as_ref(), proof_account) - .unwrap(); + let proof_location = Self::confidential_transfer_create_proof_location( + proof_data.as_ref(), + proof_account, + 1, + ) + .unwrap(); self.process_ixs( &confidential_transfer_fee::instruction::withdraw_withheld_tokens_from_mint( @@ -3702,9 +3153,12 @@ where // cannot panic as long as either `proof_data` or `proof_account` is `Some(..)`, // which is guaranteed by the previous check - let proof_location = - Self::confidential_transfer_create_proof_location(proof_data.as_ref(), proof_account) - .unwrap(); + let proof_location = Self::confidential_transfer_create_proof_location( + proof_data.as_ref(), + proof_account, + 1, + ) + .unwrap(); self.process_ixs( &confidential_transfer_fee::instruction::withdraw_withheld_tokens_from_accounts( @@ -3793,10 +3247,11 @@ where fn confidential_transfer_create_proof_location<'a, ZK: ZkProofData, U: Pod>( proof_data: Option<&'a ZK>, proof_account: Option<&'a ProofAccount>, + instruction_offset: i8, ) -> Option> { if let Some(proof_data) = proof_data { Some(ProofLocation::InstructionOffset( - 1.try_into().unwrap(), + instruction_offset.try_into().unwrap(), ProofData::InstructionData(proof_data), )) } else if let Some(proof_account) = proof_account { @@ -3804,10 +3259,10 @@ where ProofAccount::ContextAccount(context_state_account) => { Some(ProofLocation::ContextStateAccount(context_state_account)) } - ProofAccount::RecordAccount(record_account, offset) => { + ProofAccount::RecordAccount(record_account, data_offset) => { Some(ProofLocation::InstructionOffset( - 1.try_into().unwrap(), - ProofData::RecordAccount(record_account, *offset), + instruction_offset.try_into().unwrap(), + ProofData::RecordAccount(record_account, *data_offset), )) } } From 4c8c53bcefc167ee9394ca552989438c5abc5088 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 9 Aug 2024 12:20:36 +0900 Subject: [PATCH 6/9] update program-2022 tests --- .../tests/confidential_transfer.rs | 2217 ++++++++--------- .../tests/confidential_transfer_fee.rs | 28 + .../program-2022-test/tests/transfer_hook.rs | 2 + 3 files changed, 1044 insertions(+), 1203 deletions(-) diff --git a/token/program-2022-test/tests/confidential_transfer.rs b/token/program-2022-test/tests/confidential_transfer.rs index ce125d5e029..c2e8a657f8f 100644 --- a/token/program-2022-test/tests/confidential_transfer.rs +++ b/token/program-2022-test/tests/confidential_transfer.rs @@ -10,7 +10,7 @@ use { instruction::InstructionError, pubkey::Pubkey, signature::Signer, - signer::keypair::Keypair, + signer::{keypair::Keypair, signers::Signers}, system_instruction, transaction::{Transaction, TransactionError}, transport::TransportError, @@ -22,10 +22,6 @@ use { confidential_transfer::{ self, account_info::{EmptyAccountAccountInfo, TransferAccountInfo, WithdrawAccountInfo}, - instruction::{ - CloseSplitContextStateAccounts, TransferSplitContextStateAccounts, - TransferWithFeeSplitContextStateAccounts, - }, ConfidentialTransferAccount, MAXIMUM_DEPOSIT_TRANSFER_AMOUNT, }, BaseStateWithExtensions, ExtensionType, @@ -39,8 +35,11 @@ use { }, }, spl_token_client::{ + client::ProgramBanksClientProcessTransaction, proof_generation::{transfer_with_fee_split_proof_data, ProofAccount}, - token::{ComputeUnitLimit, ExtensionInitializationParams, TokenError as TokenClientError}, + token::{ + ExtensionInitializationParams, Token, TokenError as TokenClientError, TokenResult, + }, }, std::{convert::TryInto, mem::size_of}, }; @@ -1021,9 +1020,314 @@ async fn confidential_transfer_withdraw_with_record_account() { .unwrap(); } +#[derive(Clone, Copy)] +pub enum ConfidentialTransferOption { + InstructionData, + RecordAccount, + ContextStateAccount, +} + +#[allow(clippy::too_many_arguments)] +#[cfg(feature = "zk-ops")] +async fn confidential_transfer_with_option( + token: &Token, + source_account: &Pubkey, + destination_account: &Pubkey, + source_authority: &Pubkey, + transfer_amount: u64, + source_elgamal_keypair: &ElGamalKeypair, + source_aes_key: &AeKey, + destination_elgamal_pubkey: &ElGamalPubkey, + auditor_elgamal_pubkey: Option<&ElGamalPubkey>, + memo: Option<(&str, Vec)>, + signing_keypairs: &S, + option: ConfidentialTransferOption, +) -> TokenResult<()> { + match option { + ConfidentialTransferOption::InstructionData => { + let transfer_token = if let Some((memo, signing_pubkey)) = memo { + token.with_memo(memo, signing_pubkey) + } else { + token + }; + + transfer_token + .confidential_transfer_transfer( + source_account, + destination_account, + source_authority, + None, + None, + None, + transfer_amount, + None, + source_elgamal_keypair, + source_aes_key, + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + signing_keypairs, + ) + .await + } + ConfidentialTransferOption::RecordAccount => { + let state = token.get_account_info(source_account).await.unwrap(); + let extension = state + .get_extension::() + .unwrap(); + let transfer_account_info = TransferAccountInfo::new(extension); + + let (equality_proof_data, ciphertext_validity_proof_data, range_proof_data) = + transfer_account_info + .generate_split_transfer_proof_data( + transfer_amount, + source_elgamal_keypair, + source_aes_key, + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + ) + .unwrap(); + + let equality_proof_record_account = Keypair::new(); + let ciphertext_validity_proof_record_account = Keypair::new(); + let range_proof_record_account = Keypair::new(); + let record_account_authority = Keypair::new(); + + token + .confidential_transfer_create_record_account( + &equality_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + &equality_proof_data, + &equality_proof_record_account, + &record_account_authority, + ) + .await + .unwrap(); + + let equality_proof_account = ProofAccount::RecordAccount( + equality_proof_record_account.pubkey(), + RecordData::WRITABLE_START_INDEX as u32, + ); + + token + .confidential_transfer_create_record_account( + &ciphertext_validity_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + &ciphertext_validity_proof_data, + &ciphertext_validity_proof_record_account, + &record_account_authority, + ) + .await + .unwrap(); + + let ciphertext_validity_proof_account = ProofAccount::RecordAccount( + ciphertext_validity_proof_record_account.pubkey(), + RecordData::WRITABLE_START_INDEX as u32, + ); + + token + .confidential_transfer_create_record_account( + &range_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + &range_proof_data, + &range_proof_record_account, + &record_account_authority, + ) + .await + .unwrap(); + + let range_proof_account = ProofAccount::RecordAccount( + range_proof_record_account.pubkey(), + RecordData::WRITABLE_START_INDEX as u32, + ); + + let transfer_token = if let Some((memo, signing_pubkey)) = memo { + token.with_memo(memo, signing_pubkey) + } else { + token + }; + + let result = transfer_token + .confidential_transfer_transfer( + source_account, + destination_account, + source_authority, + Some(&equality_proof_account), + Some(&ciphertext_validity_proof_account), + Some(&range_proof_account), + transfer_amount, + None, + source_elgamal_keypair, + source_aes_key, + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + signing_keypairs, + ) + .await; + + token + .confidential_transfer_close_record_account( + &equality_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + source_account, + &record_account_authority, + ) + .await + .unwrap(); + + token + .confidential_transfer_close_record_account( + &ciphertext_validity_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + source_account, + &record_account_authority, + ) + .await + .unwrap(); + + token + .confidential_transfer_close_record_account( + &range_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + source_account, + &record_account_authority, + ) + .await + .unwrap(); + + result + } + ConfidentialTransferOption::ContextStateAccount => { + let state = token.get_account_info(source_account).await.unwrap(); + let extension = state + .get_extension::() + .unwrap(); + let transfer_account_info = TransferAccountInfo::new(extension); + + let (equality_proof_data, ciphertext_validity_proof_data, range_proof_data) = + transfer_account_info + .generate_split_transfer_proof_data( + transfer_amount, + source_elgamal_keypair, + source_aes_key, + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + ) + .unwrap(); + + let equality_proof_context_account = Keypair::new(); + let ciphertext_validity_proof_context_account = Keypair::new(); + let range_proof_context_account = Keypair::new(); + let context_account_authority = Keypair::new(); + + token + .create_equality_proof_context_state_for_transfer( + &equality_proof_context_account.pubkey(), + &context_account_authority.pubkey(), + &equality_proof_data, + &equality_proof_context_account, + ) + .await + .unwrap(); + + let equality_proof_context_proof_account = + ProofAccount::ContextAccount(equality_proof_context_account.pubkey()); + + token + .create_ciphertext_validity_proof_context_state_for_transfer( + &ciphertext_validity_proof_context_account.pubkey(), + &context_account_authority.pubkey(), + &ciphertext_validity_proof_data, + &ciphertext_validity_proof_context_account, + ) + .await + .unwrap(); + + let ciphertext_validity_proof_context_proof_account = + ProofAccount::ContextAccount(ciphertext_validity_proof_context_account.pubkey()); + + token + .create_range_proof_context_state_for_transfer( + &range_proof_context_account.pubkey(), + &context_account_authority.pubkey(), + &range_proof_data, + &range_proof_context_account, + ) + .await + .unwrap(); + + let range_proof_context_proof_account = + ProofAccount::ContextAccount(range_proof_context_account.pubkey()); + + let transfer_token = if let Some((memo, signing_pubkey)) = memo { + token.with_memo(memo, signing_pubkey) + } else { + token + }; + + let result = transfer_token + .confidential_transfer_transfer( + source_account, + destination_account, + source_authority, + Some(&equality_proof_context_proof_account), + Some(&ciphertext_validity_proof_context_proof_account), + Some(&range_proof_context_proof_account), + transfer_amount, + None, + source_elgamal_keypair, + source_aes_key, + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + signing_keypairs, + ) + .await; + + token + .confidential_transfer_close_context_state( + &equality_proof_context_account.pubkey(), + source_account, + &context_account_authority.pubkey(), + &context_account_authority, + ) + .await + .unwrap(); + + token + .confidential_transfer_close_context_state( + &ciphertext_validity_proof_context_account.pubkey(), + source_account, + &context_account_authority.pubkey(), + &context_account_authority, + ) + .await + .unwrap(); + + token + .confidential_transfer_close_context_state( + &range_proof_context_account.pubkey(), + source_account, + &context_account_authority.pubkey(), + &context_account_authority, + ) + .await + .unwrap(); + + result + } + } +} + #[cfg(feature = "zk-ops")] #[tokio::test] async fn confidential_transfer_transfer() { + confidential_transfer_transfer_with_option(ConfidentialTransferOption::InstructionData).await; + confidential_transfer_transfer_with_option(ConfidentialTransferOption::RecordAccount).await; + confidential_transfer_transfer_with_option(ConfidentialTransferOption::ContextStateAccount) + .await; +} + +#[cfg(feature = "zk-ops")] +async fn confidential_transfer_transfer_with_option(option: ConfidentialTransferOption) { let authority = Keypair::new(); let auto_approve_new_accounts = true; let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); @@ -1065,22 +1369,22 @@ async fn confidential_transfer_transfer() { let bob_meta = ConfidentialTokenAccountMeta::new(&token, &bob, Some(2), false, false).await; // Self-transfer of 0 tokens - token - .confidential_transfer_transfer( - &alice_meta.token_account, - &alice_meta.token_account, - &alice.pubkey(), - None, - 0, - None, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - alice_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - &[&alice], - ) - .await - .unwrap(); + confidential_transfer_with_option( + &token, + &alice_meta.token_account, + &alice_meta.token_account, + &alice.pubkey(), + 0, + &alice_meta.elgamal_keypair, + &alice_meta.aes_key, + alice_meta.elgamal_keypair.pubkey(), + Some(auditor_elgamal_keypair.pubkey()), + None, + &[&alice], + option, + ) + .await + .unwrap(); alice_meta .check_balances( @@ -1095,22 +1399,22 @@ async fn confidential_transfer_transfer() { .await; // Self-transfer of N tokens - token - .confidential_transfer_transfer( - &alice_meta.token_account, - &alice_meta.token_account, - &alice.pubkey(), - None, - 42, - None, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - alice_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - &[&alice], - ) - .await - .unwrap(); + confidential_transfer_with_option( + &token, + &alice_meta.token_account, + &alice_meta.token_account, + &alice.pubkey(), + 42, + &alice_meta.elgamal_keypair, + &alice_meta.aes_key, + alice_meta.elgamal_keypair.pubkey(), + Some(auditor_elgamal_keypair.pubkey()), + None, + &[&alice], + option, + ) + .await + .unwrap(); alice_meta .check_balances( @@ -1148,22 +1452,22 @@ async fn confidential_transfer_transfer() { ) .await; - token - .confidential_transfer_transfer( - &alice_meta.token_account, - &bob_meta.token_account, - &alice.pubkey(), - None, - 42, - None, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - bob_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - &[&alice], - ) - .await - .unwrap(); + confidential_transfer_with_option( + &token, + &alice_meta.token_account, + &bob_meta.token_account, + &alice.pubkey(), + 42, + &alice_meta.elgamal_keypair, + &alice_meta.aes_key, + bob_meta.elgamal_keypair.pubkey(), + Some(auditor_elgamal_keypair.pubkey()), + None, + &[&alice], + option, + ) + .await + .unwrap(); alice_meta .check_balances( @@ -1189,39 +1493,39 @@ async fn confidential_transfer_transfer() { ) .await; - token - .confidential_transfer_transfer( - &bob_meta.token_account, - &bob_meta.token_account, - &bob.pubkey(), - None, - 0, - None, - &bob_meta.elgamal_keypair, - &bob_meta.aes_key, - bob_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - &[&bob], - ) - .await - .unwrap(); + confidential_transfer_with_option( + &token, + &bob_meta.token_account, + &bob_meta.token_account, + &bob.pubkey(), + 0, + &bob_meta.elgamal_keypair, + &bob_meta.aes_key, + bob_meta.elgamal_keypair.pubkey(), + Some(auditor_elgamal_keypair.pubkey()), + None, + &[&bob], + option, + ) + .await + .unwrap(); - let err = token - .confidential_transfer_transfer( - &bob_meta.token_account, - &bob_meta.token_account, - &bob.pubkey(), - None, - 0, - None, - &bob_meta.elgamal_keypair, - &bob_meta.aes_key, - bob_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - &[&bob], - ) - .await - .unwrap_err(); + let err = confidential_transfer_with_option( + &token, + &bob_meta.token_account, + &bob_meta.token_account, + &bob.pubkey(), + 0, + &bob_meta.elgamal_keypair, + &bob_meta.aes_key, + bob_meta.elgamal_keypair.pubkey(), + Some(auditor_elgamal_keypair.pubkey()), + None, + &[&bob], + option, + ) + .await + .unwrap_err(); assert_eq!( err, @@ -1260,9 +1564,476 @@ async fn confidential_transfer_transfer() { .await; } +#[allow(clippy::too_many_arguments)] +#[cfg(feature = "zk-ops")] +async fn confidential_transfer_with_fee_with_option( + token: &Token, + source_account: &Pubkey, + destination_account: &Pubkey, + source_authority: &Pubkey, + transfer_amount: u64, + source_elgamal_keypair: &ElGamalKeypair, + source_aes_key: &AeKey, + destination_elgamal_pubkey: &ElGamalPubkey, + auditor_elgamal_pubkey: Option<&ElGamalPubkey>, + withdraw_withheld_authority_elgamal_pubkey: &ElGamalPubkey, + fee_rate_basis_points: u16, + maximum_fee: u64, + memo: Option<(&str, Vec)>, + signing_keypairs: &S, + option: ConfidentialTransferOption, +) -> TokenResult<()> { + match option { + ConfidentialTransferOption::InstructionData => { + let transfer_token = if let Some((memo, signing_pubkey)) = memo { + token.with_memo(memo, signing_pubkey) + } else { + token + }; + + transfer_token + .confidential_transfer_transfer_with_fee( + source_account, + destination_account, + source_authority, + None, + None, + None, + None, + None, + transfer_amount, + None, + source_elgamal_keypair, + source_aes_key, + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + withdraw_withheld_authority_elgamal_pubkey, + fee_rate_basis_points, + maximum_fee, + signing_keypairs, + ) + .await + } + ConfidentialTransferOption::RecordAccount => { + let state = token.get_account_info(source_account).await.unwrap(); + let extension = state + .get_extension::() + .unwrap(); + let transfer_account_info = TransferAccountInfo::new(extension); + + let current_source_available_balance = + transfer_account_info.available_balance.try_into().unwrap(); + let current_source_decryptable_balance = transfer_account_info + .decryptable_available_balance + .try_into() + .unwrap(); + + let fee_parameters = FeeParameters { + fee_rate_basis_points, + maximum_fee, + }; + + let ( + equality_proof_data, + transfer_amount_ciphertext_validity_proof_data, + fee_sigma_proof_data, + fee_ciphertext_validity_proof_data, + range_proof_data, + ) = transfer_with_fee_split_proof_data( + ¤t_source_available_balance, + ¤t_source_decryptable_balance, + transfer_amount, + source_elgamal_keypair, + source_aes_key, + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + withdraw_withheld_authority_elgamal_pubkey, + &fee_parameters, + ) + .unwrap(); + + let equality_proof_record_account = Keypair::new(); + let transfer_amount_ciphertext_validity_proof_record_account = Keypair::new(); + let fee_sigma_proof_record_account = Keypair::new(); + let fee_ciphertext_validity_proof_record_account = Keypair::new(); + let range_proof_record_account = Keypair::new(); + let record_account_authority = Keypair::new(); + + token + .confidential_transfer_create_record_account( + &equality_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + &equality_proof_data, + &equality_proof_record_account, + &record_account_authority, + ) + .await + .unwrap(); + + let equality_proof_account = ProofAccount::RecordAccount( + equality_proof_record_account.pubkey(), + RecordData::WRITABLE_START_INDEX as u32, + ); + + token + .confidential_transfer_create_record_account( + &transfer_amount_ciphertext_validity_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + &transfer_amount_ciphertext_validity_proof_data, + &transfer_amount_ciphertext_validity_proof_record_account, + &record_account_authority, + ) + .await + .unwrap(); + + let transfer_amount_ciphertext_validity_proof_account = ProofAccount::RecordAccount( + transfer_amount_ciphertext_validity_proof_record_account.pubkey(), + RecordData::WRITABLE_START_INDEX as u32, + ); + + token + .confidential_transfer_create_record_account( + &fee_sigma_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + &fee_sigma_proof_data, + &fee_sigma_proof_record_account, + &record_account_authority, + ) + .await + .unwrap(); + + let fee_sigma_proof_account = ProofAccount::RecordAccount( + fee_sigma_proof_record_account.pubkey(), + RecordData::WRITABLE_START_INDEX as u32, + ); + + token + .confidential_transfer_create_record_account( + &fee_ciphertext_validity_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + &fee_ciphertext_validity_proof_data, + &fee_ciphertext_validity_proof_record_account, + &record_account_authority, + ) + .await + .unwrap(); + + let fee_ciphertext_validity_proof_account = ProofAccount::RecordAccount( + fee_ciphertext_validity_proof_record_account.pubkey(), + RecordData::WRITABLE_START_INDEX as u32, + ); + + token + .confidential_transfer_create_record_account( + &range_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + &range_proof_data, + &range_proof_record_account, + &record_account_authority, + ) + .await + .unwrap(); + + let range_proof_account = ProofAccount::RecordAccount( + range_proof_record_account.pubkey(), + RecordData::WRITABLE_START_INDEX as u32, + ); + + let transfer_token = if let Some((memo, signing_pubkey)) = memo { + token.with_memo(memo, signing_pubkey) + } else { + token + }; + + let result = transfer_token + .confidential_transfer_transfer_with_fee( + source_account, + destination_account, + source_authority, + Some(&equality_proof_account), + Some(&transfer_amount_ciphertext_validity_proof_account), + Some(&fee_sigma_proof_account), + Some(&fee_ciphertext_validity_proof_account), + Some(&range_proof_account), + transfer_amount, + None, + source_elgamal_keypair, + source_aes_key, + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + withdraw_withheld_authority_elgamal_pubkey, + fee_rate_basis_points, + maximum_fee, + signing_keypairs, + ) + .await; + + token + .confidential_transfer_close_record_account( + &equality_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + source_account, + &record_account_authority, + ) + .await + .unwrap(); + + token + .confidential_transfer_close_record_account( + &transfer_amount_ciphertext_validity_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + source_account, + &record_account_authority, + ) + .await + .unwrap(); + + token + .confidential_transfer_close_record_account( + &fee_sigma_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + source_account, + &record_account_authority, + ) + .await + .unwrap(); + + token + .confidential_transfer_close_record_account( + &fee_ciphertext_validity_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + source_account, + &record_account_authority, + ) + .await + .unwrap(); + + token + .confidential_transfer_close_record_account( + &range_proof_record_account.pubkey(), + &record_account_authority.pubkey(), + source_account, + &record_account_authority, + ) + .await + .unwrap(); + + result + } + ConfidentialTransferOption::ContextStateAccount => { + let state = token.get_account_info(source_account).await.unwrap(); + let extension = state + .get_extension::() + .unwrap(); + let transfer_account_info = TransferAccountInfo::new(extension); + + let current_source_available_balance = + transfer_account_info.available_balance.try_into().unwrap(); + let current_source_decryptable_balance = transfer_account_info + .decryptable_available_balance + .try_into() + .unwrap(); + + let fee_parameters = FeeParameters { + fee_rate_basis_points, + maximum_fee, + }; + + let ( + equality_proof_data, + transfer_amount_ciphertext_validity_proof_data, + fee_sigma_proof_data, + fee_ciphertext_validity_proof_data, + range_proof_data, + ) = transfer_with_fee_split_proof_data( + ¤t_source_available_balance, + ¤t_source_decryptable_balance, + transfer_amount, + source_elgamal_keypair, + source_aes_key, + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + withdraw_withheld_authority_elgamal_pubkey, + &fee_parameters, + ) + .unwrap(); + + let equality_proof_context_account = Keypair::new(); + let transfer_amount_ciphertext_validity_proof_context_account = Keypair::new(); + let fee_sigma_proof_context_account = Keypair::new(); + let fee_ciphertext_validity_proof_context_account = Keypair::new(); + let range_proof_context_account = Keypair::new(); + let context_account_authority = Keypair::new(); + + token + .create_equality_proof_context_state_for_transfer_with_fee( + &equality_proof_context_account.pubkey(), + &context_account_authority.pubkey(), + &equality_proof_data, + &equality_proof_context_account, + ) + .await + .unwrap(); + + let equality_proof_context_proof_account = + ProofAccount::ContextAccount(equality_proof_context_account.pubkey()); + + token + .create_transfer_amount_ciphertext_validity_proof_context_state_for_transfer_with_fee( + &transfer_amount_ciphertext_validity_proof_context_account.pubkey(), + &context_account_authority.pubkey(), + &transfer_amount_ciphertext_validity_proof_data, + &transfer_amount_ciphertext_validity_proof_context_account, + ) + .await + .unwrap(); + + let transfer_amount_ciphertext_validity_proof_context_proof_account = + ProofAccount::ContextAccount( + transfer_amount_ciphertext_validity_proof_context_account.pubkey(), + ); + + token + .create_fee_sigma_proof_context_state_for_transfer_with_fee( + &fee_sigma_proof_context_account.pubkey(), + &context_account_authority.pubkey(), + &fee_sigma_proof_data, + &fee_sigma_proof_context_account, + ) + .await + .unwrap(); + + let fee_sigma_proof_context_proof_account = + ProofAccount::ContextAccount(fee_sigma_proof_context_account.pubkey()); + + token + .create_fee_ciphertext_validity_proof_context_state_for_transfer_with_fee( + &fee_ciphertext_validity_proof_context_account.pubkey(), + &context_account_authority.pubkey(), + &fee_ciphertext_validity_proof_data, + &fee_ciphertext_validity_proof_context_account, + ) + .await + .unwrap(); + + let fee_ciphertext_validity_proof_context_proof_account = ProofAccount::ContextAccount( + fee_ciphertext_validity_proof_context_account.pubkey(), + ); + + token + .create_range_proof_context_state_for_transfer_with_fee( + &range_proof_context_account.pubkey(), + &context_account_authority.pubkey(), + &range_proof_data, + &range_proof_context_account, + ) + .await + .unwrap(); + + let range_proof_context_proof_account = + ProofAccount::ContextAccount(range_proof_context_account.pubkey()); + + let transfer_token = if let Some((memo, signing_pubkey)) = memo { + token.with_memo(memo, signing_pubkey) + } else { + token + }; + + let result = transfer_token + .confidential_transfer_transfer_with_fee( + source_account, + destination_account, + source_authority, + Some(&equality_proof_context_proof_account), + Some(&transfer_amount_ciphertext_validity_proof_context_proof_account), + Some(&fee_sigma_proof_context_proof_account), + Some(&fee_ciphertext_validity_proof_context_proof_account), + Some(&range_proof_context_proof_account), + transfer_amount, + None, + source_elgamal_keypair, + source_aes_key, + destination_elgamal_pubkey, + auditor_elgamal_pubkey, + withdraw_withheld_authority_elgamal_pubkey, + fee_rate_basis_points, + maximum_fee, + signing_keypairs, + ) + .await; + + token + .confidential_transfer_close_context_state( + &equality_proof_context_account.pubkey(), + source_account, + &context_account_authority.pubkey(), + &context_account_authority, + ) + .await + .unwrap(); + + token + .confidential_transfer_close_context_state( + &transfer_amount_ciphertext_validity_proof_context_account.pubkey(), + source_account, + &context_account_authority.pubkey(), + &context_account_authority, + ) + .await + .unwrap(); + + token + .confidential_transfer_close_context_state( + &fee_sigma_proof_context_account.pubkey(), + source_account, + &context_account_authority.pubkey(), + &context_account_authority, + ) + .await + .unwrap(); + + token + .confidential_transfer_close_context_state( + &fee_ciphertext_validity_proof_context_account.pubkey(), + source_account, + &context_account_authority.pubkey(), + &context_account_authority, + ) + .await + .unwrap(); + + token + .confidential_transfer_close_context_state( + &range_proof_context_account.pubkey(), + source_account, + &context_account_authority.pubkey(), + &context_account_authority, + ) + .await + .unwrap(); + + result + } + } +} + #[cfg(feature = "zk-ops")] #[tokio::test] async fn confidential_transfer_transfer_with_fee() { + confidential_transfer_transfer_with_fee_with_option( + ConfidentialTransferOption::InstructionData, + ) + .await; + confidential_transfer_transfer_with_fee_with_option(ConfidentialTransferOption::RecordAccount) + .await; + confidential_transfer_transfer_with_fee_with_option( + ConfidentialTransferOption::ContextStateAccount, + ) + .await; +} + +#[cfg(feature = "zk-ops")] +async fn confidential_transfer_transfer_with_fee_with_option(option: ConfidentialTransferOption) { let transfer_fee_authority = Keypair::new(); let withdraw_withheld_authority = Keypair::new(); @@ -1322,25 +2093,25 @@ async fn confidential_transfer_transfer_with_fee() { let bob_meta = ConfidentialTokenAccountMeta::new(&token, &bob, None, false, true).await; // Self-transfer of 0 tokens - token - .confidential_transfer_transfer_with_fee( - &alice_meta.token_account, - &alice_meta.token_account, - &alice.pubkey(), - None, - 0, - None, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - alice_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - withdraw_withheld_authority_elgamal_keypair.pubkey(), - TEST_FEE_BASIS_POINTS, - TEST_MAXIMUM_FEE, - &[&alice], - ) - .await - .unwrap(); + confidential_transfer_with_fee_with_option( + &token, + &alice_meta.token_account, + &alice_meta.token_account, + &alice.pubkey(), + 0, + &alice_meta.elgamal_keypair, + &alice_meta.aes_key, + alice_meta.elgamal_keypair.pubkey(), + Some(auditor_elgamal_keypair.pubkey()), + withdraw_withheld_authority_elgamal_keypair.pubkey(), + TEST_FEE_BASIS_POINTS, + TEST_MAXIMUM_FEE, + None, + &[&alice], + option, + ) + .await + .unwrap(); alice_meta .check_balances( @@ -1355,25 +2126,25 @@ async fn confidential_transfer_transfer_with_fee() { .await; // Self-transfers does not incur a fee - token - .confidential_transfer_transfer_with_fee( - &alice_meta.token_account, - &alice_meta.token_account, - &alice.pubkey(), - None, - 100, - None, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - alice_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - withdraw_withheld_authority_elgamal_keypair.pubkey(), - TEST_FEE_BASIS_POINTS, - TEST_MAXIMUM_FEE, - &[&alice], - ) - .await - .unwrap(); + confidential_transfer_with_fee_with_option( + &token, + &alice_meta.token_account, + &alice_meta.token_account, + &alice.pubkey(), + 100, + &alice_meta.elgamal_keypair, + &alice_meta.aes_key, + alice_meta.elgamal_keypair.pubkey(), + Some(auditor_elgamal_keypair.pubkey()), + withdraw_withheld_authority_elgamal_keypair.pubkey(), + TEST_FEE_BASIS_POINTS, + TEST_MAXIMUM_FEE, + None, + &[&alice], + option, + ) + .await + .unwrap(); alice_meta .check_balances( @@ -1411,25 +2182,25 @@ async fn confidential_transfer_transfer_with_fee() { ) .await; - token - .confidential_transfer_transfer_with_fee( - &alice_meta.token_account, - &bob_meta.token_account, - &alice.pubkey(), - None, - 100, - None, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - bob_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - withdraw_withheld_authority_elgamal_keypair.pubkey(), - TEST_FEE_BASIS_POINTS, - TEST_MAXIMUM_FEE, - &[&alice], - ) - .await - .unwrap(); + confidential_transfer_with_fee_with_option( + &token, + &alice_meta.token_account, + &bob_meta.token_account, + &alice.pubkey(), + 100, + &alice_meta.elgamal_keypair, + &alice_meta.aes_key, + bob_meta.elgamal_keypair.pubkey(), + Some(auditor_elgamal_keypair.pubkey()), + withdraw_withheld_authority_elgamal_keypair.pubkey(), + TEST_FEE_BASIS_POINTS, + TEST_MAXIMUM_FEE, + None, + &[&alice], + option, + ) + .await + .unwrap(); alice_meta .check_balances( @@ -1517,6 +2288,18 @@ async fn confidential_transfer_transfer_with_fee() { #[cfg(feature = "zk-ops")] #[tokio::test] async fn confidential_transfer_transfer_memo() { + confidential_transfer_transfer_memo_with_option(ConfidentialTransferOption::InstructionData) + .await; + confidential_transfer_transfer_memo_with_option(ConfidentialTransferOption::RecordAccount) + .await; + confidential_transfer_transfer_memo_with_option( + ConfidentialTransferOption::ContextStateAccount, + ) + .await; +} + +#[cfg(feature = "zk-ops")] +async fn confidential_transfer_transfer_memo_with_option(option: ConfidentialTransferOption) { let authority = Keypair::new(); let auto_approve_new_accounts = true; let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); @@ -1558,22 +2341,22 @@ async fn confidential_transfer_transfer_memo() { let bob_meta = ConfidentialTokenAccountMeta::new(&token, &bob, None, true, false).await; // transfer without memo - let err = token - .confidential_transfer_transfer( - &alice_meta.token_account, - &bob_meta.token_account, - &alice.pubkey(), - None, - 42, - None, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - bob_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - &[&alice], - ) - .await - .unwrap_err(); + let err = confidential_transfer_with_option( + &token, + &alice_meta.token_account, + &bob_meta.token_account, + &alice.pubkey(), + 42, + &alice_meta.elgamal_keypair, + &alice_meta.aes_key, + bob_meta.elgamal_keypair.pubkey(), + Some(auditor_elgamal_keypair.pubkey()), + None, + &[&alice], + option, + ) + .await + .unwrap_err(); assert_eq!( err, @@ -1586,23 +2369,22 @@ async fn confidential_transfer_transfer_memo() { ); // transfer with memo - token - .with_memo("🦖", vec![alice.pubkey()]) - .confidential_transfer_transfer( - &alice_meta.token_account, - &bob_meta.token_account, - &alice.pubkey(), - None, - 42, - None, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - bob_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - &[&alice], - ) - .await - .unwrap(); + confidential_transfer_with_option( + &token, + &alice_meta.token_account, + &bob_meta.token_account, + &alice.pubkey(), + 42, + &alice_meta.elgamal_keypair, + &alice_meta.aes_key, + bob_meta.elgamal_keypair.pubkey(), + Some(auditor_elgamal_keypair.pubkey()), + Some(("🦖", vec![alice.pubkey()])), + &[&alice], + option, + ) + .await + .unwrap(); alice_meta .check_balances( @@ -1632,6 +2414,24 @@ async fn confidential_transfer_transfer_memo() { #[cfg(feature = "zk-ops")] #[tokio::test] async fn confidential_transfer_transfer_with_fee_and_memo() { + confidential_transfer_transfer_with_fee_and_memo_option( + ConfidentialTransferOption::InstructionData, + ) + .await; + confidential_transfer_transfer_with_fee_and_memo_option( + ConfidentialTransferOption::RecordAccount, + ) + .await; + confidential_transfer_transfer_with_fee_and_memo_option( + ConfidentialTransferOption::ContextStateAccount, + ) + .await; +} + +#[cfg(feature = "zk-ops")] +async fn confidential_transfer_transfer_with_fee_and_memo_option( + option: ConfidentialTransferOption, +) { let transfer_fee_authority = Keypair::new(); let withdraw_withheld_authority = Keypair::new(); @@ -1690,25 +2490,25 @@ async fn confidential_transfer_transfer_with_fee_and_memo() { let bob_meta = ConfidentialTokenAccountMeta::new(&token, &bob, None, true, true).await; - let err = token - .confidential_transfer_transfer_with_fee( - &alice_meta.token_account, - &bob_meta.token_account, - &alice.pubkey(), - None, - 100, - None, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - bob_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - withdraw_withheld_authority_elgamal_keypair.pubkey(), - TEST_FEE_BASIS_POINTS, - TEST_MAXIMUM_FEE, - &[&alice], - ) - .await - .unwrap_err(); + let err = confidential_transfer_with_fee_with_option( + &token, + &alice_meta.token_account, + &bob_meta.token_account, + &alice.pubkey(), + 100, + &alice_meta.elgamal_keypair, + &alice_meta.aes_key, + bob_meta.elgamal_keypair.pubkey(), + Some(auditor_elgamal_keypair.pubkey()), + withdraw_withheld_authority_elgamal_keypair.pubkey(), + TEST_FEE_BASIS_POINTS, + TEST_MAXIMUM_FEE, + None, + &[&alice], + option, + ) + .await + .unwrap_err(); assert_eq!( err, @@ -1720,26 +2520,25 @@ async fn confidential_transfer_transfer_with_fee_and_memo() { ))) ); - token - .with_memo("🦖", vec![alice.pubkey()]) - .confidential_transfer_transfer_with_fee( - &alice_meta.token_account, - &bob_meta.token_account, - &alice.pubkey(), - None, - 100, - None, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - bob_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - withdraw_withheld_authority_elgamal_keypair.pubkey(), - TEST_FEE_BASIS_POINTS, - TEST_MAXIMUM_FEE, - &[&alice], - ) - .await - .unwrap(); + confidential_transfer_with_fee_with_option( + &token, + &alice_meta.token_account, + &bob_meta.token_account, + &alice.pubkey(), + 100, + &alice_meta.elgamal_keypair, + &alice_meta.aes_key, + bob_meta.elgamal_keypair.pubkey(), + Some(auditor_elgamal_keypair.pubkey()), + withdraw_withheld_authority_elgamal_keypair.pubkey(), + TEST_FEE_BASIS_POINTS, + TEST_MAXIMUM_FEE, + Some(("🦖", vec![alice.pubkey()])), + &[&alice], + option, + ) + .await + .unwrap(); alice_meta .check_balances( @@ -2277,991 +3076,3 @@ async fn confidential_transfer_withdraw_with_proof_context() { ))) ); } - -#[tokio::test] -async fn confidential_transfer_transfer_with_proof_context() { - let authority = Keypair::new(); - let auto_approve_new_accounts = true; - let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); - let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into(); - - let mut context = TestContext::new().await; - context - .init_token_with_mint(vec![ - ExtensionInitializationParams::ConfidentialTransferMint { - authority: Some(authority.pubkey()), - auto_approve_new_accounts, - auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey), - }, - ]) - .await - .unwrap(); - - let TokenContext { - token, - alice, - bob, - mint_authority, - decimals, - .. - } = context.token_context.unwrap(); - - let alice_meta = ConfidentialTokenAccountMeta::new_with_tokens( - &token, - &alice, - None, - false, - false, - &mint_authority, - 42, - decimals, - ) - .await; - - let bob_meta = ConfidentialTokenAccountMeta::new_with_tokens( - &token, - &bob, - None, - false, - false, - &mint_authority, - 0, - decimals, - ) - .await; - - let context_state_account = Keypair::new(); - - // create context state - { - let context_state_authority = Keypair::new(); - let space = size_of::>(); - - let instruction_type = ProofInstruction::VerifyTransfer; - - let context_state_info = ContextStateInfo { - context_state_account: &context_state_account.pubkey(), - context_state_authority: &context_state_authority.pubkey(), - }; - - let state = token - .get_account_info(&alice_meta.token_account) - .await - .unwrap(); - let extension = state - .get_extension::() - .unwrap(); - let current_available_balance = extension.available_balance.try_into().unwrap(); - - let proof_data = confidential_transfer::instruction::TransferData::new( - 42, - (42, ¤t_available_balance), - &alice_meta.elgamal_keypair, - ( - bob_meta.elgamal_keypair.pubkey(), - auditor_elgamal_keypair.pubkey(), - ), - ) - .unwrap(); - - let mut ctx = context.context.lock().await; - let rent = ctx.banks_client.get_rent().await.unwrap(); - - let instructions = vec![ - system_instruction::create_account( - &ctx.payer.pubkey(), - &context_state_account.pubkey(), - rent.minimum_balance(space), - space as u64, - &zk_token_proof_program::id(), - ), - instruction_type.encode_verify_proof(Some(context_state_info), &proof_data), - ]; - - let last_blockhash = ctx.get_new_latest_blockhash().await.unwrap(); - let tx = Transaction::new_signed_with_payer( - &instructions, - Some(&ctx.payer.pubkey()), - &[&ctx.payer, &context_state_account], - last_blockhash, - ); - ctx.banks_client.process_transaction(tx).await.unwrap(); - } - - token - .confidential_transfer_transfer( - &alice_meta.token_account, - &bob_meta.token_account, - &alice.pubkey(), - Some(&ProofAccount::ContextAccount( - context_state_account.pubkey(), - )), - 42, - None, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - bob_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - &[&alice], - ) - .await - .unwrap(); - - // attempt to create an account with a wrong proof type context state - let context_state_account = Keypair::new(); - - { - let context_state_authority = Keypair::new(); - let space = size_of::>(); - - let instruction_type = ProofInstruction::VerifyWithdraw; - - let context_state_info = ContextStateInfo { - context_state_account: &context_state_account.pubkey(), - context_state_authority: &context_state_authority.pubkey(), - }; - - let state = token - .get_account_info(&alice_meta.token_account) - .await - .unwrap(); - let extension = state - .get_extension::() - .unwrap(); - let current_ciphertext = extension.available_balance.try_into().unwrap(); - - let proof_data = confidential_transfer::instruction::WithdrawData::new( - 0, - &alice_meta.elgamal_keypair, - 0, - ¤t_ciphertext, - ) - .unwrap(); - - let mut ctx = context.context.lock().await; - let rent = ctx.banks_client.get_rent().await.unwrap(); - - let instructions = vec![ - system_instruction::create_account( - &ctx.payer.pubkey(), - &context_state_account.pubkey(), - rent.minimum_balance(space), - space as u64, - &zk_token_proof_program::id(), - ), - instruction_type.encode_verify_proof(Some(context_state_info), &proof_data), - ]; - - let last_blockhash = ctx.get_new_latest_blockhash().await.unwrap(); - let tx = Transaction::new_signed_with_payer( - &instructions, - Some(&ctx.payer.pubkey()), - &[&ctx.payer, &context_state_account], - last_blockhash, - ); - ctx.banks_client.process_transaction(tx).await.unwrap(); - } - - let err = token - .confidential_transfer_transfer( - &alice_meta.token_account, - &bob_meta.token_account, - &alice.pubkey(), - Some(&ProofAccount::ContextAccount( - context_state_account.pubkey(), - )), - 0, - None, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - bob_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - &[&alice], - ) - .await - .unwrap_err(); - - assert_eq!( - err, - TokenClientError::Client(Box::new(TransportError::TransactionError( - TransactionError::InstructionError(0, InstructionError::InvalidArgument,) - ))) - ) -} - -#[tokio::test] -async fn confidential_transfer_transfer_with_split_proof_context() { - let authority = Keypair::new(); - let auto_approve_new_accounts = true; - let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); - let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into(); - - let mut context = TestContext::new().await; - context - .init_token_with_mint(vec![ - ExtensionInitializationParams::ConfidentialTransferMint { - authority: Some(authority.pubkey()), - auto_approve_new_accounts, - auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey), - }, - ]) - .await - .unwrap(); - - let TokenContext { - token, - alice, - bob, - mint_authority, - decimals, - .. - } = context.token_context.unwrap(); - - let alice_meta = ConfidentialTokenAccountMeta::new_with_tokens( - &token, - &alice, - None, - false, - false, - &mint_authority, - 42, - decimals, - ) - .await; - - let bob_meta = ConfidentialTokenAccountMeta::new_with_tokens( - &token, - &bob, - None, - false, - false, - &mint_authority, - 0, - decimals, - ) - .await; - - let state = token - .get_account_info(&alice_meta.token_account) - .await - .unwrap(); - let extension = state - .get_extension::() - .unwrap(); - let transfer_account_info = TransferAccountInfo::new(extension); - - let ( - equality_proof_data, - ciphertext_validity_proof_data, - range_proof_data, - source_decrypt_handles, - ) = transfer_account_info - .generate_split_transfer_proof_data( - 42, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - bob_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - ) - .unwrap(); - - let context_state_authority = Keypair::new(); - let equality_proof_context_state_account = Keypair::new(); - let ciphertext_validity_proof_context_state_account = Keypair::new(); - let range_proof_context_state_account = Keypair::new(); - - let transfer_context_state_accounts = TransferSplitContextStateAccounts { - equality_proof: &equality_proof_context_state_account.pubkey(), - ciphertext_validity_proof: &ciphertext_validity_proof_context_state_account.pubkey(), - range_proof: &range_proof_context_state_account.pubkey(), - authority: &context_state_authority.pubkey(), - no_op_on_uninitialized_split_context_state: false, - close_split_context_state_accounts: None, - }; - - // create context state accounts - token - .create_equality_and_ciphertext_validity_proof_context_states_for_transfer( - transfer_context_state_accounts, - &equality_proof_data, - &ciphertext_validity_proof_data, - &[ - &equality_proof_context_state_account, - &ciphertext_validity_proof_context_state_account, - ], - ) - .await - .unwrap(); - - token - .create_range_proof_context_state_for_transfer( - transfer_context_state_accounts, - &range_proof_data, - &range_proof_context_state_account, - ) - .await - .unwrap(); - - // create token22 confidential transfer instruction - token - .confidential_transfer_transfer_with_split_proofs( - &alice_meta.token_account, - &bob_meta.token_account, - &alice.pubkey(), - transfer_context_state_accounts, - 42, - None, - &alice_meta.aes_key, - &source_decrypt_handles, - &[&alice], - ) - .await - .unwrap(); - - // close context state accounts - token - .confidential_transfer_close_context_state( - &equality_proof_context_state_account.pubkey(), - &alice_meta.token_account, - &context_state_authority.pubkey(), - &[&context_state_authority], - ) - .await - .unwrap(); - - token - .confidential_transfer_close_context_state( - &ciphertext_validity_proof_context_state_account.pubkey(), - &alice_meta.token_account, - &context_state_authority.pubkey(), - &[&context_state_authority], - ) - .await - .unwrap(); - - token - .confidential_transfer_close_context_state( - &range_proof_context_state_account.pubkey(), - &alice_meta.token_account, - &context_state_authority.pubkey(), - &[&context_state_authority], - ) - .await - .unwrap(); - - // check balances - alice_meta - .check_balances( - &token, - ConfidentialTokenAccountBalances { - pending_balance_lo: 0, - pending_balance_hi: 0, - available_balance: 0, - decryptable_available_balance: 0, - }, - ) - .await; - - bob_meta - .check_balances( - &token, - ConfidentialTokenAccountBalances { - pending_balance_lo: 42, - pending_balance_hi: 0, - available_balance: 0, - decryptable_available_balance: 0, - }, - ) - .await; -} - -#[tokio::test] -async fn confidential_transfer_transfer_with_split_proof_contexts_in_parallel() { - let authority = Keypair::new(); - let auto_approve_new_accounts = true; - let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); - let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into(); - - let mut context = TestContext::new().await; - context - .init_token_with_mint(vec![ - ExtensionInitializationParams::ConfidentialTransferMint { - authority: Some(authority.pubkey()), - auto_approve_new_accounts, - auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey), - }, - ]) - .await - .unwrap(); - - let TokenContext { - token, - alice, - bob, - mint_authority, - decimals, - .. - } = context.token_context.unwrap(); - - let alice_meta = ConfidentialTokenAccountMeta::new_with_tokens( - &token, - &alice, - None, - false, - false, - &mint_authority, - 42, - decimals, - ) - .await; - - let bob_meta = ConfidentialTokenAccountMeta::new_with_tokens( - &token, - &bob, - None, - false, - false, - &mint_authority, - 0, - decimals, - ) - .await; - - let context_state_authority = Keypair::new(); - let equality_proof_context_state_account = Keypair::new(); - let ciphertext_validity_proof_context_state_account = Keypair::new(); - let range_proof_context_state_account = Keypair::new(); - - let lamport_destination = Pubkey::new_unique(); - - let close_split_context_state_accounts = CloseSplitContextStateAccounts { - lamport_destination: &lamport_destination, - zk_token_proof_program: &zk_token_proof_program::id(), - }; - - let transfer_context_state_accounts = TransferSplitContextStateAccounts { - equality_proof: &equality_proof_context_state_account.pubkey(), - ciphertext_validity_proof: &ciphertext_validity_proof_context_state_account.pubkey(), - range_proof: &range_proof_context_state_account.pubkey(), - authority: &context_state_authority.pubkey(), - no_op_on_uninitialized_split_context_state: true, - close_split_context_state_accounts: Some(close_split_context_state_accounts), - }; - - let equality_and_ciphertext_proof_signers = vec![ - &alice, - &equality_proof_context_state_account, - &ciphertext_validity_proof_context_state_account, - &context_state_authority, - ]; - let range_proof_signers = vec![ - &alice, - &range_proof_context_state_account, - &context_state_authority, - ]; - // With split proofs in parallel, one of the transactions does more work - // than the other, which isn't caught during the simulation to discover the - // compute unit limit. - let token = token.with_compute_unit_limit(ComputeUnitLimit::Static(500_000)); - token - .confidential_transfer_transfer_with_split_proofs_in_parallel( - &alice_meta.token_account, - &bob_meta.token_account, - &alice.pubkey(), - transfer_context_state_accounts, - 42, - None, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - bob_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - &equality_and_ciphertext_proof_signers, - &range_proof_signers, - ) - .await - .unwrap(); - - alice_meta - .check_balances( - &token, - ConfidentialTokenAccountBalances { - pending_balance_lo: 0, - pending_balance_hi: 0, - available_balance: 0, - decryptable_available_balance: 0, - }, - ) - .await; - - bob_meta - .check_balances( - &token, - ConfidentialTokenAccountBalances { - pending_balance_lo: 42, - pending_balance_hi: 0, - available_balance: 0, - decryptable_available_balance: 0, - }, - ) - .await; - - let error = token - .get_account(equality_proof_context_state_account.pubkey()) - .await - .unwrap_err(); - assert_eq!(error, TokenClientError::AccountNotFound); - - let error = token - .get_account(ciphertext_validity_proof_context_state_account.pubkey()) - .await - .unwrap_err(); - assert_eq!(error, TokenClientError::AccountNotFound); - - let error = token - .get_account(range_proof_context_state_account.pubkey()) - .await - .unwrap_err(); - assert_eq!(error, TokenClientError::AccountNotFound); - - let lamport_destination = token.get_account(lamport_destination).await.unwrap(); - assert!(lamport_destination.lamports > 0); -} - -#[tokio::test] -async fn confidential_transfer_transfer_with_fee_and_split_proof_context() { - let transfer_fee_authority = Keypair::new(); - let withdraw_withheld_authority = Keypair::new(); - - let confidential_transfer_authority = Keypair::new(); - let auto_approve_new_accounts = true; - let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); - let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into(); - - let confidential_transfer_fee_authority = Keypair::new(); - let withdraw_withheld_authority_elgamal_keypair = ElGamalKeypair::new_rand(); - let withdraw_withheld_authority_elgamal_pubkey = - (*withdraw_withheld_authority_elgamal_keypair.pubkey()).into(); - - let mut context = TestContext::new().await; - context - .init_token_with_mint(vec![ - ExtensionInitializationParams::TransferFeeConfig { - transfer_fee_config_authority: Some(transfer_fee_authority.pubkey()), - withdraw_withheld_authority: Some(withdraw_withheld_authority.pubkey()), - transfer_fee_basis_points: TEST_FEE_BASIS_POINTS, - maximum_fee: TEST_MAXIMUM_FEE, - }, - ExtensionInitializationParams::ConfidentialTransferMint { - authority: Some(confidential_transfer_authority.pubkey()), - auto_approve_new_accounts, - auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey), - }, - ExtensionInitializationParams::ConfidentialTransferFeeConfig { - authority: Some(confidential_transfer_fee_authority.pubkey()), - withdraw_withheld_authority_elgamal_pubkey, - }, - ]) - .await - .unwrap(); - - let TokenContext { - token, - alice, - bob, - mint_authority, - decimals, - .. - } = context.token_context.unwrap(); - - let alice_meta = ConfidentialTokenAccountMeta::new_with_tokens( - &token, - &alice, - None, - false, - true, - &mint_authority, - 100, - decimals, - ) - .await; - - let bob_meta = ConfidentialTokenAccountMeta::new(&token, &bob, None, false, true).await; - - let state = token - .get_account_info(&alice_meta.token_account) - .await - .unwrap(); - let extension = state - .get_extension::() - .unwrap(); - let transfer_account_info = TransferAccountInfo::new(extension); - - let current_source_available_balance = - transfer_account_info.available_balance.try_into().unwrap(); - let current_decryptable_available_balance = transfer_account_info - .decryptable_available_balance - .try_into() - .unwrap(); - - let fee_parameters = FeeParameters { - fee_rate_basis_points: TEST_FEE_BASIS_POINTS, - maximum_fee: TEST_MAXIMUM_FEE, - }; - - let ( - equality_proof_data, - transfer_amount_ciphertext_validity_proof_data, - fee_sigma_proof_data, - fee_ciphertext_validity_proof_data, - range_proof_data, - source_decrypt_handles, - ) = transfer_with_fee_split_proof_data( - ¤t_source_available_balance, - ¤t_decryptable_available_balance, - 100, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - bob_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - withdraw_withheld_authority_elgamal_keypair.pubkey(), - &fee_parameters, - ) - .unwrap(); - - let context_state_authority = Keypair::new(); - let equality_proof_context_state_account = Keypair::new(); - let transfer_amount_ciphertext_validity_proof_context_state_account = Keypair::new(); - let fee_sigma_proof_context_state_account = Keypair::new(); - let fee_ciphertext_validity_proof_context_state_account = Keypair::new(); - let range_proof_context_state_account = Keypair::new(); - - let transfer_context_state_accounts = TransferWithFeeSplitContextStateAccounts { - equality_proof: &equality_proof_context_state_account.pubkey(), - transfer_amount_ciphertext_validity_proof: - &transfer_amount_ciphertext_validity_proof_context_state_account.pubkey(), - fee_sigma_proof: &fee_sigma_proof_context_state_account.pubkey(), - fee_ciphertext_validity_proof: &fee_ciphertext_validity_proof_context_state_account - .pubkey(), - range_proof: &range_proof_context_state_account.pubkey(), - authority: &context_state_authority.pubkey(), - no_op_on_uninitialized_split_context_state: false, - close_split_context_state_accounts: None, - }; - - // create context state accounts - token - .create_equality_and_ciphertext_validity_proof_context_states_for_transfer_with_fee( - transfer_context_state_accounts, - &equality_proof_data, - &transfer_amount_ciphertext_validity_proof_data, - &[ - &equality_proof_context_state_account, - &transfer_amount_ciphertext_validity_proof_context_state_account, - ], - ) - .await - .unwrap(); - - token - .create_fee_sigma_and_ciphertext_validity_proof_context_states_for_transfer_with_fee( - transfer_context_state_accounts, - &fee_sigma_proof_data, - &fee_ciphertext_validity_proof_data, - &[ - &fee_sigma_proof_context_state_account, - &fee_ciphertext_validity_proof_context_state_account, - ], - ) - .await - .unwrap(); - - token - .create_range_proof_context_state_for_transfer_with_fee( - transfer_context_state_accounts, - &range_proof_data, - &[&range_proof_context_state_account], - ) - .await - .unwrap(); - - // create token22 confidential transfer instruction - token - .confidential_transfer_transfer_with_fee_and_split_proofs( - &alice_meta.token_account, - &bob_meta.token_account, - &alice.pubkey(), - transfer_context_state_accounts, - 100, - None, - &alice_meta.aes_key, - &source_decrypt_handles, - &[&alice], - ) - .await - .unwrap(); - - // close context state accounts - token - .confidential_transfer_close_context_state( - &equality_proof_context_state_account.pubkey(), - &alice_meta.token_account, - &context_state_authority.pubkey(), - &[&context_state_authority], - ) - .await - .unwrap(); - - token - .confidential_transfer_close_context_state( - &transfer_amount_ciphertext_validity_proof_context_state_account.pubkey(), - &alice_meta.token_account, - &context_state_authority.pubkey(), - &[&context_state_authority], - ) - .await - .unwrap(); - - token - .confidential_transfer_close_context_state( - &fee_sigma_proof_context_state_account.pubkey(), - &alice_meta.token_account, - &context_state_authority.pubkey(), - &[&context_state_authority], - ) - .await - .unwrap(); - - token - .confidential_transfer_close_context_state( - &fee_ciphertext_validity_proof_context_state_account.pubkey(), - &alice_meta.token_account, - &context_state_authority.pubkey(), - &[&context_state_authority], - ) - .await - .unwrap(); - - token - .confidential_transfer_close_context_state( - &range_proof_context_state_account.pubkey(), - &alice_meta.token_account, - &context_state_authority.pubkey(), - &[&context_state_authority], - ) - .await - .unwrap(); - - // check balances - alice_meta - .check_balances( - &token, - ConfidentialTokenAccountBalances { - pending_balance_lo: 0, - pending_balance_hi: 0, - available_balance: 0, - decryptable_available_balance: 0, - }, - ) - .await; - - bob_meta - .check_balances( - &token, - ConfidentialTokenAccountBalances { - pending_balance_lo: 97, - pending_balance_hi: 0, - available_balance: 0, - decryptable_available_balance: 0, - }, - ) - .await; -} - -#[tokio::test] -async fn confidential_transfer_transfer_with_fee_and_split_proof_context_in_parallel() { - let transfer_fee_authority = Keypair::new(); - let withdraw_withheld_authority = Keypair::new(); - - let confidential_transfer_authority = Keypair::new(); - let auto_approve_new_accounts = true; - let auditor_elgamal_keypair = ElGamalKeypair::new_rand(); - let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into(); - - let confidential_transfer_fee_authority = Keypair::new(); - let withdraw_withheld_authority_elgamal_keypair = ElGamalKeypair::new_rand(); - let withdraw_withheld_authority_elgamal_pubkey = - (*withdraw_withheld_authority_elgamal_keypair.pubkey()).into(); - - let mut context = TestContext::new().await; - context - .init_token_with_mint(vec![ - ExtensionInitializationParams::TransferFeeConfig { - transfer_fee_config_authority: Some(transfer_fee_authority.pubkey()), - withdraw_withheld_authority: Some(withdraw_withheld_authority.pubkey()), - transfer_fee_basis_points: TEST_FEE_BASIS_POINTS, - maximum_fee: TEST_MAXIMUM_FEE, - }, - ExtensionInitializationParams::ConfidentialTransferMint { - authority: Some(confidential_transfer_authority.pubkey()), - auto_approve_new_accounts, - auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey), - }, - ExtensionInitializationParams::ConfidentialTransferFeeConfig { - authority: Some(confidential_transfer_fee_authority.pubkey()), - withdraw_withheld_authority_elgamal_pubkey, - }, - ]) - .await - .unwrap(); - - let TokenContext { - token, - alice, - bob, - mint_authority, - decimals, - .. - } = context.token_context.unwrap(); - - let alice_meta = ConfidentialTokenAccountMeta::new_with_tokens( - &token, - &alice, - None, - false, - true, - &mint_authority, - 100, - decimals, - ) - .await; - - let bob_meta = ConfidentialTokenAccountMeta::new(&token, &bob, None, false, true).await; - - let context_state_authority = Keypair::new(); - let equality_proof_context_state_account = Keypair::new(); - let transfer_amount_ciphertext_validity_proof_context_state_account = Keypair::new(); - let fee_sigma_proof_context_state_account = Keypair::new(); - let fee_ciphertext_validity_proof_context_state_account = Keypair::new(); - let range_proof_context_state_account = Keypair::new(); - - let lamport_destination = Pubkey::new_unique(); - - let close_split_context_state_accounts = CloseSplitContextStateAccounts { - lamport_destination: &lamport_destination, - zk_token_proof_program: &zk_token_proof_program::id(), - }; - - let transfer_context_state_accounts = TransferWithFeeSplitContextStateAccounts { - equality_proof: &equality_proof_context_state_account.pubkey(), - transfer_amount_ciphertext_validity_proof: - &transfer_amount_ciphertext_validity_proof_context_state_account.pubkey(), - fee_sigma_proof: &fee_sigma_proof_context_state_account.pubkey(), - fee_ciphertext_validity_proof: &fee_ciphertext_validity_proof_context_state_account - .pubkey(), - range_proof: &range_proof_context_state_account.pubkey(), - authority: &context_state_authority.pubkey(), - no_op_on_uninitialized_split_context_state: true, - close_split_context_state_accounts: Some(close_split_context_state_accounts), - }; - - let equality_and_ciphertext_proof_signers = vec![ - &alice, - &equality_proof_context_state_account, - &transfer_amount_ciphertext_validity_proof_context_state_account, - &context_state_authority, - ]; - let fee_sigma_proof_signers = vec![ - &alice, - &fee_sigma_proof_context_state_account, - &fee_ciphertext_validity_proof_context_state_account, - &context_state_authority, - ]; - let range_proof_signers = vec![ - &alice, - &range_proof_context_state_account, - &context_state_authority, - ]; - // With split proofs in parallel, one of the transactions does more work - // than the other, which isn't caught during the simulation to discover the - // compute unit limit. - let token = token.with_compute_unit_limit(ComputeUnitLimit::Static(500_000)); - token - .confidential_transfer_transfer_with_fee_and_split_proofs_in_parallel( - &alice_meta.token_account, - &bob_meta.token_account, - &alice.pubkey(), - transfer_context_state_accounts, - 100, - None, - &alice_meta.elgamal_keypair, - &alice_meta.aes_key, - bob_meta.elgamal_keypair.pubkey(), - Some(auditor_elgamal_keypair.pubkey()), - withdraw_withheld_authority_elgamal_keypair.pubkey(), - TEST_FEE_BASIS_POINTS, - TEST_MAXIMUM_FEE, - &equality_and_ciphertext_proof_signers, - &fee_sigma_proof_signers, - &range_proof_signers, - ) - .await - .unwrap(); - - // check balances - alice_meta - .check_balances( - &token, - ConfidentialTokenAccountBalances { - pending_balance_lo: 0, - pending_balance_hi: 0, - available_balance: 0, - decryptable_available_balance: 0, - }, - ) - .await; - - bob_meta - .check_balances( - &token, - ConfidentialTokenAccountBalances { - pending_balance_lo: 97, - pending_balance_hi: 0, - available_balance: 0, - decryptable_available_balance: 0, - }, - ) - .await; - - let error = token - .get_account(equality_proof_context_state_account.pubkey()) - .await - .unwrap_err(); - assert_eq!(error, TokenClientError::AccountNotFound); - - let error = token - .get_account(transfer_amount_ciphertext_validity_proof_context_state_account.pubkey()) - .await - .unwrap_err(); - assert_eq!(error, TokenClientError::AccountNotFound); - - let error = token - .get_account(fee_sigma_proof_context_state_account.pubkey()) - .await - .unwrap_err(); - assert_eq!(error, TokenClientError::AccountNotFound); - - let error = token - .get_account(fee_ciphertext_validity_proof_context_state_account.pubkey()) - .await - .unwrap_err(); - assert_eq!(error, TokenClientError::AccountNotFound); - - let error = token - .get_account(range_proof_context_state_account.pubkey()) - .await - .unwrap_err(); - assert_eq!(error, TokenClientError::AccountNotFound); - - let lamport_destination = token.get_account(lamport_destination).await.unwrap(); - assert!(lamport_destination.lamports > 0); -} diff --git a/token/program-2022-test/tests/confidential_transfer_fee.rs b/token/program-2022-test/tests/confidential_transfer_fee.rs index 1221c773828..83b1921b24d 100644 --- a/token/program-2022-test/tests/confidential_transfer_fee.rs +++ b/token/program-2022-test/tests/confidential_transfer_fee.rs @@ -521,6 +521,10 @@ async fn confidential_transfer_withdraw_withheld_tokens_from_mint() { &bob_meta.token_account, &alice.pubkey(), None, + None, + None, + None, + None, 100, None, &alice_meta.elgamal_keypair, @@ -679,6 +683,10 @@ async fn confidential_transfer_withdraw_withheld_tokens_from_mint_with_record_ac &bob_meta.token_account, &alice.pubkey(), None, + None, + None, + None, + None, 100, None, &alice_meta.elgamal_keypair, @@ -841,6 +849,10 @@ async fn confidential_transfer_withdraw_withheld_tokens_from_accounts() { &bob_meta.token_account, &alice.pubkey(), None, + None, + None, + None, + None, 100, None, &alice_meta.elgamal_keypair, @@ -971,6 +983,10 @@ async fn confidential_transfer_withdraw_withheld_tokens_from_accounts_with_recor &bob_meta.token_account, &alice.pubkey(), None, + None, + None, + None, + None, 100, None, &alice_meta.elgamal_keypair, @@ -1137,6 +1153,10 @@ async fn confidential_transfer_withdraw_withheld_tokens_from_mint_with_proof_con &bob_meta.token_account, &alice.pubkey(), None, + None, + None, + None, + None, 100, None, &alice_meta.elgamal_keypair, @@ -1308,6 +1328,10 @@ async fn confidential_transfer_withdraw_withheld_tokens_from_accounts_with_proof &bob_meta.token_account, &alice.pubkey(), None, + None, + None, + None, + None, 100, None, &alice_meta.elgamal_keypair, @@ -1501,6 +1525,10 @@ async fn confidential_transfer_harvest_withheld_tokens_to_mint() { &bob_meta.token_account, &alice.pubkey(), None, + None, + None, + None, + None, 100, None, &alice_meta.elgamal_keypair, diff --git a/token/program-2022-test/tests/transfer_hook.rs b/token/program-2022-test/tests/transfer_hook.rs index 5d9f301591e..7fba3db80f0 100644 --- a/token/program-2022-test/tests/transfer_hook.rs +++ b/token/program-2022-test/tests/transfer_hook.rs @@ -854,6 +854,8 @@ async fn success_confidential_transfer() { &bob_meta.token_account, &alice.pubkey(), None, + None, + None, amount, None, &alice_meta.elgamal_keypair, From b9343d95702b71b7ccbf0bb25a3c0605ecba2c0c Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 9 Aug 2024 13:36:44 +0900 Subject: [PATCH 7/9] update token-cli --- token/cli/src/command.rs | 72 +++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/token/cli/src/command.rs b/token/cli/src/command.rs index 1faf086127a..cd0909ea9fe 100644 --- a/token/cli/src/command.rs +++ b/token/cli/src/command.rs @@ -41,7 +41,6 @@ use { account_info::{ ApplyPendingBalanceAccountInfo, TransferAccountInfo, WithdrawAccountInfo, }, - instruction::TransferSplitContextStateAccounts, ConfidentialTransferAccount, ConfidentialTransferMint, }, confidential_transfer_fee::ConfidentialTransferFeeConfig, @@ -1567,6 +1566,7 @@ async fn command_transfer( }); let context_state_authority = config.fee_payer()?; + let context_state_authority_pubkey = context_state_authority.pubkey(); let equality_proof_context_state_account = Keypair::new(); let equality_proof_pubkey = equality_proof_context_state_account.pubkey(); let ciphertext_validity_proof_context_state_account = Keypair::new(); @@ -1575,91 +1575,90 @@ async fn command_transfer( let range_proof_context_state_account = Keypair::new(); let range_proof_pubkey = range_proof_context_state_account.pubkey(); - let transfer_context_state_accounts = TransferSplitContextStateAccounts { - equality_proof: &equality_proof_pubkey, - ciphertext_validity_proof: &ciphertext_validity_proof_pubkey, - range_proof: &range_proof_pubkey, - authority: &context_state_authority.pubkey(), - no_op_on_uninitialized_split_context_state: false, - close_split_context_state_accounts: None, - }; - let state = token.get_account_info(&sender).await.unwrap(); let extension = state .get_extension::() .unwrap(); let transfer_account_info = TransferAccountInfo::new(extension); - let ( - equality_proof_data, - ciphertext_validity_proof_data, - range_proof_data, - source_decrypt_handles, - ) = transfer_account_info - .generate_split_transfer_proof_data( - transfer_balance, - &args.sender_elgamal_keypair, - &args.sender_aes_key, - &recipient_elgamal_pubkey, - auditor_elgamal_pubkey.as_ref(), - ) - .unwrap(); + let (equality_proof_data, ciphertext_validity_proof_data, range_proof_data) = + transfer_account_info + .generate_split_transfer_proof_data( + transfer_balance, + &args.sender_elgamal_keypair, + &args.sender_aes_key, + &recipient_elgamal_pubkey, + auditor_elgamal_pubkey.as_ref(), + ) + .unwrap(); // setup proofs let _ = try_join!( token.create_range_proof_context_state_for_transfer( - transfer_context_state_accounts, + &range_proof_pubkey, + &context_state_authority_pubkey, &range_proof_data, &range_proof_context_state_account, ), token.create_equality_proof_context_state_for_transfer( - transfer_context_state_accounts, + &equality_proof_pubkey, + &context_state_authority_pubkey, &equality_proof_data, &equality_proof_context_state_account, ), token.create_ciphertext_validity_proof_context_state_for_transfer( - transfer_context_state_accounts, + &ciphertext_validity_proof_pubkey, + &context_state_authority_pubkey, &ciphertext_validity_proof_data, &ciphertext_validity_proof_context_state_account, ) )?; // do the transfer + let equality_proof_context_proof_account = + ProofAccount::ContextAccount(equality_proof_pubkey); + let ciphertext_validity_proof_context_proof_account = + ProofAccount::ContextAccount(ciphertext_validity_proof_pubkey); + let range_proof_context_proof_account = + ProofAccount::ContextAccount(range_proof_pubkey); + let transfer_result = token - .confidential_transfer_transfer_with_split_proofs( + .confidential_transfer_transfer( &sender, &recipient_token_account, &sender_owner, - transfer_context_state_accounts, + Some(&equality_proof_context_proof_account), + Some(&ciphertext_validity_proof_context_proof_account), + Some(&range_proof_context_proof_account), transfer_balance, Some(transfer_account_info), + &args.sender_elgamal_keypair, &args.sender_aes_key, - &source_decrypt_handles, + &recipient_elgamal_pubkey, + auditor_elgamal_pubkey.as_ref(), &bulk_signers, ) .await?; // close context state accounts - let context_state_authority_pubkey = context_state_authority.pubkey(); - let close_context_state_signers = &[context_state_authority]; let _ = try_join!( token.confidential_transfer_close_context_state( &equality_proof_pubkey, &sender, &context_state_authority_pubkey, - close_context_state_signers, + &context_state_authority, ), token.confidential_transfer_close_context_state( &ciphertext_validity_proof_pubkey, &sender, &context_state_authority_pubkey, - close_context_state_signers, + &context_state_authority, ), token.confidential_transfer_close_context_state( &range_proof_pubkey, &sender, &context_state_authority_pubkey, - close_context_state_signers, + &context_state_authority, ), )?; @@ -3361,13 +3360,12 @@ async fn command_deposit_withdraw_confidential_tokens( // close context state account let context_state_authority_pubkey = context_state_authority.pubkey(); - let close_context_state_signers = &[context_state_authority]; token .confidential_transfer_close_context_state( &context_state_pubkey, &token_account_address, &context_state_authority_pubkey, - close_context_state_signers, + &context_state_authority, ) .await? } From 231a1050849e0b694d3e7658d8d10405084053c0 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Fri, 9 Aug 2024 13:37:02 +0900 Subject: [PATCH 8/9] cargo fmt --- token/client/src/proof_generation.rs | 4 +- token/client/src/token.rs | 16 +- .../ciphertext_extraction.rs | 4 +- .../confidential_transfer/instruction.rs | 148 +++++++++++------- 4 files changed, 103 insertions(+), 69 deletions(-) diff --git a/token/client/src/proof_generation.rs b/token/client/src/proof_generation.rs index 588d718d8e0..63581eb396f 100644 --- a/token/client/src/proof_generation.rs +++ b/token/client/src/proof_generation.rs @@ -126,8 +126,8 @@ pub fn transfer_with_fee_split_proof_data( ) .map_err(|_| TokenError::ProofGeneration)?; - // encrypt the transfer amount under the source, destination and auditor ElGamal public - // key + // encrypt the transfer amount under the source, destination and auditor ElGamal + // public key let transfer_amount_destination_auditor_ciphertext_lo = GroupedElGamal::encrypt_with( [ source_elgamal_keypair.pubkey(), diff --git a/token/client/src/token.rs b/token/client/src/token.rs index b8cef8347f4..824875b6d31 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -2212,7 +2212,8 @@ where ) .map_err(|_| TokenError::ProofGeneration)?; - // if proof accounts are none, then proof data must be included as instruction data + // if proof accounts are none, then proof data must be included as instruction + // data let equality_proof_data = equality_proof_account .is_none() .then_some(equality_proof_data); @@ -2380,7 +2381,8 @@ where .await } - /// Create an equality proof context state account for a confidential transfer. + /// Create an equality proof context state account for a confidential + /// transfer. #[allow(clippy::too_many_arguments)] pub async fn create_equality_proof_context_state_for_transfer( &self, @@ -2695,7 +2697,8 @@ where self.process_ixs(&instructions, signing_keypairs).await } - /// Create equality proof context state accounts for a confidential transfer with fee. + /// Create equality proof context state accounts for a confidential transfer + /// with fee. #[allow(clippy::too_many_arguments)] pub async fn create_equality_proof_context_state_for_transfer_with_fee( &self, @@ -2777,7 +2780,8 @@ where .await } - /// Create a fee sigma proof context state account for a confidential transfer with fee. + /// Create a fee sigma proof context state account for a confidential + /// transfer with fee. #[allow(clippy::too_many_arguments)] pub async fn create_fee_sigma_proof_context_state_for_transfer_with_fee( &self, @@ -2816,8 +2820,8 @@ where .await } - /// Create a fee ciphertext validity proof context state account for a confidential transfer - /// with fee. + /// Create a fee ciphertext validity proof context state account for a + /// confidential transfer with fee. #[allow(clippy::too_many_arguments)] pub async fn create_fee_ciphertext_validity_proof_context_state_for_transfer_with_fee< S: Signer, diff --git a/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs b/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs index a43c50d1d77..d4bfd4ebb9d 100644 --- a/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs +++ b/token/program-2022/src/extension/confidential_transfer/ciphertext_extraction.rs @@ -245,8 +245,8 @@ impl TransferProofContextInfo { commitment: new_source_commitment, } = equality_proof_context; - // The ciphertext validity proof context consists of the source ElGamal public key, - // destination ElGamal + // The ciphertext validity proof context consists of the source ElGamal public + // key, destination ElGamal // public key, auditor ElGamal public key, and the transfer amount // ciphertexts. All of these fields should be returned as part of // `TransferProofContextInfo`. In addition, the commitments pertaining diff --git a/token/program-2022/src/extension/confidential_transfer/instruction.rs b/token/program-2022/src/extension/confidential_transfer/instruction.rs index 27cd136b936..595dbd30c94 100644 --- a/token/program-2022/src/extension/confidential_transfer/instruction.rs +++ b/token/program-2022/src/extension/confidential_transfer/instruction.rs @@ -244,14 +244,15 @@ pub enum ConfidentialTransferInstruction { /// Transfer tokens confidentially. /// - /// In order for this instruction to be successfully processed, it must be accompanied by - /// the following list of `zk_token_proof` program instructions: + /// In order for this instruction to be successfully processed, it must be + /// accompanied by the following list of `zk_token_proof` program + /// instructions: /// - `VerifyCiphertextCommitmentEqualityProof` /// - `VerifyBatchedGroupedCiphertext3HandlesValidityProof` /// - `VerifyBatchedRangeProofU128` - /// These instructions can be accompanied in the same transaction or can be pre-verified into a - /// context state account, in which case, only their context state account addresses need to be - /// provided. + /// These instructions can be accompanied in the same transaction or can be + /// pre-verified into a context state account, in which case, only their + /// context state account addresses need to be provided. /// /// Fails if the associated mint is extended as `NonTransferable`. /// @@ -259,22 +260,28 @@ pub enum ConfidentialTransferInstruction { /// 1. `[writable]` The source SPL Token account. /// 2. `[]` The token mint. /// 3. `[writable]` The destination SPL Token account. - /// 4. `[]` (Optional) Instructions sysvar if at least one of the `zk_token_proof` - /// instructions are included in the same transaction. - /// 5. `[]` (Optional) Equality proof record account or context state account. - /// 6. `[]` (Optional) Ciphertext validity proof record account or context state account. - /// 7. `[]` (Optional) Range proof record account or context state account. + /// 4. `[]` (Optional) Instructions sysvar if at least one of the + /// `zk_token_proof` instructions are included in the same transaction. + /// 5. `[]` (Optional) Equality proof record account or context state + /// account. + /// 6. `[]` (Optional) Ciphertext validity proof record account or context + /// state account. + /// 7. `[]` (Optional) Range proof record account or context state + /// account. /// 8. `[signer]` The single source account owner. /// /// * Multisignature owner/delegate /// 1. `[writable]` The source SPL Token account. /// 2. `[]` The token mint. /// 3. `[writable]` The destination SPL Token account. - /// 4. `[]` (Optional) Instructions sysvar if at least one of the `zk_token_proof` - /// instructions are included in the same transaction. - /// 5. `[]` (Optional) Equality proof record account or context state account. - /// 6. `[]` (Optional) Ciphertext validity proof record account or context state account. - /// 7. `[]` (Optional) Range proof record account or context state account. + /// 4. `[]` (Optional) Instructions sysvar if at least one of the + /// `zk_token_proof` instructions are included in the same transaction. + /// 5. `[]` (Optional) Equality proof record account or context state + /// account. + /// 6. `[]` (Optional) Ciphertext validity proof record account or context + /// state account. + /// 7. `[]` (Optional) Range proof record account or context state + /// account. /// 8. `[]` The multisig source account owner. /// 9.. `[signer]` Required M signer accounts for the SPL Token Multisig /// account. @@ -398,16 +405,18 @@ pub enum ConfidentialTransferInstruction { /// Transfer tokens confidentially with fee. /// - /// In order for this instruction to be successfully processed, it must be accompanied by the - /// following list of `zk_token_proof` program instructions: + /// In order for this instruction to be successfully processed, it must be + /// accompanied by the following list of `zk_token_proof` program + /// instructions: /// - `VerifyCiphertextCommitmentEqualityProof` - /// - `VerifyBatchedGroupedCiphertext3HandlesValidityProof` (transfer amount ciphertext) + /// - `VerifyBatchedGroupedCiphertext3HandlesValidityProof` (transfer amount + /// ciphertext) /// - `FeeSigmaProof` /// - `VerifyBatchedGroupedCiphertext2HandlesValidityProof` (fee ciphertext) /// - `VerifyBatchedRangeProofU256` - /// These instructions can be accompanied in the same transaction or can be pre-verified into a - /// context state account, in which case, only their context state account addresses need to be - /// provided. + /// These instructions can be accompanied in the same transaction or can be + /// pre-verified into a context state account, in which case, only their + /// context state account addresses need to be provided. /// /// The same restrictions for the `Transfer` applies to /// `TransferWithFee`. Namely, the instruction fails if the @@ -417,26 +426,36 @@ pub enum ConfidentialTransferInstruction { /// 1. `[writable]` The source SPL Token account. /// 2. `[]` The token mint. /// 3. `[writable]` The destination SPL Token account. - /// 4. `[]` (Optional) Instructions sysvar if at least one of the `zk_token_proof` - /// instructions are included in the same transaction. - /// 5. `[]` (Optional) Equality proof record account or context state account. - /// 6. `[]` (Optional) Transfer amount ciphertext validity proof record account or context state account. - /// 7. `[]` (Optional) Fee sigma proof record account or context state account. - /// 8. `[]` (Optional) Fee ciphertext validity proof record account or context state account. - /// 9. `[]` (Optional) Range proof record account or context state account. + /// 4. `[]` (Optional) Instructions sysvar if at least one of the + /// `zk_token_proof` instructions are included in the same transaction. + /// 5. `[]` (Optional) Equality proof record account or context state + /// account. + /// 6. `[]` (Optional) Transfer amount ciphertext validity proof record + /// account or context state account. + /// 7. `[]` (Optional) Fee sigma proof record account or context state + /// account. + /// 8. `[]` (Optional) Fee ciphertext validity proof record account or + /// context state account. + /// 9. `[]` (Optional) Range proof record account or context state + /// account. /// 10. `[signer]` The source account owner. /// /// * Transfer with fee /// 1. `[writable]` The source SPL Token account. /// 2. `[]` The token mint. /// 3. `[writable]` The destination SPL Token account. - /// 4. `[]` (Optional) Instructions sysvar if at least one of the `zk_token_proof` - /// instructions are included in the same transaction. - /// 5. `[]` (Optional) Equality proof record account or context state account. - /// 6. `[]` (Optional) Transfer amount ciphertext validity proof record account or context state account. - /// 7. `[]` (Optional) Fee sigma proof record account or context state account. - /// 8. `[]` (Optional) Fee ciphertext validity proof record account or context state account. - /// 9. `[]` (Optional) Range proof record account or context state account. + /// 4. `[]` (Optional) Instructions sysvar if at least one of the + /// `zk_token_proof` instructions are included in the same transaction. + /// 5. `[]` (Optional) Equality proof record account or context state + /// account. + /// 6. `[]` (Optional) Transfer amount ciphertext validity proof record + /// account or context state account. + /// 7. `[]` (Optional) Fee sigma proof record account or context state + /// account. + /// 8. `[]` (Optional) Fee ciphertext validity proof record account or + /// context state account. + /// 9. `[]` (Optional) Range proof record account or context state + /// account. /// 10. `[]` The multisig source account owner. /// 11.. `[signer]` Required M signer accounts for the SPL Token Multisig /// @@ -545,17 +564,19 @@ pub struct TransferInstructionData { /// The new source decryptable balance if the transfer succeeds #[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))] pub new_source_decryptable_available_balance: DecryptableBalance, - /// Relative location of the `ProofInstruction::VerifyCiphertextCommitmentEqualityProof` instruction + /// Relative location of the + /// `ProofInstruction::VerifyCiphertextCommitmentEqualityProof` instruction /// to the `Transfer` instruction in the transaction. If the offset is /// `0`, then use a context state account for the proof. pub equality_proof_instruction_offset: i8, - /// Relative location of the `ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidityProof` instruction - /// to the `Transfer` instruction in the transaction. If the offset is - /// `0`, then use a context state account for the proof. + /// Relative location of the + /// `ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidityProof` + /// instruction to the `Transfer` instruction in the transaction. If the + /// offset is `0`, then use a context state account for the proof. pub ciphertext_validity_proof_instruction_offset: i8, - /// Relative location of the `ProofInstruction::BatchedRangeProofU128Data` instruction - /// to the `Transfer` instruction in the transaction. If the offset is - /// `0`, then use a context state account for the proof. + /// Relative location of the `ProofInstruction::BatchedRangeProofU128Data` + /// instruction to the `Transfer` instruction in the transaction. If the + /// offset is `0`, then use a context state account for the proof. pub range_proof_instruction_offset: i8, } @@ -582,25 +603,32 @@ pub struct TransferWithFeeInstructionData { /// The new source decryptable balance if the transfer succeeds #[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))] pub new_source_decryptable_available_balance: DecryptableBalance, - /// Relative location of the `ProofInstruction::VerifyCiphertextCommitmentEqualityProof` instruction - /// to the `TransferWithFee` instruction in the transaction. If the offset is - /// `0`, then use a context state account for the proof. + /// Relative location of the + /// `ProofInstruction::VerifyCiphertextCommitmentEqualityProof` instruction + /// to the `TransferWithFee` instruction in the transaction. If the offset + /// is `0`, then use a context state account for the proof. pub equality_proof_instruction_offset: i8, - /// Relative location of the `ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidityProof` instruction - /// to the `TransferWithFee` instruction in the transaction. If the offset is - /// `0`, then use a context state account for the proof. + /// Relative location of the + /// `ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidityProof` + /// instruction to the `TransferWithFee` instruction in the transaction. + /// If the offset is `0`, then use a context state account for the + /// proof. pub transfer_amount_ciphertext_validity_proof_instruction_offset: i8, - /// Relative location of the `ProofInstruction::VerifyFeeSigmaProof` instruction - /// to the `TransferWithFee` instruction in the transaction. If the offset is - /// `0`, then use a context state account for the proof. + /// Relative location of the `ProofInstruction::VerifyFeeSigmaProof` + /// instruction to the `TransferWithFee` instruction in the transaction. + /// If the offset is `0`, then use a context state account for the + /// proof. pub fee_sigma_proof_instruction_offset: i8, - /// Relative location of the `ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidityProof` instruction - /// to the `TransferWithFee` instruction in the transaction. If the offset is - /// `0`, then use a context state account for the proof. + /// Relative location of the + /// `ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidityProof` + /// instruction to the `TransferWithFee` instruction in the transaction. + /// If the offset is `0`, then use a context state account for the + /// proof. pub fee_ciphertext_validity_proof_instruction_offset: i8, - /// Relative location of the `ProofInstruction::BatchedRangeProofU256Data` instruction - /// to the `TransferWithFee` instruction in the transaction. If the offset is - /// `0`, then use a context state account for the proof. + /// Relative location of the `ProofInstruction::BatchedRangeProofU256Data` + /// instruction to the `TransferWithFee` instruction in the transaction. + /// If the offset is `0`, then use a context state account for the + /// proof. pub range_proof_instruction_offset: i8, } @@ -1039,7 +1067,8 @@ pub fn inner_transfer( AccountMeta::new(*destination_token_account, false), ]; - // if at least one of the proof locations is an instruction offset, sysvar account is needed + // if at least one of the proof locations is an instruction offset, sysvar + // account is needed if equality_proof_data_location.is_instruction_offset() || ciphertext_validity_proof_data_location.is_instruction_offset() || range_proof_data_location.is_instruction_offset() @@ -1365,7 +1394,8 @@ pub fn inner_transfer_with_fee( AccountMeta::new(*destination_token_account, false), ]; - // if at least one of the proof locations is an instruction offset, sysvar account is needed + // if at least one of the proof locations is an instruction offset, sysvar + // account is needed if equality_proof_data_location.is_instruction_offset() || transfer_amount_ciphertext_validity_proof_data_location.is_instruction_offset() || fee_sigma_proof_data_location.is_instruction_offset() From 8e38e6e90780c89cd8b83a527506be0cefda8796 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Sun, 11 Aug 2024 11:21:01 +0900 Subject: [PATCH 9/9] Update token/program-2022/src/extension/confidential_transfer/instruction.rs Co-authored-by: Jon C --- .../src/extension/confidential_transfer/instruction.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/token/program-2022/src/extension/confidential_transfer/instruction.rs b/token/program-2022/src/extension/confidential_transfer/instruction.rs index 595dbd30c94..f1807cfaeec 100644 --- a/token/program-2022/src/extension/confidential_transfer/instruction.rs +++ b/token/program-2022/src/extension/confidential_transfer/instruction.rs @@ -460,7 +460,7 @@ pub enum ConfidentialTransferInstruction { /// 11.. `[signer]` Required M signer accounts for the SPL Token Multisig /// /// Data expected by this instruction: - /// `TransferWithFeeProofsInstructionData` + /// `TransferWithFeeInstructionData` TransferWithFee, }