From 10eeafd3d6eca190d0a1e9be035637cae167fb12 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Mon, 21 Mar 2022 16:10:33 -0400 Subject: [PATCH] zk-token-sdk: handle edge cases for transfer with fee (#23804) * zk-token-sdk: handle edge cases for transfer with fee * zk-token-sdk: clippy * zk-token-sdk: clippy * zk-token-sdk: cargo fmt --- zk-token-sdk/src/errors.rs | 2 + zk-token-sdk/src/instruction/mod.rs | 20 +- zk-token-sdk/src/instruction/transfer.rs | 176 +++++++++++------- .../src/instruction/transfer_with_fee.rs | 151 +++++++++++---- zk-token-sdk/src/sigma_proofs/fee_proof.rs | 10 +- 5 files changed, 244 insertions(+), 115 deletions(-) diff --git a/zk-token-sdk/src/errors.rs b/zk-token-sdk/src/errors.rs index ac48999931..c5a59c4b7c 100644 --- a/zk-token-sdk/src/errors.rs +++ b/zk-token-sdk/src/errors.rs @@ -6,6 +6,8 @@ use { #[derive(Error, Clone, Debug, Eq, PartialEq)] pub enum ProofError { + #[error("invalid transfer amount range")] + TransferAmount, #[error("proof generation failed")] Generation, #[error("proof failed to verify")] diff --git a/zk-token-sdk/src/instruction/mod.rs b/zk-token-sdk/src/instruction/mod.rs index f2b960b060..ec92724292 100644 --- a/zk-token-sdk/src/instruction/mod.rs +++ b/zk-token-sdk/src/instruction/mod.rs @@ -14,6 +14,7 @@ use { errors::ProofError, }, curve25519_dalek::scalar::Scalar, + subtle::ConstantTimeEq, }; pub use { close_account::CloseAccountData, transfer::TransferData, @@ -38,13 +39,22 @@ pub enum Role { /// - the `bit_length` low bits of `amount` interpretted as u64 /// - the (64 - `bit_length`) high bits of `amount` interpretted as u64 #[cfg(not(target_arch = "bpf"))] -pub fn split_u64(amount: u64, bit_length: usize) -> (u64, u64) { - assert!(bit_length <= 64); +pub fn split_u64( + amount: u64, + lo_bit_length: usize, + hi_bit_length: usize, +) -> Result<(u64, u64), ProofError> { + assert!(lo_bit_length <= 64); + assert!(hi_bit_length <= 64); - let lo = amount << (64 - bit_length) >> (64 - bit_length); - let hi = amount >> bit_length; + if !bool::from((amount >> (lo_bit_length + hi_bit_length)).ct_eq(&0u64)) { + return Err(ProofError::TransferAmount); + } - (lo, hi) + let lo = amount << (64 - lo_bit_length) >> (64 - lo_bit_length); + let hi = amount >> lo_bit_length; + + Ok((lo, hi)) } #[cfg(not(target_arch = "bpf"))] diff --git a/zk-token-sdk/src/instruction/transfer.rs b/zk-token-sdk/src/instruction/transfer.rs index 9538b43d38..b79d4ed4a9 100644 --- a/zk-token-sdk/src/instruction/transfer.rs +++ b/zk-token-sdk/src/instruction/transfer.rs @@ -25,57 +25,18 @@ use { }; #[cfg(not(target_arch = "bpf"))] -const TRANSFER_SOURCE_AMOUNT_BIT_LENGTH: usize = 64; +const TRANSFER_SOURCE_AMOUNT_BITS: usize = 64; #[cfg(not(target_arch = "bpf"))] -const TRANSFER_AMOUNT_LO_BIT_LENGTH: usize = 16; +const TRANSFER_AMOUNT_LO_BITS: usize = 16; #[cfg(not(target_arch = "bpf"))] -const TRANSFER_AMOUNT_LO_NEGATED_BIT_LENGTH: usize = 16; +const TRANSFER_AMOUNT_LO_NEGATED_BITS: usize = 16; #[cfg(not(target_arch = "bpf"))] -const TRANSFER_AMOUNT_HI_BIT_LENGTH: usize = 32; +const TRANSFER_AMOUNT_HI_BITS: usize = 32; #[cfg(not(target_arch = "bpf"))] lazy_static::lazy_static! { pub static ref COMMITMENT_MAX: PedersenCommitment = Pedersen::encode((1_u64 << - TRANSFER_AMOUNT_LO_NEGATED_BIT_LENGTH) - 1); -} - -#[derive(Clone)] -#[repr(C)] -#[cfg(not(target_arch = "bpf"))] -pub struct TransferAmountEncryption { - pub commitment: PedersenCommitment, - pub source_handle: DecryptHandle, - pub destination_handle: DecryptHandle, - pub auditor_handle: DecryptHandle, -} - -#[cfg(not(target_arch = "bpf"))] -impl TransferAmountEncryption { - pub fn new( - amount: u64, - source_pubkey: &ElGamalPubkey, - destination_pubkey: &ElGamalPubkey, - auditor_pubkey: &ElGamalPubkey, - ) -> (Self, PedersenOpening) { - let (commitment, opening) = Pedersen::new(amount); - let transfer_amount_encryption = Self { - commitment, - source_handle: source_pubkey.decrypt_handle(&opening), - destination_handle: destination_pubkey.decrypt_handle(&opening), - auditor_handle: auditor_pubkey.decrypt_handle(&opening), - }; - - (transfer_amount_encryption, opening) - } - - pub fn to_pod(&self) -> pod::TransferAmountEncryption { - pod::TransferAmountEncryption { - commitment: self.commitment.into(), - source_handle: self.source_handle.into(), - destination_handle: self.destination_handle.into(), - auditor_handle: self.auditor_handle.into(), - } - } + TRANSFER_AMOUNT_LO_NEGATED_BITS) - 1); } #[derive(Clone, Copy, Pod, Zeroable)] @@ -107,7 +68,11 @@ impl TransferData { (destination_pubkey, auditor_pubkey): (&ElGamalPubkey, &ElGamalPubkey), ) -> Result { // split and encrypt transfer amount - let (amount_lo, amount_hi) = split_u64(transfer_amount, TRANSFER_AMOUNT_LO_BIT_LENGTH); + let (amount_lo, amount_hi) = split_u64( + transfer_amount, + TRANSFER_AMOUNT_LO_BITS, + TRANSFER_AMOUNT_HI_BITS, + )?; let (ciphertext_lo, opening_lo) = TransferAmountEncryption::new( amount_lo, @@ -142,7 +107,7 @@ impl TransferData { - combine_lo_hi_ciphertexts( &transfer_amount_lo_source, &transfer_amount_hi_source, - TRANSFER_AMOUNT_LO_BIT_LENGTH, + TRANSFER_AMOUNT_LO_BITS, ); // generate transcript and append all public inputs @@ -222,7 +187,7 @@ impl TransferData { let amount_hi = ciphertext_hi.decrypt_u32(sk); if let (Some(amount_lo), Some(amount_hi)) = (amount_lo, amount_hi) { - let two_power = 1 << TRANSFER_AMOUNT_LO_BIT_LENGTH; + let two_power = 1 << TRANSFER_AMOUNT_LO_BITS; Ok(amount_lo + two_power * amount_hi) } else { Err(ProofError::Decryption) @@ -336,7 +301,7 @@ impl TransferProof { ); // generate the range proof - let range_proof = if TRANSFER_AMOUNT_LO_BIT_LENGTH == 32 { + let range_proof = if TRANSFER_AMOUNT_LO_BITS == 32 { RangeProof::new( vec![ source_new_balance, @@ -344,16 +309,16 @@ impl TransferProof { transfer_amount_hi as u64, ], vec![ - TRANSFER_SOURCE_AMOUNT_BIT_LENGTH, - TRANSFER_AMOUNT_LO_BIT_LENGTH, - TRANSFER_AMOUNT_HI_BIT_LENGTH, + TRANSFER_SOURCE_AMOUNT_BITS, + TRANSFER_AMOUNT_LO_BITS, + TRANSFER_AMOUNT_HI_BITS, ], vec![&source_opening, opening_lo, opening_hi], transcript, ) } else { let transfer_amount_lo_negated = - (1 << TRANSFER_AMOUNT_LO_NEGATED_BIT_LENGTH) - 1 - transfer_amount_lo as u64; + (1 << TRANSFER_AMOUNT_LO_NEGATED_BITS) - 1 - transfer_amount_lo as u64; let opening_lo_negated = &PedersenOpening::default() - opening_lo; RangeProof::new( @@ -364,10 +329,10 @@ impl TransferProof { transfer_amount_hi as u64, ], vec![ - TRANSFER_SOURCE_AMOUNT_BIT_LENGTH, - TRANSFER_AMOUNT_LO_BIT_LENGTH, - TRANSFER_AMOUNT_LO_NEGATED_BIT_LENGTH, - TRANSFER_AMOUNT_HI_BIT_LENGTH, + TRANSFER_SOURCE_AMOUNT_BITS, + TRANSFER_AMOUNT_LO_BITS, + TRANSFER_AMOUNT_LO_NEGATED_BITS, + TRANSFER_AMOUNT_HI_BITS, ], vec![&source_opening, opening_lo, &opening_lo_negated, opening_hi], transcript, @@ -424,7 +389,7 @@ impl TransferProof { // verify range proof let new_source_commitment = self.new_source_commitment.try_into()?; - if TRANSFER_AMOUNT_LO_BIT_LENGTH == 32 { + if TRANSFER_AMOUNT_LO_BITS == 32 { range_proof.verify( vec![ &new_source_commitment, @@ -432,9 +397,9 @@ impl TransferProof { &ciphertext_hi.commitment, ], vec![ - TRANSFER_SOURCE_AMOUNT_BIT_LENGTH, - TRANSFER_AMOUNT_LO_BIT_LENGTH, - TRANSFER_AMOUNT_HI_BIT_LENGTH, + TRANSFER_SOURCE_AMOUNT_BITS, + TRANSFER_AMOUNT_LO_BITS, + TRANSFER_AMOUNT_HI_BITS, ], transcript, )?; @@ -449,10 +414,10 @@ impl TransferProof { &ciphertext_hi.commitment, ], vec![ - TRANSFER_SOURCE_AMOUNT_BIT_LENGTH, - TRANSFER_AMOUNT_LO_BIT_LENGTH, - TRANSFER_AMOUNT_LO_NEGATED_BIT_LENGTH, - TRANSFER_AMOUNT_HI_BIT_LENGTH, + TRANSFER_SOURCE_AMOUNT_BITS, + TRANSFER_AMOUNT_LO_BITS, + TRANSFER_AMOUNT_LO_NEGATED_BITS, + TRANSFER_AMOUNT_HI_BITS, ], transcript, )?; @@ -502,6 +467,45 @@ impl TransferPubkeys { } } +#[derive(Clone)] +#[repr(C)] +#[cfg(not(target_arch = "bpf"))] +pub struct TransferAmountEncryption { + pub commitment: PedersenCommitment, + pub source_handle: DecryptHandle, + pub destination_handle: DecryptHandle, + pub auditor_handle: DecryptHandle, +} + +#[cfg(not(target_arch = "bpf"))] +impl TransferAmountEncryption { + pub fn new( + amount: u64, + source_pubkey: &ElGamalPubkey, + destination_pubkey: &ElGamalPubkey, + auditor_pubkey: &ElGamalPubkey, + ) -> (Self, PedersenOpening) { + let (commitment, opening) = Pedersen::new(amount); + let transfer_amount_encryption = Self { + commitment, + source_handle: source_pubkey.decrypt_handle(&opening), + destination_handle: destination_pubkey.decrypt_handle(&opening), + auditor_handle: auditor_pubkey.decrypt_handle(&opening), + }; + + (transfer_amount_encryption, opening) + } + + pub fn to_pod(&self) -> pod::TransferAmountEncryption { + pod::TransferAmountEncryption { + commitment: self.commitment.into(), + source_handle: self.source_handle.into(), + destination_handle: self.destination_handle.into(), + auditor_handle: self.auditor_handle.into(), + } + } +} + #[cfg(test)] mod test { use {super::*, crate::encryption::elgamal::ElGamalKeypair}; @@ -513,6 +517,8 @@ mod test { let dest_pk = ElGamalKeypair::new_rand().public; let auditor_pk = ElGamalKeypair::new_rand().public; + // Case 1: transfer 0 amount + // create source account spendable ciphertext let spendable_balance: u64 = 0; let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance); @@ -531,6 +537,29 @@ mod test { assert!(transfer_data.verify().is_ok()); + // Case 2: transfer max amount + + // create source account spendable ciphertext + let spendable_balance: u64 = u64::max_value(); + let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance); + + // transfer amount + let transfer_amount: u64 = + (1u64 << (TRANSFER_AMOUNT_LO_BITS + TRANSFER_AMOUNT_HI_BITS)) - 1; + + // create transfer data + let transfer_data = TransferData::new( + transfer_amount, + (spendable_balance, &spendable_ciphertext), + &source_keypair, + (&dest_pk, &auditor_pk), + ) + .unwrap(); + + assert!(transfer_data.verify().is_ok()); + + // Case 3: general success case + // create source account spendable ciphertext let spendable_balance: u64 = 77; let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance); @@ -548,6 +577,25 @@ mod test { .unwrap(); assert!(transfer_data.verify().is_ok()); + + // Case 4: transfer amount too big + + // create source account spendable ciphertext + let spendable_balance: u64 = u64::max_value(); + let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance); + + // transfer amount + let transfer_amount: u64 = 1u64 << (TRANSFER_AMOUNT_LO_BITS + TRANSFER_AMOUNT_HI_BITS); + + // create transfer data + let transfer_data = TransferData::new( + transfer_amount, + (spendable_balance, &spendable_ciphertext), + &source_keypair, + (&dest_pk, &auditor_pk), + ); + + assert!(transfer_data.is_err()); } #[test] diff --git a/zk-token-sdk/src/instruction/transfer_with_fee.rs b/zk-token-sdk/src/instruction/transfer_with_fee.rs index c66c2a1735..5d7d71b8a1 100644 --- a/zk-token-sdk/src/instruction/transfer_with_fee.rs +++ b/zk-token-sdk/src/instruction/transfer_with_fee.rs @@ -37,20 +37,20 @@ const MAX_FEE_BASIS_POINTS: u64 = 10_000; const ONE_IN_BASIS_POINTS: u128 = MAX_FEE_BASIS_POINTS as u128; #[cfg(not(target_arch = "bpf"))] -const TRANSFER_WITH_FEE_SOURCE_AMOUNT_BIT_LENGTH: usize = 64; +const TRANSFER_SOURCE_AMOUNT_BITS: usize = 64; #[cfg(not(target_arch = "bpf"))] -const TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH: usize = 16; +const TRANSFER_AMOUNT_LO_BITS: usize = 16; #[cfg(not(target_arch = "bpf"))] -const TRANSFER_WITH_FEE_AMOUNT_LO_NEGATED_BIT_LENGTH: usize = 16; +const TRANSFER_AMOUNT_LO_NEGATED_BITS: usize = 16; #[cfg(not(target_arch = "bpf"))] -const TRANSFER_WITH_FEE_AMOUNT_HI_BIT_LENGTH: usize = 32; +const TRANSFER_AMOUNT_HI_BITS: usize = 32; #[cfg(not(target_arch = "bpf"))] -const TRANSFER_WITH_FEE_DELTA_BIT_LENGTH: usize = 64; +const TRANSFER_DELTA_BITS: usize = 64; #[cfg(not(target_arch = "bpf"))] lazy_static::lazy_static! { - pub static ref COMMITMENT_MAX: PedersenCommitment = Pedersen::encode(1_u64 << - TRANSFER_WITH_FEE_AMOUNT_LO_NEGATED_BIT_LENGTH); + pub static ref COMMITMENT_MAX: PedersenCommitment = Pedersen::encode((1_u64 << + TRANSFER_AMOUNT_LO_NEGATED_BITS) - 1); pub static ref COMMITMENT_MAX_FEE_BASIS_POINTS: PedersenCommitment = Pedersen::encode(MAX_FEE_BASIS_POINTS); } @@ -91,8 +91,11 @@ impl TransferWithFeeData { withdraw_withheld_authority_pubkey: &ElGamalPubkey, ) -> Result { // split and encrypt transfer amount - let (amount_lo, amount_hi) = - split_u64(transfer_amount, TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH); + let (amount_lo, amount_hi) = split_u64( + transfer_amount, + TRANSFER_AMOUNT_LO_BITS, + TRANSFER_AMOUNT_HI_BITS, + )?; let (ciphertext_lo, opening_lo) = TransferAmountEncryption::new( amount_lo, @@ -126,7 +129,7 @@ impl TransferWithFeeData { - combine_lo_hi_ciphertexts( &transfer_amount_lo_source, &transfer_amount_hi_source, - TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH, + TRANSFER_AMOUNT_LO_BITS, ); // calculate and encrypt fee @@ -229,7 +232,7 @@ impl TransferWithFeeData { let amount_hi = ciphertext_hi.decrypt_u32(sk); if let (Some(amount_lo), Some(amount_hi)) = (amount_lo, amount_hi) { - let two_power = 1 << TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH; + let two_power = 1 << TRANSFER_AMOUNT_LO_BITS; Ok(amount_lo + two_power * amount_hi) } else { Err(ProofError::Verification) @@ -395,7 +398,7 @@ impl TransferWithFeeProof { // generate the range proof let opening_claimed_negated = &PedersenOpening::default() - &opening_claimed; - let range_proof = if TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH == 32 { + let range_proof = if TRANSFER_AMOUNT_LO_BITS == 32 { RangeProof::new( vec![ source_new_balance, @@ -405,11 +408,11 @@ impl TransferWithFeeProof { MAX_FEE_BASIS_POINTS - delta_fee, ], vec![ - TRANSFER_WITH_FEE_SOURCE_AMOUNT_BIT_LENGTH, - TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH, - TRANSFER_WITH_FEE_AMOUNT_HI_BIT_LENGTH, - TRANSFER_WITH_FEE_DELTA_BIT_LENGTH, - TRANSFER_WITH_FEE_DELTA_BIT_LENGTH, + TRANSFER_SOURCE_AMOUNT_BITS, + TRANSFER_AMOUNT_LO_BITS, + TRANSFER_AMOUNT_HI_BITS, + TRANSFER_DELTA_BITS, + TRANSFER_DELTA_BITS, ], vec![ &opening_source, @@ -422,7 +425,7 @@ impl TransferWithFeeProof { ) } else { let transfer_amount_lo_negated = - (1 << TRANSFER_WITH_FEE_AMOUNT_LO_NEGATED_BIT_LENGTH) - transfer_amount_lo as u64; + ((1 << TRANSFER_AMOUNT_LO_NEGATED_BITS) - 1) - transfer_amount_lo as u64; let opening_lo_negated = &PedersenOpening::default() - opening_lo; RangeProof::new( @@ -435,12 +438,12 @@ impl TransferWithFeeProof { MAX_FEE_BASIS_POINTS - delta_fee, ], vec![ - TRANSFER_WITH_FEE_SOURCE_AMOUNT_BIT_LENGTH, - TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH, - TRANSFER_WITH_FEE_AMOUNT_LO_NEGATED_BIT_LENGTH, - TRANSFER_WITH_FEE_AMOUNT_HI_BIT_LENGTH, - TRANSFER_WITH_FEE_DELTA_BIT_LENGTH, - TRANSFER_WITH_FEE_DELTA_BIT_LENGTH, + TRANSFER_SOURCE_AMOUNT_BITS, + TRANSFER_AMOUNT_LO_BITS, + TRANSFER_AMOUNT_LO_NEGATED_BITS, + TRANSFER_AMOUNT_HI_BITS, + TRANSFER_DELTA_BITS, + TRANSFER_DELTA_BITS, ], vec![ &opening_source, @@ -546,7 +549,7 @@ impl TransferWithFeeProof { let new_source_commitment = self.new_source_commitment.try_into()?; let claimed_commitment_negated = &(*COMMITMENT_MAX_FEE_BASIS_POINTS) - &claimed_commitment; - if TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH == 32 { + if TRANSFER_AMOUNT_LO_BITS == 32 { range_proof.verify( vec![ &new_source_commitment, @@ -695,13 +698,18 @@ impl FeeParameters { fn calculate_fee(transfer_amount: u64, fee_rate_basis_points: u16) -> Option<(u64, u64)> { let numerator = (transfer_amount as u128).checked_mul(fee_rate_basis_points as u128)?; let mut fee = numerator.checked_div(ONE_IN_BASIS_POINTS)?; + let mut delta_fee = 0_u128; + let remainder = numerator.checked_rem(ONE_IN_BASIS_POINTS)?; if remainder > 0 { fee = fee.checked_add(1)?; + + let scaled_fee = fee.checked_mul(ONE_IN_BASIS_POINTS)?; + delta_fee = scaled_fee.checked_sub(numerator)?; } let fee = u64::try_from(fee).ok()?; - Some((fee as u64, remainder as u64)) + Some((fee as u64, delta_fee as u64)) } #[cfg(not(target_arch = "bpf"))] @@ -714,18 +722,12 @@ fn compute_delta_commitment_and_opening( let fee_rate_scalar = Scalar::from(fee_rate_basis_points); let delta_commitment = fee_commitment * Scalar::from(MAX_FEE_BASIS_POINTS) - - &(&combine_lo_hi_commitments( - commitment_lo, - commitment_hi, - TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH, - ) * &fee_rate_scalar); + - &(&combine_lo_hi_commitments(commitment_lo, commitment_hi, TRANSFER_AMOUNT_LO_BITS) + * &fee_rate_scalar); let opening_delta = opening_fee * Scalar::from(MAX_FEE_BASIS_POINTS) - - &(&combine_lo_hi_openings( - opening_lo, - opening_hi, - TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH, - ) * &fee_rate_scalar); + - &(&combine_lo_hi_openings(opening_lo, opening_hi, TRANSFER_AMOUNT_LO_BITS) + * &fee_rate_scalar); (delta_commitment, opening_delta) } @@ -740,11 +742,8 @@ fn compute_delta_commitment( let fee_rate_scalar = Scalar::from(fee_rate_basis_points); fee_commitment * Scalar::from(MAX_FEE_BASIS_POINTS) - - &(&combine_lo_hi_commitments( - commitment_lo, - commitment_hi, - TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH, - ) * &fee_rate_scalar) + - &(&combine_lo_hi_commitments(commitment_lo, commitment_hi, TRANSFER_AMOUNT_LO_BITS) + * &fee_rate_scalar) } #[cfg(test)] @@ -758,6 +757,54 @@ mod test { let auditor_pubkey = ElGamalKeypair::new_rand().public; let withdraw_withheld_authority_pubkey = ElGamalKeypair::new_rand().public; + // Case 1: transfer 0 amount + let spendable_balance: u64 = 120; + let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance); + + let transfer_amount: u64 = 0; + + let fee_parameters = FeeParameters { + fee_rate_basis_points: 400, + maximum_fee: 3, + }; + + let fee_data = TransferWithFeeData::new( + transfer_amount, + (spendable_balance, &spendable_ciphertext), + &source_keypair, + (&destination_pubkey, &auditor_pubkey), + fee_parameters, + &withdraw_withheld_authority_pubkey, + ) + .unwrap(); + + assert!(fee_data.verify().is_ok()); + + // Case 2: transfer max amount + let spendable_balance: u64 = u64::max_value(); + let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance); + + let transfer_amount: u64 = + (1u64 << (TRANSFER_AMOUNT_LO_BITS + TRANSFER_AMOUNT_HI_BITS)) - 1; + + let fee_parameters = FeeParameters { + fee_rate_basis_points: 400, + maximum_fee: 3, + }; + + let fee_data = TransferWithFeeData::new( + transfer_amount, + (spendable_balance, &spendable_ciphertext), + &source_keypair, + (&destination_pubkey, &auditor_pubkey), + fee_parameters, + &withdraw_withheld_authority_pubkey, + ) + .unwrap(); + + assert!(fee_data.verify().is_ok()); + + // Case 3: general success case let spendable_balance: u64 = 120; let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance); @@ -779,5 +826,27 @@ mod test { .unwrap(); assert!(fee_data.verify().is_ok()); + + // Case 4: transfer amount too big + let spendable_balance: u64 = u64::max_value(); + let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance); + + let transfer_amount: u64 = 1u64 << (TRANSFER_AMOUNT_LO_BITS + TRANSFER_AMOUNT_HI_BITS); + + let fee_parameters = FeeParameters { + fee_rate_basis_points: 400, + maximum_fee: 3, + }; + + let fee_data = TransferWithFeeData::new( + transfer_amount, + (spendable_balance, &spendable_ciphertext), + &source_keypair, + (&destination_pubkey, &auditor_pubkey), + fee_parameters, + &withdraw_withheld_authority_pubkey, + ); + + assert!(fee_data.is_err()); } } diff --git a/zk-token-sdk/src/sigma_proofs/fee_proof.rs b/zk-token-sdk/src/sigma_proofs/fee_proof.rs index 1c8bd34535..85ae6bf0c3 100644 --- a/zk-token-sdk/src/sigma_proofs/fee_proof.rs +++ b/zk-token-sdk/src/sigma_proofs/fee_proof.rs @@ -494,12 +494,12 @@ mod test { #[test] fn test_fee_below_max_proof() { - let transfer_amount: u64 = 55; - let max_fee: u64 = 77; + let transfer_amount: u64 = 1; + let max_fee: u64 = 3; - let fee_rate: u16 = 555; // 5.55% - let fee_amount: u64 = 4; - let delta: u64 = 9475; // 4*10000 - 55*555 + let fee_rate: u16 = 400; // 5.55% + let fee_amount: u64 = 1; + let delta: u64 = 9600; // 4*10000 - 55*555 let (transfer_commitment, transfer_opening) = Pedersen::new(transfer_amount); let (fee_commitment, fee_opening) = Pedersen::new(fee_amount);