diff --git a/common/nym_offline_divisible_ecash/src/proofs/mod.rs b/common/nym_offline_divisible_ecash/src/proofs/mod.rs index 8b13789179..b54452411e 100644 --- a/common/nym_offline_divisible_ecash/src/proofs/mod.rs +++ b/common/nym_offline_divisible_ecash/src/proofs/mod.rs @@ -1 +1,63 @@ +use std::borrow::Borrow; +use std::convert::TryFrom; +use std::convert::TryInto; +use bls12_381::{G1Affine, G1Projective, Scalar}; +use digest::Digest; +use digest::generic_array::typenum::Unsigned; +use group::GroupEncoding; +use sha2::Sha256; + +use crate::error::{DivisibleEcashError, Result}; +use crate::scheme::keygen::PublicKeyUser; +use crate::scheme::setup::GroupParameters; +use crate::utils::try_deserialize_g1_projective; + +pub mod proof_withdrawal; + +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]>, +{ + let mut h = D::new(); + for point_representation in iter { + h.update(point_representation); + } + let digest = h.finalize(); + + // TODO: I don't like the 0 padding here (though it's what we've been using before, + // but we never had a security audit anyway...) + // instead we could maybe use the `from_bytes` variant and adding some suffix + // when computing the digest until we produce a valid scalar. + let mut bytes = [0u8; 64]; + let pad_size = 64usize + .checked_sub(D::OutputSize::to_usize()) + .unwrap_or_default(); + + bytes[pad_size..].copy_from_slice(&digest); + + Scalar::from_bytes_wide(&bytes) +} + +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, +{ + debug_assert_eq!(witnesses.len(), secrets.len()); + + witnesses + .iter() + .zip(secrets.iter()) + .map(|(w, x)| produce_response(w, challenge, x.borrow())) + .collect() +} diff --git a/common/nym_offline_divisible_ecash/src/proofs/proof_spend.rs b/common/nym_offline_divisible_ecash/src/proofs/proof_spend.rs new file mode 100644 index 0000000000..68d27595d4 --- /dev/null +++ b/common/nym_offline_divisible_ecash/src/proofs/proof_spend.rs @@ -0,0 +1,4 @@ +pub struct SpendInstance {} + +pub struct SpendWitness {} + diff --git a/common/nym_offline_divisible_ecash/src/proofs/proof_withdrawal.rs b/common/nym_offline_divisible_ecash/src/proofs/proof_withdrawal.rs new file mode 100644 index 0000000000..a7ebd52dae --- /dev/null +++ b/common/nym_offline_divisible_ecash/src/proofs/proof_withdrawal.rs @@ -0,0 +1,335 @@ +use std::convert::{TryFrom, TryInto}; + +use bls12_381::{G1Projective, Scalar}; +use group::GroupEncoding; +use itertools::izip; + +use crate::error::{DivisibleEcashError, Result}; +use crate::proofs::{ChallengeDigest, compute_challenge, produce_response, produce_responses}; +use crate::scheme::keygen::PublicKeyUser; +use crate::scheme::setup::{GroupParameters, Parameters}; +use crate::utils::try_deserialize_g1_projective; + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +// instance: g, gamma1, gamma2, gamma3, com, h, com1, com2, com3, pkUser +pub struct WithdrawalReqInstance { + // 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 = DivisibleEcashError; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() < 48 * 4 + 8 || (bytes.len() - 8) % 48 != 0 { + return Err(DivisibleEcashError::DeserializationInvalidLength { + actual: bytes.len(), + modulus_target: bytes.len() - 8, + target: 48 * 4 + 8, + modulus: 48, + object: "withdrawal request zkp instance".to_string(), + }); + } + let com_bytes: [u8; 48] = bytes[..48].try_into().unwrap(); + let com = try_deserialize_g1_projective( + &com_bytes, + DivisibleEcashError::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, + DivisibleEcashError::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(DivisibleEcashError::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, + DivisibleEcashError::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, + DivisibleEcashError::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, + // Opening for the joined commitment com + pub com_opening: Scalar, + // Openings for the pedersen commitments + pub pc_coms_openings: Vec, +} + +pub struct WithdrawalReqProof { + challenge: Scalar, + response_opening: Scalar, + response_openings: Vec, + response_attributes: Vec, +} + +impl WithdrawalReqProof { + pub(crate) fn construct( + params: &Parameters, + instance: &WithdrawalReqInstance, + witness: &WithdrawalReqWitness, + ) -> Self { + let grp = params.get_grp(); + let g1 = grp.gen1(); + let paramsU = params.get_paramsUser(); + + // generate random values to replace the witnesses + let r_com_opening = grp.random_scalar(); + let r_pedcom_openings = grp.n_random_scalars(witness.pc_coms_openings.len()); + let r_attributes = grp.n_random_scalars(witness.attributes.len()); + + // compute zkp commitments for each instance + let zkcm_com = g1 * r_com_opening + + r_attributes + .iter() + .zip(paramsU.get_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)| g1 * o_j + instance.h * m_j) + .collect::>(); + + let zkcm_user_sk = g1 * r_attributes[0]; + + // covert to bytes + let gammas_bytes = paramsU + .get_gammas() + .iter() + .map(|gamma| gamma.to_bytes()) + .collect::>(); + + let zkcm_pedcom_bytes = zkcm_pedcom + .iter() + .map(|cm| cm.to_bytes()) + .collect::>(); + + // compute zkp challenge using g1, gammas, c, h, c1, c2, c3, zk commitments + let challenge = compute_challenge::( + std::iter::once(g1.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_com_opening, &challenge, &witness.com_opening); + let response_openings = produce_responses( + &r_pedcom_openings, + &challenge, + &witness.pc_coms_openings.iter().collect::>(), + ); + let response_attributes = produce_responses( + &r_attributes, + &challenge, + &witness.attributes.iter().collect::>(), + ); + + WithdrawalReqProof { + challenge, + response_opening, + response_openings, + response_attributes, + } + } + + pub(crate) fn verify( + &self, + params: &Parameters, + instance: &WithdrawalReqInstance, + ) -> bool { + let grp = params.get_grp(); + let g1 = grp.gen1(); + let paramsU = params.get_paramsUser(); + + // recompute zk commitments for each instance + let zkcm_com = instance.com * self.challenge + + g1 * self.response_opening + + self + .response_attributes + .iter() + .zip(paramsU.get_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 + g1 * resp_o_j + instance.h * resp_m_j + }) + .collect::>(); + + let zk_commitment_user_sk = + instance.pk_user.pk * self.challenge + g1 * self.response_attributes[0]; + + // covert to bytes + let gammas_bytes = paramsU + .get_gammas() + .iter() + .map(|gamma| gamma.to_bytes()) + .collect::>(); + + let zkcm_pedcom_bytes = zkcm_pedcom + .iter() + .map(|cm| cm.to_bytes()) + .collect::>(); + + // recompute zkp challenge + let challenge = compute_challenge::( + std::iter::once(g1.to_bytes().as_ref()) + .chain(gammas_bytes.iter().map(|hs| hs.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())), + ); + + challenge == self.challenge + } +} + +#[cfg(test)] +mod tests { + use group::Group; + use rand::thread_rng; + + use crate::scheme::setup::Parameters; + use crate::utils::hash_g1; + + use super::*; + + #[test] + fn withdrawal_request_instance_roundtrip() { + let mut rng = thread_rng(); + let params = GroupParameters::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 rng = thread_rng(); + let grp = GroupParameters::new().unwrap(); + let params = Parameters::new(grp.clone()); + + + let sk = grp.random_scalar(); + let pk_user = PublicKeyUser { + pk: grp.gen1() * sk, + }; + let v = grp.random_scalar(); + let t = grp.random_scalar(); + let attr = vec![sk, v, t]; + + let com_opening = grp.random_scalar(); + let com = grp.gen1() * com_opening + + attr + .iter() + .zip(params.get_paramsUser().get_gammas()) + .map(|(&m, gamma)| gamma * m) + .sum::(); + let h = hash_g1(com.to_bytes()); + + let pc_openings = grp.n_random_scalars(attr.len()); + let pc_coms = pc_openings + .iter() + .zip(attr.iter()) + .map(|(o_j, m_j)| grp.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)) + } +} diff --git a/common/nym_offline_divisible_ecash/src/scheme/keygen.rs b/common/nym_offline_divisible_ecash/src/scheme/keygen.rs index c45058e449..ba0cc29011 100644 --- a/common/nym_offline_divisible_ecash/src/scheme/keygen.rs +++ b/common/nym_offline_divisible_ecash/src/scheme/keygen.rs @@ -365,7 +365,7 @@ impl KeyPairUser { pub fn ttp_keygen_authorities( - params: Parameters, + params: &Parameters, threshold: u64, num_authorities: u64) -> Result> { if threshold == 0 { @@ -381,10 +381,11 @@ pub fn ttp_keygen_authorities( )); } + let grp = params.get_grp(); // generate polynomials - let v = Polynomial::new_random(¶ms.get_grp(), threshold - 1); + let v = Polynomial::new_random(&grp, threshold - 1); let ws = (0..2) - .map(|_| Polynomial::new_random(¶ms.get_grp(), threshold - 1)) + .map(|_| Polynomial::new_random(&grp, threshold - 1)) .collect::>(); let polynomial_indices = (1..=num_authorities).collect::>(); @@ -404,7 +405,7 @@ pub fn ttp_keygen_authorities( let keypairs = secret_keys .zip(polynomial_indices.iter()) .map(|(secret_key, index)| { - let verification_key = secret_key.verification_key(¶ms.get_grp()); + let verification_key = secret_key.verification_key(&grp); KeyPairAuth { secret_key, verification_key, @@ -416,7 +417,7 @@ pub fn ttp_keygen_authorities( Ok(keypairs) } -pub fn ttp_keygen_users(params: Parameters) -> KeyPairUser { +pub fn ttp_keygen_users(params: &Parameters) -> KeyPairUser { let grp = params.get_grp(); let sk_user = SecretKeyUser { sk: grp.random_scalar() }; let pk_user = PublicKeyUser { pk: grp.gen1() * sk_user.sk }; diff --git a/common/nym_offline_divisible_ecash/src/scheme/mod.rs b/common/nym_offline_divisible_ecash/src/scheme/mod.rs index 8ad1c16839..5af94f915f 100644 --- a/common/nym_offline_divisible_ecash/src/scheme/mod.rs +++ b/common/nym_offline_divisible_ecash/src/scheme/mod.rs @@ -1,6 +1,119 @@ +use std::cell::Cell; + +use bls12_381::{G2Projective, Scalar}; + +use crate::Attribute; +use crate::constants::L; +use crate::error::{DivisibleEcashError, Result}; +use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth}; +use crate::scheme::setup::{GroupParameters, Parameters}; +use crate::utils::{hash_to_scalar, Signature, SignerIndex}; + pub mod aggregation; pub mod identify; pub mod keygen; pub mod setup; pub mod structure_preserving_signature; pub mod withdrawal; + +pub fn compute_kappa( + params: &GroupParameters, + verification_key: &VerificationKeyAuth, + attributes: &[Attribute], + blinding_factor: Scalar, +) -> G2Projective { + 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::() +} + +pub struct PayInfo { + pub info: [u8; 32], +} + +#[derive(Debug, Clone)] +pub struct Payment {} + +pub struct PartialWallet { + sig: Signature, + v: Scalar, + idx: Option, +} + +pub struct Wallet { + sig: Signature, + v: Scalar, + l: Cell, +} + +impl Wallet { + pub fn signature(&self) -> &Signature { + &self.sig + } + + pub fn v(&self) -> Scalar { + self.v + } + + pub fn l(&self) -> u64 { + self.l.get() + } + + fn up(&self) { + self.l.set(self.l.get() + 1); + } + + fn down(&self) { + self.l.set(self.l.get() - 1); + } + + pub(crate) fn spend( + &self, + params: &Parameters, + verification_key: &VerificationKeyAuth, + sk_user: &SecretKeyUser, + pay_info: &PayInfo, + V: u64, + ) -> Result<(Payment, &Self)> { + if self.l() + V > L { + return Err(DivisibleEcashError::Spend( + "The counter l is higher than max L".to_string(), + )); + } + + let grp = params.get_grp(); + let paramsU = params.get_paramsUser(); + // randomize signature in the wallet + let (signature_prime, sign_blinding_factor) = self.signature().randomise(grp); + // construct kappa i.e., blinded attributes for show + let attributes = vec![sk_user.sk, self.v()]; + // compute kappa + let kappa = compute_kappa( + &grp, + &verification_key, + &attributes, + sign_blinding_factor, + ); + + let r1 = grp.random_scalar(); + let r2 = grp.random_scalar(); + let phi1 = grp.gen1() * r1; + let phi2 = paramsU.get_ith_sigma(self.l.get() as usize) * self.v + paramsU.get_ith_eta(V as usize) * r1; + + // compute hash of the payment info + let rr = hash_to_scalar(pay_info.info); + let rho1 = grp.gen1() * r2; + let rho2 = (grp.gen1() * rr) * sk_user.sk + paramsU.get_ith_theta(self.l.get() as usize) * self.v + paramsU.get_ith_eta(V as usize) * r2; + + // compute the zk proof + + // output pay and updated wallet + let pay = Payment {}; + + Ok((pay, self)) + } +} \ No newline at end of file diff --git a/common/nym_offline_divisible_ecash/src/scheme/setup.rs b/common/nym_offline_divisible_ecash/src/scheme/setup.rs index 1711d1cc2f..c7ba8f4e7a 100644 --- a/common/nym_offline_divisible_ecash/src/scheme/setup.rs +++ b/common/nym_offline_divisible_ecash/src/scheme/setup.rs @@ -57,7 +57,7 @@ pub struct Parameters { } impl Parameters { - pub(crate) fn get_grp(&self) -> GroupParameters { self.grp.clone() } + pub(crate) fn get_grp(&self) -> &GroupParameters { &self.grp } pub(crate) fn get_paramsUser(&self) -> &ParametersUser { &self.paramsUser } @@ -92,7 +92,7 @@ impl Parameters { let etasUser: Vec = vec_a.iter().map(|x| g1 * x).collect(); let mut etasAuth: Vec = Default::default(); - for l in 1..=L { + for l in 1..=L + 1 { for k in 0..=l - 1 { etasAuth.push(g2 * (vec_a[l as usize].neg() * (y * Scalar::from(k)))); } @@ -109,10 +109,7 @@ impl Parameters { // Compute signature for each pair sigma, theta let paramsUser = ParametersUser { - g1: *g1, - g2: *g2, - gamma1, - gamma2, + gammas: vec![gamma1, gamma2], eta, omega, etas: etasUser, @@ -137,12 +134,7 @@ impl Parameters { #[derive(Debug, Clone)] pub struct ParametersUser { - /// Generator of the G1 group - g1: G1Affine, - /// Generator of the G2 group - g2: G2Affine, - gamma1: G1Projective, - gamma2: G1Projective, + gammas: Vec, eta: G1Projective, omega: G1Projective, etas: Vec, @@ -152,8 +144,40 @@ pub struct ParametersUser { sps_pk: SPSVerificationKey, } +impl ParametersUser { + pub(crate) fn get_gammas(&self) -> &Vec { &self.gammas } + + pub(crate) fn get_eta(&self) -> &G1Projective { &self.eta } + + pub(crate) fn get_omega(&self) -> &G1Projective { &self.omega } + + pub(crate) fn get_etas(&self) -> &[G1Projective] { &self.etas } + + pub(crate) fn get_ith_eta(&self, idx: usize) -> &G1Projective { self.etas.get(idx).unwrap() } + + pub(crate) fn get_sigmas(&self) -> &[G1Projective] { &self.sigmas } + + pub(crate) fn get_ith_sigma(&self, idx: usize) -> &G1Projective { self.sigmas.get(idx).unwrap() } + + pub(crate) fn get_thetas(&self) -> &[G1Projective] { &self.thetas } + + pub(crate) fn get_ith_theta(&self, idx: usize) -> &G1Projective { self.thetas.get(idx).unwrap() } + + pub(crate) fn get_sps_signs(&self) -> &[SPSSignature] { &self.sps_signatures } + + pub(crate) fn get_ith_sps_sign(&self, idx: usize) -> &SPSSignature { &self.sps_signatures.get(idx).unwrap() } + + pub(crate) fn get_sps_pk(&self) -> &SPSVerificationKey { &self.sps_pk } +} + #[derive(Debug, Clone)] pub struct ParametersAuthority { deltas: Vec, etas: Vec, } + +impl ParametersAuthority { + pub(crate) fn get_deltas(&self) -> &[G2Projective] { &self.deltas } + + pub(crate) fn get_etas(&self) -> &[G2Projective] { &self.etas } +} diff --git a/common/nym_offline_divisible_ecash/src/scheme/spend.rs b/common/nym_offline_divisible_ecash/src/scheme/spend.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/common/nym_offline_divisible_ecash/src/scheme/withdrawal.rs b/common/nym_offline_divisible_ecash/src/scheme/withdrawal.rs index 8b13789179..316f2c049c 100644 --- a/common/nym_offline_divisible_ecash/src/scheme/withdrawal.rs +++ b/common/nym_offline_divisible_ecash/src/scheme/withdrawal.rs @@ -1 +1,166 @@ +use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar}; +use group::{Curve, GroupEncoding}; +use crate::error::{DivisibleEcashError, Result}; +use crate::proofs::proof_withdrawal::{WithdrawalReqInstance, WithdrawalReqProof, WithdrawalReqWitness}; +use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, SecretKeyUser, VerificationKeyAuth}; +use crate::scheme::PartialWallet; +use crate::scheme::setup::{GroupParameters, Parameters}; +use crate::utils::{BlindedSignature, check_bilinear_pairing, hash_g1, Signature}; + +pub struct WithdrawalRequest { + com_hash: G1Projective, + com: G1Projective, + pc_coms: Vec, + zk_proof: WithdrawalReqProof, +} + +pub struct RequestInfo { + com_hash: G1Projective, + pc_coms_openings: Vec, + v: Scalar, +} + +pub fn withdrawal_request(params: Parameters, sk_user: SecretKeyUser) -> Result<(WithdrawalRequest, RequestInfo)> { + let grp = params.get_grp(); + let g1 = grp.gen1(); + let paramsU = params.get_paramsUser(); + let v = grp.random_scalar(); + let attributes = vec![sk_user.sk, v]; + let com_opening = grp.random_scalar(); + let commitment = g1 * com_opening + + attributes + .iter() + .zip(paramsU.get_gammas()) + .map(|(&m, gamma)| gamma * m) + .sum::(); + + // Value h in the paper + let com_hash = hash_g1(commitment.to_bytes()); + let pc_coms_openings = grp.n_random_scalars(attributes.len()); + // Compute Pedersen commitment for each attribute + let pc_coms = pc_coms_openings + .iter() + .zip(attributes.iter()) + .map(|(o_j, m_j)| g1 * o_j + com_hash * m_j) + .collect::>(); + + + // construct a zk proof of knowledge proving possession of m1, m2, o, o1, o2 + let instance = WithdrawalReqInstance { + com: commitment, + h: com_hash, + pc_coms: pc_coms.clone(), + pk_user: sk_user.public_key(¶ms.get_grp()), + }; + + let witness = WithdrawalReqWitness { + attributes, + com_opening, + pc_coms_openings: pc_coms_openings.clone(), + }; + + let zk_proof = WithdrawalReqProof::construct(¶ms, &instance, &witness); + + let req = WithdrawalRequest { + com_hash, + com: commitment, + pc_coms: pc_coms.clone(), + zk_proof, + }; + + let req_info = RequestInfo { + com_hash, + pc_coms_openings, + v, + }; + + Ok((req, req_info)) +} + +pub(crate) fn issue(params: &Parameters, req: WithdrawalRequest, pkU: PublicKeyUser, skA: SecretKeyAuth) -> Result { + let h = hash_g1(req.com.to_bytes()); + if !(h == req.com_hash) { + return Err(DivisibleEcashError::WithdrawalRequestVerification( + "Failed to verify the commitment hash".to_string(), + )); + } + + // verify zk proof + let instance = WithdrawalReqInstance { + com: req.com, + h: req.com_hash, + pc_coms: req.pc_coms.clone(), + pk_user: pkU, + }; + if !req.zk_proof.verify(¶ms, &instance) { + return Err(DivisibleEcashError::WithdrawalRequestVerification( + "Failed to verify the proof of knowledge".to_string(), + )); + } + + let sig = req + .pc_coms + .iter() + .zip(skA.ys.iter()) + .map(|(pc, yi)| pc * yi) + .chain(std::iter::once(h * skA.x)) + .sum(); + + Ok(BlindedSignature(h, sig)) +} + +pub(crate) fn issue_verify( + params: &GroupParameters, + 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 response from the authority + if !(req_info.com_hash == h) { + return Err(DivisibleEcashError::IssuanceVfy( + "Integrity verification failed".to_string(), + )); + } + + // Unblind the blinded signature on the partial wallet + 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; + + 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::(); + + // Verify the signature correctness on the wallet share + 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(DivisibleEcashError::IssuanceVfy( + "Verification of wallet share failed".to_string(), + )); + } + + Ok(PartialWallet { + sig: Signature(h, unblinded_c), + v: req_info.v, + idx: None, + }) +} \ No newline at end of file