From 761d09534fe14b5532ce8afee0d040c839156e29 Mon Sep 17 00:00:00 2001 From: aniampio Date: Thu, 14 Jul 2022 21:12:26 +0100 Subject: [PATCH] Update spend function in the compact ecash --- .../benches/benchmarks.rs | 22 +- common/nym_offline_compact_ecash/src/error.rs | 10 + .../src/proofs/proof_spend.rs | 452 +++++++++++++----- .../src/scheme/identify.rs | 110 ++++- .../src/scheme/mod.rs | 202 ++++---- .../src/scheme/withdrawal.rs | 13 +- .../src/tests/e2e.rs | 19 +- .../benches/benchmarks.rs | 0 8 files changed, 600 insertions(+), 228 deletions(-) create mode 100644 common/nym_offline_divisible_ecash/benches/benchmarks.rs diff --git a/common/nym_offline_compact_ecash/benches/benchmarks.rs b/common/nym_offline_compact_ecash/benches/benchmarks.rs index 3c25ef5b6f..024073fb0e 100644 --- a/common/nym_offline_compact_ecash/benches/benchmarks.rs +++ b/common/nym_offline_compact_ecash/benches/benchmarks.rs @@ -4,18 +4,18 @@ use std::ops::Neg; use std::time::Duration; -use bls12_381::{multi_miller_loop, G1Affine, G2Affine, G2Prepared, Scalar}; -use criterion::{criterion_group, criterion_main, Criterion}; +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 itertools::izip; use rand::seq::SliceRandom; -use nym_compact_ecash::setup::setup; use nym_compact_ecash::{ aggregate_verification_keys, aggregate_wallets, generate_keypair_user, issue_verify, - issue_wallet, ttp_keygen, withdrawal_request, PartialWallet, PayInfo, VerificationKeyAuth, + issue_wallet, PartialWallet, PayInfo, ttp_keygen, VerificationKeyAuth, withdrawal_request, }; +use nym_compact_ecash::setup::setup; #[allow(unused)] fn double_pairing(g11: &G1Affine, g21: &G2Affine, g12: &G1Affine, g22: &G2Affine) { @@ -142,7 +142,7 @@ fn bench_compact_ecash(c: &mut Criterion) { 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,), + &format!("[Issuing Authority] issue_partial_wallet_with_L_{}", case.L, ), |b| { b.iter(|| { issue_wallet( @@ -170,7 +170,7 @@ fn bench_compact_ecash(c: &mut Criterion) { let w = wallet_blinded_signatures.get(0).clone().unwrap(); let vk = verification_keys_auth.get(0).clone().unwrap(); group.bench_function( - &format!("[Client] issue_verify_a_partial_wallet_with_L_{}", case.L,), + &format!("[Client] issue_verify_a_partial_wallet_with_L_{}", case.L, ), |b| b.iter(|| issue_verify(&grparams, vk, &user_keypair.secret_key(), w, &req_info)), ); @@ -178,8 +178,8 @@ fn bench_compact_ecash(c: &mut Criterion) { wallet_blinded_signatures.iter(), verification_keys_auth.iter() ) - .map(|(w, vk)| issue_verify(&grparams, vk, &user_keypair.secret_key(), w, &req_info).unwrap()) - .collect(); + .map(|(w, vk)| issue_verify(&grparams, vk, &user_keypair.secret_key(), w, &req_info).unwrap()) + .collect(); // CLIENT BENCHMARK: aggregating all partial wallets group.bench_function( @@ -196,7 +196,7 @@ fn bench_compact_ecash(c: &mut Criterion) { &unblinded_wallet_shares, &req_info, ) - .unwrap() + .unwrap() }) }, ); @@ -209,7 +209,7 @@ fn bench_compact_ecash(c: &mut Criterion) { &unblinded_wallet_shares, &req_info, ) - .unwrap(); + .unwrap(); // SPENDING PHASE let pay_info = PayInfo { info: [6u8; 32] }; @@ -259,6 +259,8 @@ fn bench_compact_ecash(c: &mut Criterion) { }) }, ); + + // BENCHMARK IDENTIFICATION } criterion_group!(benches, bench_compact_ecash); diff --git a/common/nym_offline_compact_ecash/src/error.rs b/common/nym_offline_compact_ecash/src/error.rs index 0937ffa10e..8a3ed2ddbf 100644 --- a/common/nym_offline_compact_ecash/src/error.rs +++ b/common/nym_offline_compact_ecash/src/error.rs @@ -28,6 +28,16 @@ pub enum CompactEcashError { #[error("ZKP Proof related error: {0}")] RangeProofOutOfBound(String), + #[error("Identify Verification related error: {0}")] + Identify(String), + + #[error( + "Deserailization error, expected at least {} bytes, got {}", + min, + actual + )] + DeserializationMinLength { min: usize, actual: usize }, + #[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {} or {modulus_target} % {modulus} == 0")] DeserializationInvalidLength { actual: usize, diff --git a/common/nym_offline_compact_ecash/src/proofs/proof_spend.rs b/common/nym_offline_compact_ecash/src/proofs/proof_spend.rs index 974258dc82..a193aedf11 100644 --- a/common/nym_offline_compact_ecash/src/proofs/proof_spend.rs +++ b/common/nym_offline_compact_ecash/src/proofs/proof_spend.rs @@ -14,12 +14,12 @@ use crate::utils::{try_deserialize_g1_projective, try_deserialize_g2_projective} #[cfg_attr(test, derive(PartialEq))] pub struct SpendInstance { pub kappa: G2Projective, - pub aa: G1Projective, pub cc: G1Projective, pub dd: G1Projective, - pub ss: G1Projective, - pub tt: G1Projective, - pub kappa_l: G2Projective, + pub aa: Vec, + pub ss: Vec, + pub tt: Vec, + pub kappa_k: Vec, } impl TryFrom<&[u8]> for SpendInstance { @@ -36,41 +36,132 @@ impl TryFrom<&[u8]> for SpendInstance { }); } - let kappa_bytes = bytes[..96].try_into().unwrap(); + let mut j = 0; + let kappa_bytes = bytes[j..j + 96].try_into().unwrap(); let kappa = try_deserialize_g2_projective( &kappa_bytes, CompactEcashError::Deserialization("Failed to deserialize kappa".to_string()), )?; - let aa_bytes = bytes[96..144].try_into().unwrap(); - let aa = try_deserialize_g1_projective( - &aa_bytes, - CompactEcashError::Deserialization("Failed to deserialize A".to_string()), - )?; - let cc_bytes = bytes[144..192].try_into().unwrap(); + j += 96; + + let a_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap()); + j += 8; + if bytes[j..].len() < a_len as usize * 48 { + return Err(CompactEcashError::DeserializationMinLength { + min: a_len as usize * 48, + actual: bytes[j..].len(), + }); + } + + let mut aa = Vec::with_capacity(a_len as usize); + for i in 0..a_len as usize { + let start = j + i * 48; + let end = start + 48; + + let aa_elem_bytes = bytes[start..end].try_into().unwrap(); + let aa_elem = try_deserialize_g1_projective( + &aa_elem_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize compressed A values".to_string(), + ), + )?; + + aa.push(aa_elem) + } + j += j + a_len as usize * 48; + + + let cc_bytes = bytes[j..j + 48].try_into().unwrap(); let cc = try_deserialize_g1_projective( &cc_bytes, CompactEcashError::Deserialization("Failed to deserialize C".to_string()), )?; - let dd_bytes = bytes[192..240].try_into().unwrap(); + j += 48; + + let dd_bytes = bytes[j..j + 48].try_into().unwrap(); let dd = try_deserialize_g1_projective( &dd_bytes, CompactEcashError::Deserialization("Failed to deserialize D".to_string()), )?; - let ss_bytes = bytes[240..288].try_into().unwrap(); - let ss = try_deserialize_g1_projective( - &ss_bytes, - CompactEcashError::Deserialization("Failed to deserialize S".to_string()), - )?; - let tt_bytes = bytes[288..336].try_into().unwrap(); - let tt = try_deserialize_g1_projective( - &tt_bytes, - CompactEcashError::Deserialization("Failed to deserialize T".to_string()), - )?; - let kappa_l_bytes = bytes[336..432].try_into().unwrap(); - let kappa_l = try_deserialize_g2_projective( - &kappa_l_bytes, - CompactEcashError::Deserialization("Failed to deserialize kappa_l".to_string()), - )?; + j += 48; + + let s_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap()); + j += 8; + if bytes[j..].len() < s_len as usize * 48 { + return Err(CompactEcashError::DeserializationMinLength { + min: s_len as usize * 48, + actual: bytes[j..].len(), + }); + } + + let mut ss = Vec::with_capacity(s_len as usize); + for i in 0..s_len as usize { + let start = j + i * 48; + let end = start + 48; + + let ss_elem_bytes = bytes[start..end].try_into().unwrap(); + let ss_elem = try_deserialize_g1_projective( + &ss_elem_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize compressed S values".to_string(), + ), + )?; + + ss.push(ss_elem) + } + j += j + s_len as usize * 48; + + let t_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap()); + j += 8; + if bytes[j..].len() < t_len as usize * 48 { + return Err(CompactEcashError::DeserializationMinLength { + min: t_len as usize * 48, + actual: bytes[j..].len(), + }); + } + + let mut tt = Vec::with_capacity(t_len as usize); + for i in 0..t_len as usize { + let start = j + i * 48; + let end = start + 48; + + let tt_elem_bytes = bytes[start..end].try_into().unwrap(); + let tt_elem = try_deserialize_g1_projective( + &tt_elem_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize compressed T values".to_string(), + ), + )?; + + tt.push(tt_elem) + } + j += j + t_len as usize * 48; + + + let kappa_k_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap()); + j += 8; + if bytes[j..].len() < kappa_k_len as usize * 96 { + return Err(CompactEcashError::DeserializationMinLength { + min: kappa_k_len as usize * 96, + actual: bytes[j..].len(), + }); + } + + let mut kappa_k = Vec::with_capacity(kappa_k_len as usize); + for i in 0..kappa_k_len as usize { + let start = j + i * 48; + let end = start + 48; + + let kappa_k_elem_bytes = bytes[start..end].try_into().unwrap(); + let kappa_k_elem = try_deserialize_g2_projective( + &kappa_k_elem_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize compressed kappa_k values".to_string(), + ), + )?; + + kappa_k.push(kappa_k_elem) + } Ok(SpendInstance { kappa, @@ -79,21 +170,37 @@ impl TryFrom<&[u8]> for SpendInstance { dd, ss, tt, - kappa_l, + kappa_k, }) } } impl SpendInstance { pub(crate) fn to_bytes(&self) -> Vec { - let mut bytes = Vec::with_capacity(2 * 96 + 5 * 48); + let mut bytes: Vec = Default::default(); bytes.extend_from_slice(self.kappa.to_bytes().as_ref()); - bytes.extend_from_slice(self.aa.to_bytes().as_ref()); + + bytes.extend_from_slice(&self.aa.len().to_le_bytes()); + for a in &self.aa { + bytes.extend_from_slice(&a.to_affine().to_compressed()); + } + bytes.extend_from_slice(self.cc.to_bytes().as_ref()); bytes.extend_from_slice(self.dd.to_bytes().as_ref()); - bytes.extend_from_slice(self.ss.to_bytes().as_ref()); - bytes.extend_from_slice(self.tt.to_bytes().as_ref()); - bytes.extend_from_slice(self.kappa_l.to_bytes().as_ref()); + + bytes.extend_from_slice(&self.ss.len().to_le_bytes()); + for s in &self.ss { + bytes.extend_from_slice(&s.to_affine().to_compressed()); + } + bytes.extend_from_slice(&self.tt.len().to_le_bytes()); + for t in &self.tt { + bytes.extend_from_slice(&t.to_affine().to_compressed()); + } + + bytes.extend_from_slice(&self.kappa_k.len().to_le_bytes()); + for k in &self.kappa_k { + bytes.extend_from_slice(&k.to_affine().to_compressed()); + } bytes } } @@ -103,30 +210,30 @@ pub struct SpendWitness { pub attributes: Vec, // signature randomizing element pub r: Scalar, - pub r_l: Scalar, - pub l: Scalar, - pub o_a: Scalar, pub o_c: Scalar, pub o_d: Scalar, - pub mu: Scalar, - pub lambda: Scalar, - pub o_mu: Scalar, - pub o_lambda: Scalar, + pub lk: Vec, + pub o_a: Vec, + pub mu: Vec, + pub lambda: Vec, + pub o_mu: Vec, + pub o_lambda: Vec, + pub r_k: Vec, } #[derive(Debug, Clone)] pub struct SpendProof { challenge: Scalar, response_r: Scalar, - response_r_l: Scalar, - response_l: Scalar, - response_o_a: Scalar, + response_r_l: Vec, + response_l: Vec, + response_o_a: Vec, response_o_c: Scalar, response_o_d: Scalar, - response_mu: Scalar, - response_lambda: Scalar, - response_o_mu: Scalar, - response_o_lambda: Scalar, + response_mu: Vec, + response_lambda: Vec, + response_o_mu: Vec, + response_o_lambda: Vec, response_attributes: Vec, } @@ -136,7 +243,7 @@ impl SpendProof { instance: &SpendInstance, witness: &SpendWitness, verification_key: &VerificationKeyAuth, - rr: Scalar, + rr: &[Scalar], ) -> Self { let grparams = params.grp(); // generate random values to replace each witness @@ -145,15 +252,16 @@ impl SpendProof { let r_v = r_attributes[1]; let r_t = r_attributes[2]; let r_r = grparams.random_scalar(); - let r_r_l = grparams.random_scalar(); - let r_l = grparams.random_scalar(); - let r_o_a = grparams.random_scalar(); let r_o_c = grparams.random_scalar(); let r_o_d = grparams.random_scalar(); - let r_mu = grparams.random_scalar(); - let r_lambda = grparams.random_scalar(); - let r_o_mu = grparams.random_scalar(); - let r_o_lambda = grparams.random_scalar(); + + let r_r_lk = grparams.n_random_scalars(witness.r_k.len()); + let r_lk = grparams.n_random_scalars(witness.lk.len()); + let r_o_a = grparams.n_random_scalars(witness.o_a.len()); + let r_mu = grparams.n_random_scalars(witness.mu.len()); + let r_lambda = grparams.n_random_scalars(witness.lambda.len()); + let r_o_mu = grparams.n_random_scalars(witness.o_mu.len()); + let r_o_lambda = grparams.n_random_scalars(witness.o_lambda.len()); let g1 = *grparams.gen1(); let gamma1 = *grparams.gamma1(); @@ -172,14 +280,69 @@ impl SpendProof { .map(|(attr, beta_i)| beta_i * attr) .sum::(); - let zkcm_aa = g1 * r_o_a + gamma1 * r_l; let zkcm_cc = g1 * r_o_c + gamma1 * r_v; let zkcm_dd = g1 * r_o_d + gamma1 * r_t; - let zkcm_ss = g1 * r_mu; - let zkcm_gamma11 = (instance.aa + instance.cc + gamma1) * r_mu + g1 * r_o_mu; - let zkcm_tt = g1 * r_sk + (g1 * rr) * r_lambda; - let zkcm_gamma12 = (instance.aa + instance.dd + gamma1) * r_lambda + g1 * r_o_lambda; - let zkcm_kappa_l = grparams.gen2() * r_r_l + params.pkRP().alpha + params.pkRP().beta * r_l; + + let zkcm_aa: Vec = + r_o_a + .iter() + .zip(r_lk.iter()).map(|(r_o_a_k, r_l_k)| g1 * r_o_a_k + gamma1 * r_l_k) + .collect::>(); + + let zkcm_aa_bytes = zkcm_aa + .iter() + .map(|x| x.to_bytes()) + .collect::>(); + + let zkcm_ss = r_mu.iter().map(|r_mu_k| g1 * r_mu_k).collect::>(); + + let zkcm_ss_bytes = zkcm_ss + .iter() + .map(|x| x.to_bytes()) + .collect::>(); + + let zkcm_tt = rr + .iter() + .zip(r_lambda.iter()).map(|(rr_k, r_lambda_k)| g1 * r_sk + (g1 * rr_k) * r_lambda_k).collect::>(); + + let zkcm_tt_bytes = zkcm_tt + .iter() + .map(|x| x.to_bytes()) + .collect::>(); + + let zkcm_gamma11 = instance.aa + .iter() + .zip(r_mu.iter()) + .zip(r_o_mu.iter()) + .map(|((aa_k, r_mu_k), r_o_mu_k)| (aa_k + instance.cc + gamma1) * r_mu_k + g1 * r_o_mu_k) + .collect::>(); + + let zkcm_gamma11_bytes = zkcm_gamma11 + .iter() + .map(|x| x.to_bytes()) + .collect::>(); + + let zkcm_gamma12 = instance.aa + .iter() + .zip(r_lambda.iter()) + .zip(r_o_lambda.iter()) + .map(|((aa_k, r_lambda_k), r_o_lambda_k)| (aa_k + instance.dd + gamma1) * r_lambda_k + g1 * r_o_lambda_k) + .collect::>(); + + let zkcm_gamma12_bytes = zkcm_gamma12 + .iter() + .map(|x| x.to_bytes()) + .collect::>(); + + let zkcm_kappa_k = r_lk.iter() + .zip(r_r_lk.iter()) + .map(|(r_k, r_r_k)| params.pkRP().alpha + params.pkRP().beta * r_k + grparams.gen2() * r_r_k) + .collect::>(); + + let zkcm_kappa_k_bytes = zkcm_kappa_k + .iter() + .map(|x| x.to_bytes()) + .collect::>(); // compute the challenge let challenge = compute_challenge::( @@ -189,18 +352,14 @@ impl SpendProof { .chain(beta2_bytes.iter().map(|b| b.as_ref())) .chain(std::iter::once(instance.to_bytes().as_ref())) .chain(std::iter::once(zkcm_kappa.to_bytes().as_ref())) - .chain(std::iter::once(zkcm_aa.to_bytes().as_ref())) .chain(std::iter::once(zkcm_cc.to_bytes().as_ref())) .chain(std::iter::once(zkcm_dd.to_bytes().as_ref())) - .chain(std::iter::once(zkcm_ss.to_bytes().as_ref())) - .chain(std::iter::once(zkcm_kappa_l.to_bytes().as_ref())) - .chain(std::iter::once( - zkcm_gamma11.to_affine().to_bytes().as_ref(), - )) - .chain(std::iter::once(zkcm_tt.to_bytes().as_ref())) - .chain(std::iter::once( - zkcm_gamma12.to_affine().to_bytes().as_ref(), - )), + .chain(zkcm_aa_bytes.iter().map(|x| x.as_ref())) + .chain(zkcm_ss_bytes.iter().map(|x| x.as_ref())) + .chain(zkcm_kappa_k_bytes.iter().map(|x| x.as_ref())) + .chain(zkcm_tt_bytes.iter().map(|x| x.as_ref())) + .chain(zkcm_gamma11_bytes.iter().map(|x| x.as_ref())) + .chain(zkcm_gamma12_bytes.iter().map(|x| x.as_ref())), ); // compute response for each witness @@ -210,16 +369,16 @@ impl SpendProof { &witness.attributes.iter().collect::>(), ); let response_r = produce_response(&r_r, &challenge, &witness.r); - let response_r_l = produce_response(&r_r_l, &challenge, &witness.r_l); - let response_l = produce_response(&r_l, &challenge, &witness.l); - let response_o_a = produce_response(&r_o_a, &challenge, &witness.o_a); + let response_r_l = produce_responses(&r_r_lk, &challenge, &witness.r_k); + let response_l = produce_responses(&r_lk, &challenge, &witness.lk); + let response_o_a = produce_responses(&r_o_a, &challenge, &witness.o_a); let response_o_c = produce_response(&r_o_c, &challenge, &witness.o_c); let response_o_d = produce_response(&r_o_d, &challenge, &witness.o_d); - let response_mu = produce_response(&r_mu, &challenge, &witness.mu); - let response_lambda = produce_response(&r_lambda, &challenge, &witness.lambda); - let response_o_mu = produce_response(&r_o_mu, &challenge, &witness.o_mu); - let response_o_lambda = produce_response(&r_o_lambda, &challenge, &witness.o_lambda); + let response_mu = produce_responses(&r_mu, &challenge, &witness.mu); + let response_lambda = produce_responses(&r_lambda, &challenge, &witness.lambda); + let response_o_mu = produce_responses(&r_o_mu, &challenge, &witness.o_mu); + let response_o_lambda = produce_responses(&r_o_lambda, &challenge, &witness.o_lambda); SpendProof { challenge, @@ -241,7 +400,7 @@ impl SpendProof { params: &Parameters, instance: &SpendInstance, verification_key: &VerificationKeyAuth, - rr: Scalar, + rr: &[Scalar], ) -> bool { let grparams = params.grp(); let g1 = *grparams.gen1(); @@ -263,29 +422,87 @@ impl SpendProof { .map(|(attr, beta_i)| beta_i * attr) .sum::(); - let zkcm_aa = - g1 * self.response_o_a + gamma1 * self.response_l + instance.aa * self.challenge; + + let zkcm_aa = self.response_o_a + .iter() + .zip(self.response_l.iter()) + .zip(instance.aa.iter()) + .map(|((resp_o_a_k, resp_l_k), aa_k)| g1 * resp_o_a_k + gamma1 * resp_l_k + aa_k * self.challenge) + .collect::>(); + + let zkcm_aa_bytes = zkcm_aa + .iter() + .map(|x| x.to_bytes()) + .collect::>(); + let zkcm_cc = g1 * self.response_o_c + gamma1 * self.response_attributes[1] + instance.cc * self.challenge; let zkcm_dd = g1 * self.response_o_d + gamma1 * self.response_attributes[2] + instance.dd * self.challenge; - let zkcm_ss = g1 * self.response_mu + instance.ss * self.challenge; - let zkcm_gamma11 = (instance.aa + instance.cc + gamma1) * self.response_mu - + g1 * self.response_o_mu - + gamma1 * self.challenge; - let zkcm_tt = g1 * self.response_attributes[0] - + (g1 * rr) * self.response_lambda - + instance.tt * self.challenge; - let zkcm_gamma12 = (instance.aa + instance.dd + gamma1) * self.response_lambda - + g1 * self.response_o_lambda - + gamma1 * self.challenge; - let zkcm_kappa_l = instance.kappa_l * self.challenge - + grparams.gen2() * self.response_r_l - + params.pkRP().alpha * (Scalar::one() - self.challenge) - + params.pkRP().beta * self.response_l; + let zkcm_ss = self.response_mu + .iter() + .zip(instance.ss.iter()) + .map(|(resp_mu_k, ss_k)| g1 * resp_mu_k + ss_k * self.challenge) + .collect::>(); + + let zkcm_ss_bytes = zkcm_ss + .iter() + .map(|x| x.to_bytes()) + .collect::>(); + + let zkcm_tt = self.response_lambda + .iter() + .zip(rr.iter()) + .zip(instance.tt.iter()) + .map(|((resp_lambda_k, rr_k), tt_k)| g1 * self.response_attributes[0] + (g1 * rr_k) * resp_lambda_k + tt_k * self.challenge) + .collect::>(); + + let zkcm_tt_bytes = zkcm_tt + .iter() + .map(|x| x.to_bytes()) + .collect::>(); + + let zkcm_gamma11 = instance.aa + .iter() + .zip(self.response_mu.iter()) + .zip(self.response_o_mu.iter()) + .map(|((aa_k, resp_mu_k), resp_o_mu_k)| (aa_k + instance.cc + gamma1) * resp_mu_k + + g1 * resp_o_mu_k + gamma1 * self.challenge) + .collect::>(); + + let zkcm_gamma11_bytes = zkcm_gamma11 + .iter() + .map(|x| x.to_bytes()) + .collect::>(); + + + let zkcm_gamma12 = instance.aa + .iter() + .zip(self.response_lambda.iter()) + .zip(self.response_o_lambda.iter()) + .map(|((aa_k, resp_lambda_k), resp_o_lambda_k)| (aa_k + instance.dd + gamma1) * resp_lambda_k + + g1 * resp_o_lambda_k + gamma1 * self.challenge) + .collect::>(); + + let zkcm_gamma12_bytes = zkcm_gamma12 + .iter() + .map(|x| x.to_bytes()) + .collect::>(); + + let zkcm_kappa_k = instance.kappa_k + .iter() + .zip(self.response_r_l.iter()) + .zip(self.response_l.iter()) + .map(|((kappa_k, resp_r_k), resp_r_l_k)| kappa_k * self.challenge + grparams.gen2() * resp_r_k + params.pkRP().alpha * (Scalar::one() - self.challenge) + params.pkRP().beta * resp_r_l_k) + .collect::>(); + + let zkcm_kappa_k_bytes = zkcm_kappa_k + .iter() + .map(|x| x.to_bytes()) + .collect::>(); // re-compute the challenge let challenge = compute_challenge::( @@ -295,18 +512,14 @@ impl SpendProof { .chain(beta2_bytes.iter().map(|b| b.as_ref())) .chain(std::iter::once(instance.to_bytes().as_ref())) .chain(std::iter::once(zkcm_kappa.to_bytes().as_ref())) - .chain(std::iter::once(zkcm_aa.to_bytes().as_ref())) .chain(std::iter::once(zkcm_cc.to_bytes().as_ref())) .chain(std::iter::once(zkcm_dd.to_bytes().as_ref())) - .chain(std::iter::once(zkcm_ss.to_bytes().as_ref())) - .chain(std::iter::once(zkcm_kappa_l.to_bytes().as_ref())) - .chain(std::iter::once( - zkcm_gamma11.to_affine().to_bytes().as_ref(), - )) - .chain(std::iter::once(zkcm_tt.to_bytes().as_ref())) - .chain(std::iter::once( - zkcm_gamma12.to_affine().to_bytes().as_ref(), - )), + .chain(zkcm_aa_bytes.iter().map(|x| x.as_ref())) + .chain(zkcm_ss_bytes.iter().map(|x| x.as_ref())) + .chain(zkcm_kappa_k_bytes.iter().map(|x| x.as_ref())) + .chain(zkcm_tt_bytes.iter().map(|x| x.as_ref())) + .chain(zkcm_gamma11_bytes.iter().map(|x| x.as_ref())) + .chain(zkcm_gamma12_bytes.iter().map(|x| x.as_ref())), ); challenge == self.challenge @@ -391,33 +604,34 @@ mod tests { // randomise the signature associated with value l let (sign_l_prime, r_l) = sign_l.randomise(grparams); // compute kappa_l - let kappa_l = + let kappa_k = grparams.gen2() * r_l + params.pkRP().alpha + params.pkRP().beta * Scalar::from(l); let instance = SpendInstance { kappa, - aa, + aa: vec![aa], cc, dd, - ss, - tt, - kappa_l, + ss: vec![ss], + tt: vec![tt], + kappa_k: vec![kappa_k], }; let witness = SpendWitness { attributes, r, - r_l, - l: Scalar::from(l), - o_a, o_c, o_d, - mu, - lambda, - o_mu, - o_lambda, + lk: vec![Scalar::from(l)], + o_a: vec![o_a], + mu: vec![mu], + lambda: vec![lambda], + o_mu: vec![o_mu], + o_lambda: vec![o_lambda], + r_k: vec![r_l], }; - let zk_proof = SpendProof::construct(¶ms, &instance, &witness, &verification_key, rr); - assert!(zk_proof.verify(¶ms, &instance, &verification_key, rr)) + + let zk_proof = SpendProof::construct(¶ms, &instance, &witness, &verification_key, &[rr]); + assert!(zk_proof.verify(¶ms, &instance, &verification_key, &[rr])) } } diff --git a/common/nym_offline_compact_ecash/src/scheme/identify.rs b/common/nym_offline_compact_ecash/src/scheme/identify.rs index eaf3931150..74a9f8d105 100644 --- a/common/nym_offline_compact_ecash/src/scheme/identify.rs +++ b/common/nym_offline_compact_ecash/src/scheme/identify.rs @@ -1,9 +1,109 @@ -use crate::error::Result; +use crate::error::{CompactEcashError, Result}; +use crate::PayInfo; use crate::scheme::keygen::PublicKeyUser; use crate::scheme::Payment; -pub fn identify(pay1: Payment, pay2: Payment) -> Result { - // TODO: We should include here the check for S and payInfo - let pk_user = (pay2.tt * pay1.rr - pay1.tt * pay2.rr) * ((pay1.rr - pay2.rr).invert().unwrap()); - Ok(PublicKeyUser { pk: pk_user }) +pub enum IdentifyResult { + NotADuplicatePayment, + DuplicatePayInfo(PayInfo), + DoubleSpendingPublicKeys(PublicKeyUser), + } + +pub fn identify(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)); + } + } + } + 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_user = (pay2.tt[j] * pay1.rr[k] - pay1.tt[k] * pay2.rr[j]) * ((pay1.rr[k] - pay2.rr[j]).invert().unwrap()); + return Ok(IdentifyResult::DoubleSpendingPublicKeys(PublicKeyUser { pk: pk_user.clone() })); + } + } + 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(), + )); + } +} + +#[cfg(test)] +mod tests { + use itertools::izip; + + use crate::{aggregate_verification_keys, aggregate_wallets, generate_keypair_user, issue_verify, issue_wallet, PartialWallet, PayInfo, ttp_keygen, VerificationKeyAuth, withdrawal_request}; + use crate::scheme::setup::setup; + + #[test] + fn duplicate_payments_with_the_same_pay_info() { + let L = 32; + let params = setup(L); + let grparams = params.grp(); + let user_keypair = generate_keypair_user(&grparams); + + let (req, req_info) = withdrawal_request(grparams, &user_keypair.secret_key()).unwrap(); + let authorities_keypairs = ttp_keygen(&grparams, 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(); + + let mut wallet_blinded_signatures = Vec::new(); + for auth_keypair in authorities_keypairs { + let blind_signature = issue_wallet( + &grparams, + auth_keypair.secret_key(), + user_keypair.public_key(), + &req, + ); + wallet_blinded_signatures.push(blind_signature.unwrap()); + } + + let unblinded_wallet_shares: Vec = izip!( + wallet_blinded_signatures.iter(), + verification_keys_auth.iter() + ) + .map(|(w, vk)| issue_verify(&grparams, vk, &user_keypair.secret_key(), w, &req_info).unwrap()) + .collect(); + + // Aggregate partial wallets + let aggr_wallet = aggregate_wallets( + &grparams, + &verification_key, + &user_keypair.secret_key(), + &unblinded_wallet_shares, + &req_info, + ).unwrap(); + + // Let's try to spend some coins + let pay_info = PayInfo { info: [6u8; 32] }; + let spend_vv = 1; + + let (payment1, upd_wallet) = aggr_wallet.spend( + ¶ms, + &verification_key, + &user_keypair.secret_key(), + &pay_info, + false, + spend_vv, + ).unwrap(); + + assert!(payment1 + .spend_verify(¶ms, &verification_key, &pay_info, spend_vv) + .unwrap()); + } +} \ No newline at end of file diff --git a/common/nym_offline_compact_ecash/src/scheme/mod.rs b/common/nym_offline_compact_ecash/src/scheme/mod.rs index 0bdfc02424..35d6e09013 100644 --- a/common/nym_offline_compact_ecash/src/scheme/mod.rs +++ b/common/nym_offline_compact_ecash/src/scheme/mod.rs @@ -5,14 +5,14 @@ use std::convert::TryInto; use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar}; use group::{Curve, Group}; +use crate::Attribute; use crate::error::{CompactEcashError, Result}; use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness}; use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth}; use crate::scheme::setup::{GroupParameters, Parameters}; use crate::utils::{ - check_bilinear_pairing, hash_to_scalar, try_deserialize_g1_projective, Signature, SignerIndex, + check_bilinear_pairing, hash_to_scalar, Signature, SignerIndex, try_deserialize_g1_projective, }; -use crate::Attribute; pub mod aggregation; pub mod identify; @@ -74,8 +74,9 @@ impl Wallet { sk_user: &SecretKeyUser, pay_info: &PayInfo, bench_flag: bool, + spend_vv: u64, ) -> Result<(Payment, &Self)> { - if self.l() > params.L() { + if self.l() + spend_vv > params.L() { return Err(CompactEcashError::Spend( "The counter l is higher than max L".to_string(), )); @@ -94,87 +95,124 @@ impl Wallet { sign_blinding_factor, ); - // pick random openings o_a, o_c, o_d - let o_a = grparams.random_scalar(); + // pick random openings o_c, o_d let o_c = grparams.random_scalar(); let o_d = grparams.random_scalar(); - // compute commitments A, C, D - let aa = grparams.gen1() * o_a + grparams.gamma1() * Scalar::from(self.l()); + // compute commitments C, D let cc = grparams.gen1() * o_c + grparams.gamma1() * self.v(); let dd = grparams.gen1() * o_d + grparams.gamma1() * self.t(); - // compute hash of the payment info - let rr = hash_to_scalar(pay_info.info); - // evaluate the pseudorandom functions - let ss = pseudorandom_fgv(&grparams, self.v(), self.l()); - let tt = - grparams.gen1() * sk_user.sk + pseudorandom_fgt(&grparams, self.t(), self.l()) * rr; + let mut aa: Vec = Default::default(); + let mut ss: Vec = Default::default(); + let mut tt: Vec = Default::default(); + let mut rr: Vec = Default::default(); + let mut o_a: Vec = Default::default(); + let mut o_mu: Vec = Default::default(); + let mut mu: Vec = Default::default(); + let mut o_lambda: Vec = Default::default(); + let mut lambda: Vec = Default::default(); + let mut r_k_vec: Vec = Default::default(); + let mut kappa_k_vec: Vec = Default::default(); + let mut sign_lk_prime_vec: Vec = Default::default(); + let mut lk: Vec = Default::default(); - // compute values mu, o_mu, lambda, o_lambda - let mu: Scalar = (self.v() + Scalar::from(self.l()) + Scalar::from(1)) - .invert() - .unwrap(); - let o_mu = ((o_a + o_c) * mu).neg(); - let lambda = (self.t() + Scalar::from(self.l()) + Scalar::from(1)) - .invert() - .unwrap(); - let o_lambda = ((o_a + o_d) * lambda).neg(); + for k in 0..spend_vv { + lk.push(Scalar::from(self.l() + k)); + + // compute hashes R_k of the payment info + let rr_k = hash_to_scalar(pay_info.info); + rr.push(rr_k); + + let o_a_k = grparams.random_scalar(); + o_a.push(o_a_k); + let aa_k = grparams.gen1() * o_a_k + grparams.gamma1() * Scalar::from(self.l() + k); + aa.push(aa_k); + + // evaluate the pseudorandom functions + let ss_k = pseudorandom_fgv(&grparams, self.v(), self.l() + k); + ss.push(ss_k); + let tt_k = + grparams.gen1() * sk_user.sk + pseudorandom_fgt(&grparams, self.t(), self.l() + k) * rr_k; + tt.push(tt_k); + + // compute values mu, o_mu, lambda, o_lambda + let mu_k: Scalar = (self.v() + Scalar::from(self.l() + k) + Scalar::from(1)) + .invert() + .unwrap(); + mu.push(mu_k); + + let o_mu_k = ((o_a_k + o_c) * mu_k).neg(); + o_mu.push(o_mu_k); + + let lambda_k = (self.t() + Scalar::from(self.l() + k) + Scalar::from(1)) + .invert() + .unwrap(); + lambda.push(lambda_k); + + let o_lambda_k = ((o_a_k + o_d) * lambda_k).neg(); + o_lambda.push(o_lambda_k); + + // parse the signature associated with value l+k + let sign_lk = params.get_sign_by_idx(self.l() + k)?; + // randomise the signature associated with value l+k + let (sign_lk_prime, r_k) = sign_lk.randomise(grparams); + sign_lk_prime_vec.push(sign_lk_prime); + r_k_vec.push(r_k); + // compute kappa_k + let kappa_k = grparams.gen2() * r_k + + params.pkRP().alpha + + params.pkRP().beta * Scalar::from(self.l() + k); + kappa_k_vec.push(kappa_k); + } - // parse the signature associated with value l - let sign_l = params.get_sign_by_idx(self.l())?; - // randomise the signature associated with value l - let (sign_l_prime, sign_l_blinding_factor) = sign_l.randomise(grparams); - // compute kappa_l - let kappa_l = grparams.gen2() * sign_l_blinding_factor - + params.pkRP().alpha - + params.pkRP().beta * Scalar::from(self.l()); // construct the zkp proof let spend_instance = SpendInstance { kappa, - aa, cc, dd, - ss, - tt, - kappa_l, + aa: aa.clone(), + ss: ss.clone(), + tt: tt.clone(), + kappa_k: kappa_k_vec.clone(), }; let spend_witness = SpendWitness { attributes, r: sign_blinding_factor, - r_l: sign_l_blinding_factor, - l: Scalar::from(self.l()), - o_a, o_c, o_d, + lk, + o_a, mu, lambda, o_mu, o_lambda, + r_k: r_k_vec, }; let zk_proof = SpendProof::construct( ¶ms, &spend_instance, &spend_witness, &verification_key, - rr, + &rr, ); // output pay and updated wallet let pay = Payment { kappa, sig: signature_prime, - ss, - tt, - aa, + ss: ss.clone(), + tt: tt.clone(), + aa: aa.clone(), + rr: rr.clone(), + kappa_k: kappa_k_vec.clone(), + sig_lk: sign_lk_prime_vec, cc, dd, - rr, - kappa_l, - sig_l: sign_l_prime, zk_proof, + vv: spend_vv, }; // The number of samples collected by the benchmark process is way higher than the @@ -209,12 +247,13 @@ pub fn compute_kappa( params.gen2() * blinding_factor + verification_key.alpha + attributes - .iter() - .zip(verification_key.beta_g2.iter()) - .map(|(priv_attr, beta_i)| beta_i * priv_attr) - .sum::() + .iter() + .zip(verification_key.beta_g2.iter()) + .map(|(priv_attr, beta_i)| beta_i * priv_attr) + .sum::() } +#[derive(PartialEq)] pub struct PayInfo { pub info: [u8; 32], } @@ -223,15 +262,16 @@ pub struct PayInfo { pub struct Payment { pub kappa: G2Projective, pub sig: Signature, - pub ss: G1Projective, - pub tt: G1Projective, - pub aa: G1Projective, + pub ss: Vec, + pub tt: Vec, + pub aa: Vec, + pub rr: Vec, + pub kappa_k: Vec, + pub sig_lk: Vec, pub cc: G1Projective, pub dd: G1Projective, - pub rr: Scalar, - pub kappa_l: G2Projective, - pub sig_l: Signature, pub zk_proof: SpendProof, + pub vv: u64, } impl Payment { @@ -240,6 +280,7 @@ 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( @@ -258,28 +299,29 @@ impl Payment { )); } - if bool::from(self.sig_l.0.is_identity()) { - return Err(CompactEcashError::Spend( - "The element h of the signature on l equals the identity".to_string(), - )); - } + for k in 0..spend_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(), + )); + } - if !check_bilinear_pairing( - &self.sig_l.0.to_affine(), - &G2Prepared::from(self.kappa_l.to_affine()), - &self.sig_l.1.to_affine(), - params.grp().prepared_miller_g2(), - ) { - return Err(CompactEcashError::Spend( - "The bilinear check for kappa_l failed".to_string(), - )); - } - - // verify integrity of R - if !(self.rr == hash_to_scalar(pay_info.info)) { - return Err(CompactEcashError::Spend( - "Integrity of R does not hold".to_string(), - )); + if !check_bilinear_pairing( + &self.sig_lk[k as usize].0.to_affine(), + &G2Prepared::from(self.kappa_k[k as usize].to_affine()), + &self.sig_lk[k as usize].1.to_affine(), + params.grp().prepared_miller_g2(), + ) { + return Err(CompactEcashError::Spend( + "The bilinear check for kappa_l failed".to_string(), + )); + } + // verify integrity of R_k + if !(self.rr[k as usize] == hash_to_scalar(pay_info.info)) { + return Err(CompactEcashError::Spend( + "Integrity of R_k does not hold".to_string(), + )); + } } //TODO: verify whether payinfo contains merchent's identifier @@ -287,17 +329,17 @@ impl Payment { // verify the zk proof let instance = SpendInstance { kappa: self.kappa, - aa: self.aa, + aa: self.aa.clone(), cc: self.cc, dd: self.dd, - ss: self.ss, - tt: self.tt, - kappa_l: self.kappa_l, + ss: self.ss.clone(), + tt: self.tt.clone(), + kappa_k: self.kappa_k.clone(), }; if !self .zk_proof - .verify(¶ms, &instance, &verification_key, self.rr) + .verify(¶ms, &instance, &verification_key, &self.rr) { return Err(CompactEcashError::Spend( "ZkProof verification failed".to_string(), diff --git a/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs b/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs index 90b9d5d39d..ad455984fb 100644 --- a/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs +++ b/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs @@ -5,10 +5,10 @@ use crate::error::{CompactEcashError, Result}; use crate::proofs::proof_withdrawal::{ WithdrawalReqInstance, WithdrawalReqProof, WithdrawalReqWitness, }; -use crate::scheme::keygen::ttp_keygen; use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, SecretKeyUser, VerificationKeyAuth}; -use crate::scheme::setup::{GroupParameters, Parameters}; +use crate::scheme::keygen::ttp_keygen; use crate::scheme::PartialWallet; +use crate::scheme::setup::{GroupParameters, Parameters}; use crate::utils::{check_bilinear_pairing, hash_g1}; use crate::utils::{BlindedSignature, Signature}; @@ -57,14 +57,15 @@ pub fn withdrawal_request( let com_opening = params.random_scalar(); let com = params.gen1() * com_opening + attributes - .iter() - .zip(gammas) - .map(|(&m, gamma)| gamma * m) - .sum::(); + .iter() + .zip(gammas) + .map(|(&m, gamma)| gamma * m) + .sum::(); // Value h in the paper let com_hash = hash_g1(com.to_bytes()); + // For each private attribute we compute a pedersen commitment let pc_coms_openings = params.n_random_scalars(attributes.len()); // Compute Pedersen commitment for each attribute diff --git a/common/nym_offline_compact_ecash/src/tests/e2e.rs b/common/nym_offline_compact_ecash/src/tests/e2e.rs index e09ec85927..ad4fa99705 100644 --- a/common/nym_offline_compact_ecash/src/tests/e2e.rs +++ b/common/nym_offline_compact_ecash/src/tests/e2e.rs @@ -60,6 +60,7 @@ fn main() -> Result<(), CompactEcashError> { // Let's try to spend some coins let pay_info = PayInfo { info: [6u8; 32] }; + let spend_vv = 1; let (payment, upd_wallet) = aggr_wallet.spend( ¶ms, @@ -67,10 +68,11 @@ fn main() -> Result<(), CompactEcashError> { &user_keypair.secret_key(), &pay_info, false, + spend_vv, )?; assert!(payment - .spend_verify(¶ms, &verification_key, &pay_info) + .spend_verify(¶ms, &verification_key, &pay_info, spend_vv) .unwrap()); // try to spend twice the same payment with different payInfo @@ -82,19 +84,20 @@ fn main() -> Result<(), CompactEcashError> { kappa: payment1.kappa.clone(), sig: payment1.sig.clone(), ss: payment1.ss.clone(), - tt: grparams.gen1() * user_keypair.secret_key().sk - + pseudorandom_fgt(&grparams, aggr_wallet.t(), l2) * rr2, + tt: vec![grparams.gen1() * user_keypair.secret_key().sk + + pseudorandom_fgt(&grparams, aggr_wallet.t(), l2) * rr2], aa: payment1.aa.clone(), cc: payment1.cc.clone(), dd: payment1.dd.clone(), - rr: rr2, - kappa_l: payment1.kappa_l.clone(), - sig_l: payment1.sig_l.clone(), + rr: vec![rr2], + kappa_k: payment1.kappa_k.clone(), + sig_lk: payment1.sig_lk.clone(), zk_proof: payment1.zk_proof.clone(), + vv: spend_vv, }; - let identified_user = identify(payment1, payment2).unwrap(); - assert_eq!(user_keypair.public_key().pk, identified_user.pk); + let identified_user = identify(payment1, payment2, pay_info, pay_info2).unwrap(); + // assert_eq!(user_keypair.public_key().pk, identified_user.pk); Ok(()) } diff --git a/common/nym_offline_divisible_ecash/benches/benchmarks.rs b/common/nym_offline_divisible_ecash/benches/benchmarks.rs new file mode 100644 index 0000000000..e69de29bb2