From d00bef2ddf18c94147760d030aab49366ccf4ad5 Mon Sep 17 00:00:00 2001 From: aniampio Date: Wed, 23 Mar 2022 15:50:30 +0000 Subject: [PATCH] Fix a bug in the commitment computation --- common/nym-compact-ecash/src/error.rs | 3 + common/nym-compact-ecash/src/proofs/mod.rs | 330 +++++++++++++----- common/nym-compact-ecash/src/scheme/mod.rs | 12 +- common/nym-compact-ecash/src/scheme/setup.rs | 10 +- .../src/scheme/withdrawal.rs | 131 ++++--- common/nym-compact-ecash/src/tests/e2e.rs | 30 +- common/nym-compact-ecash/src/utils.rs | 15 +- 7 files changed, 389 insertions(+), 142 deletions(-) diff --git a/common/nym-compact-ecash/src/error.rs b/common/nym-compact-ecash/src/error.rs index 66462ab8e2..9057472934 100644 --- a/common/nym-compact-ecash/src/error.rs +++ b/common/nym-compact-ecash/src/error.rs @@ -16,6 +16,9 @@ pub enum CompactEcashError { #[error("Interpolation error: {0}")] Interpolation(String), + #[error("Issuance Verification related error: {0}")] + IssuanceVfy(String), + #[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-compact-ecash/src/proofs/mod.rs b/common/nym-compact-ecash/src/proofs/mod.rs index 53e73ac9d2..5da7a85e5d 100644 --- a/common/nym-compact-ecash/src/proofs/mod.rs +++ b/common/nym-compact-ecash/src/proofs/mod.rs @@ -1,22 +1,27 @@ use std::borrow::Borrow; +use std::convert::TryFrom; +use std::convert::TryInto; use bls12_381::{G1Affine, G1Projective, Scalar}; -use digest::generic_array::typenum::Unsigned; use digest::Digest; +use digest::generic_array::typenum::Unsigned; use group::GroupEncoding; +use itertools::izip; use sha2::Sha256; +use crate::error::{CompactEcashError, Result}; use crate::scheme::keygen::PublicKeyUser; use crate::scheme::setup::Parameters; +use crate::utils::try_deserialize_g1_projective; type ChallengeDigest = Sha256; /// Generates a Scalar [or Fp] challenge by hashing a number of elliptic curve points. fn compute_challenge(iter: I) -> Scalar -where - D: Digest, - I: Iterator, - B: AsRef<[u8]>, + where + D: Digest, + I: Iterator, + B: AsRef<[u8]>, { let mut h = D::new(); for point_representation in iter { @@ -38,14 +43,14 @@ where Scalar::from_bytes_wide(&bytes) } -fn produce_response(witness: &Scalar, challenge: &Scalar, secret: &Scalar) -> Scalar { - witness - challenge * secret +fn produce_response(witness_replacement: &Scalar, challenge: &Scalar, secret: &Scalar) -> Scalar { + witness_replacement - challenge * secret } // note: it's caller's responsibility to ensure witnesses.len() = secrets.len() fn produce_responses(witnesses: &[Scalar], challenge: &Scalar, secrets: &[S]) -> Vec -where - S: Borrow, + where + S: Borrow, { debug_assert_eq!(witnesses.len(), secrets.len()); @@ -56,21 +61,113 @@ where .collect() } +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] // instance: g, gamma1, gamma2, gamma3, com, h, com1, com2, com3, pkUser pub struct WithdrawalReqInstance { - pub g1_gen: G1Affine, - pub gammas: Vec, - pub attrs_commitment: G1Projective, - pub attrs_commitment_hash: G1Projective, - pub pc_commitments: Vec, + // Joined commitment to all attributes + pub com: G1Projective, + // Hash of the joined commitment com + pub h: G1Projective, + // Pedersen commitments to each attribute + pub pc_coms: Vec, + // Public key of a user pub pk_user: PublicKeyUser, } +impl TryFrom<&[u8]> for WithdrawalReqInstance { + type Error = CompactEcashError; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() < 48 * 4 + 8 || (bytes.len() - 8) % 48 != 0 { + return Err(CompactEcashError::DeserializationInvalidLength { + actual: bytes.len(), + modulus_target: bytes.len() - 8, + target: 48 * 4 + 8, + modulus: 48, + object: "secret key".to_string(), + }); + } + let com_bytes: [u8; 48] = bytes[..48].try_into().unwrap(); + let com = try_deserialize_g1_projective( + &com_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize com".to_string(), + ), + )?; + let h_bytes: [u8; 48] = bytes[48..96].try_into().unwrap(); + let h = try_deserialize_g1_projective( + &h_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize h".to_string(), + ), + )?; + let pc_coms_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap()); + let actual_pc_coms_len = (bytes.len() - 152) / 48; + if pc_coms_len as usize != actual_pc_coms_len { + return Err(CompactEcashError::Deserialization(format!( + "Tried to deserialize pedersen commitments with inconsistent pc_coms_len (expected {}, got {})", + pc_coms_len, actual_pc_coms_len + ))); + } + let mut pc_coms = Vec::new(); + let mut pc_coms_end: usize = 0; + for i in 0..pc_coms_len { + let start = (104 + i * 48) as usize; + let end = (start + 48) as usize; + let pc_i_bytes = bytes[start..end].try_into().unwrap(); + let pc_i = try_deserialize_g1_projective( + &pc_i_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize pedersen commitment".to_string(), + ), + )?; + pc_coms_end = end; + pc_coms.push(pc_i); + } + let pk_bytes = bytes[pc_coms_end..].try_into().unwrap(); + let pk = try_deserialize_g1_projective( + &pk_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize user's public key".to_string(), + ), + )?; + + Ok(WithdrawalReqInstance { + com, + h, + pc_coms, + pk_user: PublicKeyUser { pk }, + }) + } +} + +impl WithdrawalReqInstance { + pub(crate) fn to_bytes(&self) -> Vec { + let pc_coms_len = self.pc_coms.len(); + let mut bytes = Vec::with_capacity(8 + (pc_coms_len + 3) as usize * 48); + bytes.extend_from_slice(self.com.to_bytes().as_ref()); + bytes.extend_from_slice(self.h.to_bytes().as_ref()); + bytes.extend_from_slice(&pc_coms_len.to_le_bytes()); + for pc in self.pc_coms.iter() { + bytes.extend_from_slice((pc.to_bytes()).as_ref()); + } + bytes.extend_from_slice(self.pk_user.pk.to_bytes().as_ref()); + bytes + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + WithdrawalReqInstance::try_from(bytes) + } +} + // witness: m1, m2, m3, o, o1, o2, o3, pub struct WithdrawalReqWitness { pub attributes: Vec, - pub attrs_commitment_opening: Scalar, - pub pc_openings: Vec, + // Opening for the joined commitment com + pub com_opening: Scalar, + // Openings for the pedersen commitments + pub pc_coms_openings: Vec, } pub struct WithdrawalReqProof { @@ -87,75 +184,62 @@ impl WithdrawalReqProof { witness: &WithdrawalReqWitness, ) -> Self { // generate random values to replace the witnesses - let r_commitment_opening = params.random_scalar(); - let r_pedersen_commitments_openings = params.n_random_scalars(witness.pc_openings.len()); - let r_witness_attributes = params.n_random_scalars(witness.attributes.len()); + let r_com_opening = params.random_scalar(); + let r_pedcom_openings = params.n_random_scalars(witness.pc_coms_openings.len()); + let r_attributes = params.n_random_scalars(witness.attributes.len()); - // compute zkp commitments - let zk_commitment_attributes = instance.g1_gen * r_commitment_opening - + r_witness_attributes - .iter() - .zip(instance.gammas.iter()) - .map(|(wm_i, gamma_i)| gamma_i * wm_i) - .sum::(); - - let zk_pc_commitments_attributes = r_pedersen_commitments_openings + // compute zkp commitments for each instance + let zkcm_com = params.gen1() * r_com_opening + + r_attributes .iter() - .zip(r_witness_attributes.iter()) - .map(|(o_j, m_j)| instance.g1_gen * o_j + instance.attrs_commitment_hash * m_j) + .zip(params.gammas().iter()) + .map(|(rm_i, gamma_i)| gamma_i * rm_i) + .sum::(); + + let zkcm_pedcom = r_pedcom_openings + .iter() + .zip(r_attributes.iter()) + .map(|(o_j, m_j)| params.gen1() * o_j + instance.h * m_j) .collect::>(); - let zk_commitment_user_sk = instance.g1_gen * r_witness_attributes[0]; + let zkcm_user_sk = params.gen1() * r_attributes[0]; // covert to bytes - let gammas_bytes = instance - .gammas + let gammas_bytes = params + .gammas() .iter() .map(|gamma| gamma.to_bytes()) .collect::>(); - let pc_commitments_bytes = instance - .pc_commitments + let zkcm_pedcom_bytes = zkcm_pedcom .iter() .map(|cm| cm.to_bytes()) .collect::>(); - let zk_commitments_attributes_bytes = zk_pc_commitments_attributes - .iter() - .map(|cm| cm.to_bytes()) - .collect::>(); - - // compute zkp challenge + println!("Zk commitments to com {:?}", zkcm_com.to_bytes()); + // compute zkp challenge using g1, gammas, c, h, c1, c2, c3, zk commitments let challenge = compute_challenge::( - std::iter::once(instance.g1_gen.to_bytes().as_ref()) - .chain(gammas_bytes.iter().map(|hs| hs.as_ref())) - .chain(std::iter::once( - instance.attrs_commitment.to_bytes().as_ref(), - )) - .chain(std::iter::once( - instance.attrs_commitment_hash.to_bytes().as_ref(), - )) - .chain(pc_commitments_bytes.iter().map(|pcm| pcm.as_ref())) - .chain(std::iter::once( - zk_commitment_attributes.to_bytes().as_ref(), - )) - .chain(zk_commitments_attributes_bytes.iter().map(|c| c.as_ref())) - .chain(std::iter::once(zk_commitment_user_sk.to_bytes().as_ref())), + std::iter::once(params.gen1().to_bytes().as_ref()) + .chain(gammas_bytes.iter().map(|gamma| gamma.as_ref())) + .chain(std::iter::once(instance.to_bytes().as_ref())) + .chain(std::iter::once(zkcm_com.to_bytes().as_ref())) + .chain(zkcm_pedcom_bytes.iter().map(|c| c.as_ref())) + .chain(std::iter::once(zkcm_user_sk.to_bytes().as_ref())), ); // compute response let response_opening = produce_response( - &r_commitment_opening, + &r_com_opening, &challenge, - &witness.attrs_commitment_opening, + &witness.com_opening, ); let response_openings = produce_responses( - &r_pedersen_commitments_openings, + &r_pedcom_openings, &challenge, - &witness.pc_openings.iter().collect::>(), + &witness.pc_coms_openings.iter().collect::>(), ); let response_attributes = produce_responses( - &r_witness_attributes, + &r_attributes, &challenge, &witness.attributes.iter().collect::>(), ); @@ -168,61 +252,119 @@ impl WithdrawalReqProof { } } - pub(crate) fn verify(&self, instance: &WithdrawalReqInstance) -> bool { - // recompute zk commitments - let zk_commitment_attributes = instance.g1_gen * self.response_opening + pub(crate) fn verify(&self, params: &Parameters, instance: &WithdrawalReqInstance) -> bool { + // recompute zk commitments for each instance + let zkcm_com = instance.com * self.challenge + + params.gen1() * self.response_opening + self - .response_attributes - .iter() - .zip(instance.gammas.iter()) - .map(|(wm_i, gamma_i)| gamma_i * wm_i) - .sum::(); - - let zk_pc_commitments_attributes = self - .response_openings + .response_attributes .iter() - .zip(self.response_attributes.iter()) - .map(|(o_j, m_j)| instance.g1_gen * o_j + instance.attrs_commitment_hash * m_j) + .zip(params.gammas().iter()) + .map(|(m_i, gamma_i)| gamma_i * m_i) + .sum::(); + + let zkcm_pedcom = izip!( + instance.pc_coms.iter(), + self.response_openings.iter(), + self.response_attributes.iter()) + .map(|(cm_j, resp_o_j, resp_m_j)| cm_j * self.challenge + params.gen1() * resp_o_j + instance.h * resp_m_j) .collect::>(); - let zk_commitment_user_sk = instance.g1_gen * self.response_attributes[0]; + let zk_commitment_user_sk = instance.pk_user.pk * self.challenge + params.gen1() * self.response_attributes[0]; // covert to bytes - let gammas_bytes = instance - .gammas + let gammas_bytes = params + .gammas() .iter() .map(|gamma| gamma.to_bytes()) .collect::>(); - let pc_commitments_bytes = instance - .pc_commitments + let zkcm_pedcom_bytes = zkcm_pedcom .iter() .map(|cm| cm.to_bytes()) .collect::>(); - let zk_commitments_attributes_bytes = zk_pc_commitments_attributes - .iter() - .map(|cm| cm.to_bytes()) - .collect::>(); + println!("Zk commitments to com Vfy {:?}", zkcm_com.to_bytes()); // recompute zkp challenge let challenge = compute_challenge::( - std::iter::once(instance.g1_gen.to_bytes().as_ref()) + std::iter::once(params.gen1().to_bytes().as_ref()) .chain(gammas_bytes.iter().map(|hs| hs.as_ref())) - .chain(std::iter::once( - instance.attrs_commitment.to_bytes().as_ref(), - )) - .chain(std::iter::once( - instance.attrs_commitment_hash.to_bytes().as_ref(), - )) - .chain(pc_commitments_bytes.iter().map(|pcm| pcm.as_ref())) - .chain(std::iter::once( - zk_commitment_attributes.to_bytes().as_ref(), - )) - .chain(zk_commitments_attributes_bytes.iter().map(|c| c.as_ref())) + .chain(std::iter::once(instance.to_bytes().as_ref())) + .chain(std::iter::once(zkcm_com.to_bytes().as_ref())) + .chain(zkcm_pedcom_bytes.iter().map(|c| c.as_ref())) .chain(std::iter::once(zk_commitment_user_sk.to_bytes().as_ref())), ); + println!("Original challenge: {:?}", self.challenge); + println!("Recomputed challenge: {:?}", challenge); challenge == self.challenge } } + +#[cfg(test)] +mod tests { + use group::Group; + use rand::thread_rng; + + use crate::utils::hash_g1; + + use super::*; + + #[test] + fn withdrawal_request_instance_roundtrip() { + let mut rng = thread_rng(); + let params = Parameters::new().unwrap(); + let instance = WithdrawalReqInstance { + com: G1Projective::random(&mut rng), + h: G1Projective::random(&mut rng), + pc_coms: vec![G1Projective::random(&mut rng), G1Projective::random(&mut rng), G1Projective::random(&mut rng)], + pk_user: PublicKeyUser { pk: params.gen1() * params.random_scalar() }, + }; + + let instance_bytes = instance.to_bytes(); + let instance_p = WithdrawalReqInstance::from_bytes(&instance_bytes).unwrap(); + assert_eq!(instance, instance_p) + } + + #[test] + fn withdrawal_proof_construct_and_verify() { + let mut rng = thread_rng(); + let params = Parameters::new().unwrap(); + let sk = params.random_scalar(); + let pk_user = PublicKeyUser { pk: params.gen1() * sk }; + let v = params.random_scalar(); + let t = params.random_scalar(); + let attr = vec![sk, v, t]; + + let com_opening = params.random_scalar(); + let com = params.gen1() * com_opening + attr + .iter() + .zip(params.gammas()) + .map(|(&m, gamma)| gamma * m) + .sum::(); + let h = hash_g1(com.to_bytes()); + + let pc_openings = params.n_random_scalars(attr.len()); + let pc_coms = pc_openings + .iter() + .zip(attr.iter()) + .map(|(o_j, m_j)| params.gen1() * o_j + h * m_j) + .collect::>(); + + let instance = WithdrawalReqInstance { + com, + h, + pc_coms, + pk_user, + }; + + let witness = WithdrawalReqWitness { + attributes: attr, + com_opening, + pc_coms_openings: pc_openings, + }; + let zk_proof = WithdrawalReqProof::construct(¶ms, &instance, &witness); + assert!(zk_proof.verify(¶ms, &instance)) + } +} \ No newline at end of file diff --git a/common/nym-compact-ecash/src/scheme/mod.rs b/common/nym-compact-ecash/src/scheme/mod.rs index 165e340c9c..bc2705d4d9 100644 --- a/common/nym-compact-ecash/src/scheme/mod.rs +++ b/common/nym-compact-ecash/src/scheme/mod.rs @@ -1,4 +1,4 @@ -use bls12_381::G1Projective; +use bls12_381::{G1Projective, Scalar}; pub mod aggregation; pub mod keygen; @@ -8,6 +8,16 @@ pub mod withdrawal; pub type SignerIndex = u64; +#[derive(Debug, Clone, Copy)] +#[cfg_attr(test, derive(PartialEq))] +pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective); + #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] pub struct BlindedSignature(G1Projective, G1Projective); + +pub struct Wallet { + sig: Signature, + v: Scalar, + idx: Option, +} \ No newline at end of file diff --git a/common/nym-compact-ecash/src/scheme/setup.rs b/common/nym-compact-ecash/src/scheme/setup.rs index d5136f29d4..667f77b65d 100644 --- a/common/nym-compact-ecash/src/scheme/setup.rs +++ b/common/nym-compact-ecash/src/scheme/setup.rs @@ -1,4 +1,4 @@ -use bls12_381::{G1Affine, G2Affine, Scalar}; +use bls12_381::{G1Affine, G2Affine, G2Prepared, Scalar}; use ff::Field; use group::{Curve, GroupEncoding}; use rand::thread_rng; @@ -18,6 +18,8 @@ pub struct Parameters { gammas: Vec, /// Value of wallet L: usize, + /// Precomputed G2 generator used for the miller loop + _g2_prepared_miller: G2Prepared, } impl Parameters { @@ -30,6 +32,7 @@ impl Parameters { g2: G2Affine::generator(), gammas, L: MAX_COIN_VALUE, + _g2_prepared_miller: G2Prepared::from(G2Affine::generator()), }) } @@ -44,6 +47,7 @@ impl Parameters { pub(crate) fn gammas(&self) -> &Vec { &self.gammas } + pub fn random_scalar(&self) -> Scalar { // lazily-initialized thread-local random number generator, seeded by the system @@ -54,6 +58,10 @@ impl Parameters { pub fn n_random_scalars(&self, n: usize) -> Vec { (0..n).map(|_| self.random_scalar()).collect() } + + pub(crate) fn prepared_miller_g2(&self) -> &G2Prepared { + &self._g2_prepared_miller + } } pub fn setup() -> Result { diff --git a/common/nym-compact-ecash/src/scheme/withdrawal.rs b/common/nym-compact-ecash/src/scheme/withdrawal.rs index 52564f90a7..a4f0fa1786 100644 --- a/common/nym-compact-ecash/src/scheme/withdrawal.rs +++ b/common/nym-compact-ecash/src/scheme/withdrawal.rs @@ -1,29 +1,30 @@ -use bls12_381::{G1Projective, Scalar}; -use group::GroupEncoding; +use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar}; +use group::{Curve, GroupEncoding}; use crate::error::{CompactEcashError, Result}; use crate::proofs::{WithdrawalReqInstance, WithdrawalReqProof, WithdrawalReqWitness}; -use crate::scheme::BlindedSignature; -use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, SecretKeyUser}; +use crate::scheme::{BlindedSignature, Signature, Wallet}; +use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, SecretKeyUser, VerificationKeyAuth}; use crate::scheme::keygen::ttp_keygen; use crate::scheme::setup::Parameters; -use crate::utils::hash_g1; +use crate::utils::{check_bilinear_pairing, hash_g1}; pub struct WithdrawalRequest { - commitment_hash: G1Projective, - attrs_commitment: G1Projective, - pc_commitments: Vec, + com_hash: G1Projective, + com: G1Projective, + pc_coms: Vec, zk_proof: WithdrawalReqProof, } pub struct RequestInfo { - commitment_hash: G1Projective, - attrs_commitment_opening: Scalar, - pc_openings: Vec, + com_hash: G1Projective, + com_opening: Scalar, + pc_coms_openings: Vec, v: Scalar, t: Scalar, } + pub fn withdrawal_request( params: &Parameters, sk_user: &SecretKeyUser, @@ -33,55 +34,52 @@ pub fn withdrawal_request( let attributes = vec![sk_user.sk, v, t]; let gammas = params.gammas(); - let attrs_commitment_opening = params.random_scalar(); - let attrs_commitment = attributes + let com_opening = params.random_scalar(); + let com = params.gen1() * com_opening + attributes .iter() .zip(gammas) .map(|(&m, gamma)| gamma * m) .sum::(); - let attrs_commitment_hash = hash_g1(attrs_commitment.to_bytes()); + // Value h in the paper + let com_hash = hash_g1(com.to_bytes()); - let pc_openings = params.n_random_scalars(attributes.len()); + let pc_coms_openings = params.n_random_scalars(attributes.len()); // Compute Pedersen commitment for each attribute - let pc_commitments = pc_openings + let pc_coms = pc_coms_openings .iter() .zip(attributes.iter()) - .map(|(o_j, m_j)| params.gen1() * o_j + attrs_commitment_hash * m_j) + .map(|(o_j, m_j)| params.gen1() * o_j + com_hash * m_j) .collect::>(); // construct a zk proof of knowledge proving possession of m1, m2, m3, o, o1, o2, o3 let instance = WithdrawalReqInstance { - g1_gen: *params.gen1(), - gammas: gammas.clone(), - attrs_commitment, - attrs_commitment_hash, - pc_commitments: pc_commitments.clone(), - pk_user: PublicKeyUser { - pk: params.gen1() * sk_user.sk, - }, + com, + h: com_hash, + pc_coms: pc_coms.clone(), + pk_user: PublicKeyUser { pk: params.gen1() * sk_user.sk }, }; let witness = WithdrawalReqWitness { attributes, - attrs_commitment_opening, - pc_openings: pc_openings.clone(), + com_opening, + pc_coms_openings: pc_coms_openings.clone(), }; let zk_proof = WithdrawalReqProof::construct(¶ms, &instance, &witness); let req = WithdrawalRequest { - commitment_hash: attrs_commitment_hash, - attrs_commitment, - pc_commitments: pc_commitments.clone(), + com_hash, + com, + pc_coms: pc_coms.clone(), zk_proof, }; let req_info = RequestInfo { - commitment_hash: attrs_commitment_hash, - attrs_commitment_opening, - pc_openings: pc_openings.clone(), + com_hash, + com_opening, + pc_coms_openings: pc_coms_openings.clone(), v, t, }; @@ -95,8 +93,8 @@ pub fn issue_wallet( pk_user: PublicKeyUser, withdrawal_req: &WithdrawalRequest, ) -> Result { - let h = hash_g1(withdrawal_req.attrs_commitment.to_bytes()); - if !(h == withdrawal_req.commitment_hash) { + let h = hash_g1(withdrawal_req.com.to_bytes()); + if !(h == withdrawal_req.com_hash) { return Err(CompactEcashError::WithdrawalRequestVerification( "Failed to verify the commitment hash".to_string(), )); @@ -104,21 +102,19 @@ pub fn issue_wallet( // verify zk proof let instance = WithdrawalReqInstance { - g1_gen: *params.gen1(), - gammas: params.gammas().clone(), - attrs_commitment: withdrawal_req.attrs_commitment, - attrs_commitment_hash: withdrawal_req.commitment_hash, - pc_commitments: withdrawal_req.pc_commitments.clone(), + com: withdrawal_req.com, + h: withdrawal_req.com_hash, + pc_coms: withdrawal_req.pc_coms.clone(), pk_user, }; - if !withdrawal_req.zk_proof.verify(&instance) { + if !withdrawal_req.zk_proof.verify(¶ms, &instance) { return Err(CompactEcashError::WithdrawalRequestVerification( "Failed to verify the proof of knowledge".to_string(), )); } let sig = withdrawal_req - .pc_commitments + .pc_coms .iter() .zip(sk_auth.ys.iter()) .map(|(pc, yi)| pc * yi) @@ -129,6 +125,57 @@ pub fn issue_wallet( } +pub fn issue_verify(params: &Parameters, vk_auth: &VerificationKeyAuth, sk_user: &SecretKeyUser, blind_signature: &BlindedSignature, req_info: &RequestInfo) -> Result { + // Parse the blinded signature + let h = blind_signature.0; + let c = blind_signature.1; + + // Verify the integrity of the reponse from the authority + if !(req_info.com_hash == h) { + return Err(CompactEcashError::IssuanceVfy( + "Failed to verify the proof of knowledge".to_string(), + )); + } + + // Unblind the blinded signature + let blinding_removers = vk_auth + .beta_g1 + .iter() + .zip(req_info.pc_coms_openings.iter()) + .map(|(beta, opening)| beta * opening) + .sum::(); + + let unblinded_c = c - blinding_removers; + + // Verify the signature correctness on the wallet share + let attr = vec![sk_user.sk, req_info.v]; + + let signed_attributes = attr + .iter() + .zip(vk_auth.beta_g2.iter()) + .map(|(attr, beta_i)| beta_i * attr) + .sum::(); + + if !check_bilinear_pairing( + &h.to_affine(), + &G2Prepared::from((vk_auth.alpha + signed_attributes).to_affine()), + &unblinded_c.to_affine(), + params.prepared_miller_g2(), + ) { + return Err(CompactEcashError::IssuanceVfy( + "Verification of wallet share failed".to_string(), + )); + } + + + Ok(Wallet { + sig: Signature(h, + unblinded_c), + v: req_info.v, + idx: None, + }) +} + #[cfg(test)] mod tests { use super::*; diff --git a/common/nym-compact-ecash/src/tests/e2e.rs b/common/nym-compact-ecash/src/tests/e2e.rs index b967f335f9..9cf31de50b 100644 --- a/common/nym-compact-ecash/src/tests/e2e.rs +++ b/common/nym-compact-ecash/src/tests/e2e.rs @@ -1,7 +1,11 @@ +use itertools::izip; + use crate::error::CompactEcashError; -use crate::scheme::keygen::{generate_keypair_user, PublicKeyUser, SecretKeyUser, ttp_keygen}; +use crate::scheme::keygen::{generate_keypair_user, PublicKeyUser, SecretKeyUser, ttp_keygen, VerificationKeyAuth}; use crate::scheme::setup::Parameters; -use crate::scheme::withdrawal::{issue_wallet, withdrawal_request}; +use crate::scheme::Wallet; +use crate::scheme::withdrawal::{issue_verify, issue_wallet, withdrawal_request}; +use crate::VerificationKey; #[test] fn main() -> Result<(), CompactEcashError> { @@ -9,10 +13,30 @@ fn main() -> Result<(), CompactEcashError> { let user_keypair = generate_keypair_user(¶ms); let (req, req_info) = withdrawal_request(¶ms, &user_keypair.secret_key()).unwrap(); - let mut authorities_keypairs = ttp_keygen(¶ms, 1, 1).unwrap(); + let authorities_keypairs = ttp_keygen(¶ms, 2, 3).unwrap(); + + let verification_keys_auth: Vec = authorities_keypairs + .iter() + .map(|keypair| keypair.verification_key()) + .collect(); + + let mut wallet_blinded_signatures = Vec::new(); for auth_keypair in authorities_keypairs { let blind_signature = issue_wallet(¶ms, 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(¶ms, + // vk, + // &user_keypair.secret_key(), + // w, + // &req_info) + // .unwrap() + // }) + // .collect(); + Ok(()) } diff --git a/common/nym-compact-ecash/src/utils.rs b/common/nym-compact-ecash/src/utils.rs index 2846f9db98..93640c4fa8 100644 --- a/common/nym-compact-ecash/src/utils.rs +++ b/common/nym-compact-ecash/src/utils.rs @@ -4,10 +4,12 @@ use core::iter::Sum; use core::ops::Mul; use std::convert::TryInto; +use std::ops::Neg; -use bls12_381::{G1Affine, G1Projective, G2Affine, G2Projective, Scalar}; +use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, multi_miller_loop, Scalar}; use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; use ff::Field; +use group::Group; use crate::error::{CompactEcashError, Result}; use crate::scheme::setup::Parameters; @@ -175,6 +177,17 @@ pub fn try_deserialize_g2_projective(bytes: &[u8; 96], err: CompactEcashError) - .map(G2Projective::from) } +/// Checks whether e(P, Q) * e(-R, S) == id +pub fn check_bilinear_pairing(p: &G1Affine, q: &G2Prepared, r: &G1Affine, s: &G2Prepared) -> bool { + // checking e(P, Q) * e(-R, S) == id + // is equivalent to checking e(P, Q) == e(R, S) + // but requires only a single final exponentiation rather than two of them + // and therefore, as seen via benchmarks.rs, is almost 50% faster + // (1.47ms vs 2.45ms, tested on R9 5900X) + + let multi_miller = multi_miller_loop(&[(p, q), (&r.neg(), s)]); + multi_miller.final_exponentiation().is_identity().into() +} #[cfg(test)] mod tests {