From dd323ce4932fee60a0f2846553056aa5e5c8f996 Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Wed, 18 Oct 2023 14:17:16 +0200 Subject: [PATCH] update bls12_381 dependency --- .../20231020120000_add_ecash_table.sql | 14 ++ .../credential-storage/src/backends/memory.rs | 32 +++- .../credential-storage/src/backends/sqlite.rs | 23 +++ .../src/ephemeral_storage.rs | 14 ++ common/credential-storage/src/models.rs | 11 ++ .../src/persistent_storage.rs | 14 ++ common/credential-storage/src/storage.rs | 16 ++ common/nym_offline_compact_ecash/Cargo.toml | 3 +- common/nym_offline_compact_ecash/src/error.rs | 9 +- .../src/impls/clone.rs | 14 ++ .../src/impls/mod.rs | 2 + .../src/impls/serde.rs | 47 ++++++ common/nym_offline_compact_ecash/src/lib.rs | 13 +- .../src/scheme/keygen.rs | 84 ++++++++++- .../src/scheme/mod.rs | 95 +++++++----- .../src/scheme/withdrawal.rs | 137 ++++++++++++++++-- common/nym_offline_compact_ecash/src/utils.rs | 57 +++++++- common/nym_offline_divisible_ecash/Cargo.toml | 2 +- 18 files changed, 512 insertions(+), 75 deletions(-) create mode 100644 common/credential-storage/migrations/20231020120000_add_ecash_table.sql create mode 100644 common/nym_offline_compact_ecash/src/impls/clone.rs create mode 100644 common/nym_offline_compact_ecash/src/impls/mod.rs create mode 100644 common/nym_offline_compact_ecash/src/impls/serde.rs diff --git a/common/credential-storage/migrations/20231020120000_add_ecash_table.sql b/common/credential-storage/migrations/20231020120000_add_ecash_table.sql new file mode 100644 index 0000000000..1b94abec73 --- /dev/null +++ b/common/credential-storage/migrations/20231020120000_add_ecash_table.sql @@ -0,0 +1,14 @@ +/* + * Copyright 2023 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + +CREATE TABLE ecash_credentials +( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + voucher_value TEXT NOT NULL, + voucher_info TEXT NOT NULL, + wallet TEXT NOT NULL UNIQUE, + epoch_id TEXT NOT NULL, + consumed BOOLEAN NOT NULL +); \ No newline at end of file diff --git a/common/credential-storage/src/backends/memory.rs b/common/credential-storage/src/backends/memory.rs index 2dbdff0925..9291f652de 100644 --- a/common/credential-storage/src/backends/memory.rs +++ b/common/credential-storage/src/backends/memory.rs @@ -1,13 +1,14 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::models::CoconutCredential; +use crate::models::{CoconutCredential, EcashCredential}; use std::sync::Arc; use tokio::sync::RwLock; #[derive(Clone)] pub struct CoconutCredentialManager { inner: Arc>>, + ecash: Arc>>, } impl CoconutCredentialManager { @@ -15,6 +16,7 @@ impl CoconutCredentialManager { pub fn new() -> Self { CoconutCredentialManager { inner: Arc::new(RwLock::new(Vec::new())), + ecash: Arc::new(RwLock::new(Vec::new())), } } @@ -50,6 +52,34 @@ impl CoconutCredentialManager { }); } + /// Inserts provided signature into the database. + /// + /// # Arguments + /// + /// * `voucher_value`: Plaintext bandwidth value of the credential. + /// * `voucher_info`: Plaintext information of the credential. + /// * `serial_number`: Base58 representation of the serial number attribute. + /// * `binding_number`: Base58 representation of the binding number attribute. + /// * `signature`: Coconut credential in the form of a signature. + pub async fn insert_ecash_credential( + &self, + voucher_value: String, + voucher_info: String, + wallet: String, + epoch_id: String, + ) { + let mut creds = self.ecash.write().await; + let id = creds.len() as i64; + creds.push(EcashCredential { + id, + voucher_value, + voucher_info, + wallet, + epoch_id, + consumed: false, + }); + } + /// Tries to retrieve one of the stored, unused credentials. pub async fn get_next_coconut_credential(&self) -> Option { let creds = self.inner.read().await; diff --git a/common/credential-storage/src/backends/sqlite.rs b/common/credential-storage/src/backends/sqlite.rs index f64ef811b0..0d9aff35a5 100644 --- a/common/credential-storage/src/backends/sqlite.rs +++ b/common/credential-storage/src/backends/sqlite.rs @@ -45,6 +45,29 @@ impl CoconutCredentialManager { Ok(()) } + /// Inserts provided signature into the database. + /// + /// # Arguments + /// + /// * `voucher_value`: Plaintext bandwidth value of the credential. + /// * `voucher_info`: Plaintext information of the credential. + /// * `signature`: Coconut credential in the form of a signature. + pub async fn insert_ecash_credential( + &self, + voucher_value: String, + voucher_info: String, + wallet: String, + epoch_id: String, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + "INSERT INTO ecash_credentials(voucher_value, voucher_info, wallet, epoch_id, consumed) VALUES (?, ?, ?, ?, ?)", + voucher_value, voucher_info, wallet, epoch_id, false + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + /// Tries to retrieve one of the stored, unused credentials. pub async fn get_next_coconut_credential( &self, diff --git a/common/credential-storage/src/ephemeral_storage.rs b/common/credential-storage/src/ephemeral_storage.rs index 577a2de8c5..3d835be523 100644 --- a/common/credential-storage/src/ephemeral_storage.rs +++ b/common/credential-storage/src/ephemeral_storage.rs @@ -50,6 +50,20 @@ impl Storage for EphemeralStorage { Ok(()) } + async fn insert_ecash_credential( + &self, + voucher_value: String, + voucher_info: String, + wallet: String, + epoch_id: String, + ) -> Result<(), StorageError> { + self.coconut_credential_manager + .insert_ecash_credential(voucher_value, voucher_info, wallet, epoch_id) + .await; + + Ok(()) + } + async fn get_next_coconut_credential(&self) -> Result { let credential = self .coconut_credential_manager diff --git a/common/credential-storage/src/models.rs b/common/credential-storage/src/models.rs index 014054f3fa..741f7242fd 100644 --- a/common/credential-storage/src/models.rs +++ b/common/credential-storage/src/models.rs @@ -13,3 +13,14 @@ pub struct CoconutCredential { pub epoch_id: String, pub consumed: bool, } + +#[derive(Clone)] +pub struct EcashCredential { + #[allow(dead_code)] + pub id: i64, + pub voucher_value: String, + pub voucher_info: String, + pub wallet: String, + pub epoch_id: String, + pub consumed: bool, +} diff --git a/common/credential-storage/src/persistent_storage.rs b/common/credential-storage/src/persistent_storage.rs index f214e40345..b470608a84 100644 --- a/common/credential-storage/src/persistent_storage.rs +++ b/common/credential-storage/src/persistent_storage.rs @@ -81,6 +81,20 @@ impl Storage for PersistentStorage { Ok(()) } + async fn insert_ecash_credential( + &self, + voucher_value: String, + voucher_info: String, + wallet: String, + epoch_id: String, + ) -> Result<(), StorageError> { + self.coconut_credential_manager + .insert_ecash_credential(voucher_value, voucher_info, wallet, epoch_id) + .await?; + + Ok(()) + } + async fn get_next_coconut_credential(&self) -> Result { let credential = self .coconut_credential_manager diff --git a/common/credential-storage/src/storage.rs b/common/credential-storage/src/storage.rs index e4fa9cbcba..fc60a3fb0d 100644 --- a/common/credential-storage/src/storage.rs +++ b/common/credential-storage/src/storage.rs @@ -29,6 +29,22 @@ pub trait Storage: Send + Sync { epoch_id: String, ) -> Result<(), Self::StorageError>; + /// Inserts provided wallet into the database. + /// + /// # Arguments + /// + /// * `voucher_value`: How much bandwidth is in the credential. + /// * `voucher_info`: What type of credential it is. + /// * `signature`: Ecash wallet credential in the form of a wallet. + /// * `epoch_id`: The epoch when it was signed. + async fn insert_ecash_credential( + &self, + voucher_value: String, + voucher_info: String, + signature: String, + epoch_id: String, + ) -> Result<(), Self::StorageError>; + /// Tries to retrieve one of the stored, unused credentials. async fn get_next_coconut_credential(&self) -> Result; diff --git a/common/nym_offline_compact_ecash/Cargo.toml b/common/nym_offline_compact_ecash/Cargo.toml index 6b5b9944c6..93bf439728 100644 --- a/common/nym_offline_compact_ecash/Cargo.toml +++ b/common/nym_offline_compact_ecash/Cargo.toml @@ -8,13 +8,14 @@ edition = "2021" [dependencies] #bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] } -bls12_381 = { path = "/Users/ania/Documents/Git/andrew_bls12_381", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] } +bls12_381 = { path = "/Users/simon/Documents/Nym/github/andrew_bls12_381", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] } itertools = "0.10" digest = "0.9" rand = "0.8" thiserror = "1.0" sha2 = "0.9" bs58 = "0.4.0" +serde = "1.0.189" [dev-dependencies] criterion = { version = "0.3", features = ["html_reports"] } diff --git a/common/nym_offline_compact_ecash/src/error.rs b/common/nym_offline_compact_ecash/src/error.rs index 8a3ed2ddbf..b3c1daa7bd 100644 --- a/common/nym_offline_compact_ecash/src/error.rs +++ b/common/nym_offline_compact_ecash/src/error.rs @@ -31,10 +31,13 @@ pub enum CompactEcashError { #[error("Identify Verification related error: {0}")] Identify(String), + #[error("Could not decode base 58 string - {0}")] + MalformedString(#[from] bs58::decode::Error), + #[error( - "Deserailization error, expected at least {} bytes, got {}", - min, - actual + "Deserailization error, expected at least {} bytes, got {}", + min, + actual )] DeserializationMinLength { min: usize, actual: usize }, diff --git a/common/nym_offline_compact_ecash/src/impls/clone.rs b/common/nym_offline_compact_ecash/src/impls/clone.rs new file mode 100644 index 0000000000..349cc6c332 --- /dev/null +++ b/common/nym_offline_compact_ecash/src/impls/clone.rs @@ -0,0 +1,14 @@ +use crate::scheme::withdrawal::WithdrawalRequest; +use crate::traits::Bytable; + +macro_rules! impl_clone { + ($struct:ident) => { + impl Clone for $struct { + fn clone(&self) -> Self { + Self::try_from_byte_slice(&self.to_byte_vec()).unwrap() + } + } + }; +} + +impl_clone!(WithdrawalRequest); diff --git a/common/nym_offline_compact_ecash/src/impls/mod.rs b/common/nym_offline_compact_ecash/src/impls/mod.rs new file mode 100644 index 0000000000..efacad272d --- /dev/null +++ b/common/nym_offline_compact_ecash/src/impls/mod.rs @@ -0,0 +1,2 @@ +mod clone; +mod serde; diff --git a/common/nym_offline_compact_ecash/src/impls/serde.rs b/common/nym_offline_compact_ecash/src/impls/serde.rs new file mode 100644 index 0000000000..ffd4ee0f58 --- /dev/null +++ b/common/nym_offline_compact_ecash/src/impls/serde.rs @@ -0,0 +1,47 @@ +use crate::traits::Base58; +use serde::de::Unexpected; +use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; + +use crate::scheme::withdrawal::WithdrawalRequest; + +macro_rules! impl_serde { + ($struct:ident, $visitor:ident) => { + pub struct $visitor {} + + impl Serialize for $struct { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_bs58()) + } + } + + impl<'de> Visitor<'de> for $visitor { + type Value = $struct; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "A base58 encoded struct") + } + + fn visit_str(self, s: &str) -> Result { + match $struct::try_from_bs58(s) { + Ok(x) => Ok(x), + Err(_) => Err(Error::invalid_value(Unexpected::Str(s), &self)), + } + } + } + + impl<'de> Deserialize<'de> for $struct { + fn deserialize(deserializer: D) -> Result<$struct, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_str($visitor {}) + } + } + }; +} + +impl_serde!(WithdrawalRequest, V1); diff --git a/common/nym_offline_compact_ecash/src/lib.rs b/common/nym_offline_compact_ecash/src/lib.rs index b5790f345d..028295d8c9 100644 --- a/common/nym_offline_compact_ecash/src/lib.rs +++ b/common/nym_offline_compact_ecash/src/lib.rs @@ -5,27 +5,28 @@ use bls12_381::Scalar; pub use scheme::aggregation::aggregate_verification_keys; pub use scheme::aggregation::aggregate_wallets; pub use scheme::identify; -pub use scheme::keygen::{PublicKeyUser, SecretKeyUser, VerificationKeyAuth}; pub use scheme::keygen::generate_keypair_user; pub use scheme::keygen::ttp_keygen; -pub use scheme::PartialWallet; -pub use scheme::PayInfo; +pub use scheme::keygen::{PublicKeyUser, SecretKeyUser, VerificationKeyAuth}; pub use scheme::setup; pub use scheme::withdrawal::issue_verify; pub use scheme::withdrawal::issue_wallet; pub use scheme::withdrawal::withdrawal_request; +pub use scheme::PartialWallet; +pub use scheme::PayInfo; pub use traits::Base58; use crate::error::CompactEcashError; use crate::traits::Bytable; -mod error; +pub mod error; +mod impls; mod proofs; -mod scheme; +pub mod scheme; #[cfg(test)] mod tests; mod traits; -mod utils; +pub mod utils; pub type Attribute = Scalar; diff --git a/common/nym_offline_compact_ecash/src/scheme/keygen.rs b/common/nym_offline_compact_ecash/src/scheme/keygen.rs index 13d670fb20..db019f57f8 100644 --- a/common/nym_offline_compact_ecash/src/scheme/keygen.rs +++ b/common/nym_offline_compact_ecash/src/scheme/keygen.rs @@ -5,17 +5,18 @@ 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; use crate::scheme::setup::GroupParameters; use crate::scheme::SignerIndex; +use crate::traits::Bytable; +use crate::utils::Polynomial; use crate::utils::{ try_deserialize_g1_projective, try_deserialize_g2_projective, try_deserialize_scalar, try_deserialize_scalar_vec, }; -use crate::utils::Polynomial; #[derive(Debug, PartialEq, Clone)] pub struct SecretKeyAuth { @@ -242,13 +243,13 @@ impl<'a> Mul for &'a VerificationKeyAuth { } impl Sum for VerificationKeyAuth - where - T: Borrow, +where + T: Borrow, { #[inline] fn sum(iter: I) -> Self - where - I: Iterator, + where + I: Iterator, { let mut peekable = iter.peekable(); let head_attributes = match peekable.peek() { @@ -330,6 +331,15 @@ impl SecretKeyUser { pk: params.gen1() * self.sk, } } + + pub fn to_bytes(&self) -> Vec { + self.sk.to_bytes().to_vec() + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + let sk = Scalar::try_from_byte_slice(bytes)?; + Ok(SecretKeyUser { sk }) + } } #[derive(Debug, Eq, PartialEq, Clone, Hash)] @@ -337,6 +347,39 @@ pub struct PublicKeyUser { pub(crate) pk: G1Projective, } +impl PublicKeyUser { + pub fn to_base58_string(&self) -> String { + bs58::encode(&self.pk.to_bytes()).into_string() + } + + pub fn from_base58_string>(val: I) -> Result { + let bytes = bs58::decode(val) + .into_vec() + .map_err(|source| CompactEcashError::Deserialization(source.to_string()))?; + Self::from_bytes(&bytes) + } + + pub fn to_bytes(&self) -> Vec { + self.pk.to_affine().to_compressed().to_vec() + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != 48 { + return Err(CompactEcashError::Deserialization( + "Failed to deserialize : Invalid length".to_string(), + )); + } + let pk_bytes: &[u8; 48] = bytes[..48].try_into().unwrap(); + let pk = try_deserialize_g1_projective( + pk_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize verification key G1 point".to_string(), + ), + )?; + Ok(PublicKeyUser { pk }) + } +} + pub struct KeyPairAuth { secret_key: SecretKeyAuth, verification_key: VerificationKeyAuth, @@ -345,6 +388,17 @@ pub struct KeyPairAuth { } impl KeyPairAuth { + pub fn new( + sk: SecretKeyAuth, + vk: VerificationKeyAuth, + index: Option, + ) -> KeyPairAuth { + KeyPairAuth { + secret_key: sk, + verification_key: vk, + index, + } + } pub fn secret_key(&self) -> SecretKeyAuth { self.secret_key.clone() } @@ -367,6 +421,24 @@ impl KeyPairUser { pub fn public_key(&self) -> PublicKeyUser { self.public_key.clone() } + + pub fn to_bytes(&self) -> Vec { + [self.secret_key.to_bytes(), self.public_key.to_bytes()].concat() + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != 32 + 48 { + return Err(CompactEcashError::Deserialization( + "Failed to deserialize keypair : Invalid length".to_string(), + )); + } + let sk = SecretKeyUser::from_bytes(&bytes[..32])?; + let pk = PublicKeyUser::from_bytes(&bytes[32..32 + 48])?; + Ok(KeyPairUser { + secret_key: sk, + public_key: pk, + }) + } } pub fn generate_keypair_user(params: &GroupParameters) -> KeyPairUser { diff --git a/common/nym_offline_compact_ecash/src/scheme/mod.rs b/common/nym_offline_compact_ecash/src/scheme/mod.rs index 87bed21716..3d21c4c692 100644 --- a/common/nym_offline_compact_ecash/src/scheme/mod.rs +++ b/common/nym_offline_compact_ecash/src/scheme/mod.rs @@ -3,15 +3,16 @@ use std::cell::Cell; use bls12_381::{G1Projective, G2Prepared, G2Projective, Scalar}; use group::Curve; -use crate::Attribute; use crate::error::{CompactEcashError, Result}; use crate::proofs::proof_spend::{SpendInstance, SpendProof, SpendWitness}; use crate::scheme::keygen::{SecretKeyUser, VerificationKeyAuth}; use crate::scheme::setup::{GroupParameters, Parameters}; +use crate::traits::Bytable; use crate::utils::{ - check_bilinear_pairing, hash_to_scalar, Signature, SignerIndex, - try_deserialize_g1_projective, try_deserialize_scalar, try_deserialize_g2_projective, + check_bilinear_pairing, hash_to_scalar, try_deserialize_g1_projective, + try_deserialize_g2_projective, try_deserialize_scalar, Signature, SignerIndex, }; +use crate::{Attribute, Base58}; pub mod aggregation; pub mod identify; @@ -36,7 +37,7 @@ impl PartialWallet { pub fn index(&self) -> Option { self.idx } - pub fn to_bytes(&self) -> [u8; 136]{ + 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()); @@ -66,15 +67,11 @@ impl TryFrom<&[u8]> for PartialWallet { 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){ + if !idx_bytes.iter().all(|&x| x == 0) { let idx = Some(u64::from_le_bytes(*idx_bytes)); } - Ok(PartialWallet{ - sig, - v, - idx, - }) + Ok(PartialWallet { sig, v, idx }) } } @@ -98,7 +95,7 @@ impl Wallet { self.l.get() } - pub fn to_bytes(&self) -> [u8; 136]{ + 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()); @@ -109,7 +106,6 @@ impl Wallet { self.l.set(self.l.get() + 1); } - pub fn spend( &self, params: &Parameters, @@ -144,7 +140,6 @@ impl Wallet { // compute commitments C let cc = grparams.gen1() * o_c + grparams.gamma1() * self.v(); - let mut aa: Vec = Default::default(); let mut ss: Vec = Default::default(); let mut tt: Vec = Default::default(); @@ -172,8 +167,8 @@ impl Wallet { // evaluate the pseudorandom functions let ss_k = pseudorandom_f_delta_v(&grparams, self.v(), self.l() + k); ss.push(ss_k); - let tt_k = - grparams.gen1() * sk_user.sk + pseudorandom_f_g_v(&grparams, self.v(), self.l() + k) * rr_k; + let tt_k = grparams.gen1() * sk_user.sk + + pseudorandom_f_g_v(&grparams, self.v(), self.l() + k) * rr_k; tt.push(tt_k); // compute values mu, o_mu, lambda, o_lambda @@ -198,7 +193,6 @@ impl Wallet { kappa_k_vec.push(kappa_k); } - // construct the zkp proof let spend_instance = SpendInstance { kappa, @@ -274,14 +268,22 @@ impl TryFrom<&[u8]> for Wallet { let v = Scalar::from_bytes(&v_bytes).unwrap(); let l = Cell::new(u64::from_le_bytes(*l_bytes)); - Ok(Wallet{ - sig, - v, - l - }) + Ok(Wallet { sig, v, l }) } } +impl Bytable for Wallet { + fn to_byte_vec(&self) -> Vec { + self.to_bytes().to_vec() + } + + fn try_from_byte_slice(slice: &[u8]) -> std::result::Result { + Wallet::try_from(slice) + } +} + +impl Base58 for Wallet {} + 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 @@ -301,10 +303,10 @@ pub fn compute_kappa( 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::() + .iter() + .zip(verification_key.beta_g2.iter()) + .map(|(priv_attr, beta_i)| beta_i * priv_attr) + .sum::() } #[derive(PartialEq, Eq, Debug, Clone)] @@ -405,25 +407,38 @@ impl Payment { 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 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); - + (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 { @@ -506,7 +521,7 @@ impl TryFrom<&[u8]> for Payment { idx += 48; } - let tt_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize; + 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 { @@ -519,7 +534,7 @@ impl TryFrom<&[u8]> for Payment { idx += 48; } - let aa_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize; + 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 { @@ -532,7 +547,7 @@ impl TryFrom<&[u8]> for Payment { idx += 48; } - let rr_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize; + 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 { @@ -545,21 +560,23 @@ impl TryFrom<&[u8]> for Payment { idx += 32; } - let kappa_k_len = u64::from_le_bytes(bytes[idx..idx+8].try_into().unwrap()) as usize; + 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()), + 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; + 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 { @@ -590,4 +607,4 @@ impl TryFrom<&[u8]> for Payment { 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 70bb891cd8..b75be24b79 100644 --- a/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs +++ b/common/nym_offline_compact_ecash/src/scheme/withdrawal.rs @@ -6,10 +6,14 @@ 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; -use crate::utils::{check_bilinear_pairing, hash_g1, try_deserialize_g1_projective}; +use crate::scheme::PartialWallet; +use crate::traits::Bytable; +use crate::utils::{ + check_bilinear_pairing, hash_g1, try_deserialize_g1_projective, try_deserialize_scalar, +}; use crate::utils::{BlindedSignature, Signature}; +use crate::Base58; #[derive(Debug, PartialEq)] pub struct WithdrawalRequest { @@ -20,13 +24,14 @@ pub struct WithdrawalRequest { } impl WithdrawalRequest { - pub fn to_bytes(&self) -> Vec{ + 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()); + 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()); @@ -37,7 +42,6 @@ impl WithdrawalRequest { bytes.extend_from_slice(&zk_proof_bytes); bytes - } } @@ -45,7 +49,7 @@ impl TryFrom<&[u8]> for WithdrawalRequest { type Error = CompactEcashError; fn try_from(bytes: &[u8]) -> Result { - if bytes.len() < 48 + 48 + 8 + 48{ + if bytes.len() < 48 + 48 + 8 + 48 { return Err(CompactEcashError::DeserializationMinLength { min: 48 + 48 + 8 + 48, actual: bytes.len(), @@ -100,7 +104,7 @@ impl TryFrom<&[u8]> for WithdrawalRequest { let zk_proof = WithdrawalReqProof::try_from(&bytes[j + pc_len as usize * 48..])?; - Ok(WithdrawalRequest{ + Ok(WithdrawalRequest { com_hash, com, pc_coms, @@ -109,6 +113,18 @@ impl TryFrom<&[u8]> for WithdrawalRequest { } } +impl Bytable for WithdrawalRequest { + fn to_byte_vec(&self) -> Vec { + self.to_bytes() + } + + fn try_from_byte_slice(slice: &[u8]) -> Result { + WithdrawalRequest::try_from(slice) + } +} + +impl Base58 for WithdrawalRequest {} + pub struct RequestInfo { com_hash: G1Projective, com_opening: Scalar, @@ -129,6 +145,104 @@ impl RequestInfo { pub fn get_v(&self) -> Scalar { self.v } + + pub fn to_bytes(&self) -> Vec { + let com_hash_bytes = self.com_hash.to_affine().to_compressed(); + let com_opening_bytes = self.com_opening.to_bytes(); + let pr_coms_openings_len = self.pc_coms_openings.len() as u64; + let v_bytes = self.v.to_bytes(); + + let mut bytes = Vec::with_capacity(48 + 32 + 8 + pr_coms_openings_len as usize * 32 + 32); + bytes.extend_from_slice(&com_hash_bytes); + bytes.extend_from_slice(&com_opening_bytes); + bytes.extend_from_slice(&pr_coms_openings_len.to_le_bytes()); + for c in &self.pc_coms_openings { + bytes.extend_from_slice(&c.to_bytes()); + } + + bytes.extend_from_slice(&v_bytes); + + bytes + } +} + +impl TryFrom<&[u8]> for RequestInfo { + type Error = CompactEcashError; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() < 48 + 32 + 8 + 32 { + return Err(CompactEcashError::DeserializationMinLength { + min: 48 + 32 + 8 + 32, + actual: bytes.len(), + }); + } + + let mut j = 0; + let commitment_hash_bytes_len = 48; + let com_hash_bytes = bytes[j..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_opening_bytes_len = 32; + let com_opening_bytes = bytes[j..j + com_opening_bytes_len].try_into().unwrap(); + let com_opening = try_deserialize_scalar( + &com_opening_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize commitment opening".to_string(), + ), + )?; + j += com_opening_bytes_len; + + let pc_coms_openings_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap()); + j += 8; + if bytes[j..].len() < pc_coms_openings_len as usize * 32 { + return Err(CompactEcashError::DeserializationMinLength { + min: pc_coms_openings_len as usize * 32, + actual: bytes[j..].len(), + }); + } + let mut pc_coms_openings = Vec::with_capacity(pc_coms_openings_len as usize); + for i in 0..pc_coms_openings_len as usize { + let start = j + i * 32; + let end = start + 32; + + let pc_com_opening_bytes = bytes[start..end].try_into().unwrap(); + let pc_com_opening = try_deserialize_scalar( + &pc_com_opening_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize compressed Pedersen commitment opening".to_string(), + ), + )?; + + pc_coms_openings.push(pc_com_opening) + } + j += pc_coms_openings_len as usize * 32; + + let v_len = 32; + if bytes[j..].len() != v_len { + return Err(CompactEcashError::DeserializationMinLength { + min: v_len, + actual: bytes[j..].len(), + }); + } + let v_bytes = bytes[j..j + v_len].try_into().unwrap(); + let v = try_deserialize_scalar( + v_bytes, + CompactEcashError::Deserialization("Failed to deserialize v".to_string()), + )?; + + Ok(RequestInfo { + com_hash, + com_opening, + pc_coms_openings, + v, + }) + } } pub fn withdrawal_request( @@ -142,10 +256,10 @@ pub fn withdrawal_request( let com_opening = params.random_scalar(); let com = params.gen1() * com_opening + attributes - .iter() - .zip(gammas) - .map(|(&m, gamma)| gamma * m) - .sum::(); + .iter() + .zip(gammas) + .map(|(&m, gamma)| gamma * m) + .sum::(); // Value h in the paper let com_hash = hash_g1(com.to_bytes()); @@ -286,4 +400,3 @@ pub fn issue_verify( idx: None, }) } - diff --git a/common/nym_offline_compact_ecash/src/utils.rs b/common/nym_offline_compact_ecash/src/utils.rs index 45f3588599..4abbadde6c 100644 --- a/common/nym_offline_compact_ecash/src/utils.rs +++ b/common/nym_offline_compact_ecash/src/utils.rs @@ -6,10 +6,10 @@ use core::ops::Mul; use std::convert::{TryFrom, TryInto}; use std::ops::Neg; -use bls12_381::{ - G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, multi_miller_loop, Scalar, -}; use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve, HashToField}; +use bls12_381::{ + multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Scalar, +}; use ff::Field; use group::{Curve, Group}; @@ -85,9 +85,9 @@ pub(crate) fn perform_lagrangian_interpolation_at_origin( points: &[SignerIndex], values: &[T], ) -> Result - where - T: Sum, - for<'a> &'a T: Mul, +where + T: Sum, + for<'a> &'a T: Mul, { if points.is_empty() || values.is_empty() { return Err(CompactEcashError::Interpolation( @@ -276,6 +276,51 @@ impl Bytable for Signature { #[cfg_attr(test, derive(PartialEq))] pub struct BlindedSignature(pub(crate) G1Projective, pub(crate) G1Projective); +impl TryFrom<&[u8]> for BlindedSignature { + type Error = CompactEcashError; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() != 96 { + return Err(CompactEcashError::Deserialization(format!( + "BlindedSignature must be exactly 96 bytes, got {}", + bytes.len() + ))); + } + + let bsig1_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48"); + let bsig2_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48"); + + let bsig1 = try_deserialize_g1_projective( + bsig1_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize compressed bsig1".to_string(), + ), + )?; + + let bsig2 = try_deserialize_g1_projective( + bsig2_bytes, + CompactEcashError::Deserialization( + "Failed to deserialize compressed bsig2".to_string(), + ), + )?; + + Ok(BlindedSignature(bsig1, bsig2)) + } +} + +impl BlindedSignature { + pub fn to_bytes(&self) -> [u8; 96] { + let mut bytes = [0u8; 96]; + bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed()); + bytes[48..].copy_from_slice(&self.1.to_affine().to_compressed()); + bytes + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + BlindedSignature::try_from(bytes) + } +} + pub struct SignatureShare { signature: Signature, index: SignerIndex, diff --git a/common/nym_offline_divisible_ecash/Cargo.toml b/common/nym_offline_divisible_ecash/Cargo.toml index 10fc986131..37c940443d 100644 --- a/common/nym_offline_divisible_ecash/Cargo.toml +++ b/common/nym_offline_divisible_ecash/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] #bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", branch = "gt-serialisation", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] } -bls12_381 = { path = "/Users/ania/Documents/Git/andrew_bls12_381", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] } +bls12_381 = { path = "/Users/simon/Documents/Nym/github/andrew_bls12_381", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] } itertools = "0.10" digest = "0.9" rand = "0.8"