diff --git a/common/nym_offline_compact_ecash/src/scheme/mod.rs b/common/nym_offline_compact_ecash/src/scheme/mod.rs index 35a3161d17..87bed21716 100644 --- a/common/nym_offline_compact_ecash/src/scheme/mod.rs +++ b/common/nym_offline_compact_ecash/src/scheme/mod.rs @@ -400,7 +400,7 @@ impl Payment { Ok(true) } - pub fn to_bytes(&self) -> Vec{ + 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(); diff --git a/common/nym_offline_compact_ecash/src/tests/e2e.rs b/common/nym_offline_compact_ecash/src/tests/e2e.rs index c3079dbafb..2a6f3bc45c 100644 --- a/common/nym_offline_compact_ecash/src/tests/e2e.rs +++ b/common/nym_offline_compact_ecash/src/tests/e2e.rs @@ -87,7 +87,6 @@ fn main() -> Result<(), CompactEcashError> { .unwrap()); let payment_bytes = payment.to_bytes(); - println!("{:?}", payment_bytes.len()); let payment2 = Payment::try_from(&payment_bytes[..]).unwrap(); assert_eq!(payment, payment2); diff --git a/common/nym_offline_divisible_ecash/src/proofs/proof_spend.rs b/common/nym_offline_divisible_ecash/src/proofs/proof_spend.rs index b619d58174..8059276e87 100644 --- a/common/nym_offline_divisible_ecash/src/proofs/proof_spend.rs +++ b/common/nym_offline_divisible_ecash/src/proofs/proof_spend.rs @@ -8,6 +8,8 @@ use crate::proofs::{ChallengeDigest, compute_challenge, produce_response, produc use crate::scheme::{Phi, VarPhi, Wallet}; use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth}; use crate::scheme::setup::Parameters; +use crate::error::{DivisibleEcashError, Result}; +use crate::utils::{try_deserialize_scalar}; pub struct SpendInstance { pub kappa: G2Projective, @@ -84,7 +86,7 @@ pub struct SpendWitness { pub rho3: Scalar, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct SpendProof { challenge: Scalar, response_r: Scalar, @@ -106,6 +108,29 @@ pub struct SpendProof { } impl SpendProof { + pub fn to_bytes(&self) -> Vec { + let mut bytes: Vec = Vec::with_capacity(32 * 17); // 17 fields, each 32 bytes + + bytes.extend_from_slice(&self.challenge.to_bytes()); + bytes.extend_from_slice(&self.response_r.to_bytes()); + bytes.extend_from_slice(&self.response_r_sk_u.to_bytes()); + bytes.extend_from_slice(&self.response_r_v.to_bytes()); + bytes.extend_from_slice(&self.response_r_r.to_bytes()); + bytes.extend_from_slice(&self.response_r_r1.to_bytes()); + bytes.extend_from_slice(&self.response_r_r2.to_bytes()); + bytes.extend_from_slice(&self.response_r_varsig1.to_bytes()); + bytes.extend_from_slice(&self.response_r_theta1.to_bytes()); + bytes.extend_from_slice(&self.response_r_varsig2.to_bytes()); + bytes.extend_from_slice(&self.response_r_theta2.to_bytes()); + bytes.extend_from_slice(&self.response_r_rr.to_bytes()); + bytes.extend_from_slice(&self.response_r_ss.to_bytes()); + bytes.extend_from_slice(&self.response_r_tt.to_bytes()); + bytes.extend_from_slice(&self.response_r_rho1.to_bytes()); + bytes.extend_from_slice(&self.response_r_rho2.to_bytes()); + bytes.extend_from_slice(&self.response_r_rho3.to_bytes()); + + bytes + } pub fn construct( params: &Parameters, instance: &SpendInstance, @@ -279,6 +304,142 @@ impl SpendProof { } } +impl TryFrom<&[u8]> for SpendProof { + type Error = DivisibleEcashError; + + fn try_from(bytes: &[u8]) -> Result { + const FIELD_SIZE: usize = 32; // Each Scalar field is 32 bytes + + if bytes.len() != FIELD_SIZE * 17 { + return Err(DivisibleEcashError::Deserialization( + "Invalid byte array for SpendProof deserialization".to_string(), + )); + } + + let challenge_bytes = bytes[0..FIELD_SIZE].try_into().unwrap(); + let challenge = try_deserialize_scalar( + &challenge_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize challenge".to_string()), + )?; + + let response_r_bytes = bytes[FIELD_SIZE..FIELD_SIZE * 2].try_into().unwrap(); + let response_r = try_deserialize_scalar( + &response_r_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r".to_string()), + )?; + + let response_r_sk_u_bytes = bytes[FIELD_SIZE * 2..FIELD_SIZE * 3].try_into().unwrap(); + let response_r_sk_u = try_deserialize_scalar( + &response_r_sk_u_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r_sk_u".to_string()), + )?; + + let response_r_v_bytes = bytes[FIELD_SIZE * 3..FIELD_SIZE * 4].try_into().unwrap(); + let response_r_v = try_deserialize_scalar( + &response_r_v_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r_v".to_string()), + )?; + + let response_r_r_bytes = bytes[FIELD_SIZE * 4..FIELD_SIZE * 5].try_into().unwrap(); + let response_r_r = try_deserialize_scalar( + &response_r_r_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r_r".to_string()), + )?; + + let response_r_r1_bytes = bytes[FIELD_SIZE * 5..FIELD_SIZE * 6].try_into().unwrap(); + let response_r_r1 = try_deserialize_scalar( + &response_r_r1_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r_r1".to_string()), + )?; + + let response_r_r2_bytes = bytes[FIELD_SIZE * 6..FIELD_SIZE * 7].try_into().unwrap(); + let response_r_r2 = try_deserialize_scalar( + &response_r_r2_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r_r2".to_string()), + )?; + + let response_r_varsig1_bytes = bytes[FIELD_SIZE * 7..FIELD_SIZE * 8].try_into().unwrap(); + let response_r_varsig1 = try_deserialize_scalar( + response_r_varsig1_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r_varsig1".to_string()), + )?; + + let response_r_theta1_bytes = bytes[FIELD_SIZE * 8..FIELD_SIZE * 9].try_into().unwrap(); + let response_r_theta1 = try_deserialize_scalar( + &response_r_theta1_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r_theta1".to_string()), + )?; + + let response_r_varsig2_bytes = bytes[FIELD_SIZE * 9..FIELD_SIZE * 10].try_into().unwrap(); + let response_r_varsig2 = try_deserialize_scalar( + &response_r_varsig2_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r_varsig2".to_string()), + )?; + + let response_r_theta2_bytes = bytes[FIELD_SIZE * 10..FIELD_SIZE * 11].try_into().unwrap(); + let response_r_theta2 = try_deserialize_scalar( + &response_r_theta2_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r_theta2".to_string()), + )?; + + let response_r_rr_bytes = bytes[FIELD_SIZE * 11..FIELD_SIZE * 12].try_into().unwrap(); + let response_r_rr = try_deserialize_scalar( + &response_r_rr_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r_rr".to_string()), + )?; + + let response_r_ss_bytes = bytes[FIELD_SIZE * 12..FIELD_SIZE * 13].try_into().unwrap(); + let response_r_ss = try_deserialize_scalar( + &response_r_ss_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r_ss".to_string()), + )?; + + let response_r_tt_bytes = bytes[FIELD_SIZE * 13..FIELD_SIZE * 14].try_into().unwrap(); + let response_r_tt = try_deserialize_scalar( + &response_r_tt_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r_tt".to_string()), + )?; + + let response_r_rho1_bytes = bytes[FIELD_SIZE * 14..FIELD_SIZE * 15].try_into().unwrap(); + let response_r_rho1 = try_deserialize_scalar( + &response_r_rho1_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r_rho1".to_string()), + )?; + + let response_r_rho2_bytes = bytes[FIELD_SIZE * 15..FIELD_SIZE * 16].try_into().unwrap(); + let response_r_rho2 = try_deserialize_scalar( + &response_r_rho2_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r_rho2".to_string()), + )?; + + let response_r_rho3_bytes = bytes[FIELD_SIZE * 16..FIELD_SIZE * 17].try_into().unwrap(); + let response_r_rho3 = try_deserialize_scalar( + &response_r_rho3_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize response_r_rho3".to_string()), + )?; + + Ok(SpendProof { + challenge, + response_r, + response_r_sk_u, + response_r_v, + response_r_r, + response_r_r1, + response_r_r2, + response_r_varsig1, + response_r_theta1, + response_r_varsig2, + response_r_theta2, + response_r_rr, + response_r_ss, + response_r_tt, + response_r_rho1, + response_r_rho2, + response_r_rho3, + }) + } +} + #[cfg(test)] mod tests { use std::ops::Neg; @@ -439,7 +600,13 @@ mod tests { // compute the zk proof let zk_proof = SpendProof::construct(¶ms, &instance, &witness, &verification_key, vv); - assert!(zk_proof.verify(¶ms, &instance, &verification_key, vv)) + assert!(zk_proof.verify(¶ms, &instance, &verification_key, vv)); + + // do a to and from bytes check + let zk_proof_bytes = zk_proof.to_bytes(); + let zk_proof2 = SpendProof::try_from(&zk_proof_bytes[..]).unwrap(); + assert_eq!(zk_proof, zk_proof2); + } } diff --git a/common/nym_offline_divisible_ecash/src/scheme/mod.rs b/common/nym_offline_divisible_ecash/src/scheme/mod.rs index 62364b76f6..6562bb1341 100644 --- a/common/nym_offline_divisible_ecash/src/scheme/mod.rs +++ b/common/nym_offline_divisible_ecash/src/scheme/mod.rs @@ -11,7 +11,7 @@ use crate::error::{DivisibleEcashError, Result}; use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness}; use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth}; use crate::scheme::setup::{GroupParameters, Parameters}; -use crate::utils::{check_bilinear_pairing, hash_to_scalar, Signature, SignerIndex, try_deserialize_g1_projective}; +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; pub mod keygen; @@ -116,7 +116,7 @@ pub struct PayInfo { pub info: [u8; 32], } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Payment { pub kappa: G2Projective, pub sig: Signature, @@ -149,6 +149,152 @@ impl Payment { pub fn get_rr(&self) -> Scalar { self.rr } pub fn get_zk_proof(&self) -> SpendProof { self.zk_proof.clone() } pub fn get_vv(&self) -> u64 { self.vv } + pub fn to_bytes(&self) -> Vec{ + let kappa_bytes = self.kappa.to_affine().to_compressed(); + let sig_bytes = self.sig.to_bytes(); + let phi_bytes = self.phi.to_bytes(); + let varphi_bytes = self.varphi.to_bytes(); + let varsig_prime1_bytes = self.varsig_prime1.to_affine().to_compressed(); + let varsig_prime2_bytes = self.varsig_prime2.to_affine().to_compressed(); + let theta_prime1_bytes = self.theta_prime1.to_affine().to_compressed(); + let theta_prime2 = self.theta_prime2.to_affine().to_compressed(); + let rr_prime_bytes = self.rr_prime.to_affine().to_compressed(); + let ss_prime_bytes = self.ss_prime.to_affine().to_compressed(); + let tt_prime_bytes = self.tt_prime.to_affine().to_compressed(); + let rr_bytes = self.rr.to_bytes(); + let zk_proof_bytes = self.zk_proof.to_bytes(); + let vv_bytes = self.vv.to_le_bytes(); + + let mut bytes: Vec = Vec::with_capacity(760); + bytes.extend_from_slice(&kappa_bytes); + bytes.extend_from_slice(&sig_bytes); + bytes.extend_from_slice(&phi_bytes); + bytes.extend_from_slice(&varphi_bytes); + bytes.extend_from_slice(&varsig_prime1_bytes); + bytes.extend_from_slice(&varsig_prime2_bytes); + bytes.extend_from_slice(&theta_prime1_bytes); + bytes.extend_from_slice(&theta_prime2); + bytes.extend_from_slice(&rr_prime_bytes); + bytes.extend_from_slice(&ss_prime_bytes); + bytes.extend_from_slice(&tt_prime_bytes); + bytes.extend_from_slice(&rr_bytes); + bytes.extend_from_slice(&vv_bytes); + bytes.extend_from_slice(&zk_proof_bytes); + + bytes + } +} + +impl TryFrom<&[u8]> for Payment { + type Error = DivisibleEcashError; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() < 760 { + return Err(DivisibleEcashError::Deserialization( + "Invalid byte array for Payment deserialization".to_string(), + )); + } + + let mut idx = 0; + let kappa_bytes: [u8; 96] = bytes[idx..idx+96].try_into().unwrap(); + let kappa = try_deserialize_g2_projective( + &kappa_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize kappa".to_string()), + )?; + idx += 96; + + let sig_bytes: [u8; 96] = bytes[idx..idx+96].try_into().unwrap(); + let sig = Signature::try_from(sig_bytes.as_slice())?; + idx += 96; + + let phi_bytes: [u8; 96] = bytes[idx..idx+96].try_into().unwrap(); + let phi = Phi::from_bytes(&phi_bytes).unwrap(); + idx += 96; + + let varphi_bytes: [u8; 96] = bytes[idx..idx+96].try_into().unwrap(); + let varphi = VarPhi::from_bytes(&varphi_bytes).unwrap(); + idx += 96; + + let varsig_prime1_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap(); + let varsig_prime1 = try_deserialize_g1_projective( + &varsig_prime1_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize varsig_prime1".to_string()), + )?; + idx += 48; + + let varsig_prime2_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap(); + let varsig_prime2 = try_deserialize_g1_projective( + &varsig_prime2_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize varsig_prime2".to_string()), + )?; + idx += 48; + + let theta_prime1_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap(); + let theta_prime1 = try_deserialize_g1_projective( + &theta_prime1_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize theta_prime1".to_string()), + )?; + idx += 48; + + let theta_prime2_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap(); + let theta_prime2 = try_deserialize_g1_projective( + &theta_prime2_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize theta_prime2".to_string()), + )?; + idx += 48; + + let rr_prime_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap(); + let rr_prime = try_deserialize_g1_projective( + &rr_prime_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize rr_prime".to_string()), + )?; + idx += 48; + + let ss_prime_bytes: [u8; 48] = bytes[idx..idx+48].try_into().unwrap(); + let ss_prime = try_deserialize_g1_projective( + &ss_prime_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize ss_prime".to_string()), + )?; + idx += 48; + + let tt_prime_bytes: [u8; 96] = bytes[idx..idx+96].try_into().unwrap(); + let tt_prime = try_deserialize_g2_projective( + &tt_prime_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize tt_prime".to_string()), + )?; + idx += 96; + + let rr_bytes: [u8; 32] = bytes[idx..idx+32].try_into().unwrap(); + let rr = try_deserialize_scalar( + &rr_bytes, + DivisibleEcashError::Deserialization("Failed to deserialize rr element".to_string()), + )?; + idx += 32; + + let vv = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()); + idx += 8; + + // Deserialize the SpendProof struct + let zk_proof_bytes = &bytes[idx..]; + let zk_proof = SpendProof::try_from(zk_proof_bytes)?; + + Ok(Payment{ + kappa, + sig, + phi, + varphi, + varsig_prime1, + varsig_prime2, + theta_prime1, + theta_prime2, + rr_prime, + ss_prime, + tt_prime, + rr, + zk_proof, + vv, + }) + } } pub struct PartialWallet { diff --git a/common/nym_offline_divisible_ecash/src/tests/e2e.rs b/common/nym_offline_divisible_ecash/src/tests/e2e.rs index f3d0face34..65d14e86fe 100644 --- a/common/nym_offline_divisible_ecash/src/tests/e2e.rs +++ b/common/nym_offline_divisible_ecash/src/tests/e2e.rs @@ -55,6 +55,9 @@ fn main() -> Result<(), DivisibleEcashError> { // SPEND VERIFICATION assert!(payment.spend_verify(¶ms, &verification_key, &pay_info).unwrap()); + let payment_bytes = payment.to_bytes(); + let payment2 = Payment::try_from(&payment_bytes[..]).unwrap(); + assert_eq!(payment, payment2); Ok(()) } diff --git a/common/nym_offline_divisible_ecash/src/utils.rs b/common/nym_offline_divisible_ecash/src/utils.rs index 14d4066f8a..622de7eb53 100644 --- a/common/nym_offline_divisible_ecash/src/utils.rs +++ b/common/nym_offline_divisible_ecash/src/utils.rs @@ -206,8 +206,7 @@ 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)] pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective); pub type PartialSignature = Signature;