diff --git a/Cargo.lock b/Cargo.lock index 3052b20e..bab5b46e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -749,8 +749,8 @@ dependencies = [ "ark-serialize", "ark-std", "bincode", + "generic-array", "rand 0.8.5", - "rand_core 0.6.4", "serde", "serde_with", ] diff --git a/ferveo-common/Cargo.toml b/ferveo-common/Cargo.toml index c5b5a58e..10c7aee4 100644 --- a/ferveo-common/Cargo.toml +++ b/ferveo-common/Cargo.toml @@ -11,8 +11,8 @@ ark-ec = "0.4" ark-serialize = { version = "0.4", features = ["derive"] } ark-std = "0.4" bincode = "1.3.3" +generic-array = "0.14.7" rand = "0.8" -rand_core = "0.6" serde = { version = "1.0", features = ["derive"] } serde_with = "2.2.0" diff --git a/ferveo-common/src/keypair.rs b/ferveo-common/src/keypair.rs index 70716bfd..485241b3 100644 --- a/ferveo-common/src/keypair.rs +++ b/ferveo-common/src/keypair.rs @@ -6,28 +6,26 @@ use ark_std::{ rand::{prelude::StdRng, RngCore, SeedableRng}, UniformRand, }; -use rand_core::Error; +use generic_array::{typenum::U96, GenericArray}; use serde::*; use serde_with::serde_as; -use crate::serialization; +use crate::{serialization, Error, Result}; // Normally, we would use a custom trait for this, but we can't because // the arkworks will not let us create a blanket implementation for G1Affine // and Fr types. So instead, we're using this shared utility function: -pub fn to_bytes( - item: &T, -) -> Result, ark_serialize::SerializationError> { +pub fn to_bytes(item: &T) -> Result> { let mut writer = Vec::new(); - item.serialize_compressed(&mut writer)?; + item.serialize_compressed(&mut writer) + .map_err(Error::SerializationError)?; Ok(writer) } -pub fn from_bytes( - bytes: &[u8], -) -> Result { +pub fn from_bytes(bytes: &[u8]) -> Result { let mut reader = io::Cursor::new(bytes); - let item = T::deserialize_compressed(&mut reader)?; + let item = T::deserialize_compressed(&mut reader) + .map_err(Error::SerializationError)?; Ok(item) } @@ -39,17 +37,25 @@ pub struct PublicKey { } impl PublicKey { - pub fn to_bytes( - &self, - ) -> Result, ark_serialize::SerializationError> { - to_bytes(&self.encryption_key) + pub fn to_bytes(&self) -> Result> { + let as_bytes = to_bytes(&self.encryption_key)?; + Ok(GenericArray::::from_slice(&as_bytes).to_owned()) } - pub fn from_bytes( - bytes: &[u8], - ) -> Result { - let encryption_key = from_bytes(bytes)?; - Ok(PublicKey:: { encryption_key }) + pub fn from_bytes(bytes: &[u8]) -> Result> { + let bytes = + GenericArray::::from_exact_iter(bytes.iter().cloned()) + .ok_or_else(|| { + Error::InvalidByteLength( + Self::serialized_size(), + bytes.len(), + ) + })?; + from_bytes(&bytes).map(|encryption_key| PublicKey { encryption_key }) + } + + pub fn serialized_size() -> usize { + 96 } } @@ -129,9 +135,9 @@ impl Keypair { 32 } - pub fn from_secure_randomness(bytes: &[u8]) -> Result { + pub fn from_secure_randomness(bytes: &[u8]) -> Result { if bytes.len() != Self::secure_randomness_size() { - return Err(Error::new("Invalid seed length")); + return Err(Error::InvalidSeedLength(bytes.len())); } let mut seed = [0; 32]; seed.copy_from_slice(bytes); diff --git a/ferveo-common/src/lib.rs b/ferveo-common/src/lib.rs index f8420468..c041b6da 100644 --- a/ferveo-common/src/lib.rs +++ b/ferveo-common/src/lib.rs @@ -1,5 +1,36 @@ pub mod keypair; pub mod serialization; +use std::{fmt, fmt::Formatter}; + pub use keypair::*; pub use serialization::*; + +#[derive(Debug)] +pub enum Error { + InvalidByteLength(usize, usize), + SerializationError(ark_serialize::SerializationError), + InvalidSeedLength(usize), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Error::InvalidByteLength(expected, actual) => { + write!( + f, + "Invalid byte length: expected {}, actual {}", + expected, actual + ) + } + Error::SerializationError(e) => { + write!(f, "Serialization error: {}", e) + } + Error::InvalidSeedLength(len) => { + write!(f, "Invalid seed length: {}", len) + } + } + } +} + +type Result = std::result::Result; diff --git a/ferveo-python/ferveo/__init__.pyi b/ferveo-python/ferveo/__init__.pyi index 170e98b0..b648301e 100644 --- a/ferveo-python/ferveo/__init__.pyi +++ b/ferveo-python/ferveo/__init__.pyi @@ -36,6 +36,9 @@ class FerveoPublicKey: def __hash__(self) -> int: ... + def __richcmp__(self, other: FerveoPublicKey, op: int) -> bool: + ... + class Validator: diff --git a/ferveo-python/test/test_serialization.py b/ferveo-python/test/test_serialization.py index 00f800b0..30ba0ee9 100644 --- a/ferveo-python/test/test_serialization.py +++ b/ferveo-python/test/test_serialization.py @@ -2,7 +2,9 @@ Keypair, Validator, Dkg, - DkgPublicKey + DkgPublicKey, + FerveoPublicKey, + SharedSecret, ) @@ -34,26 +36,44 @@ def make_dkg_public_key(): def make_shared_secret(): - # TODO: implement this + # TODO: Implement this + # SharedSecret.from_bytes(os.urandom(584)) pass +def make_pk(): + return Keypair.random().public_key() + + # def test_shared_secret_serialization(): -# shared_secret = create_shared_secret_instance() +# shared_secret = make_shared_secret() # serialized = bytes(shared_secret) # deserialized = SharedSecret.from_bytes(serialized) -# TODO: Implement comparison -# assert shared_secret == deserialized +# # TODO: Implement __richcmp__ +# # assert shared_secret == deserialized +# assert serialized == bytes(deserialized) def test_keypair_serialization(): keypair = Keypair.random() serialized = bytes(keypair) deserialized = Keypair.from_bytes(serialized) - # TODO: Implement comparison - # assert keypair == deserialized + # TODO: Implement __richcmp__ + # assert serialized == deserialized + assert serialized == bytes(deserialized) def test_dkg_public_key_serialization(): dkg_pk = make_dkg_public_key() serialized = bytes(dkg_pk) + deserialized = DkgPublicKey.from_bytes(serialized) + # TODO: Implement __richcmp__ + assert serialized == bytes(deserialized) assert len(serialized) == DkgPublicKey.serialized_size() + + +def test_public_key_serialization(): + pk = make_pk() + serialized = bytes(pk) + deserialized = FerveoPublicKey.from_bytes(serialized) + assert pk == deserialized + assert len(serialized) == FerveoPublicKey.serialized_size() diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index 04995dfe..398d4582 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -84,7 +84,12 @@ impl DkgPublicKey { pub fn from_bytes(bytes: &[u8]) -> Result { let bytes = GenericArray::::from_exact_iter(bytes.iter().cloned()) - .ok_or(Error::InvalidByteLength(48, bytes.len()))?; + .ok_or_else(|| { + Error::InvalidByteLength( + Self::serialized_size(), + bytes.len(), + ) + })?; from_bytes(&bytes).map(DkgPublicKey) } @@ -198,7 +203,7 @@ impl AggregatedTranscript { shares_num: u32, messages: &[ValidatorMessage], ) -> Result { - let pvss_params = crate::pvss::PubliclyVerifiableParams::::default(); + let pvss_params = PubliclyVerifiableParams::::default(); let domain = Radix2EvaluationDomain::::new(shares_num as usize) .expect("Unable to construct an evaluation domain"); @@ -357,6 +362,14 @@ mod test_ferveo_api { (messages, validators, validator_keypairs) } + #[test] + fn test_dkg_pk_serialization() { + let dkg_pk = DkgPublicKey::random(); + let serialized = dkg_pk.to_bytes().unwrap(); + let deserialized = DkgPublicKey::from_bytes(&serialized).unwrap(); + assert_eq!(dkg_pk, deserialized); + } + #[test] fn test_server_api_tdec_precomputed() { let rng = &mut StdRng::seed_from_u64(0); diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs index c324e76f..614b3608 100644 --- a/ferveo/src/bindings_python.rs +++ b/ferveo/src/bindings_python.rs @@ -172,7 +172,7 @@ where } } -macro_rules! generate_common_methods { +macro_rules! generate_bytes_serialization { ($struct_name:ident) => { #[pymethods] impl $struct_name { @@ -184,17 +184,35 @@ macro_rules! generate_common_methods { fn __bytes__(&self) -> PyResult { to_py_bytes(&self.0) } + } + }; +} - // TODO: Consider implementing this for all structs - Requires PartialOrd and other traits +macro_rules! generate_boxed_bytes_serialization { + ($struct_name:ident, $inner_struct_name:ident) => { + #[pymethods] + impl $struct_name { + #[staticmethod] + pub fn from_bytes(bytes: &[u8]) -> PyResult { + Ok($struct_name( + $inner_struct_name::from_bytes(bytes).map_err(|err| { + FerveoPythonError::Other(err.to_string()) + })?, + )) + } - // fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult { - // richcmp(self, other, op) - // } + fn __bytes__(&self) -> PyResult { + let bytes = self + .0 + .to_bytes() + .map_err(|err| FerveoPythonError::Other(err.to_string()))?; + as_py_bytes(&bytes) + } - // fn __hash__(&self) -> PyResult { - // let bytes = self.0.to_bytes()?; - // hash(stringify!($struct_name), &bytes) - // } + #[staticmethod] + pub fn serialized_size() -> usize { + $inner_struct_name::serialized_size() + } } }; } @@ -253,13 +271,13 @@ pub fn decrypt_with_shared_secret( #[derive(derive_more::AsRef)] pub struct SharedSecret(api::SharedSecret); -generate_common_methods!(SharedSecret); +generate_bytes_serialization!(SharedSecret); #[pyclass(module = "ferveo")] #[derive(derive_more::From, derive_more::AsRef)] pub struct Keypair(api::Keypair); -generate_common_methods!(Keypair); +generate_bytes_serialization!(Keypair); #[pymethods] impl Keypair { @@ -285,16 +303,19 @@ impl Keypair { } } +type InnerPublicKey = api::PublicKey; + #[pyclass(module = "ferveo")] #[derive( Clone, PartialEq, PartialOrd, Eq, derive_more::From, derive_more::AsRef, )] -pub struct FerveoPublicKey(api::PublicKey); +pub struct FerveoPublicKey(InnerPublicKey); -generate_common_methods!(FerveoPublicKey); +generate_boxed_bytes_serialization!(FerveoPublicKey, InnerPublicKey); #[pymethods] impl FerveoPublicKey { + // We implement `__richcmp__` because FerveoPublicKeys must be sortable in some cases fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult { richcmp(self, other, op) } @@ -303,7 +324,7 @@ impl FerveoPublicKey { let bytes = self .0 .to_bytes() - .map_err(|err| FerveoPythonError::FerveoError(err.into()))?; + .map_err(|err| FerveoPythonError::Other(err.to_string()))?; hash("FerveoPublicKey", &bytes) } } @@ -339,33 +360,15 @@ impl Validator { #[derive(Clone, derive_more::From, derive_more::AsRef)] pub struct Transcript(api::Transcript); -generate_common_methods!(Transcript); +generate_bytes_serialization!(Transcript); + +type InnerDkgPublicKey = api::DkgPublicKey; #[pyclass(module = "ferveo")] #[derive(Clone, derive_more::From, derive_more::AsRef)] -pub struct DkgPublicKey(api::DkgPublicKey); - -#[pymethods] -impl DkgPublicKey { - #[staticmethod] - pub fn from_bytes(bytes: &[u8]) -> PyResult { - Ok(Self( - api::DkgPublicKey::from_bytes(bytes) - .map_err(FerveoPythonError::FerveoError)?, - )) - } - - fn __bytes__(&self) -> PyResult { - let bytes = - self.0.to_bytes().map_err(FerveoPythonError::FerveoError)?; - as_py_bytes(&bytes) - } +pub struct DkgPublicKey(InnerDkgPublicKey); - #[staticmethod] - pub fn serialized_size() -> usize { - api::DkgPublicKey::serialized_size() - } -} +generate_boxed_bytes_serialization!(DkgPublicKey, InnerDkgPublicKey); #[pyclass(module = "ferveo")] #[derive(derive_more::From, derive_more::AsRef, Clone)] @@ -462,25 +465,25 @@ impl Dkg { )] pub struct Ciphertext(api::Ciphertext); -generate_common_methods!(Ciphertext); +generate_bytes_serialization!(Ciphertext); #[pyclass(module = "ferveo")] #[derive(Clone, derive_more::AsRef, derive_more::From)] pub struct DecryptionShareSimple(api::DecryptionShareSimple); -generate_common_methods!(DecryptionShareSimple); +generate_bytes_serialization!(DecryptionShareSimple); #[pyclass(module = "ferveo")] #[derive(Clone, derive_more::AsRef, derive_more::From)] pub struct DecryptionSharePrecomputed(api::DecryptionSharePrecomputed); -generate_common_methods!(DecryptionSharePrecomputed); +generate_bytes_serialization!(DecryptionSharePrecomputed); #[pyclass(module = "ferveo")] #[derive(derive_more::From, derive_more::AsRef)] pub struct AggregatedTranscript(api::AggregatedTranscript); -generate_common_methods!(AggregatedTranscript); +generate_bytes_serialization!(AggregatedTranscript); #[pymethods] impl AggregatedTranscript { @@ -844,7 +847,6 @@ mod test_ferveo_python { let shared_secret = combine_decryption_shares_simple(decryption_shares); - // TODO: Fails because of a bad shared secret let plaintext = decrypt_with_shared_secret(&ciphertext, aad, &shared_secret) .unwrap(); diff --git a/ferveo/src/bindings_wasm.rs b/ferveo/src/bindings_wasm.rs index e4b976a3..8e071564 100644 --- a/ferveo/src/bindings_wasm.rs +++ b/ferveo/src/bindings_wasm.rs @@ -99,7 +99,19 @@ fn unwrap_messages_js( Ok(messages) } -macro_rules! generate_common_methods { +macro_rules! generate_equals { + ($struct_name:ident) => { + #[wasm_bindgen] + impl $struct_name { + #[wasm_bindgen] + pub fn equals(&self, other: &$struct_name) -> bool { + self.0 == other.0 + } + } + }; +} + +macro_rules! generate_bytes_serialization { ($struct_name:ident) => { #[wasm_bindgen] impl $struct_name { @@ -112,15 +124,43 @@ macro_rules! generate_common_methods { pub fn to_bytes(&self) -> JsResult> { to_js_bytes(&self.0) } + } + }; +} - #[wasm_bindgen] - pub fn equals(&self, other: &$struct_name) -> bool { - self.0 == other.0 +macro_rules! generate_boxed_bytes_serialization { + ($struct_name:ident, $inner_struct_name:ident) => { + #[wasm_bindgen] + impl $struct_name { + #[wasm_bindgen(js_name = "fromBytes")] + pub fn from_bytes(bytes: &[u8]) -> JsResult<$struct_name> { + $inner_struct_name::from_bytes(bytes) + .map_err(map_js_err) + .map(Self) + } + + #[wasm_bindgen(js_name = "toBytes")] + pub fn to_bytes(&self) -> JsResult> { + let bytes = self.0.to_bytes().map_err(map_js_err)?; + let bytes: Box<[u8]> = bytes.as_slice().into(); + Ok(bytes) + } + + #[wasm_bindgen(js_name = "serializedSize")] + pub fn serialized_size() -> usize { + $inner_struct_name::serialized_size() } } }; } +macro_rules! generate_common_methods { + ($struct_name:ident) => { + generate_equals!($struct_name); + generate_bytes_serialization!($struct_name); + }; +} + #[derive(TryFromJsValue)] #[wasm_bindgen] #[derive(Clone, Debug, derive_more::AsRef, derive_more::From)] @@ -135,13 +175,16 @@ pub struct DecryptionSharePrecomputed(tpke::api::DecryptionSharePrecomputed); generate_common_methods!(DecryptionSharePrecomputed); +type InnerPublicKey = api::PublicKey; + #[wasm_bindgen] #[derive( Clone, Debug, derive_more::AsRef, derive_more::From, derive_more::Into, )] -pub struct FerveoPublicKey(api::PublicKey); +pub struct FerveoPublicKey(InnerPublicKey); -generate_common_methods!(FerveoPublicKey); +generate_equals!(FerveoPublicKey); +generate_boxed_bytes_serialization!(FerveoPublicKey, InnerPublicKey); #[wasm_bindgen] #[derive( @@ -212,39 +255,20 @@ pub fn decrypt_with_shared_secret( .map_err(map_js_err) } -#[wasm_bindgen] -pub struct DkgPublicKey(api::DkgPublicKey); +type InnerDkgPublicKey = api::DkgPublicKey; #[wasm_bindgen] -impl DkgPublicKey { - #[wasm_bindgen(js_name = "fromBytes")] - pub fn from_bytes(bytes: &[u8]) -> JsResult { - api::DkgPublicKey::from_bytes(bytes) - .map_err(map_js_err) - .map(Self) - } +pub struct DkgPublicKey(InnerDkgPublicKey); - #[wasm_bindgen(js_name = "toBytes")] - pub fn to_bytes(&self) -> JsResult> { - let bytes = self.0.to_bytes().map_err(map_js_err)?; - let bytes: Box<[u8]> = bytes.as_slice().into(); - Ok(bytes) - } +generate_equals!(DkgPublicKey); +generate_boxed_bytes_serialization!(DkgPublicKey, InnerDkgPublicKey); +#[wasm_bindgen] +impl DkgPublicKey { #[wasm_bindgen] pub fn random() -> DkgPublicKey { Self(api::DkgPublicKey::random()) } - - #[wasm_bindgen(js_name = "serializedSize")] - pub fn serialized_size() -> usize { - api::DkgPublicKey::serialized_size() - } - - #[wasm_bindgen] - pub fn equals(&self, other: &DkgPublicKey) -> bool { - self.0 == other.0 - } } #[wasm_bindgen]