zk-token-sdk: generalize range proof (#23506)
* zk-token-sdk: update range proof in transfers for more flexible setting of params * zk-token-sdk: clippy
This commit is contained in:
@@ -24,6 +24,8 @@ pub enum ProofError {
|
|||||||
"`zk_token_elgamal::pod::ElGamalCiphertext` contains invalid ElGamalCiphertext ciphertext"
|
"`zk_token_elgamal::pod::ElGamalCiphertext` contains invalid ElGamalCiphertext ciphertext"
|
||||||
)]
|
)]
|
||||||
InconsistentCTData,
|
InconsistentCTData,
|
||||||
|
#[error("failed to decrypt ciphertext from transfer data")]
|
||||||
|
Decryption,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Clone, Debug, Eq, PartialEq)]
|
#[derive(Error, Clone, Debug, Eq, PartialEq)]
|
||||||
|
@@ -21,10 +21,6 @@ pub use {
|
|||||||
withdraw_withheld::WithdrawWithheldTokensData,
|
withdraw_withheld::WithdrawWithheldTokensData,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Constant for 2^32
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
|
||||||
const TWO_32: u64 = 4294967296;
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub trait Verifiable {
|
pub trait Verifiable {
|
||||||
fn verify(&self) -> Result<(), ProofError>;
|
fn verify(&self) -> Result<(), ProofError>;
|
||||||
@@ -38,35 +34,45 @@ pub enum Role {
|
|||||||
Auditor,
|
Auditor,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Split u64 number into two u32 numbers
|
/// Takes in a 64-bit number `amount` and a bit length `bit_length`. It returns:
|
||||||
|
/// - 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"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub fn split_u64_into_u32(amount: u64) -> (u32, u32) {
|
pub fn split_u64(amount: u64, bit_length: usize) -> (u64, u64) {
|
||||||
let lo = amount as u32;
|
assert!(bit_length <= 64);
|
||||||
let hi = (amount >> 32) as u32;
|
|
||||||
|
let lo = amount << (64 - bit_length) >> (64 - bit_length);
|
||||||
|
let hi = amount >> bit_length;
|
||||||
|
|
||||||
(lo, hi)
|
(lo, hi)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
fn combine_u32_ciphertexts(
|
fn combine_lo_hi_ciphertexts(
|
||||||
ciphertext_lo: &ElGamalCiphertext,
|
ciphertext_lo: &ElGamalCiphertext,
|
||||||
ciphertext_hi: &ElGamalCiphertext,
|
ciphertext_hi: &ElGamalCiphertext,
|
||||||
|
bit_length: usize,
|
||||||
) -> ElGamalCiphertext {
|
) -> ElGamalCiphertext {
|
||||||
ciphertext_lo + &(ciphertext_hi * &Scalar::from(TWO_32))
|
let two_power = (1_u64) << bit_length;
|
||||||
|
ciphertext_lo + &(ciphertext_hi * &Scalar::from(two_power))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub fn combine_u32_commitments(
|
pub fn combine_lo_hi_commitments(
|
||||||
comm_lo: &PedersenCommitment,
|
comm_lo: &PedersenCommitment,
|
||||||
comm_hi: &PedersenCommitment,
|
comm_hi: &PedersenCommitment,
|
||||||
|
bit_length: usize,
|
||||||
) -> PedersenCommitment {
|
) -> PedersenCommitment {
|
||||||
comm_lo + comm_hi * &Scalar::from(TWO_32)
|
let two_power = (1_u64) << bit_length;
|
||||||
|
comm_lo + comm_hi * &Scalar::from(two_power)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
pub fn combine_u32_openings(
|
pub fn combine_lo_hi_openings(
|
||||||
opening_lo: &PedersenOpening,
|
opening_lo: &PedersenOpening,
|
||||||
opening_hi: &PedersenOpening,
|
opening_hi: &PedersenOpening,
|
||||||
|
bit_length: usize,
|
||||||
) -> PedersenOpening {
|
) -> PedersenOpening {
|
||||||
opening_lo + opening_hi * &Scalar::from(TWO_32)
|
let two_power = (1_u64) << bit_length;
|
||||||
|
opening_lo + opening_hi * &Scalar::from(two_power)
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ use {
|
|||||||
pedersen::{Pedersen, PedersenCommitment, PedersenOpening},
|
pedersen::{Pedersen, PedersenCommitment, PedersenOpening},
|
||||||
},
|
},
|
||||||
errors::ProofError,
|
errors::ProofError,
|
||||||
instruction::{combine_u32_ciphertexts, split_u64_into_u32, Role, Verifiable, TWO_32},
|
instruction::{combine_lo_hi_ciphertexts, split_u64, Role, Verifiable},
|
||||||
range_proof::RangeProof,
|
range_proof::RangeProof,
|
||||||
sigma_proofs::{
|
sigma_proofs::{
|
||||||
equality_proof::CtxtCommEqualityProof, validity_proof::AggregatedValidityProof,
|
equality_proof::CtxtCommEqualityProof, validity_proof::AggregatedValidityProof,
|
||||||
@@ -27,10 +27,18 @@ use {
|
|||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
const TRANSFER_SOURCE_AMOUNT_BIT_LENGTH: usize = 64;
|
const TRANSFER_SOURCE_AMOUNT_BIT_LENGTH: usize = 64;
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
const TRANSFER_AMOUNT_LO_BIT_LENGTH: usize = 32;
|
const TRANSFER_AMOUNT_LO_BIT_LENGTH: usize = 16;
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
const TRANSFER_AMOUNT_LO_NEGATED_BIT_LENGTH: usize = 16;
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
const TRANSFER_AMOUNT_HI_BIT_LENGTH: usize = 32;
|
const TRANSFER_AMOUNT_HI_BIT_LENGTH: 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);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
@@ -44,7 +52,7 @@ pub struct TransferAmountEncryption {
|
|||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
impl TransferAmountEncryption {
|
impl TransferAmountEncryption {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
amount: u32,
|
amount: u64,
|
||||||
source_pubkey: &ElGamalPubkey,
|
source_pubkey: &ElGamalPubkey,
|
||||||
destination_pubkey: &ElGamalPubkey,
|
destination_pubkey: &ElGamalPubkey,
|
||||||
auditor_pubkey: &ElGamalPubkey,
|
auditor_pubkey: &ElGamalPubkey,
|
||||||
@@ -99,7 +107,7 @@ 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_into_u32(transfer_amount);
|
let (amount_lo, amount_hi) = split_u64(transfer_amount, TRANSFER_AMOUNT_LO_BIT_LENGTH);
|
||||||
|
|
||||||
let (ciphertext_lo, opening_lo) = TransferAmountEncryption::new(
|
let (ciphertext_lo, opening_lo) = TransferAmountEncryption::new(
|
||||||
amount_lo,
|
amount_lo,
|
||||||
@@ -107,6 +115,7 @@ impl TransferData {
|
|||||||
destination_pubkey,
|
destination_pubkey,
|
||||||
auditor_pubkey,
|
auditor_pubkey,
|
||||||
);
|
);
|
||||||
|
|
||||||
let (ciphertext_hi, opening_hi) = TransferAmountEncryption::new(
|
let (ciphertext_hi, opening_hi) = TransferAmountEncryption::new(
|
||||||
amount_hi,
|
amount_hi,
|
||||||
&source_keypair.public,
|
&source_keypair.public,
|
||||||
@@ -130,7 +139,11 @@ impl TransferData {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let new_source_ciphertext = ciphertext_old_source
|
let new_source_ciphertext = ciphertext_old_source
|
||||||
- combine_u32_ciphertexts(&transfer_amount_lo_source, &transfer_amount_hi_source);
|
- combine_lo_hi_ciphertexts(
|
||||||
|
&transfer_amount_lo_source,
|
||||||
|
&transfer_amount_hi_source,
|
||||||
|
TRANSFER_AMOUNT_LO_BIT_LENGTH,
|
||||||
|
);
|
||||||
|
|
||||||
// generate transcript and append all public inputs
|
// generate transcript and append all public inputs
|
||||||
let pod_transfer_pubkeys = pod::TransferPubkeys {
|
let pod_transfer_pubkeys = pod::TransferPubkeys {
|
||||||
@@ -201,11 +214,6 @@ impl TransferData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Decrypts transfer amount from transfer data
|
/// Decrypts transfer amount from transfer data
|
||||||
///
|
|
||||||
/// TODO: This function should run in constant time. Use `subtle::Choice` for the if statement
|
|
||||||
/// and make sure that the function does not terminate prematurely due to errors
|
|
||||||
///
|
|
||||||
/// TODO: Define specific error type for decryption error
|
|
||||||
pub fn decrypt_amount(&self, role: Role, sk: &ElGamalSecretKey) -> Result<u64, ProofError> {
|
pub fn decrypt_amount(&self, role: Role, sk: &ElGamalSecretKey) -> Result<u64, ProofError> {
|
||||||
let ciphertext_lo = self.ciphertext_lo(role)?;
|
let ciphertext_lo = self.ciphertext_lo(role)?;
|
||||||
let ciphertext_hi = self.ciphertext_hi(role)?;
|
let ciphertext_hi = self.ciphertext_hi(role)?;
|
||||||
@@ -214,9 +222,10 @@ 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) {
|
||||||
Ok(amount_lo + TWO_32 * amount_hi)
|
let two_power = 1 << TRANSFER_AMOUNT_LO_BIT_LENGTH;
|
||||||
|
Ok(amount_lo + two_power * amount_hi)
|
||||||
} else {
|
} else {
|
||||||
Err(ProofError::Verification)
|
Err(ProofError::Decryption)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,7 +261,7 @@ impl Verifiable for TransferData {
|
|||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct TransferProof {
|
pub struct TransferProof {
|
||||||
/// New Pedersen commitment for the remaining balance in source
|
/// New Pedersen commitment for the remaining balance in source
|
||||||
pub commitment_new_source: pod::PedersenCommitment,
|
pub new_source_commitment: pod::PedersenCommitment,
|
||||||
|
|
||||||
/// Associated equality proof
|
/// Associated equality proof
|
||||||
pub equality_proof: pod::CtxtCommEqualityProof,
|
pub equality_proof: pod::CtxtCommEqualityProof,
|
||||||
@@ -295,7 +304,7 @@ impl TransferProof {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
(transfer_amount_lo, transfer_amount_hi): (u32, u32),
|
(transfer_amount_lo, transfer_amount_hi): (u64, u64),
|
||||||
source_keypair: &ElGamalKeypair,
|
source_keypair: &ElGamalKeypair,
|
||||||
(destination_pubkey, auditor_pubkey): (&ElGamalPubkey, &ElGamalPubkey),
|
(destination_pubkey, auditor_pubkey): (&ElGamalPubkey, &ElGamalPubkey),
|
||||||
opening_lo: &PedersenOpening,
|
opening_lo: &PedersenOpening,
|
||||||
@@ -304,10 +313,10 @@ impl TransferProof {
|
|||||||
transcript: &mut Transcript,
|
transcript: &mut Transcript,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// generate a Pedersen commitment for the remaining balance in source
|
// generate a Pedersen commitment for the remaining balance in source
|
||||||
let (commitment_new_source, source_opening) = Pedersen::new(source_new_balance);
|
let (new_source_commitment, source_opening) = Pedersen::new(source_new_balance);
|
||||||
|
|
||||||
let pod_commitment_new_source: pod::PedersenCommitment = commitment_new_source.into();
|
let pod_new_source_commitment: pod::PedersenCommitment = new_source_commitment.into();
|
||||||
transcript.append_commitment(b"commitment-new-source", &pod_commitment_new_source);
|
transcript.append_commitment(b"commitment-new-source", &pod_new_source_commitment);
|
||||||
|
|
||||||
// generate equality_proof
|
// generate equality_proof
|
||||||
let equality_proof = CtxtCommEqualityProof::new(
|
let equality_proof = CtxtCommEqualityProof::new(
|
||||||
@@ -327,23 +336,46 @@ impl TransferProof {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// generate the range proof
|
// generate the range proof
|
||||||
let range_proof = RangeProof::new(
|
let range_proof = if TRANSFER_AMOUNT_LO_BIT_LENGTH == 32 {
|
||||||
vec![
|
RangeProof::new(
|
||||||
source_new_balance,
|
vec![
|
||||||
transfer_amount_lo as u64,
|
source_new_balance,
|
||||||
transfer_amount_hi as u64,
|
transfer_amount_lo as u64,
|
||||||
],
|
transfer_amount_hi as u64,
|
||||||
vec![
|
],
|
||||||
TRANSFER_SOURCE_AMOUNT_BIT_LENGTH,
|
vec![
|
||||||
TRANSFER_AMOUNT_LO_BIT_LENGTH,
|
TRANSFER_SOURCE_AMOUNT_BIT_LENGTH,
|
||||||
TRANSFER_AMOUNT_HI_BIT_LENGTH,
|
TRANSFER_AMOUNT_LO_BIT_LENGTH,
|
||||||
],
|
TRANSFER_AMOUNT_HI_BIT_LENGTH,
|
||||||
vec![&source_opening, opening_lo, opening_hi],
|
],
|
||||||
transcript,
|
vec![&source_opening, opening_lo, opening_hi],
|
||||||
);
|
transcript,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let transfer_amount_lo_negated =
|
||||||
|
(1 << TRANSFER_AMOUNT_LO_NEGATED_BIT_LENGTH) - transfer_amount_lo as u64;
|
||||||
|
let opening_lo_negated = &PedersenOpening::default() - opening_lo;
|
||||||
|
|
||||||
|
RangeProof::new(
|
||||||
|
vec![
|
||||||
|
source_new_balance,
|
||||||
|
transfer_amount_lo as u64,
|
||||||
|
transfer_amount_lo_negated,
|
||||||
|
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,
|
||||||
|
],
|
||||||
|
vec![&source_opening, opening_lo, &opening_lo_negated, opening_hi],
|
||||||
|
transcript,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
commitment_new_source: pod_commitment_new_source,
|
new_source_commitment: pod_new_source_commitment,
|
||||||
equality_proof: equality_proof.into(),
|
equality_proof: equality_proof.into(),
|
||||||
validity_proof: validity_proof.into(),
|
validity_proof: validity_proof.into(),
|
||||||
range_proof: range_proof.try_into().expect("range proof: length error"),
|
range_proof: range_proof.try_into().expect("range proof: length error"),
|
||||||
@@ -358,9 +390,9 @@ impl TransferProof {
|
|||||||
ciphertext_new_spendable: &ElGamalCiphertext,
|
ciphertext_new_spendable: &ElGamalCiphertext,
|
||||||
transcript: &mut Transcript,
|
transcript: &mut Transcript,
|
||||||
) -> Result<(), ProofError> {
|
) -> Result<(), ProofError> {
|
||||||
transcript.append_commitment(b"commitment-new-source", &self.commitment_new_source);
|
transcript.append_commitment(b"commitment-new-source", &self.new_source_commitment);
|
||||||
|
|
||||||
let commitment: PedersenCommitment = self.commitment_new_source.try_into()?;
|
let commitment: PedersenCommitment = self.new_source_commitment.try_into()?;
|
||||||
let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?;
|
let equality_proof: CtxtCommEqualityProof = self.equality_proof.try_into()?;
|
||||||
let aggregated_validity_proof: AggregatedValidityProof = self.validity_proof.try_into()?;
|
let aggregated_validity_proof: AggregatedValidityProof = self.validity_proof.try_into()?;
|
||||||
let range_proof: RangeProof = self.range_proof.try_into()?;
|
let range_proof: RangeProof = self.range_proof.try_into()?;
|
||||||
@@ -391,16 +423,40 @@ impl TransferProof {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
// verify range proof
|
// verify range proof
|
||||||
let commitment_new_source = self.commitment_new_source.try_into()?;
|
let new_source_commitment = self.new_source_commitment.try_into()?;
|
||||||
range_proof.verify(
|
if TRANSFER_AMOUNT_LO_BIT_LENGTH == 32 {
|
||||||
vec![
|
range_proof.verify(
|
||||||
&commitment_new_source,
|
vec![
|
||||||
&ciphertext_lo.commitment,
|
&new_source_commitment,
|
||||||
&ciphertext_hi.commitment,
|
&ciphertext_lo.commitment,
|
||||||
],
|
&ciphertext_hi.commitment,
|
||||||
vec![64_usize, 32_usize, 32_usize],
|
],
|
||||||
transcript,
|
vec![
|
||||||
)?;
|
TRANSFER_SOURCE_AMOUNT_BIT_LENGTH,
|
||||||
|
TRANSFER_AMOUNT_LO_BIT_LENGTH,
|
||||||
|
TRANSFER_AMOUNT_HI_BIT_LENGTH,
|
||||||
|
],
|
||||||
|
transcript,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
let commitment_lo_negated = &(*COMMITMENT_MAX) - &ciphertext_lo.commitment;
|
||||||
|
|
||||||
|
range_proof.verify(
|
||||||
|
vec![
|
||||||
|
&new_source_commitment,
|
||||||
|
&ciphertext_lo.commitment,
|
||||||
|
&commitment_lo_negated,
|
||||||
|
&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,
|
||||||
|
],
|
||||||
|
transcript,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -492,11 +548,11 @@ mod test {
|
|||||||
} = ElGamalKeypair::new_rand();
|
} = ElGamalKeypair::new_rand();
|
||||||
|
|
||||||
// create source account spendable ciphertext
|
// create source account spendable ciphertext
|
||||||
let spendable_balance: u64 = 77;
|
let spendable_balance: u64 = 770000;
|
||||||
let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance);
|
let spendable_ciphertext = source_keypair.public.encrypt(spendable_balance);
|
||||||
|
|
||||||
// transfer amount
|
// transfer amount
|
||||||
let transfer_amount: u64 = 55;
|
let transfer_amount: u64 = 550000;
|
||||||
|
|
||||||
// create transfer data
|
// create transfer data
|
||||||
let transfer_data = TransferData::new(
|
let transfer_data = TransferData::new(
|
||||||
@@ -511,19 +567,19 @@ mod test {
|
|||||||
transfer_data
|
transfer_data
|
||||||
.decrypt_amount(Role::Source, &source_keypair.secret)
|
.decrypt_amount(Role::Source, &source_keypair.secret)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
55_u64,
|
550000_u64,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
transfer_data.decrypt_amount(Role::Dest, &dest_sk).unwrap(),
|
transfer_data.decrypt_amount(Role::Dest, &dest_sk).unwrap(),
|
||||||
55_u64,
|
550000_u64,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
transfer_data
|
transfer_data
|
||||||
.decrypt_amount(Role::Auditor, &auditor_sk)
|
.decrypt_amount(Role::Auditor, &auditor_sk)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
55_u64,
|
550000_u64,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,8 +13,8 @@ use {
|
|||||||
},
|
},
|
||||||
errors::ProofError,
|
errors::ProofError,
|
||||||
instruction::{
|
instruction::{
|
||||||
combine_u32_ciphertexts, combine_u32_commitments, combine_u32_openings,
|
combine_lo_hi_ciphertexts, combine_lo_hi_commitments, combine_lo_hi_openings,
|
||||||
split_u64_into_u32, transfer::TransferAmountEncryption, Role, Verifiable, TWO_32,
|
split_u64, transfer::TransferAmountEncryption, Role, Verifiable,
|
||||||
},
|
},
|
||||||
range_proof::RangeProof,
|
range_proof::RangeProof,
|
||||||
sigma_proofs::{
|
sigma_proofs::{
|
||||||
@@ -39,7 +39,9 @@ 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_WITH_FEE_SOURCE_AMOUNT_BIT_LENGTH: usize = 64;
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
const TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH: usize = 32;
|
const TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH: usize = 16;
|
||||||
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
|
const TRANSFER_WITH_FEE_AMOUNT_LO_NEGATED_BIT_LENGTH: usize = 16;
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
const TRANSFER_WITH_FEE_AMOUNT_HI_BIT_LENGTH: usize = 32;
|
const TRANSFER_WITH_FEE_AMOUNT_HI_BIT_LENGTH: usize = 32;
|
||||||
#[cfg(not(target_arch = "bpf"))]
|
#[cfg(not(target_arch = "bpf"))]
|
||||||
@@ -47,6 +49,8 @@ const TRANSFER_WITH_FEE_DELTA_BIT_LENGTH: 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 <<
|
||||||
|
TRANSFER_WITH_FEE_AMOUNT_LO_NEGATED_BIT_LENGTH);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +91,8 @@ 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) = split_u64_into_u32(transfer_amount);
|
let (amount_lo, amount_hi) =
|
||||||
|
split_u64(transfer_amount, TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH);
|
||||||
|
|
||||||
let (ciphertext_lo, opening_lo) = TransferAmountEncryption::new(
|
let (ciphertext_lo, opening_lo) = TransferAmountEncryption::new(
|
||||||
amount_lo,
|
amount_lo,
|
||||||
@@ -118,7 +123,11 @@ impl TransferWithFeeData {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let new_source_ciphertext = old_source_ciphertext
|
let new_source_ciphertext = old_source_ciphertext
|
||||||
- combine_u32_ciphertexts(&transfer_amount_lo_source, &transfer_amount_hi_source);
|
- combine_lo_hi_ciphertexts(
|
||||||
|
&transfer_amount_lo_source,
|
||||||
|
&transfer_amount_hi_source,
|
||||||
|
TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH,
|
||||||
|
);
|
||||||
|
|
||||||
// calculate and encrypt fee
|
// calculate and encrypt fee
|
||||||
let (fee_amount, delta_fee) =
|
let (fee_amount, delta_fee) =
|
||||||
@@ -212,11 +221,6 @@ impl TransferWithFeeData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Decrypts transfer amount from transfer-with-fee data
|
/// Decrypts transfer amount from transfer-with-fee data
|
||||||
///
|
|
||||||
/// TODO: This function should run in constant time. Use `subtle::Choice` for the if statement
|
|
||||||
/// and make sure that the function does not terminate prematurely due to errors
|
|
||||||
///
|
|
||||||
/// TODO: Define specific error type for decryption error
|
|
||||||
pub fn decrypt_amount(&self, role: Role, sk: &ElGamalSecretKey) -> Result<u64, ProofError> {
|
pub fn decrypt_amount(&self, role: Role, sk: &ElGamalSecretKey) -> Result<u64, ProofError> {
|
||||||
let ciphertext_lo = self.ciphertext_lo(role)?;
|
let ciphertext_lo = self.ciphertext_lo(role)?;
|
||||||
let ciphertext_hi = self.ciphertext_hi(role)?;
|
let ciphertext_hi = self.ciphertext_hi(role)?;
|
||||||
@@ -225,7 +229,8 @@ 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) {
|
||||||
Ok(amount_lo + TWO_32 * amount_hi)
|
let two_power = 1 << TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH;
|
||||||
|
Ok(amount_lo + two_power * amount_hi)
|
||||||
} else {
|
} else {
|
||||||
Err(ProofError::Verification)
|
Err(ProofError::Verification)
|
||||||
}
|
}
|
||||||
@@ -324,8 +329,8 @@ impl TransferWithFeeProof {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[allow(clippy::many_single_char_names)]
|
#[allow(clippy::many_single_char_names)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
transfer_amount_lo_data: (u32, &TransferAmountEncryption, &PedersenOpening),
|
transfer_amount_lo_data: (u64, &TransferAmountEncryption, &PedersenOpening),
|
||||||
transfer_amount_hi_data: (u32, &TransferAmountEncryption, &PedersenOpening),
|
transfer_amount_hi_data: (u64, &TransferAmountEncryption, &PedersenOpening),
|
||||||
source_keypair: &ElGamalKeypair,
|
source_keypair: &ElGamalKeypair,
|
||||||
(destination_pubkey, auditor_pubkey): (&ElGamalPubkey, &ElGamalPubkey),
|
(destination_pubkey, auditor_pubkey): (&ElGamalPubkey, &ElGamalPubkey),
|
||||||
(source_new_balance, new_source_ciphertext): (u64, &ElGamalCiphertext),
|
(source_new_balance, new_source_ciphertext): (u64, &ElGamalCiphertext),
|
||||||
@@ -388,31 +393,66 @@ impl TransferWithFeeProof {
|
|||||||
transcript,
|
transcript,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// generate the range proof
|
||||||
let opening_claimed_negated = &PedersenOpening::default() - &opening_claimed;
|
let opening_claimed_negated = &PedersenOpening::default() - &opening_claimed;
|
||||||
let range_proof = RangeProof::new(
|
let range_proof = if TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH == 32 {
|
||||||
vec![
|
RangeProof::new(
|
||||||
source_new_balance,
|
vec![
|
||||||
transfer_amount_lo as u64,
|
source_new_balance,
|
||||||
transfer_amount_hi as u64,
|
transfer_amount_lo as u64,
|
||||||
delta_fee,
|
transfer_amount_hi as u64,
|
||||||
MAX_FEE_BASIS_POINTS - delta_fee,
|
delta_fee,
|
||||||
],
|
MAX_FEE_BASIS_POINTS - delta_fee,
|
||||||
vec![
|
],
|
||||||
TRANSFER_WITH_FEE_SOURCE_AMOUNT_BIT_LENGTH,
|
vec![
|
||||||
TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH,
|
TRANSFER_WITH_FEE_SOURCE_AMOUNT_BIT_LENGTH,
|
||||||
TRANSFER_WITH_FEE_AMOUNT_HI_BIT_LENGTH,
|
TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH,
|
||||||
TRANSFER_WITH_FEE_DELTA_BIT_LENGTH,
|
TRANSFER_WITH_FEE_AMOUNT_HI_BIT_LENGTH,
|
||||||
TRANSFER_WITH_FEE_DELTA_BIT_LENGTH,
|
TRANSFER_WITH_FEE_DELTA_BIT_LENGTH,
|
||||||
],
|
TRANSFER_WITH_FEE_DELTA_BIT_LENGTH,
|
||||||
vec![
|
],
|
||||||
&opening_source,
|
vec![
|
||||||
opening_lo,
|
&opening_source,
|
||||||
opening_hi,
|
opening_lo,
|
||||||
&opening_claimed,
|
opening_hi,
|
||||||
&opening_claimed_negated,
|
&opening_claimed,
|
||||||
],
|
&opening_claimed_negated,
|
||||||
transcript,
|
],
|
||||||
);
|
transcript,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let transfer_amount_lo_negated =
|
||||||
|
(1 << TRANSFER_WITH_FEE_AMOUNT_LO_NEGATED_BIT_LENGTH) - transfer_amount_lo as u64;
|
||||||
|
let opening_lo_negated = &PedersenOpening::default() - opening_lo;
|
||||||
|
|
||||||
|
RangeProof::new(
|
||||||
|
vec![
|
||||||
|
source_new_balance,
|
||||||
|
transfer_amount_lo as u64,
|
||||||
|
transfer_amount_lo_negated,
|
||||||
|
transfer_amount_hi as u64,
|
||||||
|
delta_fee,
|
||||||
|
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,
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
&opening_source,
|
||||||
|
opening_lo,
|
||||||
|
&opening_lo_negated,
|
||||||
|
opening_hi,
|
||||||
|
&opening_claimed,
|
||||||
|
&opening_claimed_negated,
|
||||||
|
],
|
||||||
|
transcript,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
new_source_commitment: pod_new_source_commitment,
|
new_source_commitment: pod_new_source_commitment,
|
||||||
@@ -458,8 +498,6 @@ impl TransferWithFeeProof {
|
|||||||
transcript,
|
transcript,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
println!("here");
|
|
||||||
|
|
||||||
// verify that the transfer amount is encrypted correctly
|
// verify that the transfer amount is encrypted correctly
|
||||||
ciphertext_amount_validity_proof.verify(
|
ciphertext_amount_validity_proof.verify(
|
||||||
(
|
(
|
||||||
@@ -504,18 +542,38 @@ impl TransferWithFeeProof {
|
|||||||
transcript,
|
transcript,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// verify range proof
|
||||||
|
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;
|
||||||
range_proof.verify(
|
|
||||||
vec![
|
if TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH == 32 {
|
||||||
&new_source_commitment,
|
range_proof.verify(
|
||||||
&ciphertext_lo.commitment,
|
vec![
|
||||||
&ciphertext_hi.commitment,
|
&new_source_commitment,
|
||||||
&claimed_commitment,
|
&ciphertext_lo.commitment,
|
||||||
&claimed_commitment_negated,
|
&ciphertext_hi.commitment,
|
||||||
],
|
&claimed_commitment,
|
||||||
vec![64, 32, 32, 64, 64],
|
&claimed_commitment_negated,
|
||||||
transcript,
|
],
|
||||||
)?;
|
vec![64, 32, 32, 64, 64],
|
||||||
|
transcript,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
let commitment_lo_negated = &(*COMMITMENT_MAX) - &ciphertext_lo.commitment;
|
||||||
|
|
||||||
|
range_proof.verify(
|
||||||
|
vec![
|
||||||
|
&new_source_commitment,
|
||||||
|
&ciphertext_lo.commitment,
|
||||||
|
&commitment_lo_negated,
|
||||||
|
&ciphertext_hi.commitment,
|
||||||
|
&claimed_commitment,
|
||||||
|
&claimed_commitment_negated,
|
||||||
|
],
|
||||||
|
vec![64, 16, 16, 32, 64, 64],
|
||||||
|
transcript,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -656,10 +714,18 @@ 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_u32_commitments(commitment_lo, commitment_hi) * &fee_rate_scalar);
|
- &(&combine_lo_hi_commitments(
|
||||||
|
commitment_lo,
|
||||||
|
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_u32_openings(opening_lo, opening_hi) * &fee_rate_scalar);
|
- &(&combine_lo_hi_openings(
|
||||||
|
opening_lo,
|
||||||
|
opening_hi,
|
||||||
|
TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH,
|
||||||
|
) * &fee_rate_scalar);
|
||||||
|
|
||||||
(delta_commitment, opening_delta)
|
(delta_commitment, opening_delta)
|
||||||
}
|
}
|
||||||
@@ -674,7 +740,11 @@ 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_u32_commitments(commitment_lo, commitment_hi) * &fee_rate_scalar)
|
- &(&combine_lo_hi_commitments(
|
||||||
|
commitment_lo,
|
||||||
|
commitment_hi,
|
||||||
|
TRANSFER_WITH_FEE_AMOUNT_LO_BIT_LENGTH,
|
||||||
|
) * &fee_rate_scalar)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
Reference in New Issue
Block a user