diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dba0f9..5335d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Artifact::empty()` removed, the user should return `None` instead. ([#46]) - `EchoBroadcast` and `DirectMessage` now use `ProtocolMessagePart` trait for their methods. ([#47]) - Added normal broadcasts support in addition to echo ones; signatures of `Round` methods changed accordingly; added `Round::make_normal_broadcast()`. ([#47]) +- Serialization format is a part of `SessionParameters` now; `Round` and `Protocol` methods receive dynamic serializers/deserializers. ([#33]) ### Added @@ -27,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#32]: https://github.com/entropyxyz/manul/pull/32 +[#33]: https://github.com/entropyxyz/manul/pull/33 [#36]: https://github.com/entropyxyz/manul/pull/36 [#37]: https://github.com/entropyxyz/manul/pull/37 [#40]: https://github.com/entropyxyz/manul/pull/40 diff --git a/Cargo.lock b/Cargo.lock index 613a6a6..0555796 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -71,15 +80,6 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" -[[package]] -name = "bincode" -version = "2.0.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95" -dependencies = [ - "serde", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -165,6 +165,12 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + [[package]] name = "cpufeatures" version = "0.2.14" @@ -210,6 +216,12 @@ dependencies = [ "itertools", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -278,6 +290,28 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "erased-serde" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +dependencies = [ + "serde", + "typeid", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -315,6 +349,29 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + [[package]] name = "hermit-abi" version = "0.4.0" @@ -389,6 +446,16 @@ version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.22" @@ -399,15 +466,17 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" name = "manul" version = "0.0.2-dev" dependencies = [ - "bincode", "criterion", "digest", "displaydoc", + "erased-serde", "impls", + "postcard", "rand", "rand_core", "serde", "serde-encoded-bytes", + "serde-persistent-deserializer", "serde_asn1_der", "serde_json", "signature", @@ -418,9 +487,9 @@ dependencies = [ name = "manul-example" version = "0.0.0" dependencies = [ - "bincode", "digest", "manul", + "postcard", "rand", "rand_core", "serde", @@ -534,6 +603,19 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "postcard" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7f0a8d620d71c457dd1d47df76bb18960378da56af4527aaa10f515eee732e" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -661,6 +743,15 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.18" @@ -676,6 +767,18 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "serde" version = "1.0.210" @@ -696,6 +799,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde-persistent-deserializer" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f52002cbca6cb233262e7ab1a04d4214c3a13f4ee93cec344286ff14b01147" +dependencies = [ + "serde", +] + [[package]] name = "serde_asn1_der" version = "0.8.0" @@ -764,6 +876,21 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "syn" version = "2.0.77" @@ -878,6 +1005,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "typeid" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" + [[package]] name = "typenum" version = "1.17.0" diff --git a/examples/Cargo.toml b/examples/Cargo.toml index dace9db..04edd4c 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -10,7 +10,7 @@ readme = "README.md" [dependencies] manul = { path = "../manul" } -bincode = { version = "2.0.0-rc.3", default-features = false, features = ["alloc", "serde"] } +postcard = { version = "1", features = ["alloc"] } serde = "1" sha3 = "0.10" rand_core = "0.6" diff --git a/examples/src/simple.rs b/examples/src/simple.rs index 40aa96d..a152086 100644 --- a/examples/src/simple.rs +++ b/examples/src/simple.rs @@ -40,6 +40,7 @@ impl ProtocolError for SimpleProtocolError { fn verify_messages_constitute_error( &self, + deserializer: &Deserializer, _echo_broadcast: &EchoBroadcast, _normal_broadcast: &NormalBroadcast, direct_message: &DirectMessage, @@ -50,12 +51,12 @@ impl ProtocolError for SimpleProtocolError { ) -> Result<(), ProtocolValidationError> { match self { SimpleProtocolError::Round1InvalidPosition => { - let _message = direct_message.deserialize::()?; + let _message = direct_message.deserialize::(deserializer)?; // Message contents would be checked here Ok(()) } SimpleProtocolError::Round2InvalidPosition => { - let _r1_message = direct_message.deserialize::()?; + let _r1_message = direct_message.deserialize::(deserializer)?; let r1_echos_serialized = combined_echos .get(&RoundId::new(1)) .ok_or_else(|| LocalError::new("Could not find combined echos for Round 1"))?; @@ -63,7 +64,7 @@ impl ProtocolError for SimpleProtocolError { // Deserialize the echos let _r1_echos = r1_echos_serialized .iter() - .map(|echo| echo.deserialize::()) + .map(|echo| echo.deserialize::(deserializer)) .collect::, _>>()?; // Message contents would be checked here @@ -78,35 +79,26 @@ impl Protocol for SimpleProtocol { type ProtocolError = SimpleProtocolError; type CorrectnessProof = (); - fn serialize(value: T) -> Result, LocalError> { - bincode::serde::encode_to_vec(value, bincode::config::standard()) - .map(|vec| vec.into()) - .map_err(|err| LocalError::new(err.to_string())) - } - - fn deserialize<'de, T: Deserialize<'de>>(bytes: &'de [u8]) -> Result { - bincode::serde::decode_borrowed_from_slice(bytes, bincode::config::standard()) - .map_err(|err| DeserializationError::new(err.to_string())) - } - fn verify_direct_message_is_invalid( + deserializer: &Deserializer, round_id: RoundId, message: &DirectMessage, ) -> Result<(), MessageValidationError> { match round_id { - r if r == RoundId::new(1) => message.verify_is_not::(), - r if r == RoundId::new(2) => message.verify_is_not::(), + r if r == RoundId::new(1) => message.verify_is_not::(deserializer), + r if r == RoundId::new(2) => message.verify_is_not::(deserializer), _ => Err(MessageValidationError::InvalidEvidence("Invalid round number".into())), } } fn verify_echo_broadcast_is_invalid( + deserializer: &Deserializer, round_id: RoundId, message: &EchoBroadcast, ) -> Result<(), MessageValidationError> { match round_id { r if r == RoundId::new(1) => message.verify_is_some(), - r if r == RoundId::new(2) => message.verify_is_not::(), + r if r == RoundId::new(2) => message.verify_is_not::(deserializer), _ => Err(MessageValidationError::InvalidEvidence("Invalid round number".into())), } } @@ -195,7 +187,11 @@ impl Round for Round1 { &self.context.other_ids } - fn make_normal_broadcast(&self, _rng: &mut impl CryptoRngCore) -> Result { + fn make_normal_broadcast( + &self, + _rng: &mut impl CryptoRngCore, + serializer: &Serializer, + ) -> Result { debug!("{:?}: making normal broadcast", self.context.id); let message = Round1Broadcast { @@ -203,22 +199,27 @@ impl Round for Round1 { my_position: self.context.ids_to_positions[&self.context.id], }; - Self::serialize_normal_broadcast(message) + NormalBroadcast::new(serializer, message) } - fn make_echo_broadcast(&self, _rng: &mut impl CryptoRngCore) -> Result { + fn make_echo_broadcast( + &self, + _rng: &mut impl CryptoRngCore, + serializer: &Serializer, + ) -> Result { debug!("{:?}: making echo broadcast", self.context.id); let message = Round1Echo { my_position: self.context.ids_to_positions[&self.context.id], }; - Self::serialize_echo_broadcast(message) + EchoBroadcast::new(serializer, message) } fn make_direct_message( &self, _rng: &mut impl CryptoRngCore, + serializer: &Serializer, destination: &Id, ) -> Result { debug!("{:?}: making direct message for {:?}", self.context.id, destination); @@ -227,13 +228,13 @@ impl Round for Round1 { my_position: self.context.ids_to_positions[&self.context.id], your_position: self.context.ids_to_positions[destination], }; - let dm = Self::serialize_direct_message(message)?; - Ok(dm) + DirectMessage::new(serializer, message) } fn receive_message( &self, _rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, from: &Id, echo_broadcast: EchoBroadcast, normal_broadcast: NormalBroadcast, @@ -241,9 +242,9 @@ impl Round for Round1 { ) -> Result> { debug!("{:?}: receiving message from {:?}", self.context.id, from); - let _echo = echo_broadcast.deserialize::()?; - let _normal = normal_broadcast.deserialize::()?; - let message = direct_message.deserialize::()?; + let _echo = echo_broadcast.deserialize::(deserializer)?; + let _normal = normal_broadcast.deserialize::(deserializer)?; + let message = direct_message.deserialize::(deserializer)?; debug!("{:?}: received message: {:?}", self.context.id, message); @@ -312,11 +313,10 @@ impl Round for Round2 { &self.context.other_ids } - // Does not send echo broadcasts - fn make_direct_message( &self, _rng: &mut impl CryptoRngCore, + serializer: &Serializer, destination: &Id, ) -> Result { debug!("{:?}: making direct message for {:?}", self.context.id, destination); @@ -325,13 +325,13 @@ impl Round for Round2 { my_position: self.context.ids_to_positions[&self.context.id], your_position: self.context.ids_to_positions[destination], }; - let dm = Self::serialize_direct_message(message)?; - Ok(dm) + DirectMessage::new(serializer, message) } fn receive_message( &self, _rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, from: &Id, echo_broadcast: EchoBroadcast, normal_broadcast: NormalBroadcast, @@ -342,7 +342,7 @@ impl Round for Round2 { echo_broadcast.assert_is_none()?; normal_broadcast.assert_is_none()?; - let message = direct_message.deserialize::()?; + let message = direct_message.deserialize::(deserializer)?; debug!("{:?}: received message: {:?}", self.context.id, message); @@ -391,7 +391,7 @@ mod tests { use manul::{ session::{signature::Keypair, SessionOutcome}, - testing::{run_sync, TestSessionParams, TestSigner, TestVerifier}, + testing::{run_sync, BinaryFormat, TestSessionParams, TestSigner, TestVerifier}, }; use rand_core::OsRng; use tracing_subscriber::EnvFilter; @@ -421,7 +421,7 @@ mod tests { .with_env_filter(EnvFilter::from_default_env()) .finish(); let reports = tracing::subscriber::with_default(my_subscriber, || { - run_sync::, TestSessionParams>(&mut OsRng, inputs).unwrap() + run_sync::, TestSessionParams>(&mut OsRng, inputs).unwrap() }); for (_id, report) in reports { diff --git a/examples/src/simple_malicious.rs b/examples/src/simple_malicious.rs index 6977889..b4ecae5 100644 --- a/examples/src/simple_malicious.rs +++ b/examples/src/simple_malicious.rs @@ -4,10 +4,13 @@ use core::fmt::Debug; use manul::{ protocol::{ Artifact, DirectMessage, FinalizeError, FinalizeOutcome, FirstRound, LocalError, Payload, ProtocolMessagePart, - Round, + Round, Serializer, }, session::signature::Keypair, - testing::{round_override, run_sync, RoundOverride, RoundWrapper, TestSessionParams, TestSigner, TestVerifier}, + testing::{ + round_override, run_sync, BinaryFormat, RoundOverride, RoundWrapper, TestSessionParams, TestSigner, + TestVerifier, + }, }; use rand_core::{CryptoRngCore, OsRng}; use tracing_subscriber::EnvFilter; @@ -60,19 +63,22 @@ impl FirstRound for Malicio } impl RoundOverride for MaliciousRound1 { - fn make_direct_message(&self, rng: &mut impl CryptoRngCore, destination: &Id) -> Result { + fn make_direct_message( + &self, + rng: &mut impl CryptoRngCore, + serializer: &Serializer, + destination: &Id, + ) -> Result { if matches!(self.behavior, Behavior::SerializedGarbage) { - let dm = DirectMessage::new::<>::Protocol, _>(&[99u8]).unwrap(); - Ok(dm) + DirectMessage::new(serializer, [99u8]) } else if matches!(self.behavior, Behavior::AttributableFailure) { let message = Round1Message { my_position: self.round.context.ids_to_positions[&self.round.context.id], your_position: self.round.context.ids_to_positions[&self.round.context.id], }; - let dm = DirectMessage::new::<>::Protocol, _>(&message)?; - Ok(dm) + DirectMessage::new(serializer, message) } else { - self.inner_round_ref().make_direct_message(rng, destination) + self.inner_round_ref().make_direct_message(rng, serializer, destination) } } @@ -120,16 +126,20 @@ impl RoundWrapper for Malic } impl RoundOverride for MaliciousRound2 { - fn make_direct_message(&self, rng: &mut impl CryptoRngCore, destination: &Id) -> Result { + fn make_direct_message( + &self, + rng: &mut impl CryptoRngCore, + serializer: &Serializer, + destination: &Id, + ) -> Result { if matches!(self.behavior, Behavior::AttributableFailureRound2) { let message = Round2Message { my_position: self.round.context.ids_to_positions[&self.round.context.id], your_position: self.round.context.ids_to_positions[&self.round.context.id], }; - let dm = DirectMessage::new::<>::Protocol, _>(&message)?; - Ok(dm) + DirectMessage::new(serializer, message) } else { - self.inner_round_ref().make_direct_message(rng, destination) + self.inner_round_ref().make_direct_message(rng, serializer, destination) } } } @@ -167,7 +177,7 @@ fn serialized_garbage() { .with_env_filter(EnvFilter::from_default_env()) .finish(); let mut reports = tracing::subscriber::with_default(my_subscriber, || { - run_sync::, TestSessionParams>(&mut OsRng, run_inputs).unwrap() + run_sync::, TestSessionParams>(&mut OsRng, run_inputs).unwrap() }); let v0 = signers[0].verifying_key(); @@ -213,7 +223,7 @@ fn attributable_failure() { .with_env_filter(EnvFilter::from_default_env()) .finish(); let mut reports = tracing::subscriber::with_default(my_subscriber, || { - run_sync::, TestSessionParams>(&mut OsRng, run_inputs).unwrap() + run_sync::, TestSessionParams>(&mut OsRng, run_inputs).unwrap() }); let v0 = signers[0].verifying_key(); @@ -259,7 +269,7 @@ fn attributable_failure_round2() { .with_env_filter(EnvFilter::from_default_env()) .finish(); let mut reports = tracing::subscriber::with_default(my_subscriber, || { - run_sync::, TestSessionParams>(&mut OsRng, run_inputs).unwrap() + run_sync::, TestSessionParams>(&mut OsRng, run_inputs).unwrap() }); let v0 = signers[0].verifying_key(); diff --git a/examples/tests/async_runner.rs b/examples/tests/async_runner.rs index 7e52af0..20bccc2 100644 --- a/examples/tests/async_runner.rs +++ b/examples/tests/async_runner.rs @@ -10,7 +10,7 @@ use manul::{ signature::Keypair, CanFinalize, LocalError, MessageBundle, RoundOutcome, Session, SessionId, SessionParameters, SessionReport, }, - testing::{TestSessionParams, TestSigner}, + testing::{BinaryFormat, TestSessionParams, TestSigner}, }; use manul_example::simple::{Inputs, Round1, SimpleProtocol}; use rand::Rng; @@ -40,8 +40,8 @@ async fn run_session( session: Session, ) -> Result, LocalError> where - P: 'static + Protocol, - SP: 'static + SessionParameters + Debug, + P: Protocol, + SP: SessionParameters + Debug, { let rng = &mut OsRng; @@ -155,7 +155,7 @@ async fn message_dispatcher( txs: BTreeMap>>, rx: mpsc::Receiver>, ) where - SP: SessionParameters, + SP: SessionParameters + Debug, { let mut rx = rx; let mut messages = Vec::>::new(); @@ -196,8 +196,8 @@ async fn message_dispatcher( async fn run_nodes(sessions: Vec>) -> Vec> where - P: 'static + Protocol + Send, - SP: 'static + SessionParameters + Debug, + P: Protocol + Send, + SP: SessionParameters + Debug, P::Result: Send, SP::Signer: Send, { @@ -241,7 +241,7 @@ where #[tokio::test] async fn async_run() { // The kind of Session we need to run the `SimpleProtocol`. - type SimpleSession = Session; + type SimpleSession = Session>; // Create 4 parties let signers = (0..3).map(TestSigner::new).collect::>(); @@ -249,7 +249,7 @@ async fn async_run() { .iter() .map(|signer| signer.verifying_key()) .collect::>(); - let session_id = SessionId::random::(&mut OsRng); + let session_id = SessionId::random::>(&mut OsRng); // Create 4 `Session`s let sessions = signers diff --git a/manul/Cargo.toml b/manul/Cargo.toml index 7f1c8f2..3ecc86d 100644 --- a/manul/Cargo.toml +++ b/manul/Cargo.toml @@ -12,24 +12,30 @@ categories = ["cryptography", "no-std"] [dependencies] serde = { version = "1", default-features = false, features = ["alloc", "serde_derive"] } +erased-serde = { version = "0.4", default-features = false, features = ["alloc"] } serde-encoded-bytes = { version = "0.1", default-features = false, features = ["hex", "base64"] } digest = { version = "0.10", default-features = false } signature = { version = "2", default-features = false, features = ["digest", "rand_core"] } rand_core = { version = "0.6.4", default-features = false } tracing = { version = "0.1", default-features = false } displaydoc = { version = "0.2", default-features = false } + rand = { version = "0.8", default-features = false, optional = true } +serde-persistent-deserializer = { version = "0.3", optional = true } +postcard = { version = "1", default-features = false, features = ["alloc"], optional = true } +serde_json = { version = "1", default-features = false, features = ["alloc"], optional = true } [dev-dependencies] impls = "1" rand = { version = "0.8", default-features = false } -bincode = { version = "2.0.0-rc.3", default-features = false, features = ["alloc", "serde"] } serde_asn1_der = "0.8" -serde_json = "1" criterion = "0.5" +serde-persistent-deserializer = "0.3" +postcard = { version = "1", default-features = false, features = ["alloc"] } +serde_json = { version = "1", default-features = false, features = ["alloc"] } [features] -testing = ["rand"] +testing = ["rand", "postcard", "serde_json", "serde-persistent-deserializer"] [package.metadata.docs.rs] all-features = true diff --git a/manul/benches/empty_rounds.rs b/manul/benches/empty_rounds.rs index 632ad32..072de69 100644 --- a/manul/benches/empty_rounds.rs +++ b/manul/benches/empty_rounds.rs @@ -6,12 +6,12 @@ use core::fmt::Debug; use criterion::{criterion_group, criterion_main, Criterion}; use manul::{ protocol::{ - Artifact, DeserializationError, DirectMessage, EchoBroadcast, FinalizeError, FinalizeOutcome, FirstRound, - LocalError, NormalBroadcast, Payload, Protocol, ProtocolError, ProtocolMessagePart, ProtocolValidationError, - ReceiveError, Round, RoundId, + Artifact, Deserializer, DirectMessage, EchoBroadcast, FinalizeError, FinalizeOutcome, FirstRound, LocalError, + NormalBroadcast, Payload, Protocol, ProtocolError, ProtocolMessagePart, ProtocolValidationError, ReceiveError, + Round, RoundId, Serializer, }, session::{signature::Keypair, SessionOutcome}, - testing::{run_sync, TestSessionParams, TestSigner, TestVerifier}, + testing::{run_sync, BinaryFormat, TestSessionParams, TestSigner, TestVerifier}, }; use rand_core::{CryptoRngCore, OsRng}; use serde::{Deserialize, Serialize}; @@ -25,6 +25,7 @@ pub struct EmptyProtocolError; impl ProtocolError for EmptyProtocolError { fn verify_messages_constitute_error( &self, + _deserializer: &Deserializer, _echo_broadcast: &EchoBroadcast, _normal_broadcast: &NormalBroadcast, _direct_message: &DirectMessage, @@ -41,17 +42,6 @@ impl Protocol for EmptyProtocol { type Result = (); type ProtocolError = EmptyProtocolError; type CorrectnessProof = (); - - fn serialize(value: T) -> Result, LocalError> { - bincode::serde::encode_to_vec(value, bincode::config::standard()) - .map(|vec| vec.into()) - .map_err(|err| LocalError::new(err.to_string())) - } - - fn deserialize<'de, T: Deserialize<'de>>(bytes: &'de [u8]) -> Result { - bincode::serde::decode_borrowed_from_slice(bytes, bincode::config::standard()) - .map_err(|err| DeserializationError::new(err.to_string())) - } } #[derive(Debug)] @@ -111,9 +101,13 @@ impl Round for EmptyRound Result { + fn make_echo_broadcast( + &self, + _rng: &mut impl CryptoRngCore, + serializer: &Serializer, + ) -> Result { if self.inputs.echo { - Self::serialize_echo_broadcast(Round1EchoBroadcast) + EchoBroadcast::new(serializer, Round1EchoBroadcast) } else { Ok(EchoBroadcast::none()) } @@ -122,9 +116,10 @@ impl Round for EmptyRound Result<(DirectMessage, Option), LocalError> { - let dm = Self::serialize_direct_message(Round1DirectMessage)?; + let dm = DirectMessage::new(serializer, Round1DirectMessage)?; let artifact = Artifact::new(Round1Artifact); Ok((dm, Some(artifact))) } @@ -132,18 +127,19 @@ impl Round for EmptyRound Result> { if self.inputs.echo { - let _echo_broadcast = echo_broadcast.deserialize::()?; + let _echo_broadcast = echo_broadcast.deserialize::(deserializer)?; } else { echo_broadcast.assert_is_none()?; } normal_broadcast.assert_is_none()?; - let _direct_message = direct_message.deserialize::()?; + let _direct_message = direct_message.deserialize::(deserializer)?; Ok(Payload::new(Round1Payload)) } @@ -211,12 +207,13 @@ fn bench_empty_rounds(c: &mut Criterion) { group.bench_function("25 nodes, 5 rounds, no echo", |b| { b.iter(|| { - assert!( - run_sync::, TestSessionParams>(&mut OsRng, inputs_no_echo.clone()) - .unwrap() - .values() - .all(|report| matches!(report.outcome, SessionOutcome::Result(_))) + assert!(run_sync::, TestSessionParams>( + &mut OsRng, + inputs_no_echo.clone() ) + .unwrap() + .values() + .all(|report| matches!(report.outcome, SessionOutcome::Result(_)))) }) }); @@ -241,12 +238,13 @@ fn bench_empty_rounds(c: &mut Criterion) { group.bench_function("25 nodes, 5 rounds, echo each round", |b| { b.iter(|| { - assert!( - run_sync::, TestSessionParams>(&mut OsRng, inputs_echo.clone()) - .unwrap() - .values() - .all(|report| matches!(report.outcome, SessionOutcome::Result(_))) + assert!(run_sync::, TestSessionParams>( + &mut OsRng, + inputs_echo.clone() ) + .unwrap() + .values() + .all(|report| matches!(report.outcome, SessionOutcome::Result(_)))) }) }); diff --git a/manul/src/protocol.rs b/manul/src/protocol.rs index f4bc469..b0b421f 100644 --- a/manul/src/protocol.rs +++ b/manul/src/protocol.rs @@ -15,6 +15,7 @@ mod errors; mod message; mod object_safe; mod round; +mod serialization; pub use errors::{ DeserializationError, DirectMessageError, EchoBroadcastError, FinalizeError, LocalError, MessageValidationError, @@ -24,6 +25,7 @@ pub use message::{DirectMessage, EchoBroadcast, NormalBroadcast, ProtocolMessage pub use round::{ AnotherRound, Artifact, FinalizeOutcome, FirstRound, Payload, Protocol, ProtocolError, Round, RoundId, }; +pub use serialization::{Deserializer, Serializer}; pub(crate) use errors::ReceiveErrorType; pub(crate) use object_safe::{ObjectSafeRound, ObjectSafeRoundWrapper}; diff --git a/manul/src/protocol/message.rs b/manul/src/protocol/message.rs index 0e3201b..ccbacee 100644 --- a/manul/src/protocol/message.rs +++ b/manul/src/protocol/message.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use super::{ errors::{DirectMessageError, EchoBroadcastError, LocalError, MessageValidationError, NormalBroadcastError}, - round::Protocol, + Deserializer, Serializer, }; mod private { @@ -43,8 +43,11 @@ pub trait ProtocolMessagePart: ProtocolMessageWrapper { } /// Creates a new serialized message. - fn new(message: T) -> Result { - let payload = MessagePayload(P::serialize(message)?); + fn new(serializer: &Serializer, message: T) -> Result + where + T: 'static + Serialize, + { + let payload = MessagePayload(serializer.serialize(message)?); Ok(Self::new_inner(Some(payload))) } @@ -67,9 +70,13 @@ pub trait ProtocolMessagePart: ProtocolMessageWrapper { /// Returns `Ok(())` if the message cannot be deserialized into `T`. /// /// This is intended to be used in the implementations of - /// [`Protocol::verify_direct_message_is_invalid`] or [`Protocol::verify_echo_broadcast_is_invalid`]. - fn verify_is_not Deserialize<'de>>(&self) -> Result<(), MessageValidationError> { - if self.deserialize::().is_err() { + /// [`Protocol::verify_direct_message_is_invalid`](`crate::protocol::Protocol::verify_direct_message_is_invalid`) or + /// [`Protocol::verify_echo_broadcast_is_invalid`](`crate::protocol::Protocol::verify_echo_broadcast_is_invalid`). + fn verify_is_not<'de, T: Deserialize<'de>>( + &'de self, + deserializer: &Deserializer, + ) -> Result<(), MessageValidationError> { + if self.deserialize::(deserializer).is_err() { Ok(()) } else { Err(MessageValidationError::InvalidEvidence( @@ -81,7 +88,8 @@ pub trait ProtocolMessagePart: ProtocolMessageWrapper { /// Returns `Ok(())` if the message contains a payload. /// /// This is intended to be used in the implementations of - /// [`Protocol::verify_direct_message_is_invalid`] or [`Protocol::verify_echo_broadcast_is_invalid`]. + /// [`Protocol::verify_direct_message_is_invalid`](`crate::protocol::Protocol::verify_direct_message_is_invalid`) or + /// [`Protocol::verify_echo_broadcast_is_invalid`](`crate::protocol::Protocol::verify_echo_broadcast_is_invalid`). fn verify_is_some(&self) -> Result<(), MessageValidationError> { if self.maybe_message().is_some() { Ok(()) @@ -93,12 +101,17 @@ pub trait ProtocolMessagePart: ProtocolMessageWrapper { } /// Deserializes the message into `T`. - fn deserialize Deserialize<'de>>(&self) -> Result { + fn deserialize<'de, T>(&'de self, deserializer: &Deserializer) -> Result + where + T: Deserialize<'de>, + { let payload = self .maybe_message() .as_ref() .ok_or_else(|| "The payload is `None` and cannot be deserialized".into())?; - P::deserialize(&payload.0).map_err(|err| err.to_string().into()) + deserializer + .deserialize(&payload.0) + .map_err(|err| err.to_string().into()) } } diff --git a/manul/src/protocol/object_safe.rs b/manul/src/protocol/object_safe.rs index dfa09de..4eacf02 100644 --- a/manul/src/protocol/object_safe.rs +++ b/manul/src/protocol/object_safe.rs @@ -11,6 +11,7 @@ use super::{ errors::{FinalizeError, LocalError, ReceiveError}, message::{DirectMessage, EchoBroadcast, NormalBroadcast}, round::{Artifact, FinalizeOutcome, Payload, Protocol, Round, RoundId}, + serialization::{Deserializer, Serializer}, }; /// Since object-safe trait methods cannot take `impl CryptoRngCore` arguments, @@ -50,16 +51,26 @@ pub(crate) trait ObjectSafeRound: 'static + Send + Sync + Debug { fn make_direct_message_with_artifact( &self, rng: &mut dyn CryptoRngCore, + serializer: &Serializer, destination: &Id, ) -> Result<(DirectMessage, Option), LocalError>; - fn make_echo_broadcast(&self, rng: &mut dyn CryptoRngCore) -> Result; + fn make_echo_broadcast( + &self, + rng: &mut dyn CryptoRngCore, + serializer: &Serializer, + ) -> Result; - fn make_normal_broadcast(&self, rng: &mut dyn CryptoRngCore) -> Result; + fn make_normal_broadcast( + &self, + rng: &mut dyn CryptoRngCore, + serializer: &Serializer, + ) -> Result; fn receive_message( &self, rng: &mut dyn CryptoRngCore, + deserializer: &Deserializer, from: &Id, echo_broadcast: EchoBroadcast, normal_broadcast: NormalBroadcast, @@ -118,34 +129,50 @@ where fn make_direct_message_with_artifact( &self, rng: &mut dyn CryptoRngCore, + serializer: &Serializer, destination: &Id, ) -> Result<(DirectMessage, Option), LocalError> { let mut boxed_rng = BoxedRng(rng); self.round - .make_direct_message_with_artifact(&mut boxed_rng, destination) + .make_direct_message_with_artifact(&mut boxed_rng, serializer, destination) } - fn make_echo_broadcast(&self, rng: &mut dyn CryptoRngCore) -> Result { + fn make_echo_broadcast( + &self, + rng: &mut dyn CryptoRngCore, + serializer: &Serializer, + ) -> Result { let mut boxed_rng = BoxedRng(rng); - self.round.make_echo_broadcast(&mut boxed_rng) + self.round.make_echo_broadcast(&mut boxed_rng, serializer) } - fn make_normal_broadcast(&self, rng: &mut dyn CryptoRngCore) -> Result { + fn make_normal_broadcast( + &self, + rng: &mut dyn CryptoRngCore, + serializer: &Serializer, + ) -> Result { let mut boxed_rng = BoxedRng(rng); - self.round.make_normal_broadcast(&mut boxed_rng) + self.round.make_normal_broadcast(&mut boxed_rng, serializer) } fn receive_message( &self, rng: &mut dyn CryptoRngCore, + deserializer: &Deserializer, from: &Id, echo_broadcast: EchoBroadcast, normal_broadcast: NormalBroadcast, direct_message: DirectMessage, ) -> Result> { let mut boxed_rng = BoxedRng(rng); - self.round - .receive_message(&mut boxed_rng, from, echo_broadcast, normal_broadcast, direct_message) + self.round.receive_message( + &mut boxed_rng, + deserializer, + from, + echo_broadcast, + normal_broadcast, + direct_message, + ) } fn finalize( diff --git a/manul/src/protocol/round.rs b/manul/src/protocol/round.rs index 4b96a3a..a0d6846 100644 --- a/manul/src/protocol/round.rs +++ b/manul/src/protocol/round.rs @@ -10,11 +10,10 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::{ - errors::{ - DeserializationError, FinalizeError, LocalError, MessageValidationError, ProtocolValidationError, ReceiveError, - }, + errors::{FinalizeError, LocalError, MessageValidationError, ProtocolValidationError, ReceiveError}, message::{DirectMessage, EchoBroadcast, NormalBroadcast, ProtocolMessagePart}, object_safe::{ObjectSafeRound, ObjectSafeRoundWrapper}, + serialization::{Deserializer, Serializer}, }; /// Possible successful outcomes of [`Round::finalize`]. @@ -29,7 +28,7 @@ pub enum FinalizeOutcome { impl FinalizeOutcome where Id: 'static + Debug, - P: 'static + Protocol, + P: Protocol, { /// A helper method to create an [`AnotherRound`](`Self::AnotherRound`) variant. pub fn another_round(round: impl Round) -> Self { @@ -45,7 +44,7 @@ pub struct AnotherRound(Box AnotherRound where Id: 'static + Debug, - P: 'static + Protocol, + P: Protocol, { /// Wraps an object implementing [`Round`]. pub fn new(round: impl Round) -> Self { @@ -117,7 +116,7 @@ impl RoundId { } /// A distributed protocol. -pub trait Protocol: Debug + Sized { +pub trait Protocol: 'static + Debug + Sized { /// The successful result of an execution of this protocol. type Result: Debug; @@ -129,17 +128,12 @@ pub trait Protocol: Debug + Sized { /// It proves that the node did its job correctly, to be adjudicated by a third party. type CorrectnessProof: Send + Serialize + for<'de> Deserialize<'de> + Debug; - /// Serializes the given object into a bytestring. - fn serialize(value: T) -> Result, LocalError>; - - /// Tries to deserialize the given bytestring as an object of type `T`. - fn deserialize<'de, T: Deserialize<'de>>(bytes: &'de [u8]) -> Result; - /// Returns `Ok(())` if the given direct message cannot be deserialized /// assuming it is a direct message from the round `round_id`. /// /// Normally one would use [`DirectMessage::verify_is_not`] when implementing this. fn verify_direct_message_is_invalid( + #[allow(unused_variables)] deserializer: &Deserializer, round_id: RoundId, #[allow(unused_variables)] message: &DirectMessage, ) -> Result<(), MessageValidationError> { @@ -153,6 +147,7 @@ pub trait Protocol: Debug + Sized { /// /// Normally one would use [`EchoBroadcast::verify_is_not`] when implementing this. fn verify_echo_broadcast_is_invalid( + #[allow(unused_variables)] deserializer: &Deserializer, round_id: RoundId, #[allow(unused_variables)] message: &EchoBroadcast, ) -> Result<(), MessageValidationError> { @@ -226,6 +221,7 @@ pub trait ProtocolError: Debug + Clone + Send { #[allow(clippy::too_many_arguments)] fn verify_messages_constitute_error( &self, + deserializer: &Deserializer, echo_broadcast: &EchoBroadcast, normal_broadcast: &NormalBroadcast, direct_message: &DirectMessage, @@ -354,9 +350,10 @@ pub trait Round: 'static + Send + Sync + Debug { fn make_direct_message_with_artifact( &self, rng: &mut impl CryptoRngCore, + serializer: &Serializer, destination: &Id, ) -> Result<(DirectMessage, Option), LocalError> { - Ok((self.make_direct_message(rng, destination)?, None)) + Ok((self.make_direct_message(rng, serializer, destination)?, None)) } /// Returns the direct message to the given destination. @@ -369,6 +366,7 @@ pub trait Round: 'static + Send + Sync + Debug { fn make_direct_message( &self, #[allow(unused_variables)] rng: &mut impl CryptoRngCore, + #[allow(unused_variables)] serializer: &Serializer, #[allow(unused_variables)] destination: &Id, ) -> Result { Ok(DirectMessage::none()) @@ -385,6 +383,7 @@ pub trait Round: 'static + Send + Sync + Debug { fn make_echo_broadcast( &self, #[allow(unused_variables)] rng: &mut impl CryptoRngCore, + #[allow(unused_variables)] serializer: &Serializer, ) -> Result { Ok(EchoBroadcast::none()) } @@ -399,6 +398,7 @@ pub trait Round: 'static + Send + Sync + Debug { fn make_normal_broadcast( &self, #[allow(unused_variables)] rng: &mut impl CryptoRngCore, + #[allow(unused_variables)] serializer: &Serializer, ) -> Result { Ok(NormalBroadcast::none()) } @@ -410,6 +410,7 @@ pub trait Round: 'static + Send + Sync + Debug { fn receive_message( &self, rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, from: &Id, echo_broadcast: EchoBroadcast, normal_broadcast: NormalBroadcast, @@ -433,22 +434,4 @@ pub trait Round: 'static + Send + Sync + Debug { /// The execution layer will not call [`finalize`](`Self::finalize`) until all these nodes have responded /// (and the corresponding [`receive_message`](`Self::receive_message`) finished successfully). fn expecting_messages_from(&self) -> &BTreeSet; - - /// A convenience method to create an [`EchoBroadcast`] object - /// to return in [`make_echo_broadcast`](`Self::make_echo_broadcast`). - fn serialize_echo_broadcast(message: impl Serialize) -> Result { - EchoBroadcast::new::(message) - } - - /// A convenience method to create a [`NormalBroadcast`] object - /// to return in [`make_normal_broadcast`](`Self::make_normal_broadcast`). - fn serialize_normal_broadcast(message: impl Serialize) -> Result { - NormalBroadcast::new::(message) - } - - /// A convenience method to create a [`DirectMessage`] object - /// to return in [`make_direct_message`](`Self::make_direct_message`). - fn serialize_direct_message(message: impl Serialize) -> Result { - DirectMessage::new::(message) - } } diff --git a/manul/src/protocol/serialization.rs b/manul/src/protocol/serialization.rs new file mode 100644 index 0000000..cb0abb7 --- /dev/null +++ b/manul/src/protocol/serialization.rs @@ -0,0 +1,98 @@ +use alloc::{boxed::Box, format}; +use core::{fmt::Debug, marker::PhantomData}; + +use serde::{Deserialize, Serialize}; + +use super::errors::{DeserializationError, LocalError}; +use crate::session::WireFormat; + +// Serialization + +trait ObjectSafeSerializer: Debug { + fn serialize(&self, value: Box) -> Result, LocalError>; +} + +#[derive(Debug)] +struct SerializerWrapper(PhantomData); + +impl ObjectSafeSerializer for SerializerWrapper { + fn serialize(&self, value: Box) -> Result, LocalError> { + F::serialize(&value) + } +} + +/// A serializer for protocol messages. +#[derive(Debug)] +pub struct Serializer(Box); + +impl Serializer { + pub(crate) fn new() -> Self { + Self(Box::new(SerializerWrapper::(PhantomData))) + } + + /// Serializes a `serde`-serializable object. + pub fn serialize(&self, value: T) -> Result, LocalError> + where + T: 'static + Serialize, + { + let boxed_value: Box = Box::new(value); + self.0.serialize(boxed_value) + } +} + +// Deserialization + +#[derive(Debug)] +struct DeserializerFactoryWrapper(PhantomData); + +trait ObjectSafeDeserializerFactory: Debug { + fn make_erased_deserializer<'de>(&self, bytes: &'de [u8]) -> Box + 'de>; +} + +impl ObjectSafeDeserializerFactory for DeserializerFactoryWrapper +where + F: WireFormat, +{ + fn make_erased_deserializer<'de>(&self, bytes: &'de [u8]) -> Box + 'de> { + let deserializer = F::deserializer(bytes); + Box::new(>::erase(deserializer)) + } +} + +/// A deserializer for protocol messages. +#[derive(Debug)] +pub struct Deserializer(Box); + +impl Deserializer { + pub(crate) fn new() -> Self + where + F: WireFormat, + { + Self(Box::new(DeserializerFactoryWrapper(PhantomData::))) + } + + /// Deserializes a `serde`-deserializable object. + pub fn deserialize<'de, T>(&self, bytes: &'de [u8]) -> Result + where + T: Deserialize<'de>, + { + let mut deserializer = self.0.make_erased_deserializer(bytes); + erased_serde::deserialize::(&mut deserializer) + .map_err(|err| DeserializationError::new(format!("Deserialization error: {err:?}"))) + } +} + +#[cfg(test)] +mod tests { + use impls::impls; + + use super::{Deserializer, Serializer}; + + #[test] + fn test_concurrency_bounds() { + assert!(impls!(Serializer: Send)); + assert!(impls!(Serializer: Sync)); + assert!(impls!(Deserializer: Send)); + assert!(impls!(Deserializer: Sync)); + } +} diff --git a/manul/src/session.rs b/manul/src/session.rs index db7c68f..49bea89 100644 --- a/manul/src/session.rs +++ b/manul/src/session.rs @@ -13,11 +13,13 @@ mod message; #[allow(clippy::module_inception)] mod session; mod transcript; +mod wire_format; pub use crate::protocol::{LocalError, RemoteError}; pub use message::MessageBundle; pub use session::{CanFinalize, RoundAccumulator, RoundOutcome, Session, SessionId, SessionParameters}; pub use transcript::{SessionOutcome, SessionReport}; +pub use wire_format::WireFormat; pub(crate) use echo::EchoRoundError; diff --git a/manul/src/session/echo.rs b/manul/src/session/echo.rs index e04ccf4..05049d6 100644 --- a/manul/src/session/echo.rs +++ b/manul/src/session/echo.rs @@ -17,8 +17,8 @@ use super::{ }; use crate::{ protocol::{ - Artifact, DirectMessage, EchoBroadcast, FinalizeError, FinalizeOutcome, NormalBroadcast, ObjectSafeRound, - Payload, Protocol, ProtocolMessagePart, ReceiveError, Round, RoundId, + Artifact, Deserializer, DirectMessage, EchoBroadcast, FinalizeError, FinalizeOutcome, NormalBroadcast, + ObjectSafeRound, Payload, Protocol, ProtocolMessagePart, ReceiveError, Round, RoundId, Serializer, }, utils::SerializableMap, }; @@ -75,7 +75,7 @@ pub struct EchoRound { impl EchoRound where P: Protocol, - SP: SessionParameters, + SP: SessionParameters + Debug, { pub fn new( verifier: SP::Verifier, @@ -126,7 +126,11 @@ where &self.destinations } - fn make_normal_broadcast(&self, _rng: &mut impl CryptoRngCore) -> Result { + fn make_normal_broadcast( + &self, + _rng: &mut impl CryptoRngCore, + serializer: &Serializer, + ) -> Result { debug!("{:?}: making an echo round message", self.verifier); // Don't send our own message the second time @@ -141,8 +145,7 @@ where let message = EchoRoundMessage:: { echo_broadcasts: echo_broadcasts.into(), }; - let bc = NormalBroadcast::new::(&message)?; - Ok(bc) + NormalBroadcast::new(serializer, message) } fn expecting_messages_from(&self) -> &BTreeSet { @@ -152,6 +155,7 @@ where fn receive_message( &self, _rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, from: &SP::Verifier, echo_broadcast: EchoBroadcast, normal_broadcast: NormalBroadcast, @@ -162,7 +166,7 @@ where echo_broadcast.assert_is_none()?; direct_message.assert_is_none()?; - let message = normal_broadcast.deserialize::>()?; + let message = normal_broadcast.deserialize::>(deserializer)?; // Check that the received message contains entries from `destinations` sans `from` // It is an unprovable fault. @@ -209,7 +213,7 @@ where continue; } - let verified_echo = match echo.clone().verify::(sender) { + let verified_echo = match echo.clone().verify::(sender) { Ok(echo) => echo, Err(MessageVerificationError::Local(error)) => return Err(error.into()), // This means `from` sent us an incorrectly signed message. diff --git a/manul/src/session/evidence.rs b/manul/src/session/evidence.rs index 7474582..3912623 100644 --- a/manul/src/session/evidence.rs +++ b/manul/src/session/evidence.rs @@ -12,8 +12,9 @@ use super::{ }; use crate::{ protocol::{ - DirectMessage, DirectMessageError, EchoBroadcast, EchoBroadcastError, MessageValidationError, NormalBroadcast, - NormalBroadcastError, Protocol, ProtocolError, ProtocolMessagePart, ProtocolValidationError, RoundId, + Deserializer, DirectMessage, DirectMessageError, EchoBroadcast, EchoBroadcastError, MessageValidationError, + NormalBroadcast, NormalBroadcastError, Protocol, ProtocolError, ProtocolMessagePart, ProtocolValidationError, + RoundId, }, utils::SerializableMap, }; @@ -160,7 +161,6 @@ where evidence: EvidenceEnum::InvalidEchoPack(InvalidEchoPackEvidence { normal_broadcast, invalid_echo_sender: from, - phantom: core::marker::PhantomData, }), }), EchoRoundError::MismatchedBroadcasts { @@ -175,7 +175,6 @@ where error, we_received, echoed_to_us, - phantom: core::marker::PhantomData, }), }), } @@ -235,12 +234,13 @@ where } pub fn verify(&self, party: &SP::Verifier) -> Result<(), EvidenceError> { + let deserializer = Deserializer::new::(); match &self.evidence { - EvidenceEnum::Protocol(evidence) => evidence.verify::(party), - EvidenceEnum::InvalidDirectMessage(evidence) => evidence.verify::(party), - EvidenceEnum::InvalidEchoBroadcast(evidence) => evidence.verify::(party), + EvidenceEnum::Protocol(evidence) => evidence.verify::(party, &deserializer), + EvidenceEnum::InvalidDirectMessage(evidence) => evidence.verify::(party, &deserializer), + EvidenceEnum::InvalidEchoBroadcast(evidence) => evidence.verify::(party, &deserializer), EvidenceEnum::InvalidNormalBroadcast(evidence) => evidence.verify::(party), - EvidenceEnum::InvalidEchoPack(evidence) => evidence.verify(party), + EvidenceEnum::InvalidEchoPack(evidence) => evidence.verify(party, &deserializer), EvidenceEnum::MismatchedBroadcasts(evidence) => evidence.verify::(party), } } @@ -252,25 +252,23 @@ enum EvidenceEnum { InvalidDirectMessage(InvalidDirectMessageEvidence

), InvalidEchoBroadcast(InvalidEchoBroadcastEvidence

), InvalidNormalBroadcast(InvalidNormalBroadcastEvidence

), - InvalidEchoPack(InvalidEchoPackEvidence), - MismatchedBroadcasts(MismatchedBroadcastsEvidence

), + InvalidEchoPack(InvalidEchoPackEvidence), + MismatchedBroadcasts(MismatchedBroadcastsEvidence), } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct InvalidEchoPackEvidence { +pub struct InvalidEchoPackEvidence { normal_broadcast: SignedMessage, invalid_echo_sender: SP::Verifier, - phantom: core::marker::PhantomData

, } -impl InvalidEchoPackEvidence +impl InvalidEchoPackEvidence where - P: Protocol, SP: SessionParameters, { - fn verify(&self, verifier: &SP::Verifier) -> Result<(), EvidenceError> { - let verified = self.normal_broadcast.clone().verify::(verifier)?; - let deserialized = verified.payload().deserialize::>()?; + fn verify(&self, verifier: &SP::Verifier, deserializer: &Deserializer) -> Result<(), EvidenceError> { + let verified = self.normal_broadcast.clone().verify::(verifier)?; + let deserialized = verified.payload().deserialize::>(deserializer)?; let invalid_echo = deserialized .echo_broadcasts .get(&self.invalid_echo_sender) @@ -281,7 +279,7 @@ where )) })?; - let verified_echo = match invalid_echo.clone().verify::(&self.invalid_echo_sender) { + let verified_echo = match invalid_echo.clone().verify::(&self.invalid_echo_sender) { Ok(echo) => echo, Err(MessageVerificationError::Local(error)) => return Err(EvidenceError::Local(error)), // The message was indeed incorrectly signed - fault proven @@ -302,23 +300,19 @@ where } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MismatchedBroadcastsEvidence { +pub struct MismatchedBroadcastsEvidence { error: MismatchedBroadcastsError, we_received: SignedMessage, echoed_to_us: SignedMessage, - phantom: core::marker::PhantomData

, } -impl

MismatchedBroadcastsEvidence

-where - P: Protocol, -{ +impl MismatchedBroadcastsEvidence { fn verify(&self, verifier: &SP::Verifier) -> Result<(), EvidenceError> where SP: SessionParameters, { - let we_received = self.we_received.clone().verify::(verifier)?; - let echoed_to_us = self.echoed_to_us.clone().verify::(verifier)?; + let we_received = self.we_received.clone().verify::(verifier)?; + let echoed_to_us = self.echoed_to_us.clone().verify::(verifier)?; if we_received.metadata() == echoed_to_us.metadata() && we_received.payload() != echoed_to_us.payload() { return Ok(()); @@ -340,12 +334,13 @@ impl

InvalidDirectMessageEvidence

where P: Protocol, { - fn verify(&self, verifier: &SP::Verifier) -> Result<(), EvidenceError> + fn verify(&self, verifier: &SP::Verifier, deserializer: &Deserializer) -> Result<(), EvidenceError> where SP: SessionParameters, { - let verified_direct_message = self.direct_message.clone().verify::(verifier)?; + let verified_direct_message = self.direct_message.clone().verify::(verifier)?; Ok(P::verify_direct_message_is_invalid( + deserializer, self.direct_message.metadata().round_id(), verified_direct_message.payload(), )?) @@ -362,12 +357,13 @@ impl

InvalidEchoBroadcastEvidence

where P: Protocol, { - fn verify(&self, verifier: &SP::Verifier) -> Result<(), EvidenceError> + fn verify(&self, verifier: &SP::Verifier, deserializer: &Deserializer) -> Result<(), EvidenceError> where SP: SessionParameters, { - let verified_echo_broadcast = self.echo_broadcast.clone().verify::(verifier)?; + let verified_echo_broadcast = self.echo_broadcast.clone().verify::(verifier)?; Ok(P::verify_echo_broadcast_is_invalid( + deserializer, self.echo_broadcast.metadata().round_id(), verified_echo_broadcast.payload(), )?) @@ -388,7 +384,7 @@ where where SP: SessionParameters, { - let verified_normal_broadcast = self.normal_broadcast.clone().verify::(verifier)?; + let verified_normal_broadcast = self.normal_broadcast.clone().verify::(verifier)?; Ok(P::verify_normal_broadcast_is_invalid( self.normal_broadcast.metadata().round_id(), verified_normal_broadcast.payload(), @@ -412,17 +408,17 @@ impl

ProtocolEvidence

where P: Protocol, { - fn verify(&self, verifier: &SP::Verifier) -> Result<(), EvidenceError> + fn verify(&self, verifier: &SP::Verifier, deserializer: &Deserializer) -> Result<(), EvidenceError> where SP: SessionParameters, { let session_id = self.direct_message.metadata().session_id(); - let verified_direct_message = self.direct_message.clone().verify::(verifier)?.payload().clone(); + let verified_direct_message = self.direct_message.clone().verify::(verifier)?.payload().clone(); let mut verified_direct_messages = BTreeMap::new(); for (round_id, direct_message) in self.direct_messages.iter() { - let verified_direct_message = direct_message.clone().verify::(verifier)?; + let verified_direct_message = direct_message.clone().verify::(verifier)?; let metadata = verified_direct_message.metadata(); if metadata.session_id() != session_id || metadata.round_id() != *round_id { return Err(EvidenceError::InvalidEvidence( @@ -432,7 +428,7 @@ where verified_direct_messages.insert(*round_id, verified_direct_message.payload().clone()); } - let verified_echo_broadcast = self.echo_broadcast.clone().verify::(verifier)?.payload().clone(); + let verified_echo_broadcast = self.echo_broadcast.clone().verify::(verifier)?.payload().clone(); if self.echo_broadcast.metadata().session_id() != session_id || self.echo_broadcast.metadata().round_id() != self.direct_message.metadata().round_id() { @@ -441,12 +437,7 @@ where )); } - let verified_normal_broadcast = self - .normal_broadcast - .clone() - .verify::(verifier)? - .payload() - .clone(); + let verified_normal_broadcast = self.normal_broadcast.clone().verify::(verifier)?.payload().clone(); if self.normal_broadcast.metadata().session_id() != session_id || self.normal_broadcast.metadata().round_id() != self.direct_message.metadata().round_id() { @@ -457,7 +448,7 @@ where let mut verified_echo_broadcasts = BTreeMap::new(); for (round_id, echo_broadcast) in self.echo_broadcasts.iter() { - let verified_echo_broadcast = echo_broadcast.clone().verify::(verifier)?; + let verified_echo_broadcast = echo_broadcast.clone().verify::(verifier)?; let metadata = verified_echo_broadcast.metadata(); if metadata.session_id() != session_id || metadata.round_id() != *round_id { return Err(EvidenceError::InvalidEvidence( @@ -469,7 +460,7 @@ where let mut verified_normal_broadcasts = BTreeMap::new(); for (round_id, normal_broadcast) in self.normal_broadcasts.iter() { - let verified_normal_broadcast = normal_broadcast.clone().verify::(verifier)?; + let verified_normal_broadcast = normal_broadcast.clone().verify::(verifier)?; let metadata = verified_normal_broadcast.metadata(); if metadata.session_id() != session_id || metadata.round_id() != *round_id { return Err(EvidenceError::InvalidEvidence( @@ -481,7 +472,7 @@ where let mut combined_echos = BTreeMap::new(); for (round_id, combined_echo) in self.combined_echos.iter() { - let verified_combined_echo = combined_echo.clone().verify::(verifier)?; + let verified_combined_echo = combined_echo.clone().verify::(verifier)?; let metadata = verified_combined_echo.metadata(); if metadata.session_id() != session_id || metadata.round_id().non_echo() != *round_id { return Err(EvidenceError::InvalidEvidence( @@ -490,11 +481,11 @@ where } let echo_set = verified_combined_echo .payload() - .deserialize::>()?; + .deserialize::>(deserializer)?; let mut verified_echo_set = Vec::new(); for (other_verifier, echo_broadcast) in echo_set.echo_broadcasts.iter() { - let verified_echo_broadcast = echo_broadcast.clone().verify::(other_verifier)?; + let verified_echo_broadcast = echo_broadcast.clone().verify::(other_verifier)?; let metadata = verified_echo_broadcast.metadata(); if metadata.session_id() != session_id || metadata.round_id() != *round_id { return Err(EvidenceError::InvalidEvidence( @@ -507,6 +498,7 @@ where } Ok(self.error.verify_messages_constitute_error( + deserializer, &verified_echo_broadcast, &verified_normal_broadcast, &verified_direct_message, diff --git a/manul/src/session/message.rs b/manul/src/session/message.rs index 425dccc..3e25e39 100644 --- a/manul/src/session/message.rs +++ b/manul/src/session/message.rs @@ -8,28 +8,27 @@ use signature::{DigestVerifier, RandomizedDigestSigner}; use super::{ session::{SessionId, SessionParameters}, + wire_format::WireFormat, LocalError, }; -use crate::protocol::{DeserializationError, DirectMessage, EchoBroadcast, NormalBroadcast, Protocol, RoundId}; +use crate::protocol::{DeserializationError, DirectMessage, EchoBroadcast, NormalBroadcast, RoundId}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub(crate) struct SerializedSignature(#[serde(with = "SliceLike::")] Box<[u8]>); impl SerializedSignature { - pub fn new(signature: &SP::Signature) -> Result + pub fn new(signature: SP::Signature) -> Result where - P: Protocol, SP: SessionParameters, { - P::serialize(signature).map(Self) + SP::WireFormat::serialize(signature).map(Self) } - pub fn deserialize(&self) -> Result + pub fn deserialize(&self) -> Result where - P: Protocol, SP: SessionParameters, { - P::deserialize::(&self.0) + SP::WireFormat::deserialize::(&self.0) } } @@ -81,7 +80,7 @@ impl SignedMessage where M: Serialize, { - pub fn new( + pub fn new( rng: &mut impl CryptoRngCore, signer: &SP::Signer, session_id: &SessionId, @@ -89,18 +88,17 @@ where message: M, ) -> Result where - P: Protocol, SP: SessionParameters, { let metadata = MessageMetadata::new(session_id, round_id); let message_with_metadata = MessageWithMetadata { metadata, message }; - let message_bytes = P::serialize(&message_with_metadata)?; + let message_bytes = SP::WireFormat::serialize(&message_with_metadata)?; let digest = SP::Digest::new_with_prefix(b"SignedMessage").chain_update(message_bytes); let signature = signer .try_sign_digest_with_rng(rng, digest) .map_err(|err| LocalError::new(format!("Failed to sign: {:?}", err)))?; Ok(Self { - signature: SerializedSignature::new::(&signature)?, + signature: SerializedSignature::new::(signature)?, message_with_metadata, }) } @@ -113,16 +111,16 @@ where &self.message_with_metadata.message } - pub(crate) fn verify(self, verifier: &SP::Verifier) -> Result, MessageVerificationError> + pub(crate) fn verify(self, verifier: &SP::Verifier) -> Result, MessageVerificationError> where - P: Protocol, SP: SessionParameters, { - let message_bytes = P::serialize(&self.message_with_metadata).map_err(MessageVerificationError::Local)?; + let message_bytes = + SP::WireFormat::serialize(&self.message_with_metadata).map_err(MessageVerificationError::Local)?; let digest = SP::Digest::new_with_prefix(b"SignedMessage").chain_update(message_bytes); let signature = self .signature - .deserialize::() + .deserialize::() .map_err(|_| MessageVerificationError::InvalidSignature)?; if verifier.verify_digest(digest, &signature).is_ok() { Ok(VerifiedMessage { @@ -174,7 +172,8 @@ pub struct MessageBundle { } impl MessageBundle { - pub(crate) fn new( + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( rng: &mut impl CryptoRngCore, signer: &SP::Signer, session_id: &SessionId, @@ -184,10 +183,9 @@ impl MessageBundle { normal_broadcast: SignedMessage, ) -> Result where - P: Protocol, SP: SessionParameters, { - let direct_message = SignedMessage::new::(rng, signer, session_id, round_id, direct_message)?; + let direct_message = SignedMessage::new::(rng, signer, session_id, round_id, direct_message)?; Ok(Self { direct_message, echo_broadcast, @@ -231,14 +229,14 @@ impl CheckedMessageBundle { &self.metadata } - pub fn verify(self, verifier: &SP::Verifier) -> Result, MessageVerificationError> + pub fn verify(self, verifier: &SP::Verifier) -> Result, MessageVerificationError> where - P: Protocol, SP: SessionParameters, { - let direct_message = self.direct_message.verify::(verifier)?; - let echo_broadcast = self.echo_broadcast.verify::(verifier)?; - let normal_broadcast = self.normal_broadcast.verify::(verifier)?; + let direct_message = self.direct_message.verify::(verifier)?; + let echo_broadcast = self.echo_broadcast.verify::(verifier)?; + let normal_broadcast = self.normal_broadcast.verify::(verifier)?; + Ok(VerifiedMessageBundle { from: verifier.clone(), metadata: self.metadata, diff --git a/manul/src/session/session.rs b/manul/src/session/session.rs index d57a357..8928502 100644 --- a/manul/src/session/session.rs +++ b/manul/src/session/session.rs @@ -18,19 +18,20 @@ use super::{ evidence::Evidence, message::{MessageBundle, MessageVerificationError, SignedMessage, VerifiedMessageBundle}, transcript::{SessionOutcome, SessionReport, Transcript}, + wire_format::WireFormat, LocalError, RemoteError, }; use crate::protocol::{ - Artifact, DirectMessage, EchoBroadcast, FinalizeError, FinalizeOutcome, FirstRound, NormalBroadcast, + Artifact, Deserializer, DirectMessage, EchoBroadcast, FinalizeError, FinalizeOutcome, FirstRound, NormalBroadcast, ObjectSafeRound, ObjectSafeRoundWrapper, Payload, Protocol, ProtocolMessagePart, ReceiveError, ReceiveErrorType, - Round, RoundId, + Round, RoundId, Serializer, }; /// A set of types needed to execute a session. /// /// These will be generally determined by the user, depending on what signature type /// is used in the network in which they are running the protocol. -pub trait SessionParameters { +pub trait SessionParameters: 'static { /// The signer type. type Signer: Debug + RandomizedDigestSigner + Keypair; @@ -49,6 +50,9 @@ pub trait SessionParameters { /// The signature type corresponding to [`Signer`](`Self::Signer`) and [`Verifier`](`Self::Verifier`). type Signature: Serialize + for<'de> Deserialize<'de>; + + /// The type used to (de)serialize messages. + type WireFormat: WireFormat; } /// A session identifier shared between the parties. @@ -106,6 +110,8 @@ pub struct Session { session_id: SessionId, signer: SP::Signer, verifier: SP::Verifier, + serializer: Serializer, + deserializer: Deserializer, round: Box>, message_destinations: BTreeSet, echo_broadcast: SignedMessage, @@ -130,8 +136,8 @@ pub enum RoundOutcome { impl Session where - P: 'static + Protocol, - SP: 'static + SessionParameters + Debug, + P: Protocol, + SP: SessionParameters + Debug, { /// Initializes a new session. pub fn new( @@ -141,7 +147,7 @@ where inputs: R::Inputs, ) -> Result where - R: FirstRound + Round + 'static, + R: FirstRound + Round, { let verifier = signer.verifying_key(); let first_round = Box::new(ObjectSafeRoundWrapper::new(R::new( @@ -150,23 +156,35 @@ where verifier.clone(), inputs, )?)); - Self::new_for_next_round(rng, session_id, signer, first_round, Transcript::new()) + let serializer = Serializer::new::(); + let deserializer = Deserializer::new::(); + Self::new_for_next_round( + rng, + session_id, + signer, + serializer, + deserializer, + first_round, + Transcript::new(), + ) } fn new_for_next_round( rng: &mut impl CryptoRngCore, session_id: SessionId, signer: SP::Signer, + serializer: Serializer, + deserializer: Deserializer, round: Box>, transcript: Transcript, ) -> Result { let verifier = signer.verifying_key(); - let echo = round.make_echo_broadcast(rng)?; - let echo_broadcast = SignedMessage::new::(rng, &signer, &session_id, round.id(), echo)?; + let echo = round.make_echo_broadcast(rng, &serializer)?; + let echo_broadcast = SignedMessage::new::(rng, &signer, &session_id, round.id(), echo)?; - let normal = round.make_normal_broadcast(rng)?; - let normal_broadcast = SignedMessage::new::(rng, &signer, &session_id, round.id(), normal)?; + let normal = round.make_normal_broadcast(rng, &serializer)?; + let normal_broadcast = SignedMessage::new::(rng, &signer, &session_id, round.id(), normal)?; let message_destinations = round.message_destinations().clone(); @@ -180,6 +198,8 @@ where session_id, signer, verifier, + serializer, + deserializer, round, echo_broadcast, normal_broadcast, @@ -212,9 +232,11 @@ where rng: &mut impl CryptoRngCore, destination: &SP::Verifier, ) -> Result<(MessageBundle, ProcessedArtifact), LocalError> { - let (direct_message, artifact) = self.round.make_direct_message_with_artifact(rng, destination)?; + let (direct_message, artifact) = + self.round + .make_direct_message_with_artifact(rng, &self.serializer, destination)?; - let bundle = MessageBundle::new::( + let bundle = MessageBundle::new::( rng, &self.signer, &self.session_id, @@ -314,7 +336,7 @@ where // Verify the signature now - let verified_message = match checked_message.verify::(from) { + let verified_message = match checked_message.verify::(from) { Ok(verified_message) => verified_message, Err(MessageVerificationError::InvalidSignature) => { let err = "The signature could not be deserialized."; @@ -364,6 +386,7 @@ where ) -> ProcessedMessage { let processed = self.round.receive_message( rng, + &self.deserializer, message.from(), message.echo_broadcast().clone(), message.normal_broadcast().clone(), @@ -434,7 +457,15 @@ where accum.artifacts, ))); let cached_messages = filter_messages(accum.cached, round.id()); - let session = Session::new_for_next_round(rng, self.session_id, self.signer, round, transcript)?; + let session = Session::new_for_next_round( + rng, + self.session_id, + self.signer, + self.serializer, + self.deserializer, + round, + transcript, + )?; return Ok(RoundOutcome::AnotherRound { session, cached_messages, @@ -463,7 +494,15 @@ where .filter(|message| !transcript.is_banned(message.from())) .collect::>(); - let session = Session::new_for_next_round(rng, self.session_id, self.signer, round, transcript)?; + let session = Session::new_for_next_round( + rng, + self.session_id, + self.signer, + self.serializer, + self.deserializer, + round, + transcript, + )?; RoundOutcome::AnotherRound { cached_messages, session, @@ -732,7 +771,7 @@ fn filter_messages( #[cfg(test)] mod tests { - use alloc::{boxed::Box, collections::BTreeMap, vec::Vec}; + use alloc::{collections::BTreeMap, vec::Vec}; use impls::impls; use serde::{Deserialize, Serialize}; @@ -740,10 +779,10 @@ mod tests { use super::{MessageBundle, ProcessedArtifact, ProcessedMessage, Session, VerifiedMessageBundle}; use crate::{ protocol::{ - DeserializationError, DirectMessage, EchoBroadcast, LocalError, NormalBroadcast, Protocol, ProtocolError, + Deserializer, DirectMessage, EchoBroadcast, NormalBroadcast, Protocol, ProtocolError, ProtocolValidationError, RoundId, }, - testing::TestSessionParams, + testing::{BinaryFormat, TestSessionParams}, }; #[test] @@ -765,6 +804,7 @@ mod tests { impl ProtocolError for DummyProtocolError { fn verify_messages_constitute_error( &self, + _deserializer: &Deserializer, _echo_broadcast: &EchoBroadcast, _normal_broadcast: &NormalBroadcast, _direct_message: &DirectMessage, @@ -781,31 +821,21 @@ mod tests { type Result = (); type ProtocolError = DummyProtocolError; type CorrectnessProof = (); - fn serialize(_: T) -> Result, LocalError> - where - T: Serialize, - { - unimplemented!() - } - fn deserialize<'de, T>(_: &[u8]) -> Result - where - T: Deserialize<'de>, - { - unimplemented!() - } } + type SP = TestSessionParams; + // We need `Session` to be `Send` so that we send a `Session` object to a task // to run the loop there. - assert!(impls!(Session: Send)); + assert!(impls!(Session: Send)); // This is needed so that message processing offloaded to a task could use `&Session`. - assert!(impls!(Session: Sync)); + assert!(impls!(Session: Sync)); // These objects are sent to/from message processing tasks assert!(impls!(MessageBundle: Send)); - assert!(impls!(ProcessedArtifact: Send)); - assert!(impls!(VerifiedMessageBundle: Send)); - assert!(impls!(ProcessedMessage: Send)); + assert!(impls!(ProcessedArtifact: Send)); + assert!(impls!(VerifiedMessageBundle: Send)); + assert!(impls!(ProcessedMessage: Send)); } } diff --git a/manul/src/session/wire_format.rs b/manul/src/session/wire_format.rs new file mode 100644 index 0000000..d673d98 --- /dev/null +++ b/manul/src/session/wire_format.rs @@ -0,0 +1,45 @@ +use alloc::{boxed::Box, format}; +use core::fmt::Debug; + +use serde::{Deserialize, Serialize}; + +use crate::protocol::{DeserializationError, LocalError}; + +/* +Why the asymmetry between serialization and deserialization? + +If we had a method returning an object of a type implementing `serde::Serializer`, +we could organize the serialization in the same way as deserialization. +But libraries generally expose `T where &mut T: Serializer`, +and it's tricky to write a similar persistent wrapper as we do for the deserializer +(see https://github.com/fjarri/serde-persistent-deserializer/issues/2). + +So for serialization we have to instead type-erase the value itself and pass it somewhere +where the serializer type is known (`ObjectSafeSerializer::serialize()` impl); +but for the deserialization we instead type-erase the deserializer and pass it somewhere +the type of the target value is known (`Deserializer::deserialize()`). + +One consequence of this is the `'static` requirement for the serialized type, +because we have to put the value in a box; +if we could instead type-erase the serializer, we wouldn't need that. +*/ + +/// A (de)serializer that will be used for the protocol messages. +pub trait WireFormat: 'static + Send + Sync + Debug { + /// Serializes the given object into a bytestring. + fn serialize(value: T) -> Result, LocalError>; + + /// The deserializer type. + type Deserializer<'de>: serde::Deserializer<'de>; + + /// Creates a `serde` deserializer given a bytestring. + fn deserializer(bytes: &[u8]) -> Self::Deserializer<'_>; + + // A helper method for use on the session level when both `WireFormat` and `T` are known at the same point. + + /// Deserializes the given bytestring into `T`. + fn deserialize<'de, T: Deserialize<'de>>(bytes: &'de [u8]) -> Result { + let deserializer = Self::deserializer(bytes); + T::deserialize(deserializer).map_err(|err| DeserializationError::new(format!("Deserialization error: {err:?}"))) + } +} diff --git a/manul/src/testing.rs b/manul/src/testing.rs index 3165961..499f855 100644 --- a/manul/src/testing.rs +++ b/manul/src/testing.rs @@ -15,7 +15,9 @@ The [`run_sync()`] method is helpful to execute a protocol synchronously and col mod identity; mod macros; mod run_sync; +mod wire_format; pub use identity::{TestHasher, TestSessionParams, TestSignature, TestSigner, TestVerifier}; pub use macros::{round_override, RoundOverride, RoundWrapper}; pub use run_sync::run_sync; +pub use wire_format::{BinaryFormat, HumanReadableFormat}; diff --git a/manul/src/testing/identity.rs b/manul/src/testing/identity.rs index db3b1a1..16a380f 100644 --- a/manul/src/testing/identity.rs +++ b/manul/src/testing/identity.rs @@ -2,7 +2,7 @@ use digest::generic_array::typenum; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; -use crate::session::SessionParameters; +use crate::session::{SessionParameters, WireFormat}; /// A simple signer for testing purposes. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] @@ -89,13 +89,14 @@ impl digest::OutputSizeUser for TestHasher { /// An implementation of [`SessionParameters`] using the testing signer/verifier types. #[derive(Debug, Clone, Copy)] -pub struct TestSessionParams; +pub struct TestSessionParams(core::marker::PhantomData); -impl SessionParameters for TestSessionParams { +impl SessionParameters for TestSessionParams { type Signer = TestSigner; type Verifier = TestVerifier; type Signature = TestSignature; type Digest = TestHasher; + type WireFormat = F; } #[cfg(test)] diff --git a/manul/src/testing/macros.rs b/manul/src/testing/macros.rs index 5d6f8a0..2b411c5 100644 --- a/manul/src/testing/macros.rs +++ b/manul/src/testing/macros.rs @@ -3,7 +3,8 @@ use alloc::collections::BTreeMap; use rand_core::CryptoRngCore; use crate::protocol::{ - Artifact, DirectMessage, EchoBroadcast, FinalizeError, FinalizeOutcome, LocalError, NormalBroadcast, Payload, Round, + Artifact, DirectMessage, EchoBroadcast, FinalizeError, FinalizeOutcome, LocalError, NormalBroadcast, Payload, + Round, Serializer, }; /// A trait defining a wrapper around an existing type implementing [`Round`]. @@ -28,25 +29,39 @@ pub trait RoundOverride: RoundWrapper { fn make_direct_message_with_artifact( &self, rng: &mut impl CryptoRngCore, + serializer: &Serializer, destination: &Id, ) -> Result<(DirectMessage, Option), LocalError> { - let dm = self.make_direct_message(rng, destination)?; + let dm = self.make_direct_message(rng, serializer, destination)?; Ok((dm, None)) } /// An override for [`Round::make_direct_message`]. - fn make_direct_message(&self, rng: &mut impl CryptoRngCore, destination: &Id) -> Result { - self.inner_round_ref().make_direct_message(rng, destination) + fn make_direct_message( + &self, + rng: &mut impl CryptoRngCore, + serializer: &Serializer, + destination: &Id, + ) -> Result { + self.inner_round_ref().make_direct_message(rng, serializer, destination) } /// An override for [`Round::make_echo_broadcast`]. - fn make_echo_broadcast(&self, rng: &mut impl CryptoRngCore) -> Result { - self.inner_round_ref().make_echo_broadcast(rng) + fn make_echo_broadcast( + &self, + rng: &mut impl CryptoRngCore, + serializer: &Serializer, + ) -> Result { + self.inner_round_ref().make_echo_broadcast(rng, serializer) } /// An override for [`Round::make_normal_broadcast`]. - fn make_normal_broadcast(&self, rng: &mut impl CryptoRngCore) -> Result { - self.inner_round_ref().make_normal_broadcast(rng) + fn make_normal_broadcast( + &self, + rng: &mut impl CryptoRngCore, + serializer: &Serializer, + ) -> Result { + self.inner_round_ref().make_normal_broadcast(rng, serializer) } /// An override for [`Round::finalize`]. @@ -96,43 +111,48 @@ macro_rules! round_override { fn make_direct_message( &self, rng: &mut impl CryptoRngCore, + serializer: &$crate::protocol::Serializer, destination: &Id, ) -> Result<$crate::protocol::DirectMessage, $crate::protocol::LocalError> { - >::make_direct_message(self, rng, destination) + >::make_direct_message(self, rng, serializer, destination) } fn make_direct_message_with_artifact( &self, rng: &mut impl CryptoRngCore, + serializer: &$crate::protocol::Serializer, destination: &Id, ) -> Result<($crate::protocol::DirectMessage, Option<$crate::protocol::Artifact>), $crate::protocol::LocalError> { - >::make_direct_message_with_artifact(self, rng, destination) + >::make_direct_message_with_artifact(self, rng, serializer, destination) } fn make_echo_broadcast( &self, rng: &mut impl CryptoRngCore, + serializer: &$crate::protocol::Serializer, ) -> Result<$crate::protocol::EchoBroadcast, $crate::protocol::LocalError> { - >::make_echo_broadcast(self, rng) + >::make_echo_broadcast(self, rng, serializer) } fn make_normal_broadcast( &self, rng: &mut impl CryptoRngCore, + serializer: &$crate::protocol::Serializer, ) -> Result<$crate::protocol::NormalBroadcast, $crate::protocol::LocalError> { - >::make_normal_broadcast(self, rng) + >::make_normal_broadcast(self, rng, serializer) } fn receive_message( &self, rng: &mut impl CryptoRngCore, + deserializer: &$crate::protocol::Deserializer, from: &Id, echo_broadcast: $crate::protocol::EchoBroadcast, normal_broadcast: $crate::protocol::NormalBroadcast, direct_message: $crate::protocol::DirectMessage, ) -> Result<$crate::protocol::Payload, $crate::protocol::ReceiveError> { self.inner_round_ref() - .receive_message(rng, from, echo_broadcast, normal_broadcast, direct_message) + .receive_message(rng, deserializer, from, echo_broadcast, normal_broadcast, direct_message) } fn finalize( diff --git a/manul/src/testing/wire_format.rs b/manul/src/testing/wire_format.rs new file mode 100644 index 0000000..ec9e4ae --- /dev/null +++ b/manul/src/testing/wire_format.rs @@ -0,0 +1,69 @@ +use alloc::{boxed::Box, string::ToString}; + +use serde::Serialize; +use serde_persistent_deserializer::{AsTransientDeserializer, PersistentDeserializer}; + +use crate::{protocol::LocalError, session::WireFormat}; + +/// A binary format to use in tests. +#[derive(Debug, Clone, Copy)] +pub struct BinaryFormat; + +/// A wrapper for a Postcard deserializer. +#[allow(missing_debug_implementations)] +pub struct PostcardDeserializer<'de>(postcard::Deserializer<'de, postcard::de_flavors::Slice<'de>>); + +impl<'de> AsTransientDeserializer<'de> for PostcardDeserializer<'de> { + type Error = postcard::Error; + + fn as_transient_deserializer<'a>(&'a mut self) -> impl serde::Deserializer<'de, Error = Self::Error> { + &mut self.0 + } +} + +impl WireFormat for BinaryFormat { + fn serialize(value: T) -> Result, LocalError> { + postcard::to_allocvec(&value) + .map(|vec| vec.into()) + .map_err(|err| LocalError::new(err.to_string())) + } + + type Deserializer<'de> = PersistentDeserializer>; + + fn deserializer(bytes: &[u8]) -> Self::Deserializer<'_> { + let flavor = postcard::de_flavors::Slice::new(bytes); + let deserializer = postcard::Deserializer::from_flavor(flavor); + PersistentDeserializer::new(PostcardDeserializer(deserializer)) + } +} + +/// A human-readable format to use in tests. +#[derive(Debug, Clone, Copy)] +pub struct HumanReadableFormat; + +/// A wrapper for a JSON deserializer. +#[allow(missing_debug_implementations)] +pub struct JSONDeserializer<'de>(serde_json::Deserializer>); + +impl<'de> AsTransientDeserializer<'de> for JSONDeserializer<'de> { + type Error = serde_json::Error; + + fn as_transient_deserializer<'a>(&'a mut self) -> impl serde::Deserializer<'de, Error = Self::Error> { + &mut self.0 + } +} + +impl WireFormat for HumanReadableFormat { + fn serialize(value: T) -> Result, LocalError> { + serde_json::to_vec(&value) + .map(|vec| vec.into()) + .map_err(|err| LocalError::new(err.to_string())) + } + + type Deserializer<'de> = PersistentDeserializer>; + + fn deserializer(bytes: &[u8]) -> Self::Deserializer<'_> { + let deserializer = serde_json::Deserializer::from_slice(bytes); + PersistentDeserializer::new(JSONDeserializer(deserializer)) + } +}