From f623bfed4c4aa70bd7a5cd9bd4655e7143294440 Mon Sep 17 00:00:00 2001 From: aniampio Date: Mon, 7 Aug 2023 11:47:38 +0100 Subject: [PATCH] Add Wallet, Partial Wallet and Payment to and from bytes converstion --- .../src/proofs/proof_spend.rs | 215 +++++++++++++- .../src/proofs/proof_withdrawal.rs | 84 +++++- .../src/scheme/mod.rs | 272 +++++++++++++++++- .../src/scheme/withdrawal.rs | 93 +++++- .../src/tests/e2e.rs | 21 +- common/nym_offline_compact_ecash/src/utils.rs | 4 +- 6 files changed, 679 insertions(+), 10 deletions(-) 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 2674658c39..a130efacb2 100644 --- a/common/nym_offline_compact_ecash/src/proofs/proof_spend.rs +++ b/common/nym_offline_compact_ecash/src/proofs/proof_spend.rs @@ -7,7 +7,7 @@ use crate::error::{CompactEcashError, Result}; use crate::proofs::{ChallengeDigest, compute_challenge, produce_response, produce_responses}; use crate::scheme::keygen::VerificationKeyAuth; use crate::scheme::setup::Parameters; -use crate::utils::{try_deserialize_g1_projective, try_deserialize_g2_projective}; +use crate::utils::{try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar_vec, try_deserialize_scalar}; #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] @@ -207,7 +207,7 @@ pub struct SpendWitness { pub r_k: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct SpendProof { challenge: Scalar, response_r: Scalar, @@ -353,6 +353,7 @@ impl SpendProof { response_attributes, } } + pub fn verify( &self, params: &Parameters, @@ -464,6 +465,210 @@ impl SpendProof { challenge == self.challenge } + + pub fn to_bytes(&self) -> Vec { + let challenge_bytes = self.challenge.to_bytes(); + let response_r_bytes = self.response_r.to_bytes(); + + let rrl_len = self.response_r_l.len(); + let rrl_len_bytes = rrl_len.to_le_bytes(); + + let rl_len = self.response_l.len(); + let rl_len_bytes = rl_len.to_le_bytes(); + + let roa_len = self.response_o_a.len(); + let roa_len_bytes = roa_len.to_le_bytes(); + + let roc_bytes = self.response_o_c.to_bytes(); + + let rmu_len = self.response_mu.len(); + let rmu_len_bytes = rmu_len.to_le_bytes(); + + let romu_len = self.response_o_mu.len(); + let romu_len_bytes = romu_len.to_le_bytes(); + + let rattributes_len = self.response_attributes.len(); + let rattributes_len_bytes = rattributes_len.to_le_bytes(); + + let mut bytes: Vec = Vec::with_capacity( + 96 + (rrl_len + rl_len + roa_len + rmu_len + romu_len + rattributes_len) * 8 + + (rrl_len + rl_len + roa_len + rmu_len + romu_len + rattributes_len) * 32); + + bytes.extend_from_slice(&challenge_bytes); + bytes.extend_from_slice(&response_r_bytes); + bytes.extend_from_slice(&roc_bytes); + + bytes.extend_from_slice(&rrl_len_bytes); + for rrl in &self.response_r_l { + bytes.extend_from_slice(&rrl.to_bytes()); + } + + bytes.extend_from_slice(&rl_len_bytes); + for rl in &self.response_l { + bytes.extend_from_slice(&rl.to_bytes()); + } + + bytes.extend_from_slice(&roa_len_bytes); + for roa in &self.response_o_a { + bytes.extend_from_slice(&roa.to_bytes()); + } + + bytes.extend_from_slice(&rmu_len_bytes); + for rmu in &self.response_mu { + bytes.extend_from_slice(&rmu.to_bytes()); + } + + bytes.extend_from_slice(&romu_len_bytes); + for romu in &self.response_o_mu { + bytes.extend_from_slice(&romu.to_bytes()); + } + + bytes.extend_from_slice(&rattributes_len_bytes); + for rattr in &self.response_attributes { + bytes.extend_from_slice(&rattr.to_bytes()); + } + + bytes + + } + +} + +impl TryFrom<&[u8]> for SpendProof { + type Error = CompactEcashError; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() < 336 || (bytes.len() - 96 - 48) % 32 != 0 { + return Err(CompactEcashError::Deserialization( + "tried to deserialize proof of spending with bytes of invalid length" + .to_string(), + )); + } + + let mut idx = 0; + let challenge_bytes = bytes[idx..idx + 32].try_into().unwrap(); + idx += 32; + let response_r_bytes = bytes[idx..idx + 32].try_into().unwrap(); + idx += 32; + let response_o_c_bytes = bytes[idx..idx + 32].try_into().unwrap(); + idx += 32; + + let challenge = try_deserialize_scalar( + &challenge_bytes, + CompactEcashError::Deserialization("Failed to deserialize challenge".to_string()), + )?; + + let response_r = try_deserialize_scalar( + &response_r_bytes, + CompactEcashError::Deserialization("Failed to deserialize response_r".to_string()), + )?; + + let response_o_c = try_deserialize_scalar( + &response_o_c_bytes, + CompactEcashError::Deserialization("Failed to deserialize response_o_c".to_string()), + )?; + + let rrl_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap()); + idx += 8; + if bytes[idx..].len() < rrl_len as usize * 32 { + return Err( + CompactEcashError::Deserialization( + "tried to deserialize response_r_l".to_string()), + ); + } + let rrl_end = idx + rrl_len as usize * 32; + let response_r_l = try_deserialize_scalar_vec( + rrl_len, + &bytes[idx..rrl_end], + CompactEcashError::Deserialization("Failed to deserialize response_r_l".to_string()), + )?; + + let rl_len = u64::from_le_bytes(bytes[rrl_end..rrl_end + 8].try_into().unwrap()); + let response_l_start = rrl_end + 8; + if bytes[response_l_start..].len() < rl_len as usize * 32 { + return Err(CompactEcashError::Deserialization( + "tried to deserialize response_l".to_string(), + )); + } + let rl_end = response_l_start + rl_len as usize * 32; + let response_l = try_deserialize_scalar_vec( + rl_len, + &bytes[response_l_start..rl_end], + CompactEcashError::Deserialization("Failed to deserialize response_l".to_string()), + )?; + + let roa_len = u64::from_le_bytes(bytes[rl_end..rl_end + 8].try_into().unwrap()); + let roa_end = rl_end + 8; + if bytes[roa_end..].len() < roa_len as usize * 32 { + return Err(CompactEcashError::Deserialization( + "tried to deserialize response_o_a".to_string(), + )); + } + let roa_end = roa_end + roa_len as usize * 32; + let response_o_a = try_deserialize_scalar_vec( + roa_len, + &bytes[rl_end + 8..roa_end], + CompactEcashError::Deserialization("Failed to deserialize response_o_a".to_string()), + )?; + + let response_mu_len = u64::from_le_bytes(bytes[roa_end..roa_end + 8].try_into().unwrap()); + let response_mu_end = roa_end + 8; + if bytes[response_mu_end..].len() < response_mu_len as usize * 32 { + return Err(CompactEcashError::Deserialization( + "tried to deserialize response_mu".to_string(), + )); + } + let response_mu_end = response_mu_end + response_mu_len as usize * 32; + let response_mu = try_deserialize_scalar_vec( + response_mu_len, + &bytes[roa_end + 8..response_mu_end], + CompactEcashError::Deserialization("Failed to deserialize response_mu".to_string()), + )?; + + let response_o_mu_len = u64::from_le_bytes(bytes[response_mu_end..response_mu_end + 8].try_into().unwrap()); + let response_o_mu_end = response_mu_end + 8; + if bytes[response_o_mu_end..].len() < response_o_mu_len as usize * 32 { + return Err(CompactEcashError::Deserialization( + "tried to deserialize response_o_mu".to_string(), + )); + } + let response_o_mu_end = response_o_mu_end + response_o_mu_len as usize * 32; + let response_o_mu = try_deserialize_scalar_vec( + response_o_mu_len, + &bytes[response_mu_end + 8..response_o_mu_end], + CompactEcashError::Deserialization("Failed to deserialize response_o_mu".to_string()), + )?; + + let response_attributes_len = u64::from_le_bytes(bytes[response_o_mu_end..response_o_mu_end + 8].try_into().unwrap()); + let response_attributes_end = response_o_mu_end + 8; + if bytes[response_attributes_end..].len() < response_attributes_len as usize * 32 { + return Err(CompactEcashError::Deserialization( + "tried to deserialize response_attributes".to_string(), + )); + } + let response_attributes_end = response_attributes_end + response_attributes_len as usize * 32; + let response_attributes = try_deserialize_scalar_vec( + response_attributes_len, + &bytes[response_o_mu_end + 8..response_attributes_end], + CompactEcashError::Deserialization("Failed to deserialize response_attributes".to_string()), + )?; + + + // Construct the SpendProof struct from the deserialized data + let spend_proof = SpendProof { + challenge, + response_r, + response_o_c, + response_r_l, + response_l, + response_o_a, + response_mu, + response_o_mu, + response_attributes, + }; + + Ok(spend_proof) + } } #[cfg(test)] @@ -563,6 +768,10 @@ mod tests { }; let zk_proof = SpendProof::construct(¶ms, &instance, &witness, &verification_key, &[rr]); - assert!(zk_proof.verify(¶ms, &instance, &verification_key, &[rr])) + assert!(zk_proof.verify(¶ms, &instance, &verification_key, &[rr])); + + let zk_proof_bytes = zk_proof.to_bytes(); + let zk_proof2 = SpendProof::try_from(zk_proof_bytes.as_slice()).unwrap(); + assert_eq!(zk_proof, zk_proof2); } } diff --git a/common/nym_offline_compact_ecash/src/proofs/proof_withdrawal.rs b/common/nym_offline_compact_ecash/src/proofs/proof_withdrawal.rs index 35c4da55a8..f2c9231e1f 100644 --- a/common/nym_offline_compact_ecash/src/proofs/proof_withdrawal.rs +++ b/common/nym_offline_compact_ecash/src/proofs/proof_withdrawal.rs @@ -8,7 +8,7 @@ use crate::error::{CompactEcashError, Result}; use crate::proofs::{ChallengeDigest, compute_challenge, produce_response, produce_responses}; use crate::scheme::keygen::PublicKeyUser; use crate::scheme::setup::GroupParameters; -use crate::utils::try_deserialize_g1_projective; +use crate::utils::{try_deserialize_g1_projective, try_deserialize_scalar_vec, try_deserialize_scalar}; #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] @@ -115,6 +115,7 @@ pub struct WithdrawalReqWitness { pub pc_coms_openings: Vec, } +#[derive(Debug, PartialEq)] pub struct WithdrawalReqProof { challenge: Scalar, response_opening: Scalar, @@ -244,8 +245,89 @@ impl WithdrawalReqProof { challenge == self.challenge } + + pub fn to_bytes(&self) -> Vec{ + let challenge_bytes = self.challenge.to_bytes(); + let response_opening_bytes = self.response_opening.to_bytes(); + let ro_len = self.response_openings.len() as u64; + let ra_len = self.response_attributes.len() as u64; + + let mut bytes = Vec::with_capacity(32 + 32 + 8 + ro_len as usize * 32 + 8 + ra_len as usize * 32); + bytes.extend_from_slice(&challenge_bytes); + bytes.extend_from_slice(&response_opening_bytes); + bytes.extend_from_slice(&ro_len.to_le_bytes()); + for ro in &self.response_openings { + bytes.extend_from_slice(&ro.to_bytes()); + } + bytes.extend_from_slice(&ra_len.to_le_bytes()); + for ra in &self.response_attributes { + bytes.extend_from_slice(&ra.to_bytes()); + } + bytes + } } +impl TryFrom<&[u8]> for WithdrawalReqProof { + type Error = CompactEcashError; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() < 32 + 32 + 16 + 32 + 32 || (bytes.len() - 16) % 32 != 0 { + return Err(CompactEcashError::Deserialization( + "tried to deserialize proof of withdrawal with bytes of invalid length" + .to_string(), + )); + } + + let mut idx = 0; + let challenge_bytes = bytes[idx..idx + 32].try_into().unwrap(); + idx += 32; + let response_opening_bytes = bytes[idx..idx + 32].try_into().unwrap(); + idx += 32; + + let challenge = try_deserialize_scalar( + &challenge_bytes, + CompactEcashError::Deserialization("Failed to deserialize challenge".to_string()), + )?; + + let response_opening = try_deserialize_scalar( + &response_opening_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize the response to the random".to_string(), + ), + )?; + + let ro_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap()); + idx += 8; + if bytes[idx..].len() < ro_len as usize * 32 + 8 { + return Err( + CompactEcashError::Deserialization( + "tried to deserialize response openings".to_string()), + ); + } + let ro_end = idx + ro_len as usize * 32; + let response_openings = try_deserialize_scalar_vec( + ro_len, + &bytes[idx..ro_end], + CompactEcashError::Deserialization("Failed to deserialize openings response".to_string()), + )?; + + let ra_len = u64::from_le_bytes(bytes[ro_end..ro_end + 8].try_into().unwrap()); + let response_attributes = try_deserialize_scalar_vec( + ra_len, + &bytes[ro_end + 8..], + CompactEcashError::Deserialization("Failed to deserialize attributes response".to_string()), + )?; + + Ok(WithdrawalReqProof{ + challenge, + response_opening, + response_openings, + response_attributes, + }) + } +} + + #[cfg(test)] mod tests { use group::Group; diff --git a/common/nym_offline_compact_ecash/src/scheme/mod.rs b/common/nym_offline_compact_ecash/src/scheme/mod.rs index bdda78e4c5..35a3161d17 100644 --- a/common/nym_offline_compact_ecash/src/scheme/mod.rs +++ b/common/nym_offline_compact_ecash/src/scheme/mod.rs @@ -10,6 +10,7 @@ use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth}; use crate::scheme::setup::{GroupParameters, Parameters}; use crate::utils::{ check_bilinear_pairing, hash_to_scalar, Signature, SignerIndex, + try_deserialize_g1_projective, try_deserialize_scalar, try_deserialize_g2_projective, }; pub mod aggregation; @@ -18,6 +19,7 @@ pub mod keygen; pub mod setup; pub mod withdrawal; +#[derive(Debug, Clone, PartialEq)] pub struct PartialWallet { sig: Signature, v: Scalar, @@ -34,8 +36,49 @@ impl PartialWallet { pub fn index(&self) -> Option { self.idx } + pub fn to_bytes(&self) -> [u8; 136]{ + let mut bytes = [0u8; 136]; + bytes[0..96].copy_from_slice(&self.sig.to_bytes()); + bytes[96..128].copy_from_slice(&self.v.to_bytes()); + // Check if idx is Some and copy its bytes if it exists + if let Some(idx) = &self.idx { + bytes[128..136].copy_from_slice(&idx.to_le_bytes()); + } + bytes + } } +impl TryFrom<&[u8]> for PartialWallet { + type Error = CompactEcashError; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() != 136 { + return Err(CompactEcashError::Deserialization(format!( + "PartialWallet should be exactly 136 bytes, got {}", + bytes.len() + ))); + } + + let sig_bytes: &[u8; 96] = &bytes[..96].try_into().expect("Slice size != 96"); + let v_bytes: &[u8; 32] = &bytes[96..128].try_into().expect("Slice size != 32"); + let idx_bytes: &[u8; 8] = &bytes[128..136].try_into().expect("Slice size != 8"); + + let sig = Signature::try_from(sig_bytes.as_slice()).unwrap(); + let v = Scalar::from_bytes(&v_bytes).unwrap(); + let idx = None; + if !idx_bytes.iter().all(|&x| x == 0){ + let idx = Some(u64::from_le_bytes(*idx_bytes)); + } + + Ok(PartialWallet{ + sig, + v, + idx, + }) + } +} + +#[derive(Debug, Clone, PartialEq)] pub struct Wallet { sig: Signature, v: Scalar, @@ -46,13 +89,22 @@ impl Wallet { pub fn signature(&self) -> &Signature { &self.sig } + pub fn v(&self) -> Scalar { self.v } + pub fn l(&self) -> u64 { self.l.get() } + pub fn to_bytes(&self) -> [u8; 136]{ + let mut bytes = [0u8; 136]; + bytes[0..96].copy_from_slice(&self.sig.to_bytes()); + bytes[96..128].copy_from_slice(&self.v.to_bytes()); + bytes[128..136].copy_from_slice(&self.l.get().to_le_bytes()); + bytes + } fn up(&self) { self.l.set(self.l.get() + 1); } @@ -203,6 +255,33 @@ impl Wallet { } } +impl TryFrom<&[u8]> for Wallet { + type Error = CompactEcashError; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() != 136 { + return Err(CompactEcashError::Deserialization(format!( + "Wallet should be exactly 136 bytes, got {}", + bytes.len() + ))); + } + + let sig_bytes: &[u8; 96] = &bytes[..96].try_into().expect("Slice size != 96"); + let v_bytes: &[u8; 32] = &bytes[96..128].try_into().expect("Slice size != 32"); + let l_bytes: &[u8; 8] = &bytes[128..136].try_into().expect("Slice size != 8"); + + let sig = Signature::try_from(sig_bytes.as_slice()).unwrap(); + let v = Scalar::from_bytes(&v_bytes).unwrap(); + let l = Cell::new(u64::from_le_bytes(*l_bytes)); + + Ok(Wallet{ + sig, + v, + l + }) + } +} + pub fn pseudorandom_f_delta_v(params: &GroupParameters, v: Scalar, l: u64) -> G1Projective { let pow = (v + Scalar::from(l) + Scalar::from(1)).invert().unwrap(); params.delta() * pow @@ -233,7 +312,7 @@ pub struct PayInfo { pub info: [u8; 32], } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Payment { pub kappa: G2Projective, pub sig: Signature, @@ -320,4 +399,195 @@ impl Payment { Ok(true) } + + pub fn to_bytes(&self) -> Vec{ + let kappa_bytes = self.kappa.to_affine().to_compressed(); + let sig_bytes = self.sig.to_bytes(); + let cc_bytes = self.cc.to_affine().to_compressed(); + let vv_bytes: [u8; 8] = self.vv.to_le_bytes(); + let ss_len = self.ss.len() as u64; + let tt_len = self.tt.len() as u64; + let aa_len = self.aa.len() as u64; + let rr_len = self.rr.len() as u64; + let kappa_k_len = self.kappa_k.len() as u64; + let sig_lk_len = self.sig_lk.len() as u64; + let zk_proof_bytes = self.zk_proof.to_bytes(); + let zk_proof_bytes_len = self.zk_proof.to_bytes().len() as u64; + + let mut bytes: Vec = Vec::with_capacity( + (96 + 96 + 48 + 8 + ss_len * 48 + 8 + tt_len * 48 + 8 + aa_len * 48 + 8 + rr_len * 32 + 8 + kappa_k_len * 96 + 8 + sig_lk_len * 96 + zk_proof_bytes_len) as usize); + + + bytes.extend_from_slice(&kappa_bytes); + bytes.extend_from_slice(&sig_bytes); + bytes.extend_from_slice(&cc_bytes); + bytes.extend_from_slice(&vv_bytes); + + + let ss_len_bytes = ss_len.to_le_bytes(); + bytes.extend_from_slice(&ss_len_bytes); + for s in &self.ss { + bytes.extend_from_slice(&s.to_affine().to_compressed()); + } + + let tt_len_bytes = tt_len.to_le_bytes(); + bytes.extend_from_slice(&tt_len_bytes); + for t in &self.tt { + bytes.extend_from_slice(&t.to_affine().to_compressed()); + } + + let aa_len_bytes = aa_len.to_le_bytes(); + bytes.extend_from_slice(&aa_len_bytes); + for a in &self.aa { + bytes.extend_from_slice(&a.to_affine().to_compressed()); + } + + let rr_len_bytes = rr_len.to_le_bytes(); + bytes.extend_from_slice(&rr_len_bytes); + for r in &self.rr { + bytes.extend_from_slice(&r.to_bytes()); + } + + let kappa_k_len_bytes = kappa_k_len.to_le_bytes(); + bytes.extend_from_slice(&kappa_k_len_bytes); + for kk in &self.kappa_k { + bytes.extend_from_slice(&kk.to_affine().to_compressed()); + } + + let sig_lk_len_bytes = sig_lk_len.to_le_bytes(); + bytes.extend_from_slice(&sig_lk_len_bytes); + for sig in &self.sig_lk { + bytes.extend_from_slice(&sig.to_bytes()); + } + + bytes.extend_from_slice(&zk_proof_bytes); + bytes + } } + +impl TryFrom<&[u8]> for Payment { + type Error = CompactEcashError; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() < 656 { + return Err(CompactEcashError::Deserialization( + "Invalid byte array for Payment deserialization".to_string(), + )); + } + + let kappa_bytes: [u8; 96] = bytes[..96].try_into().unwrap(); + let sig_bytes: [u8; 96] = bytes[96..192].try_into().unwrap(); + let cc_bytes: [u8; 48] = bytes[192..240].try_into().unwrap(); + let vv_bytes: [u8; 8] = bytes[240..248].try_into().unwrap(); + let ss_len = u64::from_le_bytes(bytes[248..256].try_into().unwrap()) as usize; + + // Convert the byte arrays back into their respective types + let kappa = try_deserialize_g2_projective( + &kappa_bytes, + CompactEcashError::Deserialization("Failed to deserialize kappa".to_string()), + )?; + let sig = Signature::try_from(sig_bytes.as_slice())?; + + let cc = try_deserialize_g1_projective( + &cc_bytes, + CompactEcashError::Deserialization("Failed to deserialize cc".to_string()), + )?; + let vv = u64::from_le_bytes(vv_bytes); + + let mut idx = 256; + let mut ss = Vec::with_capacity(ss_len); + for _ in 0..ss_len { + let ss_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap(); + let ss_elem = try_deserialize_g1_projective( + &ss_bytes, + CompactEcashError::Deserialization("Failed to deserialize ss element".to_string()), + )?; + ss.push(ss_elem); + idx += 48; + } + + let tt_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize; + idx += 8; + let mut tt = Vec::with_capacity(tt_len); + for _ in 0..tt_len { + let tt_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap(); + let tt_elem = try_deserialize_g1_projective( + &tt_bytes, + CompactEcashError::Deserialization("Failed to deserialize tt element".to_string()), + )?; + tt.push(tt_elem); + idx += 48; + } + + let aa_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize; + idx += 8; + let mut aa = Vec::with_capacity(aa_len); + for _ in 0..aa_len { + let aa_bytes: [u8; 48] = bytes[idx..idx + 48].try_into().unwrap(); + let aa_elem = try_deserialize_g1_projective( + &aa_bytes, + CompactEcashError::Deserialization("Failed to deserialize aa element".to_string()), + )?; + aa.push(aa_elem); + idx += 48; + } + + let rr_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize; + idx += 8; + let mut rr = Vec::with_capacity(rr_len); + for _ in 0..rr_len { + let rr_bytes: [u8; 32] = bytes[idx..idx + 32].try_into().unwrap(); + let rr_elem = try_deserialize_scalar( + &rr_bytes, + CompactEcashError::Deserialization("Failed to deserialize rr element".to_string()), + )?; + rr.push(rr_elem); + idx += 32; + } + + let kappa_k_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize; + idx += 8; + let mut kappa_k = Vec::with_capacity(kappa_k_len); + for _ in 0..kappa_k_len { + let kappa_k_bytes: [u8; 96] = bytes[idx..idx + 96].try_into().unwrap(); + let kappa_k_elem = try_deserialize_g2_projective( + &kappa_k_bytes, + CompactEcashError::Deserialization("Failed to deserialize kappa_k element".to_string()), + )?; + kappa_k.push(kappa_k_elem); + idx += 96; + } + + // sig_lk + let sig_lk_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize; + idx += 8; + let mut sig_lk = Vec::with_capacity(sig_lk_len); + for _ in 0..sig_lk_len { + let sig_lk_bytes: [u8; 96] = bytes[idx..idx + 96].try_into().unwrap(); + let sig_lk_elem = Signature::try_from(sig_lk_bytes.as_slice())?; + sig_lk.push(sig_lk_elem); + idx += 96; + } + + // Deserialize the SpendProof struct + let zk_proof_bytes = &bytes[idx..]; + let zk_proof = SpendProof::try_from(zk_proof_bytes)?; + + // Construct the Payment struct from the deserialized data + let payment = Payment { + kappa, + sig, + ss, + tt, + aa, + rr, + kappa_k, + sig_lk, + cc, + zk_proof, + vv, + }; + + Ok(payment) + } +} \ No newline at end of file diff --git a/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs b/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs index 4f8411fb63..70bb891cd8 100644 --- a/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs +++ b/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs @@ -8,9 +8,10 @@ use crate::proofs::proof_withdrawal::{ use crate::scheme::keygen::{PublicKeyUser, SecretKeyAuth, SecretKeyUser, VerificationKeyAuth}; use crate::scheme::PartialWallet; use crate::scheme::setup::GroupParameters; -use crate::utils::{check_bilinear_pairing, hash_g1}; +use crate::utils::{check_bilinear_pairing, hash_g1, try_deserialize_g1_projective}; use crate::utils::{BlindedSignature, Signature}; +#[derive(Debug, PartialEq)] pub struct WithdrawalRequest { com_hash: G1Projective, com: G1Projective, @@ -18,6 +19,96 @@ pub struct WithdrawalRequest { zk_proof: WithdrawalReqProof, } +impl WithdrawalRequest { + pub fn to_bytes(&self) -> Vec{ + let com_hash_bytes = self.com_hash.to_affine().to_compressed(); + let com_bytes = self.com.to_affine().to_compressed(); + let pr_coms_len = self.pc_coms.len() as u64; + let zk_proof_bytes = self.zk_proof.to_bytes(); + + let mut bytes = Vec::with_capacity(48 + 48 + 8 + pr_coms_len as usize * 48 + zk_proof_bytes.len()); + bytes.extend_from_slice(&com_hash_bytes); + bytes.extend_from_slice(&com_bytes); + bytes.extend_from_slice(&pr_coms_len.to_le_bytes()); + for c in &self.pc_coms { + bytes.extend_from_slice(&c.to_affine().to_compressed()); + } + + bytes.extend_from_slice(&zk_proof_bytes); + + bytes + + } +} + +impl TryFrom<&[u8]> for WithdrawalRequest { + type Error = CompactEcashError; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() < 48 + 48 + 8 + 48{ + return Err(CompactEcashError::DeserializationMinLength { + min: 48 + 48 + 8 + 48, + actual: bytes.len(), + }); + } + + let mut j = 0; + let commitment_hash_bytes_len = 48; + let commitment_bytes_len = 48; + + let com_hash_bytes = bytes[..j + commitment_hash_bytes_len].try_into().unwrap(); + let com_hash = try_deserialize_g1_projective( + &com_hash_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize compressed commitment hash".to_string(), + ), + )?; + j += commitment_hash_bytes_len; + + let com_bytes = bytes[j..j + commitment_bytes_len].try_into().unwrap(); + let com = try_deserialize_g1_projective( + &com_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize compressed commitment".to_string(), + ), + )?; + j += commitment_bytes_len; + + let pc_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap()); + j += 8; + if bytes[j..].len() < pc_len as usize * 48 { + return Err(CompactEcashError::DeserializationMinLength { + min: pc_len as usize * 48, + actual: bytes[56..].len(), + }); + } + let mut pc_coms = Vec::with_capacity(pc_len as usize); + for i in 0..pc_len as usize { + let start = j + i * 48; + let end = start + 48; + + let pc_com_bytes = bytes[start..end].try_into().unwrap(); + let pc_com = try_deserialize_g1_projective( + &pc_com_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize compressed Pedersen commitment".to_string(), + ), + )?; + + pc_coms.push(pc_com) + } + + let zk_proof = WithdrawalReqProof::try_from(&bytes[j + pc_len as usize * 48..])?; + + Ok(WithdrawalRequest{ + com_hash, + com, + pc_coms, + zk_proof, + }) + } +} + pub struct RequestInfo { com_hash: G1Projective, com_opening: Scalar, diff --git a/common/nym_offline_compact_ecash/src/tests/e2e.rs b/common/nym_offline_compact_ecash/src/tests/e2e.rs index 9469e3b5d5..c3079dbafb 100644 --- a/common/nym_offline_compact_ecash/src/tests/e2e.rs +++ b/common/nym_offline_compact_ecash/src/tests/e2e.rs @@ -7,10 +7,10 @@ use crate::scheme::aggregation::{ use crate::scheme::keygen::{ generate_keypair_user, ttp_keygen, VerificationKeyAuth, }; -use crate::scheme::PartialWallet; +use crate::scheme::{Wallet, PartialWallet, Payment}; use crate::scheme::PayInfo; use crate::scheme::setup::setup; -use crate::scheme::withdrawal::{issue_verify, issue_wallet, withdrawal_request}; +use crate::scheme::withdrawal::{issue_verify, issue_wallet, withdrawal_request, WithdrawalRequest}; #[test] fn main() -> Result<(), CompactEcashError> { @@ -20,6 +20,10 @@ fn main() -> Result<(), CompactEcashError> { let user_keypair = generate_keypair_user(&grparams); let (req, req_info) = withdrawal_request(grparams, &user_keypair.secret_key()).unwrap(); + let req_bytes = req.to_bytes(); + let req2 = WithdrawalRequest::try_from(req_bytes.as_slice()).unwrap(); + assert_eq!(req, req2); + let authorities_keypairs = ttp_keygen(&grparams, 2, 3).unwrap(); let verification_keys_auth: Vec = authorities_keypairs @@ -47,6 +51,11 @@ fn main() -> Result<(), CompactEcashError> { .map(|(w, vk)| issue_verify(&grparams, vk, &user_keypair.secret_key(), w, &req_info).unwrap()) .collect(); + let partial_wallet = unblinded_wallet_shares.get(0).unwrap().clone(); + let partial_wallet_bytes = partial_wallet.to_bytes(); + let partial_wallet2 = PartialWallet::try_from(&partial_wallet_bytes[..]).unwrap(); + assert_eq!(partial_wallet, partial_wallet2); + // Aggregate partial wallets let aggr_wallet = aggregate_wallets( &grparams, @@ -56,6 +65,10 @@ fn main() -> Result<(), CompactEcashError> { &req_info, )?; + let wallet_bytes = aggr_wallet.to_bytes(); + let wallet = Wallet::try_from(&wallet_bytes[..]).unwrap(); + assert_eq!(aggr_wallet, wallet); + // Let's try to spend some coins let pay_info = PayInfo { info: [6u8; 32] }; let spend_vv = 1; @@ -73,6 +86,10 @@ fn main() -> Result<(), CompactEcashError> { .spend_verify(¶ms, &verification_key, &pay_info) .unwrap()); + let payment_bytes = payment.to_bytes(); + println!("{:?}", payment_bytes.len()); + let payment2 = Payment::try_from(&payment_bytes[..]).unwrap(); + assert_eq!(payment, payment2); Ok(()) } diff --git a/common/nym_offline_compact_ecash/src/utils.rs b/common/nym_offline_compact_ecash/src/utils.rs index c8bfa95eff..45f3588599 100644 --- a/common/nym_offline_compact_ecash/src/utils.rs +++ b/common/nym_offline_compact_ecash/src/utils.rs @@ -199,8 +199,8 @@ pub fn check_bilinear_pairing(p: &G1Affine, q: &G2Prepared, r: &G1Affine, s: &G2 pub type SignerIndex = u64; -#[derive(Debug, Clone, Copy)] -#[cfg_attr(test, derive(PartialEq))] +#[derive(Debug, Clone, Copy, PartialEq)] +// #[cfg_attr(test, derive(PartialEq))] pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective); pub type PartialSignature = Signature;