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
This commit is contained in:
parent
cb06126388
commit
10eeafd3d6
|
@ -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")]
|
||||
|
|
|
@ -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"))]
|
||||
|
|
|
@ -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<Self, ProofError> {
|
||||
// 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]
|
||||
|
|
|
@ -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<Self, ProofError> {
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue