Add verification of the payments inside identify; speed up the identify checks

This commit is contained in:
aniampio
2022-07-19 13:18:42 +01:00
committed by durch
parent dd527e4295
commit 3691110db8
17 changed files with 475 additions and 176 deletions
Generated
+10
View File
@@ -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",
+4 -3
View File
@@ -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]]
@@ -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(
&params,
&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(
&params,
&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(&params, &verification_key, &pay_info, case.spend_vv)
.spend_verify(&params, &verification_key, &pay_info)
.unwrap()
})
},
@@ -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<IdentifyResult> {
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<IdentifyResult> {
// verify first the validity of both payments
assert!(payment1.spend_verify(&params, &verification_key, &pay_info1).unwrap());
assert!(payment2.spend_verify(&params, &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(&params, &verification_key, &pay_info1, spend_vv)
.spend_verify(&params, &verification_key, &pay_info1)
.unwrap());
let payment2 = payment1.clone();
assert!(payment2
.spend_verify(&params, &verification_key, &pay_info1, spend_vv)
.spend_verify(&params, &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(&params, &[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(&params, &verification_key, &pay_info1, spend_vv)
.spend_verify(&params, &verification_key, &pay_info1)
.unwrap());
@@ -198,10 +205,10 @@ mod tests {
).unwrap();
assert!(payment2
.spend_verify(&params, &verification_key, &pay_info2, spend_vv)
.spend_verify(&params, &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(&params, &[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<PublicKeyUser> = 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(
&params,
&verification_key,
&user_keypair.secret_key(),
@@ -263,7 +281,7 @@ mod tests {
).unwrap();
assert!(payment1
.spend_verify(&params, &verification_key, &pay_info1, spend_vv)
.spend_verify(&params, &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(
&params,
@@ -283,21 +300,10 @@ mod tests {
).unwrap();
assert!(payment2
.spend_verify(&params, &verification_key, &pay_info2, spend_vv)
.spend_verify(&params, &verification_key, &pay_info2)
.unwrap());
// GENERATE KEYS FOR OTHER USERS
let mut public_keys: Vec<PublicKeyUser> = 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(&params, &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<PublicKeyUser> = 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(
&params,
&verification_key,
&user_keypair.secret_key(),
@@ -359,7 +375,7 @@ mod tests {
).unwrap();
assert!(payment1
.spend_verify(&params, &verification_key, &pay_info1, spend_vv)
.spend_verify(&params, &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<PublicKeyUser> = 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(&params, &public_keys, &verification_key, payment1, payment2, pay_info1.clone(), pay_info2.clone()).unwrap();
assert_eq!(identify_result, IdentifyResult::DoubleSpendingPublicKeys(user_keypair.public_key()));
}
}
@@ -332,7 +332,7 @@ impl SecretKeyUser {
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct PublicKeyUser {
pub(crate) pk: G1Projective,
}
@@ -281,7 +281,6 @@ impl Payment {
params: &Parameters,
verification_key: &VerificationKeyAuth,
pay_info: &PayInfo,
spend_vv: u64,
) -> Result<bool> {
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(),
@@ -72,7 +72,7 @@ fn main() -> Result<(), CompactEcashError> {
)?;
assert!(payment
.spend_verify(&params, &verification_key, &pay_info, spend_vv)
.spend_verify(&params, &verification_key, &pay_info)
.unwrap());
@@ -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<T>(
points: &[SignerIndex],
values: &[T],
) -> Result<T>
where
T: Sum,
for<'a> &'a T: Mul<Scalar, Output = T>,
where
T: Sum,
for<'a> &'a T: Mul<Scalar, Output=T>,
{
if points.is_empty() || values.is_empty() {
return Err(CompactEcashError::Interpolation(
@@ -25,4 +25,8 @@ default-features = false
[dependencies.group]
version = "0.11"
default-features = false
default-features = false
[[bench]]
name = "benchmarks"
harness = false
@@ -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(&params, 2, 3).unwrap();
let verification_keys_auth: Vec<VerificationKeyAuth> = 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(&params, &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(&params, &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(
&params,
&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(
&params,
&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(&params, &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(&params, &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(&params, &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(&params, &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(&params, &verification_key, &pk_all_users, payment.clone(), payment2.clone(), pay_info, pay_info2).unwrap()
})
},
);
let identify_result = identify(&params, &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);
+18 -1
View File
@@ -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;
@@ -22,66 +22,63 @@ pub enum IdentifyResult {
pub fn identify(
params: &Parameters,
verification_key: &VerificationKeyAuth,
public_keys_u: &[PublicKeyUser],
public_keys_u: &HashSet<PublicKeyUser>,
payment1: Payment,
payment2: Payment,
pay_info1: PayInfo,
pay_info2: PayInfo) -> Result<IdentifyResult> {
// verify first the validaty of both payments
// verify first the validity of both payments
assert!(payment1.spend_verify(&params, &verification_key, &pay_info1).unwrap());
assert!(payment2.spend_verify(&params, &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(), &params_a.get_ith_delta(k1 as usize).to_affine())
+ pairing(&payment1.phi.0.to_affine(), &params_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(), &params_a.get_ith_delta(k as usize).to_affine())
+ pairing(&payment1.phi.0.to_affine(), &params_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(), &params_a.get_ith_delta(k2 as usize).to_affine())
+ pairing(&payment2.phi.0.to_affine(), &params_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(), &params_a.get_ith_delta(j as usize).to_affine())
+ pairing(&payment2.phi.0.to_affine(), &params_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(), &params_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(), &params_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(), &params_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(), &params_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(&params, &verification_key, &sk_user1, &pay_info1, 10).unwrap();
let (payment1, wallet1) = wallet1.spend(&params, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
// SPEND VERIFICATION for USER1
assert!(payment1.spend_verify(&params, &verification_key, &pay_info1).unwrap());
@@ -161,7 +157,8 @@ mod tests {
let pay_info2 = pay_info1.clone();
let identify_result = identify(&params, &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(&params, &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<PublicKeyUser> = 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(&params, &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(&params, &verification_key, &sk_user1, &pay_info1, 10).unwrap();
let (payment1, new_wallet1) = wallet1.spend(&params, &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(&params, &verification_key, &sk_user1, &pay_info2, 10).unwrap();
let (payment2, wallet1) = wallet1.spend(&params, &verification_key, &sk_user1, &pay_info2, 10, false).unwrap();
let identify_result = identify(&params, &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<PublicKeyUser> = 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(&params, &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(&params, &verification_key, &sk_user1, &pay_info1, 10).unwrap();
let (payment1, new_wallet1) = wallet1.spend(&params, &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(&params, &verification_key, &sk_user1, &pay_info2, 10).unwrap();
let (payment2, wallet1) = wallet1.spend(&params, &verification_key, &sk_user1, &pay_info2, 10, false).unwrap();
let identify_result = identify(&params, &verification_key, &pk_all_users, payment1, payment2, pay_info1, pay_info2).unwrap();
let identify_result = identify(&params, &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(&params, &verification_key, &sk_user1, &pay_info1, 10).unwrap();
let (payment1, wallet1) = wallet1.spend(&params, &verification_key, &sk_user1, &pay_info1, 10, false).unwrap();
// SPEND VERIFICATION for USER1
assert!(payment1.spend_verify(&params, &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(&params, &verification_key, &sk_user2, &pay_info2, 10).unwrap();
let (payment2, wallet2) = wallet2.spend(&params, &verification_key, &sk_user2, &pay_info2, 10, false).unwrap();
// SPEND VERIFICATION for USER2
assert!(payment2.spend_verify(&params, &verification_key, &pay_info2).unwrap());
let identify_result = identify(&params, &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(&params, &verification_key, &public_keys, payment1, payment2, pay_info1, pay_info2).unwrap();
assert_eq!(identify_result, IdentifyResult::NotADuplicatePayment);
}
}
@@ -340,7 +340,7 @@ impl KeyPairAuth {
#[derive(Eq, Debug, PartialEq, Clone)]
pub struct SecretKeyUser {
pub(crate) sk: Scalar,
pub sk: Scalar,
}
impl SecretKeyUser {
@@ -172,7 +172,7 @@ impl PartialWallet {
pub struct Wallet {
sig: Signature,
v: Scalar,
l: Cell<u64>,
pub l: Cell<u64>,
}
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))
}
}
@@ -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)
@@ -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<BlindedSignature> {
pub fn issue(params: &Parameters, req: &WithdrawalRequest, pk_u: PublicKeyUser, sk_a: &SecretKeyAuth) -> Result<BlindedSignature> {
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,
@@ -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(&params, 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(&params, &verification_key, &sk_user, &pay_info, 10)?;
let (payment, wallet) = wallet.spend(&params, &verification_key, &sk_user, &pay_info, 10, false)?;
// SPEND VERIFICATION
assert!(payment.spend_verify(&params, &verification_key, &pay_info).unwrap());