diff --git a/Cargo.lock b/Cargo.lock index 6d599e91cf..afa5a2f002 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7719,18 +7719,28 @@ dependencies = [ name = "nym_compact_ecash" version = "0.1.0" dependencies = [ +<<<<<<< HEAD "bls12_381 0.5.0", <<<<<<< HEAD "bs58 0.4.0", "criterion 0.3.6", ======= +======= + "bls12_381 0.6.0", +>>>>>>> 0b58e0ae2 (Small updates and code cleaning) "bs58", "criterion", >>>>>>> b6a43787b (Add bilinear equations into the zk proof; the last eq passes the tests) "digest 0.9.0", +<<<<<<< HEAD "ff 0.10.1", "group 0.10.0", "itertools 0.10.5", +======= + "ff 0.11.0", + "group 0.11.0", + "itertools", +>>>>>>> 0b58e0ae2 (Small updates and code cleaning) "rand 0.8.5", "sha2 0.9.9", "thiserror", diff --git a/common/nym_offline_compact_ecash/Cargo.toml b/common/nym_offline_compact_ecash/Cargo.toml index 0d73627ca0..6b5b9944c6 100644 --- a/common/nym_offline_compact_ecash/Cargo.toml +++ b/common/nym_offline_compact_ecash/Cargo.toml @@ -7,7 +7,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] } +#bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] } +bls12_381 = { path = "/Users/ania/Documents/Git/andrew_bls12_381", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] } itertools = "0.10" digest = "0.9" rand = "0.8" @@ -19,11 +20,11 @@ bs58 = "0.4.0" criterion = { version = "0.3", features = ["html_reports"] } [dependencies.ff] -version = "0.10" +version = "0.11" default-features = false [dependencies.group] -version = "0.10" +version = "0.11" default-features = false [[bench]] diff --git a/common/nym_offline_compact_ecash/benches/benchmarks.rs b/common/nym_offline_compact_ecash/benches/benchmarks.rs index 300dc6cdd8..d1a1f0d24f 100644 --- a/common/nym_offline_compact_ecash/benches/benchmarks.rs +++ b/common/nym_offline_compact_ecash/benches/benchmarks.rs @@ -156,7 +156,7 @@ fn bench_compact_ecash(c: &mut Criterion) { keypair.secret_key(), user_keypair.public_key(), &req, - ) + ).unwrap() }) }, ); @@ -219,28 +219,27 @@ fn bench_compact_ecash(c: &mut Criterion) { // SPENDING PHASE let pay_info = PayInfo { info: [6u8; 32] }; - let spend_vv = - // CLIENT BENCHMARK: spend a single coin from the wallet - group.bench_function( - &format!( - "[Client] spend_a_single_coin_L_{}_threshold_{}", - case.L, case.threshold_p, - ), - |b| { - b.iter(|| { - aggr_wallet - .spend( - ¶ms, - &verification_key, - &user_keypair.secret_key(), - &pay_info, - true, - case.spend_vv, - ) - .unwrap() - }) - }, - ); + // CLIENT BENCHMARK: spend a single coin from the wallet + group.bench_function( + &format!( + "[Client] spend_a_single_coin_L_{}_threshold_{}", + case.L, case.threshold_p, + ), + |b| { + b.iter(|| { + aggr_wallet + .spend( + ¶ms, + &verification_key, + &user_keypair.secret_key(), + &pay_info, + true, + case.spend_vv, + ) + .unwrap() + }) + }, + ); let (payment, upd_wallet) = aggr_wallet .spend( @@ -262,7 +261,7 @@ fn bench_compact_ecash(c: &mut Criterion) { |b| { b.iter(|| { payment - .spend_verify(¶ms, &verification_key, &pay_info, case.spend_vv) + .spend_verify(¶ms, &verification_key, &pay_info) .unwrap() }) }, diff --git a/common/nym_offline_compact_ecash/src/scheme/identify.rs b/common/nym_offline_compact_ecash/src/scheme/identify.rs index 9447e9fea6..8d9cd9280e 100644 --- a/common/nym_offline_compact_ecash/src/scheme/identify.rs +++ b/common/nym_offline_compact_ecash/src/scheme/identify.rs @@ -1,7 +1,13 @@ +use std::collections::HashSet; + +use bls12_381::G1Projective; +use group::Curve; + +use crate::{PayInfo, VerificationKeyAuth}; use crate::error::{CompactEcashError, Result}; -use crate::PayInfo; use crate::scheme::keygen::PublicKeyUser; use crate::scheme::Payment; +use crate::scheme::setup::Parameters; #[derive(Debug, Eq, PartialEq)] pub enum IdentifyResult { @@ -10,43 +16,44 @@ pub enum IdentifyResult { DoubleSpendingPublicKeys(PublicKeyUser), } -pub fn identify(public_keys_u: &[PublicKeyUser], pay1: Payment, pay2: Payment, pay_info1: PayInfo, pay_info2: PayInfo) -> Result { - let mut duplicate_serial_numbers: Vec<(u64, u64)> = Default::default(); - for k in 0..pay1.vv { - for j in 0..pay2.vv { - if pay1.ss[k as usize] == pay2.ss[j as usize] { - duplicate_serial_numbers.push((k, j)); + +pub fn identify(params: &Parameters, public_keys_u: &[PublicKeyUser], verification_key: &VerificationKeyAuth, payment1: Payment, payment2: Payment, pay_info1: PayInfo, pay_info2: PayInfo) -> Result { + // verify first the validity of both payments + assert!(payment1.spend_verify(¶ms, &verification_key, &pay_info1).unwrap()); + assert!(payment2.spend_verify(¶ms, &verification_key, &pay_info2).unwrap()); + + let mut k = 0; + let mut j = 0; + 'outer: for (id1, pay1_ss) in payment1.ss.iter().enumerate() { + 'inner: for (id2, pay2_ss) in payment2.ss.iter().enumerate() { + if pay1_ss == pay2_ss { + k = id1.clone(); + j = id2.clone(); + break 'outer; } } - } - if duplicate_serial_numbers.is_empty() { return Ok(IdentifyResult::NotADuplicatePayment); - } else { - if pay_info1 == pay_info2 { - return Ok(IdentifyResult::DuplicatePayInfo(pay_info1)); - } else { - for elem in duplicate_serial_numbers.iter() { - let k = elem.0 as usize; - let j = elem.1 as usize; - let pk = (pay2.tt[j] * pay1.rr[k] - pay1.tt[k] * pay2.rr[j]) * ((pay1.rr[k] - pay2.rr[j]).invert().unwrap()); - let pk_user = PublicKeyUser { pk: pk.clone() }; - if public_keys_u.contains(&pk_user) { - return Ok(IdentifyResult::DoubleSpendingPublicKeys(pk_user)); - } else { - return Err(CompactEcashError::Identify( - "A duplicate serial number was detected, the pay_info1 and pay_info2 are different, but we failed to identify the double-spending public key".to_string(), - )); - } - } - } - return Err(CompactEcashError::Identify( - "A duplicate serial number was detected, the pay_info1 and pay_info2 are different, but we failed to identify the double-spending public key".to_string(), - )); } + return if pay_info1 == pay_info2 { + Ok(IdentifyResult::DuplicatePayInfo(pay_info1)) + } else { + let pk = (payment2.tt[j] * payment1.rr[k] - payment1.tt[k] * payment2.rr[j]) * ((payment1.rr[k] - payment2.rr[j]).invert().unwrap()); + let pk_user = PublicKeyUser { pk: pk.clone() }; + if public_keys_u.contains(&pk_user) { + Ok(IdentifyResult::DoubleSpendingPublicKeys(pk_user)) + } else { + Err(CompactEcashError::Identify( + "A duplicate serial number was detected, the pay_info1 and pay_info2 are different, but we failed to identify the double-spending public key".to_string(), + )) + } + }; } #[cfg(test)] mod tests { + use std::collections::HashSet; + + use group::Curve; use itertools::izip; use crate::{aggregate_verification_keys, aggregate_wallets, generate_keypair_user, issue_verify, issue_wallet, PartialWallet, PayInfo, ttp_keygen, VerificationKeyAuth, withdrawal_request}; @@ -112,16 +119,16 @@ mod tests { ).unwrap(); assert!(payment1 - .spend_verify(¶ms, &verification_key, &pay_info1, spend_vv) + .spend_verify(¶ms, &verification_key, &pay_info1) .unwrap()); let payment2 = payment1.clone(); assert!(payment2 - .spend_verify(¶ms, &verification_key, &pay_info1, spend_vv) + .spend_verify(¶ms, &verification_key, &pay_info1) .unwrap()); let pay_info2 = pay_info1.clone(); - let identify_result = identify(&[user_keypair.public_key()], payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap(); + let identify_result = identify(¶ms, &[user_keypair.public_key()], &verification_key, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap(); assert_eq!(identify_result, IdentifyResult::DuplicatePayInfo(pay_info1.clone())); } @@ -183,7 +190,7 @@ mod tests { ).unwrap(); assert!(payment1 - .spend_verify(¶ms, &verification_key, &pay_info1, spend_vv) + .spend_verify(¶ms, &verification_key, &pay_info1) .unwrap()); @@ -198,10 +205,10 @@ mod tests { ).unwrap(); assert!(payment2 - .spend_verify(¶ms, &verification_key, &pay_info2, spend_vv) + .spend_verify(¶ms, &verification_key, &pay_info2) .unwrap()); - let identify_result = identify(&[user_keypair.public_key()], payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap(); + let identify_result = identify(¶ms, &[user_keypair.public_key()], &verification_key, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap(); assert_eq!(identify_result, IdentifyResult::NotADuplicatePayment); } @@ -212,6 +219,17 @@ mod tests { let grp = params.grp(); let user_keypair = generate_keypair_user(&grp); + // GENERATE KEYS FOR OTHER USERS + let mut public_keys: Vec = Default::default(); + for i in 0..50 { + let sk = grp.random_scalar(); + let sk_user = SecretKeyUser { sk }; + let pk_user = sk_user.public_key(&grp); + public_keys.push(pk_user.clone()); + } + public_keys.push(user_keypair.public_key().clone()); + + let (req, req_info) = withdrawal_request(grp, &user_keypair.secret_key()).unwrap(); let authorities_keypairs = ttp_keygen(&grp, 2, 3).unwrap(); @@ -253,7 +271,7 @@ mod tests { let pay_info1 = PayInfo { info: [6u8; 32] }; let spend_vv = 1; - let (payment1, upd_wallet) = aggr_wallet.spend( + let (payment1, _) = aggr_wallet.spend( ¶ms, &verification_key, &user_keypair.secret_key(), @@ -263,7 +281,7 @@ mod tests { ).unwrap(); assert!(payment1 - .spend_verify(¶ms, &verification_key, &pay_info1, spend_vv) + .spend_verify(¶ms, &verification_key, &pay_info1) .unwrap()); // let's reverse the spending counter in the wallet to create a double spending payment @@ -271,7 +289,6 @@ mod tests { aggr_wallet.l.set(current_l - 1); let pay_info2 = PayInfo { info: [7u8; 32] }; - let spend_vv = 1; let (payment2, _) = aggr_wallet.spend( ¶ms, @@ -283,21 +300,10 @@ mod tests { ).unwrap(); assert!(payment2 - .spend_verify(¶ms, &verification_key, &pay_info2, spend_vv) + .spend_verify(¶ms, &verification_key, &pay_info2) .unwrap()); - // GENERATE KEYS FOR OTHER USERS - let mut public_keys: Vec = Default::default(); - for i in 0..50 { - let sk = grp.random_scalar(); - let sk_user = SecretKeyUser { sk }; - let pk_user = sk_user.public_key(&grp); - public_keys.push(pk_user); - } - public_keys.push(user_keypair.public_key()); - - - let identify_result = identify(&public_keys, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap(); + let identify_result = identify(¶ms, &public_keys, &verification_key, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap(); assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key())); } @@ -308,6 +314,16 @@ mod tests { let grp = params.grp(); let user_keypair = generate_keypair_user(&grp); + // GENERATE KEYS FOR OTHER USERS + let mut public_keys: Vec = Default::default(); + for i in 0..50 { + let sk = grp.random_scalar(); + let sk_user = SecretKeyUser { sk }; + let pk_user = sk_user.public_key(&grp); + public_keys.push(pk_user.clone()); + } + public_keys.push(user_keypair.public_key().clone()); + let (req, req_info) = withdrawal_request(grp, &user_keypair.secret_key()).unwrap(); let authorities_keypairs = ttp_keygen(&grp, 2, 3).unwrap(); @@ -349,7 +365,7 @@ mod tests { let pay_info1 = PayInfo { info: [6u8; 32] }; let spend_vv = 10; - let (payment1, upd_wallet) = aggr_wallet.spend( + let (payment1, _) = aggr_wallet.spend( ¶ms, &verification_key, &user_keypair.secret_key(), @@ -359,7 +375,7 @@ mod tests { ).unwrap(); assert!(payment1 - .spend_verify(¶ms, &verification_key, &pay_info1, spend_vv) + .spend_verify(¶ms, &verification_key, &pay_info1) .unwrap()); // let's reverse the spending counter in the wallet to create a double spending payment @@ -376,17 +392,8 @@ mod tests { spend_vv, ).unwrap(); - // GENERATE KEYS FOR OTHER USERS - let mut public_keys: Vec = Default::default(); - for i in 0..50 { - let sk = grp.random_scalar(); - let sk_user = SecretKeyUser { sk }; - let pk_user = sk_user.public_key(&grp); - public_keys.push(pk_user); - } - public_keys.push(user_keypair.public_key()); - let identify_result = identify(&public_keys, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap(); + let identify_result = identify(¶ms, &public_keys, &verification_key, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap(); assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key())); } } \ No newline at end of file diff --git a/common/nym_offline_compact_ecash/src/scheme/keygen.rs b/common/nym_offline_compact_ecash/src/scheme/keygen.rs index 9e58ccb358..92f47557f0 100644 --- a/common/nym_offline_compact_ecash/src/scheme/keygen.rs +++ b/common/nym_offline_compact_ecash/src/scheme/keygen.rs @@ -332,7 +332,7 @@ impl SecretKeyUser { } } -#[derive(Debug, Eq, PartialEq, Clone)] +#[derive(Debug, Eq, PartialEq, Clone, Hash)] pub struct PublicKeyUser { pub(crate) pk: G1Projective, } diff --git a/common/nym_offline_compact_ecash/src/scheme/mod.rs b/common/nym_offline_compact_ecash/src/scheme/mod.rs index 68b6b7e771..789bfd7d97 100644 --- a/common/nym_offline_compact_ecash/src/scheme/mod.rs +++ b/common/nym_offline_compact_ecash/src/scheme/mod.rs @@ -281,7 +281,6 @@ impl Payment { params: &Parameters, verification_key: &VerificationKeyAuth, pay_info: &PayInfo, - spend_vv: u64, ) -> Result { if bool::from(self.sig.0.is_identity()) { return Err(CompactEcashError::Spend( @@ -300,7 +299,7 @@ impl Payment { )); } - for k in 0..spend_vv { + for k in 0..self.vv { if bool::from(self.sig_lk[k as usize].0.is_identity()) { return Err(CompactEcashError::Spend( "The element h of the signature on l equals the identity".to_string(), diff --git a/common/nym_offline_compact_ecash/src/tests/e2e.rs b/common/nym_offline_compact_ecash/src/tests/e2e.rs index 528faa9974..33ae9bc07a 100644 --- a/common/nym_offline_compact_ecash/src/tests/e2e.rs +++ b/common/nym_offline_compact_ecash/src/tests/e2e.rs @@ -72,7 +72,7 @@ fn main() -> Result<(), CompactEcashError> { )?; assert!(payment - .spend_verify(¶ms, &verification_key, &pay_info, spend_vv) + .spend_verify(¶ms, &verification_key, &pay_info) .unwrap()); diff --git a/common/nym_offline_compact_ecash/src/utils.rs b/common/nym_offline_compact_ecash/src/utils.rs index 42c2303aa7..c8bfa95eff 100644 --- a/common/nym_offline_compact_ecash/src/utils.rs +++ b/common/nym_offline_compact_ecash/src/utils.rs @@ -6,10 +6,10 @@ use core::ops::Mul; use std::convert::{TryFrom, TryInto}; use std::ops::Neg; -use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; use bls12_381::{ - multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar, + G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, multi_miller_loop, Scalar, }; +use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; use ff::Field; use group::{Curve, Group}; @@ -36,7 +36,7 @@ impl Polynomial { Scalar::zero() // if x is zero then we can ignore most of the expensive computation and // just return the last term of the polynomial - } else if x.is_zero() { + } else if x.is_zero().unwrap_u8() == 1 { // we checked that coefficients are not empty so unwrap here is fine *self.coefficients.first().unwrap() } else { @@ -85,9 +85,9 @@ pub(crate) fn perform_lagrangian_interpolation_at_origin( points: &[SignerIndex], values: &[T], ) -> Result -where - T: Sum, - for<'a> &'a T: Mul, + where + T: Sum, + for<'a> &'a T: Mul, { if points.is_empty() || values.is_empty() { return Err(CompactEcashError::Interpolation( diff --git a/common/nym_offline_divisible_ecash/Cargo.toml b/common/nym_offline_divisible_ecash/Cargo.toml index 4bb909795a..10fc986131 100644 --- a/common/nym_offline_divisible_ecash/Cargo.toml +++ b/common/nym_offline_divisible_ecash/Cargo.toml @@ -25,4 +25,8 @@ default-features = false [dependencies.group] version = "0.11" -default-features = false \ No newline at end of file +default-features = false + +[[bench]] +name = "benchmarks" +harness = false \ No newline at end of file diff --git a/common/nym_offline_divisible_ecash/benches/benchmarks.rs b/common/nym_offline_divisible_ecash/benches/benchmarks.rs index e69de29bb2..18960cd434 100644 --- a/common/nym_offline_divisible_ecash/benches/benchmarks.rs +++ b/common/nym_offline_divisible_ecash/benches/benchmarks.rs @@ -0,0 +1,267 @@ +use std::collections::HashSet; +use std::ops::Neg; +use std::time::Duration; + +use bls12_381::{G1Affine, G2Affine, G2Prepared, multi_miller_loop, Scalar}; +use criterion::{Criterion, criterion_group, criterion_main}; +use ff::Field; +use group::{Curve, Group}; +use rand::seq::SliceRandom; +use rand::thread_rng; + +use nym_offline_divisible_ecash::{aggregate_verification_keys, aggregate_wallets, + issue, issue_verify, PartialWallet, + PayInfo, PublicKeyUser, SecretKeyUser, ttp_keygen_authorities, ttp_keygen_users, VerificationKeyAuth, withdrawal_request}; +use nym_offline_divisible_ecash::identification::{identify, IdentifyResult}; +use nym_offline_divisible_ecash::setup::{GroupParameters, Parameters}; + +#[allow(unused)] +fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) { + let gt1 = bls12_381::pairing(g11, g21); + let gt2 = bls12_381::pairing(g12, g22); + assert_eq!(gt1, gt2) +} + +#[allow(unused)] +fn multi_miller_pairing_affine(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) { + let miller_loop_result = multi_miller_loop(&[ + (g11, &G2Prepared::from(*g21)), + (&g12.neg(), &G2Prepared::from(*g22)), + ]); + assert!(bool::from( + miller_loop_result.final_exponentiation().is_identity() + )) +} + +#[allow(unused)] +fn multi_miller_pairing_with_prepared( + g11: &G1Affine, + g21: &G2Prepared, + g12: &G1Affine, + g22: &G2Prepared, +) { + let miller_loop_result = multi_miller_loop(&[(g11, g21), (&g12.neg(), g22)]); + assert!(bool::from( + miller_loop_result.final_exponentiation().is_identity() + )) +} + +// the case of being able to prepare G2 generator +#[allow(unused)] +fn multi_miller_pairing_with_semi_prepared( + g11: &G1Affine, + g21: &G2Affine, + g12: &G1Affine, + g22: &G2Prepared, +) { + let miller_loop_result = + multi_miller_loop(&[(g11, &G2Prepared::from(*g21)), (&g12.neg(), g22)]); + assert!(bool::from( + miller_loop_result.final_exponentiation().is_identity() + )) +} + +#[allow(unused)] +fn bench_pairings(c: &mut Criterion) { + let mut rng = rand::thread_rng(); + + let g1 = G1Affine::generator(); + let g2 = G2Affine::generator(); + let r = Scalar::random(&mut rng); + let s = Scalar::random(&mut rng); + + let g11 = (g1 * r).to_affine(); + let g21 = (g2 * s).to_affine(); + let g21_prep = G2Prepared::from(g21); + + let g12 = (g1 * s).to_affine(); + let g22 = (g2 * r).to_affine(); + let g22_prep = G2Prepared::from(g22); + + c.bench_function("double pairing", |b| { + b.iter(|| double_pairing(&g11, &g21, &g12, &g22)) + }); + + c.bench_function("multi miller in affine", |b| { + b.iter(|| multi_miller_pairing_affine(&g11, &g21, &g12, &g22)) + }); + + c.bench_function("multi miller with prepared g2", |b| { + b.iter(|| multi_miller_pairing_with_prepared(&g11, &g21_prep, &g12, &g22_prep)) + }); + + c.bench_function("multi miller with semi-prepared g2", |b| { + b.iter(|| multi_miller_pairing_with_semi_prepared(&g11, &g21, &g12, &g22_prep)) + }); +} + +struct BenchCase { + num_authorities: u64, + threshold_p: f32, + L: u64, + spend_vv: u64, + case_nr_pub_keys: u64, +} + +fn bench_divisible_ecash(c: &mut Criterion) { + let mut group = c.benchmark_group("benchmark-divisible-ecash"); + group.measurement_time(Duration::from_secs(200)); + + let case = BenchCase { + num_authorities: 100, + threshold_p: 0.7, + L: 100, + spend_vv: 10, + case_nr_pub_keys: 50, + }; + + // SETUP PHASE + let grp = GroupParameters::new().unwrap(); + let params = Parameters::new(grp.clone()); + + // KEY GENERATION FOR THE AUTHORITIES + let authorities_keypairs = ttp_keygen_authorities(¶ms, 2, 3).unwrap(); + let verification_keys_auth: Vec = authorities_keypairs + .iter() + .map(|keypair| keypair.verification_key()) + .collect(); + + let verification_key = + aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap(); + + // KEY GENERATION FOR THE USER + let sk = grp.random_scalar(); + let sk_user = SecretKeyUser { sk }; + let pk_user = SecretKeyUser::public_key(&sk_user, &grp); + + // GENERATE KEYS FOR OTHER USERS + let mut pk_all_users = HashSet::new(); + for i in 0..50 { + let sk = grp.random_scalar(); + let sk_user = SecretKeyUser { sk }; + let pk_user = sk_user.public_key(&grp); + pk_all_users.insert(pk_user); + } + pk_all_users.insert(pk_user.clone()); + + // WITHDRAWAL REQUEST + let (withdrawal_req, req_info) = withdrawal_request(¶ms, &sk_user).unwrap(); + + // CLIENT BENCHMARK: prepare a single withdrawal request + group.bench_function( + &format!( + "[Client] withdrawal_request_{}_authorities_{}_L_{}_threshold", + case.num_authorities, case.L, case.threshold_p, + ), + |b| b.iter(|| withdrawal_request(¶ms, &sk_user).unwrap()), + ); + + // ISSUE PARTIAL WALLETS + // first one meaningful one just for benchmark + let mut rng = rand::thread_rng(); + let keypair = authorities_keypairs.choose(&mut rng).unwrap(); + group.bench_function( + &format!("[Issuing Authority] issue_partial_wallet_with_L_{}", case.L, ), + |b| { + b.iter(|| { + issue( + ¶ms, + &withdrawal_req, + pk_user.clone(), + &keypair.secret_key(), + ).unwrap() + }) + }, + ); + + + let mut partial_wallets = Vec::new(); + for auth_keypair in authorities_keypairs { + let blind_signature = issue( + ¶ms, + &withdrawal_req, + pk_user.clone(), + &auth_keypair.secret_key(), + ).unwrap(); + let partial_wallet = issue_verify(&grp, &auth_keypair.verification_key(), &sk_user, &blind_signature, &req_info).unwrap(); + partial_wallets.push(partial_wallet); + } + + // AGGREGATE WALLET + let mut wallet = aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets).unwrap(); + + // CLIENT BENCHMARK: aggregating all partial wallets + group.bench_function( + &format!( + "[Client] aggregate_wallets_with_L_{}_threshold_{}", + case.L, case.threshold_p, + ), + |b| { + b.iter(|| { + aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets) + .unwrap() + }) + }, + ); + + let pay_info = PayInfo { info: [67u8; 32] }; + let (payment, wallet) = wallet.spend(¶ms, &verification_key, &sk_user, &pay_info, 10, false).unwrap(); + + // CLIENT BENCHMARK: spend a single coin from the wallet + group.bench_function( + &format!( + "[Client] spend_a_single_coin_L_{}_threshold_{}", + case.L, case.threshold_p, + ), + |b| { + b.iter(|| { + wallet.spend(¶ms, &verification_key, &sk_user, &pay_info, 10, true) + .unwrap() + }) + }, + ); + + // MERCHANT BENCHMARK: verify whether the submitted payment is legit + group.bench_function( + &format!( + "[Merchant] spend_verify_of_a_single_payment_L_{}_threshold_{}", + case.L, case.threshold_p, + ), + |b| { + b.iter(|| { + payment.spend_verify(¶ms, &verification_key, &pay_info) + .unwrap() + }) + }, + ); + + // BENCHMARK IDENTIFICATION + // Let's generate a double spending payment + + // let's reverse the spending counter in the wallet to create a double spending payment + let current_l = wallet.l(); + wallet.l.set(current_l - 7); + + let pay_info2 = PayInfo { info: [52u8; 32] }; + let (payment2, wallet) = wallet.spend(¶ms, &verification_key, &sk_user, &pay_info2, 10, false).unwrap(); + + // MERCHANT BENCHMARK: identify double spending + group.bench_function( + &format!( + "[Merchant] identify_L_{}_threshold_{}_spend_vv_{}_pks_{}", + case.L, case.threshold_p, case.spend_vv, pk_all_users.len() + ), + |b| { + b.iter(|| { + identify(¶ms, &verification_key, &pk_all_users, payment.clone(), payment2.clone(), pay_info, pay_info2).unwrap() + }) + }, + ); + + + let identify_result = identify(¶ms, &verification_key, &pk_all_users, payment.clone(), payment2.clone(), pay_info, pay_info2).unwrap(); + assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(pk_user)); +} + +criterion_group!(benches, bench_divisible_ecash); +criterion_main!(benches); \ No newline at end of file diff --git a/common/nym_offline_divisible_ecash/src/lib.rs b/common/nym_offline_divisible_ecash/src/lib.rs index 7fedb8521b..523ee845fb 100644 --- a/common/nym_offline_divisible_ecash/src/lib.rs +++ b/common/nym_offline_divisible_ecash/src/lib.rs @@ -1,4 +1,21 @@ -use bls12_381::Scalar; +use bls12_381::{G1Projective, G2Prepared, G2Projective, pairing, Scalar}; + +pub use scheme::aggregation::aggregate_verification_keys; +pub use scheme::aggregation::aggregate_wallets; +pub use scheme::identification; +pub use scheme::keygen::{PublicKeyUser, SecretKeyUser, VerificationKeyAuth}; +pub use scheme::keygen::ttp_keygen_authorities; +pub use scheme::keygen::ttp_keygen_users; +pub use scheme::PartialWallet; +pub use scheme::PayInfo; +pub use scheme::setup; +pub use scheme::withdrawal::issue; +pub use scheme::withdrawal::issue_verify; +pub use scheme::withdrawal::withdrawal_request; +pub use traits::Base58; + +use crate::error::DivisibleEcashError; +use crate::traits::Bytable; mod error; mod proofs; diff --git a/common/nym_offline_divisible_ecash/src/scheme/identification.rs b/common/nym_offline_divisible_ecash/src/scheme/identification.rs index e3dc32783a..237971a095 100644 --- a/common/nym_offline_divisible_ecash/src/scheme/identification.rs +++ b/common/nym_offline_divisible_ecash/src/scheme/identification.rs @@ -22,66 +22,63 @@ pub enum IdentifyResult { pub fn identify( params: &Parameters, verification_key: &VerificationKeyAuth, - public_keys_u: &[PublicKeyUser], + public_keys_u: &HashSet, payment1: Payment, payment2: Payment, pay_info1: PayInfo, pay_info2: PayInfo) -> Result { - // verify first the validaty of both payments + // verify first the validity of both payments assert!(payment1.spend_verify(¶ms, &verification_key, &pay_info1).unwrap()); assert!(payment2.spend_verify(¶ms, &verification_key, &pay_info2).unwrap()); let params_a = params.get_params_a(); + // compute the serial numbers for k1 in [0, V1-1] let mut serial_numbers = HashMap::new(); - for k1 in 0..payment1.vv { - let sn = pairing(&payment1.phi.1.to_affine(), ¶ms_a.get_ith_delta(k1 as usize).to_affine()) - + pairing(&payment1.phi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment1.vv as usize, k1 as usize).to_affine()); - serial_numbers.insert(sn, k1); + for k in 0..payment1.vv { + let sn = pairing(&payment1.phi.1.to_affine(), ¶ms_a.get_ith_delta(k as usize).to_affine()) + + pairing(&payment1.phi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment1.vv as usize, k as usize).to_affine()); + serial_numbers.insert(sn, k); } // compute the serial numbers fo k2 in [0, V2-1] + let mut k1 = 0; + let mut k2 = 0; let mut duplicate_serial_numbers: Vec<(Gt, u64, u64)> = Default::default(); - for k2 in 0..payment2.vv { - let sn = pairing(&payment2.phi.1.to_affine(), ¶ms_a.get_ith_delta(k2 as usize).to_affine()) - + pairing(&payment2.phi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment2.vv as usize, k2 as usize).to_affine()); + for j in 0..payment2.vv { + let sn = pairing(&payment2.phi.1.to_affine(), ¶ms_a.get_ith_delta(j as usize).to_affine()) + + pairing(&payment2.phi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment2.vv as usize, j as usize).to_affine()); if !serial_numbers.contains_key(&sn) { - serial_numbers.insert(sn, k2); + serial_numbers.insert(sn, j); } else { - let k1 = *serial_numbers.get(&sn).unwrap() as u64; - duplicate_serial_numbers.push((sn, k1, k2)); + k1 = *serial_numbers.get(&sn).unwrap() as u64; + k2 = j.clone(); + break; } + return Ok(IdentifyResult::NotADuplicatePayment); } - if duplicate_serial_numbers.is_empty() { - Ok(IdentifyResult::NotADuplicatePayment) + if pay_info1 == pay_info2 { + Ok(IdentifyResult::DuplicatePayInfo(pay_info1)) } else { - if pay_info1.info == pay_info2.info { - Ok(IdentifyResult::DuplicatePayInfo(pay_info1)) - } else { - for elem in duplicate_serial_numbers.iter() { - let k1 = elem.1; - let k2 = elem.2; - let delta_k1 = params_a.get_ith_delta(k1 as usize); - let delta_k2 = params_a.get_ith_delta(k2 as usize); - let tt1 = pairing(&payment1.varphi.1.to_affine(), &delta_k1.to_affine()) - + pairing(&payment1.varphi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment1.vv as usize, k1 as usize).to_affine()); - let tt2 = pairing(&payment2.varphi.1.to_affine(), &delta_k2.to_affine()) - + pairing(&payment2.varphi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment2.vv as usize, k2 as usize).to_affine()); + let delta_k1 = params_a.get_ith_delta(k1 as usize); + let delta_k2 = params_a.get_ith_delta(k2 as usize); + let tt1 = pairing(&payment1.varphi.1.to_affine(), &delta_k1.to_affine()) + + pairing(&payment1.varphi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment1.vv as usize, k1 as usize).to_affine()); + let tt2 = pairing(&payment2.varphi.1.to_affine(), &delta_k2.to_affine()) + + pairing(&payment2.varphi.0.to_affine(), ¶ms_a.get_etas_ith_jth_elem(payment2.vv as usize, k2 as usize).to_affine()); - for pk_u in public_keys_u.iter() { - let pg_pku_deltas = pairing(&pk_u.pk.to_affine(), &(delta_k1 * payment1.rr + delta_k2 * payment2.rr.neg()).to_affine()); - if tt1 - tt2 == pg_pku_deltas { - return Ok(IdentifyResult::DoubleSpendingPublicKeys(pk_u.clone())); - } - } + for pk_u in public_keys_u.iter() { + let pg_pku_deltas = pairing(&pk_u.pk.to_affine(), &(delta_k1 * payment1.rr + delta_k2 * payment2.rr.neg()).to_affine()); + if tt1 - tt2 == pg_pku_deltas { + return Ok(IdentifyResult::DoubleSpendingPublicKeys(pk_u.clone())); } - return Err(DivisibleEcashError::Identify( - "A duplicate serial number was detected, the payinfo1 and payinfo2 are different, but we failed to identify the double-spending public key".to_string(), - )); } + return Err(DivisibleEcashError::Identify( + "A duplicate serial number was detected, the payinfo1 and payinfo2 are different, but we failed to identify the double-spending public key".to_string(), + )); } } @@ -103,7 +100,6 @@ mod tests { #[test] fn duplicate_payments_with_the_same_pay_info() { - let rng = thread_rng(); let grp = GroupParameters::new().unwrap(); let params = Parameters::new(grp.clone()); let params_u = params.get_params_u(); @@ -150,7 +146,7 @@ mod tests { let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap(); let pay_info1 = PayInfo { info: [67u8; 32] }; - let (payment1, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10).unwrap(); + let (payment1, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap(); // SPEND VERIFICATION for USER1 assert!(payment1.spend_verify(¶ms, &verification_key, &pay_info1).unwrap()); @@ -161,7 +157,8 @@ mod tests { let pay_info2 = pay_info1.clone(); - let identify_result = identify(¶ms, &verification_key, &[pk_user1, pk_user2], payment1, payment2, pay_info1, pay_info2).unwrap(); + let public_keys = HashSet::from([pk_user1, pk_user2]); + let identify_result = identify(¶ms, &verification_key, &public_keys, payment1, payment2, pay_info1, pay_info2).unwrap(); assert_eq!(identify_result, IdentifyResult::DuplicatePayInfo(pay_info1)); } @@ -188,20 +185,16 @@ mod tests { let sk_user1 = SecretKeyUser { sk: sk1 }; let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp); - // KEY GENERATION FOR THE USER2 - let sk_user2 = sk_user1.clone(); - let pk_user2 = pk_user1.clone(); // GENERATE KEYS FOR OTHER USERS - let mut pk_all_users: Vec = Default::default(); + let mut pk_all_users = HashSet::new(); for i in 0..50 { let sk = grp.random_scalar(); let sk_user = SecretKeyUser { sk }; let pk_user = sk_user.public_key(&grp); - pk_all_users.push(pk_user); + pk_all_users.insert(pk_user); } - pk_all_users.push(pk_user1.clone()); - pk_all_users.push(pk_user2.clone()); + pk_all_users.insert(pk_user1.clone()); // WITHDRAWAL REQUEST FOR USER1 let (withdrawal_req1, req_info1) = withdrawal_request(¶ms, &sk_user1).unwrap(); @@ -223,14 +216,14 @@ mod tests { let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap(); let pay_info1 = PayInfo { info: [67u8; 32] }; - let (payment1, new_wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10).unwrap(); + let (payment1, new_wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap(); // let's reverse the spending counter in the wallet to create a double spending payment let current_l = wallet1.l.get(); wallet1.l.set(current_l - 1); let pay_info2 = PayInfo { info: [52u8; 32] }; - let (payment2, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info2, 10).unwrap(); + let (payment2, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info2, 10, false).unwrap(); let identify_result = identify(¶ms, &verification_key, &pk_all_users, payment1, payment2, pay_info1, pay_info2).unwrap(); @@ -261,20 +254,15 @@ mod tests { let sk_user1 = SecretKeyUser { sk: sk1 }; let pk_user1 = SecretKeyUser::public_key(&sk_user1, &grp); - // KEY GENERATION FOR THE USER2 - let sk_user2 = sk_user1.clone(); - let pk_user2 = pk_user1.clone(); - // GENERATE KEYS FOR OTHER USERS - let mut pk_all_users: Vec = Default::default(); + let mut public_keys = HashSet::new(); for i in 0..50 { let sk = grp.random_scalar(); let sk_user = SecretKeyUser { sk }; let pk_user = sk_user.public_key(&grp); - pk_all_users.push(pk_user); + public_keys.insert(pk_user); } - pk_all_users.push(pk_user1.clone()); - pk_all_users.push(pk_user2.clone()); + public_keys.insert(pk_user1.clone()); // WITHDRAWAL REQUEST FOR USER1 let (withdrawal_req1, req_info1) = withdrawal_request(¶ms, &sk_user1).unwrap(); @@ -296,17 +284,17 @@ mod tests { let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap(); let pay_info1 = PayInfo { info: [67u8; 32] }; - let (payment1, new_wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10).unwrap(); + let (payment1, new_wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap(); // let's reverse the spending counter in the wallet to create a double spending payment let current_l = wallet1.l.get(); wallet1.l.set(current_l - 7); let pay_info2 = PayInfo { info: [52u8; 32] }; - let (payment2, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info2, 10).unwrap(); + let (payment2, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info2, 10, false).unwrap(); - let identify_result = identify(¶ms, &verification_key, &pk_all_users, payment1, payment2, pay_info1, pay_info2).unwrap(); + let identify_result = identify(¶ms, &verification_key, &public_keys, payment1, payment2, pay_info1, pay_info2).unwrap(); assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(pk_user1)); } @@ -360,7 +348,7 @@ mod tests { let mut wallet1 = aggregate_wallets(&grp, &verification_key, &sk_user1, &partial_wallets1).unwrap(); let pay_info1 = PayInfo { info: [67u8; 32] }; - let (payment1, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10).unwrap(); + let (payment1, wallet1) = wallet1.spend(¶ms, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap(); // SPEND VERIFICATION for USER1 assert!(payment1.spend_verify(¶ms, &verification_key, &pay_info1).unwrap()); @@ -385,12 +373,13 @@ mod tests { let mut wallet2 = aggregate_wallets(&grp, &verification_key, &sk_user2, &partial_wallets2).unwrap(); let pay_info2 = PayInfo { info: [67u8; 32] }; - let (payment2, wallet2) = wallet2.spend(¶ms, &verification_key, &sk_user2, &pay_info2, 10).unwrap(); + let (payment2, wallet2) = wallet2.spend(¶ms, &verification_key, &sk_user2, &pay_info2, 10, false).unwrap(); // SPEND VERIFICATION for USER2 assert!(payment2.spend_verify(¶ms, &verification_key, &pay_info2).unwrap()); - let identify_result = identify(¶ms, &verification_key, &[pk_user1, pk_user2], payment1, payment2, pay_info1, pay_info2).unwrap(); + let public_keys = HashSet::from([pk_user1, pk_user2]); + let identify_result = identify(¶ms, &verification_key, &public_keys, payment1, payment2, pay_info1, pay_info2).unwrap(); assert_eq!(identify_result, IdentifyResult::NotADuplicatePayment); } } \ No newline at end of file diff --git a/common/nym_offline_divisible_ecash/src/scheme/keygen.rs b/common/nym_offline_divisible_ecash/src/scheme/keygen.rs index fbb137fb87..b8500a2c8f 100644 --- a/common/nym_offline_divisible_ecash/src/scheme/keygen.rs +++ b/common/nym_offline_divisible_ecash/src/scheme/keygen.rs @@ -340,7 +340,7 @@ impl KeyPairAuth { #[derive(Eq, Debug, PartialEq, Clone)] pub struct SecretKeyUser { - pub(crate) sk: Scalar, + pub sk: Scalar, } impl SecretKeyUser { diff --git a/common/nym_offline_divisible_ecash/src/scheme/mod.rs b/common/nym_offline_divisible_ecash/src/scheme/mod.rs index ac8a4d2c90..62364b76f6 100644 --- a/common/nym_offline_divisible_ecash/src/scheme/mod.rs +++ b/common/nym_offline_divisible_ecash/src/scheme/mod.rs @@ -172,7 +172,7 @@ impl PartialWallet { pub struct Wallet { sig: Signature, v: Scalar, - l: Cell, + pub l: Cell, } impl Wallet { @@ -189,13 +189,14 @@ impl Wallet { } - pub(crate) fn spend( + pub fn spend( &self, params: &Parameters, verification_key: &VerificationKeyAuth, sk_user: &SecretKeyUser, pay_info: &PayInfo, vv: u64, + bench_flag: bool, ) -> Result<(Payment, &Self)> { if self.l() + vv >= L { return Err(DivisibleEcashError::Spend( @@ -345,8 +346,16 @@ impl Wallet { zk_proof, vv, }; - - self.l.set(self.l() + vv); + + // The number of samples collected by the benchmark process is way higher than the + // MAX_WALLET_VALUE we ever consider. Thus, we would execute the spending too many times + // and the initial condition at the top of this function will crush. Thus, we need a + // benchmark flag to signal that we don't want to increase the spending couter but only + // care about the function performance. + if !bench_flag { + let current_l = self.l(); + self.l.set(current_l + vv); + } Ok((pay, self)) } } diff --git a/common/nym_offline_divisible_ecash/src/scheme/setup.rs b/common/nym_offline_divisible_ecash/src/scheme/setup.rs index 15f1ce94e6..dcda367cc7 100644 --- a/common/nym_offline_divisible_ecash/src/scheme/setup.rs +++ b/common/nym_offline_divisible_ecash/src/scheme/setup.rs @@ -43,7 +43,7 @@ impl GroupParameters { &self._g2_prepared_miller } - pub(crate) fn random_scalar(&self) -> Scalar { + pub fn random_scalar(&self) -> Scalar { // lazily-initialized thread-local random number generator, seeded by the system let mut rng = thread_rng(); Scalar::random(&mut rng) diff --git a/common/nym_offline_divisible_ecash/src/scheme/withdrawal.rs b/common/nym_offline_divisible_ecash/src/scheme/withdrawal.rs index bc3a243064..42db53a22d 100644 --- a/common/nym_offline_divisible_ecash/src/scheme/withdrawal.rs +++ b/common/nym_offline_divisible_ecash/src/scheme/withdrawal.rs @@ -78,7 +78,7 @@ pub fn withdrawal_request(params: &Parameters, sk_user: &SecretKeyUser) -> Resul Ok((req, req_info)) } -pub(crate) fn issue(params: &Parameters, req: &WithdrawalRequest, pk_u: PublicKeyUser, sk_a: &SecretKeyAuth) -> Result { +pub fn issue(params: &Parameters, req: &WithdrawalRequest, pk_u: PublicKeyUser, sk_a: &SecretKeyAuth) -> Result { let h = hash_g1(req.com.to_bytes()); if !(h == req.com_hash) { return Err(DivisibleEcashError::WithdrawalRequestVerification( @@ -110,7 +110,7 @@ pub(crate) fn issue(params: &Parameters, req: &WithdrawalRequest, pk_u: PublicKe Ok(BlindedSignature(h, sig)) } -pub(crate) fn issue_verify( +pub fn issue_verify( params: &GroupParameters, vk_auth: &VerificationKeyAuth, sk_user: &SecretKeyUser, diff --git a/common/nym_offline_divisible_ecash/src/tests/e2e.rs b/common/nym_offline_divisible_ecash/src/tests/e2e.rs index 89dfc2b6e4..f3d0face34 100644 --- a/common/nym_offline_divisible_ecash/src/tests/e2e.rs +++ b/common/nym_offline_divisible_ecash/src/tests/e2e.rs @@ -13,11 +13,8 @@ use crate::scheme::withdrawal::{issue, issue_verify, withdrawal_request}; // and spending. fn main() -> Result<(), DivisibleEcashError> { // SETUP PHASE - let rng = thread_rng(); let grp = GroupParameters::new().unwrap(); let params = Parameters::new(grp.clone()); - let params_u = params.get_params_u(); - let params_a = params.get_params_a(); // KEY GENERATION FOR THE AUTHORITIES let authorities_keypairs = ttp_keygen_authorities(¶ms, 2, 3).unwrap(); @@ -54,7 +51,7 @@ fn main() -> Result<(), DivisibleEcashError> { let mut wallet = aggregate_wallets(&grp, &verification_key, &sk_user, &partial_wallets)?; let pay_info = PayInfo { info: [67u8; 32] }; - let (payment, wallet) = wallet.spend(¶ms, &verification_key, &sk_user, &pay_info, 10)?; + let (payment, wallet) = wallet.spend(¶ms, &verification_key, &sk_user, &pay_info, 10, false)?; // SPEND VERIFICATION assert!(payment.spend_verify(¶ms, &verification_key, &pay_info).unwrap());