diff --git a/common/nym-compact-ecash/src/proofs/proof_spend.rs b/common/nym-compact-ecash/src/proofs/proof_spend.rs index f6b9a44d09..76bd3d77ea 100644 --- a/common/nym-compact-ecash/src/proofs/proof_spend.rs +++ b/common/nym-compact-ecash/src/proofs/proof_spend.rs @@ -1,7 +1,13 @@ -use bls12_381::{G1Projective, G2Projective, Scalar}; +use std::convert::{TryFrom, TryInto}; -use crate::scheme::keygen::SecretKeyUser; +use bls12_381::{G1Projective, G2Projective, Scalar}; +use group::GroupEncoding; + +use crate::error::{CompactEcashError, Result}; +use crate::proofs::{ChallengeDigest, compute_challenge, produce_response, produce_responses}; +use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth}; use crate::scheme::setup::Parameters; +use crate::utils::{try_deserialize_g1_projective, try_deserialize_g2_projective}; #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] @@ -14,12 +20,80 @@ pub struct SpendInstance { pub T: G1Projective, } +impl TryFrom<&[u8]> for SpendInstance { + type Error = CompactEcashError; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() < 48 * 5 + 96 || (bytes.len()) % 48 != 0 { + return Err(CompactEcashError::DeserializationInvalidLength { + actual: bytes.len(), + modulus_target: bytes.len(), + target: 48 * 5 + 96, + modulus: 48, + object: "spend instance".to_string(), + }); + } + + let kappa_bytes = bytes[..96].try_into().unwrap(); + let kappa = try_deserialize_g2_projective( + &kappa_bytes, + CompactEcashError::Deserialization("Failed to deserialize kappa".to_string()), + )?; + let A_bytes = bytes[96..144].try_into().unwrap(); + let A = try_deserialize_g1_projective( + &A_bytes, + CompactEcashError::Deserialization("Failed to deserialize A".to_string()), + )?; + let C_bytes = bytes[144..192].try_into().unwrap(); + let C = try_deserialize_g1_projective( + &C_bytes, + CompactEcashError::Deserialization("Failed to deserialize C".to_string()), + )?; + let D_bytes = bytes[192..240].try_into().unwrap(); + let D = try_deserialize_g1_projective( + &D_bytes, + CompactEcashError::Deserialization("Failed to deserialize D".to_string()), + )?; + let S_bytes = bytes[240..288].try_into().unwrap(); + let S = try_deserialize_g1_projective( + &S_bytes, + CompactEcashError::Deserialization("Failed to deserialize S".to_string()), + )?; + let T_bytes = bytes[288..336].try_into().unwrap(); + let T = try_deserialize_g1_projective( + &T_bytes, + CompactEcashError::Deserialization("Failed to deserialize T".to_string()), + )?; + Ok(SpendInstance { + kappa, + A, + C, + D, + S, + T, + }) + } +} + +impl SpendInstance { + pub(crate) fn to_bytes(&self) -> Vec { + let mut bytes = Vec::with_capacity(96 + 5 * 48); + bytes.extend_from_slice(self.kappa.to_bytes().as_ref()); + bytes.extend_from_slice(self.A.to_bytes().as_ref()); + bytes.extend_from_slice(self.C.to_bytes().as_ref()); + bytes.extend_from_slice(self.D.to_bytes().as_ref()); + bytes.extend_from_slice(self.S.to_bytes().as_ref()); + bytes.extend_from_slice(self.T.to_bytes().as_ref()); + bytes + } +} + pub struct SpendWitness { // includes skUser, v, t pub attributes: Vec, // signature randomizing element pub r: Scalar, - pub l: u64, + pub l: Scalar, pub o_a: Scalar, pub o_c: Scalar, pub o_d: Scalar, @@ -30,11 +104,252 @@ pub struct SpendWitness { } -pub struct SpendProof {} +pub struct SpendProof { + challenge: Scalar, + response_r: Scalar, + response_l: Scalar, + response_o_a: Scalar, + response_o_c: Scalar, + response_o_d: Scalar, + response_mu: Scalar, + response_lambda: Scalar, + response_o_mu: Scalar, + response_o_lambda: Scalar, + response_attributes: Vec, +} impl SpendProof { pub fn construct(params: &Parameters, instance: &SpendInstance, - witness: &SpendWitness, ) {} - pub fn verify() {} + witness: &SpendWitness, + verification_key: &VerificationKeyAuth, + R: Scalar, ) -> Self { + // generate random values to replace each witness + let r_attributes = params.n_random_scalars(witness.attributes.len()); + let r_r = params.random_scalar(); + let r_l = params.random_scalar(); + let r_o_a = params.random_scalar(); + let r_o_c = params.random_scalar(); + let r_o_d = params.random_scalar(); + let r_mu = params.random_scalar(); + let r_lambda = params.random_scalar(); + let r_o_mu = params.random_scalar(); + let r_o_lambda = params.random_scalar(); + + let g1 = params.gen1(); + let gamma1 = params.gamma1().unwrap(); + let beta2_bytes = verification_key + .beta_g2 + .iter() + .map(|beta_i| beta_i.to_bytes()) + .collect::>(); + + // compute zkp commitment for each instance + let zkcm_kappa = params.gen2() * r_r + + verification_key.alpha + + r_attributes + .iter() + .zip(verification_key.beta_g2.iter()) + .map(|(attr, beta_i)| beta_i * attr) + .sum::(); + + let zkcm_A = g1 * r_o_a + gamma1 * r_l; + + let zkcm_C = g1 * r_o_c + gamma1 * r_attributes[1]; + let zkcm_D = g1 * r_o_d + gamma1 * r_attributes[2]; + let zkcm_S = g1 * r_mu; + let zkcm_gamma11 = (instance.A + instance.C + gamma1) * r_mu + g1 * r_o_mu; + let zkcm_T = g1 * r_attributes[0] + (g1 * R) * r_lambda; + let zkcm_gamma12 = (instance.A + instance.D + gamma1) * r_lambda + g1 * r_o_lambda; + + // TODO: Add also proof for l in [0, L-1] + + // compute the challenge + let challenge = compute_challenge::( + std::iter::once(params.gen1().to_bytes().as_ref()) + .chain(std::iter::once(gamma1.to_bytes().as_ref())) + .chain(std::iter::once(verification_key.alpha.to_bytes().as_ref())) + .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_A.to_bytes().as_ref())) + // .chain(std::iter::once(zkcm_C.to_bytes().as_ref())) + // .chain(std::iter::once(zkcm_D.to_bytes().as_ref())) + // .chain(std::iter::once(zkcm_S.to_bytes().as_ref())) + // .chain(std::iter::once(zkcm_gamma11.to_bytes().as_ref())) + // .chain(std::iter::once(zkcm_T.to_bytes().as_ref())) + // .chain(std::iter::once(zkcm_gamma12.to_bytes().as_ref())) + ); + + // compute response for each witness + let response_r = produce_response(&r_r, &challenge, &witness.r); + 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_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_attributes = produce_responses( + &r_attributes, + &challenge, + &witness.attributes.iter().collect::>(), + ); + + SpendProof { + challenge, + response_r, + response_l, + response_o_a, + response_o_c, + response_o_d, + response_mu, + response_lambda, + response_o_mu, + response_o_lambda, + response_attributes, + } + } + pub fn verify(&self, + params: &Parameters, + instance: &SpendInstance, + verification_key: &VerificationKeyAuth, + R: Scalar) -> bool { + let g1 = params.gen1(); + let gamma1 = params.gamma1().unwrap(); + let beta2_bytes = verification_key + .beta_g2 + .iter() + .map(|beta_i| beta_i.to_bytes()) + .collect::>(); + + // re-compute each zkp commitment + let zkcm_kappa = instance.kappa * self.challenge + + params.gen2() * self.response_r + + verification_key.alpha * (Scalar::one() - self.challenge) + + self.response_attributes + .iter() + .zip(verification_key.beta_g2.iter()) + .map(|(attr, beta_i)| beta_i * attr) + .sum::(); + + let zkcm_A = g1 * self.response_o_a + gamma1 * self.response_l + instance.A * self.challenge; + let zkcm_C = g1 * self.response_o_c + gamma1 * self.response_attributes[1] + instance.C * self.challenge; + let zkcm_D = g1 * self.response_o_d + gamma1 * self.response_attributes[2] + instance.D * self.challenge; + let zkcm_S = g1 * self.response_mu + instance.S * self.challenge; + let zkcm_gamma11 = (instance.A + instance.C + gamma1) * self.response_mu + g1 * self.response_o_mu + gamma1 * self.challenge; + let zkcm_T = g1 * self.response_attributes[0] + (g1 * R) * self.response_lambda + instance.T * self.challenge; + let zkcm_gamma12 = (instance.A + instance.D + gamma1) * self.response_lambda + g1 * self.response_o_lambda + gamma1 * self.challenge; + + // re-compute the challenge + let challenge = compute_challenge::( + std::iter::once(params.gen1().to_bytes().as_ref()) + .chain(std::iter::once(gamma1.to_bytes().as_ref())) + .chain(std::iter::once(verification_key.alpha.to_bytes().as_ref())) + .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_A.to_bytes().as_ref())) + // .chain(std::iter::once(zkcm_C.to_bytes().as_ref())) + // .chain(std::iter::once(zkcm_D.to_bytes().as_ref())) + // .chain(std::iter::once(zkcm_S.to_bytes().as_ref())) + // .chain(std::iter::once(zkcm_gamma11.to_bytes().as_ref())) + // .chain(std::iter::once(zkcm_T.to_bytes().as_ref())) + // .chain(std::iter::once(zkcm_gamma12.to_bytes().as_ref())) + ); + + challenge == self.challenge + } +} + +#[cfg(test)] +mod tests { + use bls12_381::{G2Projective, Scalar}; + use rand::thread_rng; + + use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness}; + use crate::scheme::aggregation::aggregate_verification_keys; + use crate::scheme::keygen::{PublicKeyUser, ttp_keygen, VerificationKeyAuth}; + use crate::scheme::setup::Parameters; + use crate::scheme::spend::{PayInfo, pseudorandom_fgt, pseudorandom_fgv}; + use crate::utils::hash_to_scalar; + + #[test] + fn spend_proof_construct_and_verify() { + let rng = thread_rng(); + let params = Parameters::new().unwrap(); + let sk = params.random_scalar(); + let pk_user = PublicKeyUser { + pk: params.gen1() * sk, + }; + let authorities_keypairs = ttp_keygen(¶ms, 2, 3).unwrap(); + let verification_keys_auth: Vec = authorities_keypairs + .iter() + .map(|keypair| keypair.verification_key()) + .collect(); + + let verification_key = aggregate_verification_keys(&verification_keys_auth, Some(&[1, 2, 3])).unwrap(); + + let v = params.random_scalar(); + let t = params.random_scalar(); + let attributes = vec![sk, v, t]; + let l = 5; + + let r = params.random_scalar(); + let kappa = params.gen2() * r + + verification_key.alpha + + attributes + .iter() + .zip(verification_key.beta_g2.iter()) + .map(|(priv_attr, beta_i)| beta_i * priv_attr) + .sum::(); + + let o_a = params.random_scalar(); + let o_c = params.random_scalar(); + let o_d = params.random_scalar(); + + // compute commitments A, C, D + let A = params.gen1() * o_a + params.gamma1().unwrap() * Scalar::from(l); + let C = params.gen1() * o_c + params.gamma1().unwrap() * v; + let D = params.gen1() * o_d + params.gamma1().unwrap() * t; + + // compute hash of the payment info + let payInfo = PayInfo { info: [5u8; 32] }; + let R = hash_to_scalar(payInfo.info); + + // evaluate the pseudorandom functions + let S = pseudorandom_fgv(¶ms, v, l); + let T = params.gen1() * sk + pseudorandom_fgt(¶ms, t, l) * R; + + // compute values mu, o_mu, lambda, o_lambda + let mu: Scalar = (v + Scalar::from(l) + Scalar::from(1)).neg(); + let o_mu = ((o_a + o_c) * mu).neg(); + let lambda = (t + Scalar::from(l) + Scalar::from(1)).neg(); + let o_lambda = ((o_a + o_d) * lambda).neg(); + + let instance = SpendInstance { + kappa, + A: Default::default(), + C: Default::default(), + D: Default::default(), + S: Default::default(), + T: Default::default(), + }; + + let witness = SpendWitness { + attributes, + r, + l: Scalar::from(l), + o_a, + o_c, + o_d, + mu, + lambda, + o_mu, + o_lambda, + }; + let zk_proof = SpendProof::construct(¶ms, &instance, &witness, &verification_key, R); + assert!(zk_proof.verify(¶ms, &instance, &verification_key, R)) + } } \ No newline at end of file diff --git a/common/nym-compact-ecash/src/proofs/proof_withdrawal.rs b/common/nym-compact-ecash/src/proofs/proof_withdrawal.rs index d0a61d032b..d07f432dd4 100644 --- a/common/nym-compact-ecash/src/proofs/proof_withdrawal.rs +++ b/common/nym-compact-ecash/src/proofs/proof_withdrawal.rs @@ -34,7 +34,7 @@ impl TryFrom<&[u8]> for WithdrawalReqInstance { modulus_target: bytes.len() - 8, target: 48 * 4 + 8, modulus: 48, - object: "secret key".to_string(), + object: "withdrawal request zkp instance".to_string(), }); } let com_bytes: [u8; 48] = bytes[..48].try_into().unwrap(); diff --git a/common/nym-compact-ecash/src/scheme/keygen.rs b/common/nym-compact-ecash/src/scheme/keygen.rs index 124ea0c828..6ae36ee415 100644 --- a/common/nym-compact-ecash/src/scheme/keygen.rs +++ b/common/nym-compact-ecash/src/scheme/keygen.rs @@ -5,7 +5,7 @@ use std::convert::TryFrom; use std::convert::TryInto; use bls12_381::{G1Projective, G2Projective, Scalar}; -use group::Curve; +use group::{Curve, GroupEncoding}; use crate::error::{CompactEcashError, Result}; use crate::scheme::aggregation::aggregate_verification_keys; diff --git a/common/nym-compact-ecash/src/scheme/spend.rs b/common/nym-compact-ecash/src/scheme/spend.rs index 4071c4dabc..999a718d00 100644 --- a/common/nym-compact-ecash/src/scheme/spend.rs +++ b/common/nym-compact-ecash/src/scheme/spend.rs @@ -11,7 +11,7 @@ use crate::scheme::Wallet; use crate::utils::hash_to_scalar; pub struct PayInfo { - info: [u8; 32], + pub(crate) info: [u8; 32], } pub fn pseudorandom_fgv(params: &Parameters, v: Scalar, l: u64) -> G1Projective { @@ -87,7 +87,7 @@ pub fn spend(params: &Parameters, wallet: &Wallet, verification_key: &Verificati let spendWitness = SpendWitness { attributes, r: sign_blinding_factor, - l: wallet.l(), + l: Scalar::from(wallet.l()), o_a, o_c, o_d, @@ -96,7 +96,7 @@ pub fn spend(params: &Parameters, wallet: &Wallet, verification_key: &Verificati o_mu, o_lambda, }; - let zkp = SpendProof::construct(¶ms, &spendInstance, &spendWitness); + let zkp = SpendProof::construct(¶ms, &spendInstance, &spendWitness, &verification_key, R); // output pay and updated wallet