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)]
|
#[derive(Error, Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum ProofError {
|
pub enum ProofError {
|
||||||
|
#[error("invalid transfer amount range")]
|
||||||
|
TransferAmount,
|
||||||
#[error("proof generation failed")]
|
#[error("proof generation failed")]
|
||||||
Generation,
|
Generation,
|
||||||
#[error("proof failed to verify")]
|
#[error("proof failed to verify")]
|
||||||
|
|
|
@ -14,6 +14,7 @@ use {
|
||||||
errors::ProofError,
|
errors::ProofError,
|
||||||
},
|
},
|
||||||
curve25519_dalek::scalar::Scalar,
|
curve25519_dalek::scalar::Scalar,
|
||||||
|
subtle::ConstantTimeEq,
|
||||||
};
|
};
|
||||||
pub use {
|
pub use {
|
||||||
close_account::CloseAccountData, transfer::TransferData,
|
close_account::CloseAccountData, transfer::TransferData,
|
||||||
|
@ -38,13 +39,22 @@ pub enum Role {
|
||||||
/// - the `bit_length` low bits of `amount` interpretted as u64
|
/// - the `bit_length` low bits of `amount` interpretted as u64
|
||||||
/// - the (64 - `bit_length`) high bits of `amount` interpretted as u64
|
/// - the (64 - `bit_length`) high bits of `amount` interpretted as u64
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub fn split_u64(amount: u64, bit_length: usize) -> (u64, u64) {
|
pub fn split_u64(
|
||||||
assert!(bit_length <= 64);
|
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);
|
if !bool::from((amount >> (lo_bit_length + hi_bit_length)).ct_eq(&0u64)) {
|
||||||
let hi = amount >> bit_length;
|
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"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
|
|
@ -25,57 +25,18 @@ use {
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[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"))]
|
#[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"))]
|
#[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"))]
|
#[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"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref COMMITMENT_MAX: PedersenCommitment = Pedersen::encode((1_u64 <<
|
pub static ref COMMITMENT_MAX: PedersenCommitment = Pedersen::encode((1_u64 <<
|
||||||
TRANSFER_AMOUNT_LO_NEGATED_BIT_LENGTH) - 1);
|
TRANSFER_AMOUNT_LO_NEGATED_BITS) - 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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||||
|
@ -107,7 +68,11 @@ impl TransferData {
|
||||||
(destination_pubkey, auditor_pubkey): (&ElGamalPubkey, &ElGamalPubkey),
|
(destination_pubkey, auditor_pubkey): (&ElGamalPubkey, &ElGamalPubkey),
|
||||||
) -> Result<Self, ProofError> {
|
) -> Result<Self, ProofError> {
|
||||||
// split and encrypt transfer amount
|
// 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(
|
let (ciphertext_lo, opening_lo) = TransferAmountEncryption::new(
|
||||||
amount_lo,
|
amount_lo,
|
||||||
|
@ -142,7 +107,7 @@ impl TransferData {
|
||||||
- combine_lo_hi_ciphertexts(
|
- combine_lo_hi_ciphertexts(
|
||||||
&transfer_amount_lo_source,
|
&transfer_amount_lo_source,
|
||||||
&transfer_amount_hi_source,
|
&transfer_amount_hi_source,
|
||||||
TRANSFER_AMOUNT_LO_BIT_LENGTH,
|
TRANSFER_AMOUNT_LO_BITS,
|
||||||
);
|
);
|
||||||
|
|
||||||
// generate transcript and append all public inputs
|
// generate transcript and append all public inputs
|
||||||
|
@ -222,7 +187,7 @@ impl TransferData {
|
||||||
let amount_hi = ciphertext_hi.decrypt_u32(sk);
|
let amount_hi = ciphertext_hi.decrypt_u32(sk);
|
||||||
|
|
||||||
if let (Some(amount_lo), Some(amount_hi)) = (amount_lo, amount_hi) {
|
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)
|
Ok(amount_lo + two_power * amount_hi)
|
||||||
} else {
|
} else {
|
||||||
Err(ProofError::Decryption)
|
Err(ProofError::Decryption)
|
||||||
|
@ -336,7 +301,7 @@ impl TransferProof {
|
||||||
);
|
);
|
||||||
|
|
||||||
// generate the range proof
|
// 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(
|
RangeProof::new(
|
||||||
vec![
|
vec![
|
||||||
source_new_balance,
|
source_new_balance,
|
||||||
|
@ -344,16 +309,16 @@ impl TransferProof {
|
||||||
transfer_amount_hi as u64,
|
transfer_amount_hi as u64,
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
TRANSFER_SOURCE_AMOUNT_BIT_LENGTH,
|
TRANSFER_SOURCE_AMOUNT_BITS,
|
||||||
TRANSFER_AMOUNT_LO_BIT_LENGTH,
|
TRANSFER_AMOUNT_LO_BITS,
|
||||||
TRANSFER_AMOUNT_HI_BIT_LENGTH,
|
TRANSFER_AMOUNT_HI_BITS,
|
||||||
],
|
],
|
||||||
vec![&source_opening, opening_lo, opening_hi],
|
vec![&source_opening, opening_lo, opening_hi],
|
||||||
transcript,
|
transcript,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let transfer_amount_lo_negated =
|
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;
|
let opening_lo_negated = &PedersenOpening::default() - opening_lo;
|
||||||
|
|
||||||
RangeProof::new(
|
RangeProof::new(
|
||||||
|
@ -364,10 +329,10 @@ impl TransferProof {
|
||||||
transfer_amount_hi as u64,
|
transfer_amount_hi as u64,
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
TRANSFER_SOURCE_AMOUNT_BIT_LENGTH,
|
TRANSFER_SOURCE_AMOUNT_BITS,
|
||||||
TRANSFER_AMOUNT_LO_BIT_LENGTH,
|
TRANSFER_AMOUNT_LO_BITS,
|
||||||
TRANSFER_AMOUNT_LO_NEGATED_BIT_LENGTH,
|
TRANSFER_AMOUNT_LO_NEGATED_BITS,
|
||||||
TRANSFER_AMOUNT_HI_BIT_LENGTH,
|
TRANSFER_AMOUNT_HI_BITS,
|
||||||
],
|
],
|
||||||
vec![&source_opening, opening_lo, &opening_lo_negated, opening_hi],
|
vec![&source_opening, opening_lo, &opening_lo_negated, opening_hi],
|
||||||
transcript,
|
transcript,
|
||||||
|
@ -424,7 +389,7 @@ impl TransferProof {
|
||||||
|
|
||||||
// verify range proof
|
// verify range proof
|
||||||
let new_source_commitment = self.new_source_commitment.try_into()?;
|
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(
|
range_proof.verify(
|
||||||
vec![
|
vec![
|
||||||
&new_source_commitment,
|
&new_source_commitment,
|
||||||
|
@ -432,9 +397,9 @@ impl TransferProof {
|
||||||
&ciphertext_hi.commitment,
|
&ciphertext_hi.commitment,
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
TRANSFER_SOURCE_AMOUNT_BIT_LENGTH,
|
TRANSFER_SOURCE_AMOUNT_BITS,
|
||||||
TRANSFER_AMOUNT_LO_BIT_LENGTH,
|
TRANSFER_AMOUNT_LO_BITS,
|
||||||
TRANSFER_AMOUNT_HI_BIT_LENGTH,
|
TRANSFER_AMOUNT_HI_BITS,
|
||||||
],
|
],
|
||||||
transcript,
|
transcript,
|
||||||
)?;
|
)?;
|
||||||
|
@ -449,10 +414,10 @@ impl TransferProof {
|
||||||
&ciphertext_hi.commitment,
|
&ciphertext_hi.commitment,
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
TRANSFER_SOURCE_AMOUNT_BIT_LENGTH,
|
TRANSFER_SOURCE_AMOUNT_BITS,
|
||||||
TRANSFER_AMOUNT_LO_BIT_LENGTH,
|
TRANSFER_AMOUNT_LO_BITS,
|
||||||
TRANSFER_AMOUNT_LO_NEGATED_BIT_LENGTH,
|
TRANSFER_AMOUNT_LO_NEGATED_BITS,
|
||||||
TRANSFER_AMOUNT_HI_BIT_LENGTH,
|
TRANSFER_AMOUNT_HI_BITS,
|
||||||
],
|
],
|
||||||
transcript,
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use {super::*, crate::encryption::elgamal::ElGamalKeypair};
|
use {super::*, crate::encryption::elgamal::ElGamalKeypair};
|
||||||
|
@ -513,6 +517,8 @@ mod test {
|
||||||
let dest_pk = ElGamalKeypair::new_rand().public;
|
let dest_pk = ElGamalKeypair::new_rand().public;
|
||||||
let auditor_pk = ElGamalKeypair::new_rand().public;
|
let auditor_pk = ElGamalKeypair::new_rand().public;
|
||||||
|
|
||||||
|
// Case 1: transfer 0 amount
|
||||||
|
|
||||||
// create source account spendable ciphertext
|
// create source account spendable ciphertext
|
||||||
let spendable_balance: u64 = 0;
|
let spendable_balance: u64 = 0;
|
||||||
let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance);
|
let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance);
|
||||||
|
@ -531,6 +537,29 @@ mod test {
|
||||||
|
|
||||||
assert!(transfer_data.verify().is_ok());
|
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
|
// create source account spendable ciphertext
|
||||||
let spendable_balance: u64 = 77;
|
let spendable_balance: u64 = 77;
|
||||||
let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance);
|
let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance);
|
||||||
|
@ -548,6 +577,25 @@ mod test {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(transfer_data.verify().is_ok());
|
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]
|
#[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;
|
const ONE_IN_BASIS_POINTS: u128 = MAX_FEE_BASIS_POINTS as u128;
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[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"))]
|
#[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"))]
|
#[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"))]
|
#[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"))]
|
#[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"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref COMMITMENT_MAX: PedersenCommitment = Pedersen::encode(1_u64 <<
|
pub static ref COMMITMENT_MAX: PedersenCommitment = Pedersen::encode((1_u64 <<
|
||||||
TRANSFER_WITH_FEE_AMOUNT_LO_NEGATED_BIT_LENGTH);
|
TRANSFER_AMOUNT_LO_NEGATED_BITS) - 1);
|
||||||
pub static ref COMMITMENT_MAX_FEE_BASIS_POINTS: PedersenCommitment = Pedersen::encode(MAX_FEE_BASIS_POINTS);
|
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,
|
withdraw_withheld_authority_pubkey: &ElGamalPubkey,
|
||||||
) -> Result<Self, ProofError> {
|
) -> Result<Self, ProofError> {
|
||||||
// split and encrypt transfer amount
|
// split and encrypt transfer amount
|
||||||
let (amount_lo, amount_hi) =
|
let (amount_lo, amount_hi) = split_u64(
|
||||||
split_u64(transfer_amount, TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH);
|
transfer_amount,
|
||||||
|
TRANSFER_AMOUNT_LO_BITS,
|
||||||
|
TRANSFER_AMOUNT_HI_BITS,
|
||||||
|
)?;
|
||||||
|
|
||||||
let (ciphertext_lo, opening_lo) = TransferAmountEncryption::new(
|
let (ciphertext_lo, opening_lo) = TransferAmountEncryption::new(
|
||||||
amount_lo,
|
amount_lo,
|
||||||
|
@ -126,7 +129,7 @@ impl TransferWithFeeData {
|
||||||
- combine_lo_hi_ciphertexts(
|
- combine_lo_hi_ciphertexts(
|
||||||
&transfer_amount_lo_source,
|
&transfer_amount_lo_source,
|
||||||
&transfer_amount_hi_source,
|
&transfer_amount_hi_source,
|
||||||
TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH,
|
TRANSFER_AMOUNT_LO_BITS,
|
||||||
);
|
);
|
||||||
|
|
||||||
// calculate and encrypt fee
|
// calculate and encrypt fee
|
||||||
|
@ -229,7 +232,7 @@ impl TransferWithFeeData {
|
||||||
let amount_hi = ciphertext_hi.decrypt_u32(sk);
|
let amount_hi = ciphertext_hi.decrypt_u32(sk);
|
||||||
|
|
||||||
if let (Some(amount_lo), Some(amount_hi)) = (amount_lo, amount_hi) {
|
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)
|
Ok(amount_lo + two_power * amount_hi)
|
||||||
} else {
|
} else {
|
||||||
Err(ProofError::Verification)
|
Err(ProofError::Verification)
|
||||||
|
@ -395,7 +398,7 @@ impl TransferWithFeeProof {
|
||||||
|
|
||||||
// generate the range proof
|
// generate the range proof
|
||||||
let opening_claimed_negated = &PedersenOpening::default() - &opening_claimed;
|
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(
|
RangeProof::new(
|
||||||
vec![
|
vec![
|
||||||
source_new_balance,
|
source_new_balance,
|
||||||
|
@ -405,11 +408,11 @@ impl TransferWithFeeProof {
|
||||||
MAX_FEE_BASIS_POINTS - delta_fee,
|
MAX_FEE_BASIS_POINTS - delta_fee,
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
TRANSFER_WITH_FEE_SOURCE_AMOUNT_BIT_LENGTH,
|
TRANSFER_SOURCE_AMOUNT_BITS,
|
||||||
TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH,
|
TRANSFER_AMOUNT_LO_BITS,
|
||||||
TRANSFER_WITH_FEE_AMOUNT_HI_BIT_LENGTH,
|
TRANSFER_AMOUNT_HI_BITS,
|
||||||
TRANSFER_WITH_FEE_DELTA_BIT_LENGTH,
|
TRANSFER_DELTA_BITS,
|
||||||
TRANSFER_WITH_FEE_DELTA_BIT_LENGTH,
|
TRANSFER_DELTA_BITS,
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
&opening_source,
|
&opening_source,
|
||||||
|
@ -422,7 +425,7 @@ impl TransferWithFeeProof {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let transfer_amount_lo_negated =
|
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;
|
let opening_lo_negated = &PedersenOpening::default() - opening_lo;
|
||||||
|
|
||||||
RangeProof::new(
|
RangeProof::new(
|
||||||
|
@ -435,12 +438,12 @@ impl TransferWithFeeProof {
|
||||||
MAX_FEE_BASIS_POINTS - delta_fee,
|
MAX_FEE_BASIS_POINTS - delta_fee,
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
TRANSFER_WITH_FEE_SOURCE_AMOUNT_BIT_LENGTH,
|
TRANSFER_SOURCE_AMOUNT_BITS,
|
||||||
TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH,
|
TRANSFER_AMOUNT_LO_BITS,
|
||||||
TRANSFER_WITH_FEE_AMOUNT_LO_NEGATED_BIT_LENGTH,
|
TRANSFER_AMOUNT_LO_NEGATED_BITS,
|
||||||
TRANSFER_WITH_FEE_AMOUNT_HI_BIT_LENGTH,
|
TRANSFER_AMOUNT_HI_BITS,
|
||||||
TRANSFER_WITH_FEE_DELTA_BIT_LENGTH,
|
TRANSFER_DELTA_BITS,
|
||||||
TRANSFER_WITH_FEE_DELTA_BIT_LENGTH,
|
TRANSFER_DELTA_BITS,
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
&opening_source,
|
&opening_source,
|
||||||
|
@ -546,7 +549,7 @@ impl TransferWithFeeProof {
|
||||||
let new_source_commitment = self.new_source_commitment.try_into()?;
|
let new_source_commitment = self.new_source_commitment.try_into()?;
|
||||||
let claimed_commitment_negated = &(*COMMITMENT_MAX_FEE_BASIS_POINTS) - &claimed_commitment;
|
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(
|
range_proof.verify(
|
||||||
vec![
|
vec![
|
||||||
&new_source_commitment,
|
&new_source_commitment,
|
||||||
|
@ -695,13 +698,18 @@ impl FeeParameters {
|
||||||
fn calculate_fee(transfer_amount: u64, fee_rate_basis_points: u16) -> Option<(u64, u64)> {
|
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 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 fee = numerator.checked_div(ONE_IN_BASIS_POINTS)?;
|
||||||
|
let mut delta_fee = 0_u128;
|
||||||
|
|
||||||
let remainder = numerator.checked_rem(ONE_IN_BASIS_POINTS)?;
|
let remainder = numerator.checked_rem(ONE_IN_BASIS_POINTS)?;
|
||||||
if remainder > 0 {
|
if remainder > 0 {
|
||||||
fee = fee.checked_add(1)?;
|
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()?;
|
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"))]
|
#[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 fee_rate_scalar = Scalar::from(fee_rate_basis_points);
|
||||||
|
|
||||||
let delta_commitment = fee_commitment * Scalar::from(MAX_FEE_BASIS_POINTS)
|
let delta_commitment = fee_commitment * Scalar::from(MAX_FEE_BASIS_POINTS)
|
||||||
- &(&combine_lo_hi_commitments(
|
- &(&combine_lo_hi_commitments(commitment_lo, commitment_hi, TRANSFER_AMOUNT_LO_BITS)
|
||||||
commitment_lo,
|
* &fee_rate_scalar);
|
||||||
commitment_hi,
|
|
||||||
TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH,
|
|
||||||
) * &fee_rate_scalar);
|
|
||||||
|
|
||||||
let opening_delta = opening_fee * Scalar::from(MAX_FEE_BASIS_POINTS)
|
let opening_delta = opening_fee * Scalar::from(MAX_FEE_BASIS_POINTS)
|
||||||
- &(&combine_lo_hi_openings(
|
- &(&combine_lo_hi_openings(opening_lo, opening_hi, TRANSFER_AMOUNT_LO_BITS)
|
||||||
opening_lo,
|
* &fee_rate_scalar);
|
||||||
opening_hi,
|
|
||||||
TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH,
|
|
||||||
) * &fee_rate_scalar);
|
|
||||||
|
|
||||||
(delta_commitment, opening_delta)
|
(delta_commitment, opening_delta)
|
||||||
}
|
}
|
||||||
|
@ -740,11 +742,8 @@ fn compute_delta_commitment(
|
||||||
let fee_rate_scalar = Scalar::from(fee_rate_basis_points);
|
let fee_rate_scalar = Scalar::from(fee_rate_basis_points);
|
||||||
|
|
||||||
fee_commitment * Scalar::from(MAX_FEE_BASIS_POINTS)
|
fee_commitment * Scalar::from(MAX_FEE_BASIS_POINTS)
|
||||||
- &(&combine_lo_hi_commitments(
|
- &(&combine_lo_hi_commitments(commitment_lo, commitment_hi, TRANSFER_AMOUNT_LO_BITS)
|
||||||
commitment_lo,
|
* &fee_rate_scalar)
|
||||||
commitment_hi,
|
|
||||||
TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH,
|
|
||||||
) * &fee_rate_scalar)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -758,6 +757,54 @@ mod test {
|
||||||
let auditor_pubkey = ElGamalKeypair::new_rand().public;
|
let auditor_pubkey = ElGamalKeypair::new_rand().public;
|
||||||
let withdraw_withheld_authority_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_balance: u64 = 120;
|
||||||
let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance);
|
let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance);
|
||||||
|
|
||||||
|
@ -779,5 +826,27 @@ mod test {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(fee_data.verify().is_ok());
|
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]
|
#[test]
|
||||||
fn test_fee_below_max_proof() {
|
fn test_fee_below_max_proof() {
|
||||||
let transfer_amount: u64 = 55;
|
let transfer_amount: u64 = 1;
|
||||||
let max_fee: u64 = 77;
|
let max_fee: u64 = 3;
|
||||||
|
|
||||||
let fee_rate: u16 = 555; // 5.55%
|
let fee_rate: u16 = 400; // 5.55%
|
||||||
let fee_amount: u64 = 4;
|
let fee_amount: u64 = 1;
|
||||||
let delta: u64 = 9475; // 4*10000 - 55*555
|
let delta: u64 = 9600; // 4*10000 - 55*555
|
||||||
|
|
||||||
let (transfer_commitment, transfer_opening) = Pedersen::new(transfer_amount);
|
let (transfer_commitment, transfer_opening) = Pedersen::new(transfer_amount);
|
||||||
let (fee_commitment, fee_opening) = Pedersen::new(fee_amount);
|
let (fee_commitment, fee_opening) = Pedersen::new(fee_amount);
|
||||||
|
|
Loading…
Reference in New Issue