From 7e80c65e9140e3a44300169a8d8c49e36c9ac3bb Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Fri, 25 Oct 2024 13:09:19 -0700 Subject: [PATCH] Add normal broadcasts along with echo broadcasts --- examples/src/simple.rs | 29 +++++++- manul/benches/empty_rounds.rs | 8 ++- manul/src/protocol.rs | 4 +- manul/src/protocol/errors.rs | 22 ++++++ manul/src/protocol/message.rs | 20 +++++- manul/src/protocol/object_safe.rs | 13 +++- manul/src/protocol/round.rs | 56 +++++++++++++-- manul/src/session/echo.rs | 20 +++--- manul/src/session/evidence.rs | 110 ++++++++++++++++++++++++++---- manul/src/session/message.rs | 29 +++++++- manul/src/session/session.rs | 51 +++++++++++--- manul/src/session/transcript.rs | 30 +++++++- manul/src/testing/macros.rs | 17 ++++- 13 files changed, 355 insertions(+), 54 deletions(-) diff --git a/examples/src/simple.rs b/examples/src/simple.rs index f94ee71..40aa96d 100644 --- a/examples/src/simple.rs +++ b/examples/src/simple.rs @@ -27,6 +27,10 @@ impl ProtocolError for SimpleProtocolError { BTreeSet::new() } + fn required_normal_broadcasts(&self) -> BTreeSet { + BTreeSet::new() + } + fn required_combined_echos(&self) -> BTreeSet { match self { Self::Round1InvalidPosition => BTreeSet::new(), @@ -37,8 +41,10 @@ impl ProtocolError for SimpleProtocolError { fn verify_messages_constitute_error( &self, _echo_broadcast: &EchoBroadcast, + _normal_broadcast: &NormalBroadcast, direct_message: &DirectMessage, _echo_broadcasts: &BTreeMap, + _normal_broadcasts: &BTreeMap, _direct_messages: &BTreeMap, combined_echos: &BTreeMap>, ) -> Result<(), ProtocolValidationError> { @@ -134,6 +140,12 @@ struct Round1Echo { my_position: u8, } +#[derive(Serialize, Deserialize)] +struct Round1Broadcast { + x: u8, + my_position: u8, +} + struct Round1Payload { x: u8, } @@ -183,6 +195,17 @@ impl Round for Round1 { &self.context.other_ids } + fn make_normal_broadcast(&self, _rng: &mut impl CryptoRngCore) -> Result { + debug!("{:?}: making normal broadcast", self.context.id); + + let message = Round1Broadcast { + x: 0, + my_position: self.context.ids_to_positions[&self.context.id], + }; + + Self::serialize_normal_broadcast(message) + } + fn make_echo_broadcast(&self, _rng: &mut impl CryptoRngCore) -> Result { debug!("{:?}: making echo broadcast", self.context.id); @@ -213,11 +236,13 @@ impl Round for Round1 { _rng: &mut impl CryptoRngCore, from: &Id, echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, direct_message: DirectMessage, ) -> Result> { debug!("{:?}: receiving message from {:?}", self.context.id, from); let _echo = echo_broadcast.deserialize::()?; + let _normal = normal_broadcast.deserialize::()?; let message = direct_message.deserialize::()?; debug!("{:?}: received message: {:?}", self.context.id, message); @@ -296,7 +321,7 @@ impl Round for Round2 { ) -> Result { debug!("{:?}: making direct message for {:?}", self.context.id, destination); - let message = Round1Message { + let message = Round2Message { my_position: self.context.ids_to_positions[&self.context.id], your_position: self.context.ids_to_positions[destination], }; @@ -309,11 +334,13 @@ impl Round for Round2 { _rng: &mut impl CryptoRngCore, from: &Id, echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, direct_message: DirectMessage, ) -> Result> { debug!("{:?}: receiving message from {:?}", self.context.id, from); echo_broadcast.assert_is_none()?; + normal_broadcast.assert_is_none()?; let message = direct_message.deserialize::()?; diff --git a/manul/benches/empty_rounds.rs b/manul/benches/empty_rounds.rs index 551f626..2a04885 100644 --- a/manul/benches/empty_rounds.rs +++ b/manul/benches/empty_rounds.rs @@ -7,8 +7,8 @@ use criterion::{criterion_group, criterion_main, Criterion}; use manul::{ protocol::{ Artifact, DeserializationError, DirectMessage, EchoBroadcast, FinalizeError, FinalizeOutcome, FirstRound, - LocalError, Payload, Protocol, ProtocolError, ProtocolMessage, ProtocolValidationError, ReceiveError, Round, - RoundId, + LocalError, NormalBroadcast, Payload, Protocol, ProtocolError, ProtocolMessage, ProtocolValidationError, + ReceiveError, Round, RoundId, }, session::{signature::Keypair, SessionOutcome}, testing::{run_sync, TestSessionParams, TestSigner, TestVerifier}, @@ -26,8 +26,10 @@ impl ProtocolError for EmptyProtocolError { fn verify_messages_constitute_error( &self, _echo_broadcast: &EchoBroadcast, + _normal_broadcast: &NormalBroadcast, _direct_message: &DirectMessage, _echo_broadcasts: &BTreeMap, + _normal_broadcasts: &BTreeMap, _direct_messages: &BTreeMap, _combined_echos: &BTreeMap>, ) -> Result<(), ProtocolValidationError> { @@ -132,6 +134,7 @@ impl Round for EmptyRound Result> { if self.inputs.echo { @@ -139,6 +142,7 @@ impl Round for EmptyRound()?; Ok(Payload::new(Round1Payload)) } diff --git a/manul/src/protocol.rs b/manul/src/protocol.rs index 192a22d..9ab3900 100644 --- a/manul/src/protocol.rs +++ b/manul/src/protocol.rs @@ -18,9 +18,9 @@ mod round; pub use errors::{ DeserializationError, DirectMessageError, EchoBroadcastError, FinalizeError, LocalError, MessageValidationError, - ProtocolValidationError, ReceiveError, RemoteError, + NormalBroadcastError, ProtocolValidationError, ReceiveError, RemoteError, }; -pub use message::{DirectMessage, EchoBroadcast, ProtocolMessage}; +pub use message::{DirectMessage, EchoBroadcast, NormalBroadcast, ProtocolMessage}; pub use round::{ AnotherRound, Artifact, FinalizeOutcome, FirstRound, Payload, Protocol, ProtocolError, Round, RoundId, }; diff --git a/manul/src/protocol/errors.rs b/manul/src/protocol/errors.rs index 36ee906..a77ecc3 100644 --- a/manul/src/protocol/errors.rs +++ b/manul/src/protocol/errors.rs @@ -40,6 +40,8 @@ pub(crate) enum ReceiveErrorType { InvalidDirectMessage(DirectMessageError), /// The given echo broadcast cannot be deserialized. InvalidEchoBroadcast(EchoBroadcastError), + /// The given normal broadcast cannot be deserialized. + InvalidNormalBroadcast(NormalBroadcastError), /// A provable error occurred. Protocol(P::ProtocolError), /// An unprovable error occurred. @@ -113,6 +115,15 @@ where } } +impl From for ReceiveError +where + P: Protocol, +{ + fn from(error: NormalBroadcastError) -> Self { + Self(ReceiveErrorType::InvalidNormalBroadcast(error)) + } +} + /// An error that can occur during [`Round::finalize`](`super::Round::finalize`). #[derive(Debug)] pub enum FinalizeError { @@ -208,3 +219,14 @@ impl From for EchoBroadcastError { Self(message) } } + +/// An error during deserialization of a normal broadcast. +#[derive(displaydoc::Display, Debug, Clone)] +#[displaydoc("Normal broadcast error: {0}")] +pub struct NormalBroadcastError(String); + +impl From for NormalBroadcastError { + fn from(message: String) -> Self { + Self(message) + } +} diff --git a/manul/src/protocol/message.rs b/manul/src/protocol/message.rs index 916925c..01bcda6 100644 --- a/manul/src/protocol/message.rs +++ b/manul/src/protocol/message.rs @@ -3,7 +3,7 @@ use alloc::string::{String, ToString}; use serde::{Deserialize, Serialize}; use super::{ - errors::{DirectMessageError, EchoBroadcastError, LocalError, MessageValidationError}, + errors::{DirectMessageError, EchoBroadcastError, LocalError, MessageValidationError, NormalBroadcastError}, round::Protocol, }; @@ -137,3 +137,21 @@ impl ProtocolMessageWrapper for EchoBroadcast { impl ProtocolMessage for EchoBroadcast { type Error = EchoBroadcastError; } + +/// A serialized regular (non-echo) broadcast. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct NormalBroadcast(Option); + +impl ProtocolMessageWrapper for NormalBroadcast { + fn new_inner(maybe_message: Option) -> Self { + Self(maybe_message) + } + + fn maybe_message(&self) -> &Option { + &self.0 + } +} + +impl ProtocolMessage for NormalBroadcast { + type Error = NormalBroadcastError; +} diff --git a/manul/src/protocol/object_safe.rs b/manul/src/protocol/object_safe.rs index bc416c9..dfa09de 100644 --- a/manul/src/protocol/object_safe.rs +++ b/manul/src/protocol/object_safe.rs @@ -9,7 +9,7 @@ use rand_core::{CryptoRng, CryptoRngCore, RngCore}; use super::{ errors::{FinalizeError, LocalError, ReceiveError}, - message::{DirectMessage, EchoBroadcast}, + message::{DirectMessage, EchoBroadcast, NormalBroadcast}, round::{Artifact, FinalizeOutcome, Payload, Protocol, Round, RoundId}, }; @@ -55,11 +55,14 @@ pub(crate) trait ObjectSafeRound: 'static + Send + Sync + Debug { fn make_echo_broadcast(&self, rng: &mut dyn CryptoRngCore) -> Result; + fn make_normal_broadcast(&self, rng: &mut dyn CryptoRngCore) -> Result; + fn receive_message( &self, rng: &mut dyn CryptoRngCore, from: &Id, echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, direct_message: DirectMessage, ) -> Result>; @@ -127,16 +130,22 @@ where self.round.make_echo_broadcast(&mut boxed_rng) } + fn make_normal_broadcast(&self, rng: &mut dyn CryptoRngCore) -> Result { + let mut boxed_rng = BoxedRng(rng); + self.round.make_normal_broadcast(&mut boxed_rng) + } + fn receive_message( &self, rng: &mut dyn CryptoRngCore, 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, direct_message) + .receive_message(&mut boxed_rng, from, echo_broadcast, normal_broadcast, direct_message) } fn finalize( diff --git a/manul/src/protocol/round.rs b/manul/src/protocol/round.rs index 4d287fc..5005509 100644 --- a/manul/src/protocol/round.rs +++ b/manul/src/protocol/round.rs @@ -13,7 +13,7 @@ use super::{ errors::{ DeserializationError, FinalizeError, LocalError, MessageValidationError, ProtocolValidationError, ReceiveError, }, - message::{DirectMessage, EchoBroadcast, ProtocolMessage}, + message::{DirectMessage, EchoBroadcast, NormalBroadcast, ProtocolMessage}, object_safe::{ObjectSafeRound, ObjectSafeRoundWrapper}, }; @@ -144,7 +144,7 @@ pub trait Protocol: Debug + Sized { #[allow(unused_variables)] message: &DirectMessage, ) -> Result<(), MessageValidationError> { Err(MessageValidationError::InvalidEvidence(format!( - "There are no direct messages in {round_id:?}" + "Invalid round number: {round_id:?}" ))) } @@ -157,7 +157,20 @@ pub trait Protocol: Debug + Sized { #[allow(unused_variables)] message: &EchoBroadcast, ) -> Result<(), MessageValidationError> { Err(MessageValidationError::InvalidEvidence(format!( - "There are no echo broadcasts in {round_id:?}" + "Invalid round number: {round_id:?}" + ))) + } + + /// Returns `Ok(())` if the given echo broadcast cannot be deserialized + /// assuming it is an echo broadcast from the round `round_id`. + /// + /// Normally one would use [`EchoBroadcast::verify_is_not`] when implementing this. + fn verify_normal_broadcast_is_invalid( + round_id: RoundId, + #[allow(unused_variables)] message: &NormalBroadcast, + ) -> Result<(), MessageValidationError> { + Err(MessageValidationError::InvalidEvidence(format!( + "Invalid round number: {round_id:?}" ))) } } @@ -181,6 +194,13 @@ pub trait ProtocolError: Debug + Clone + Send { BTreeSet::new() } + /// The rounds normal broadcasts from which are required to prove malicious behavior for this error. + /// + /// **Note:** Should not include the round where the error happened. + fn required_normal_broadcasts(&self) -> BTreeSet { + BTreeSet::new() + } + /// The rounds combined echos from which are required to prove malicious behavior for this error. /// /// **Note:** Should not include the round where the error happened. @@ -203,11 +223,14 @@ pub trait ProtocolError: Debug + Clone + Send { /// [`required_echo_broadcasts`](`Self::required_echo_broadcasts`). /// `combined_echos` are bundled echos from other parties from the previous rounds, /// as requested by [`required_combined_echos`](`Self::required_combined_echos`). + #[allow(clippy::too_many_arguments)] fn verify_messages_constitute_error( &self, echo_broadcast: &EchoBroadcast, + normal_broadcast: &NormalBroadcast, direct_message: &DirectMessage, echo_broadcasts: &BTreeMap, + normal_broadcasts: &BTreeMap, direct_messages: &BTreeMap, combined_echos: &BTreeMap>, ) -> Result<(), ProtocolValidationError>; @@ -342,6 +365,7 @@ pub trait Round: 'static + Send + Sync + Debug { /// only via [`make_direct_message_with_artifact`](`Self::make_direct_message_with_artifact`). /// /// Return [`DirectMessage::none`] if this round does not send direct messages. + /// This is also the blanket implementation. fn make_direct_message( &self, #[allow(unused_variables)] rng: &mut impl CryptoRngCore, @@ -350,9 +374,10 @@ pub trait Round: 'static + Send + Sync + Debug { Ok(DirectMessage::none()) } - /// Returns the echo broadcast for this round, or `None` if the round does not require echo-broadcasting. + /// Returns the echo broadcast for this round. /// - /// Returns `None` if not implemented. + /// Return [`EchoBroadcast::none`] if this round does not send echo-broadcast messages. + /// This is also the blanket implementation. /// /// The execution layer will guarantee that all the destinations are sure they all received the same broadcast. /// This also means that a message with the broadcasts from all nodes signed by each node is available @@ -364,6 +389,20 @@ pub trait Round: 'static + Send + Sync + Debug { Ok(EchoBroadcast::none()) } + /// Returns the normal broadcast for this round. + /// + /// Return [`NormalBroadcast::none`] if this round does not send normal broadcast messages. + /// This is also the blanket implementation. + /// + /// Unlike the echo broadcasts, these will be just sent to every node from [`Self::message_destinations`] + /// without any confirmation required. + fn make_normal_broadcast( + &self, + #[allow(unused_variables)] rng: &mut impl CryptoRngCore, + ) -> Result { + Ok(NormalBroadcast::none()) + } + /// Processes the received message and generates the payload that will be used in [`finalize`](`Self::finalize`). /// /// Note that there is no need to authenticate the message at this point; @@ -373,6 +412,7 @@ pub trait Round: 'static + Send + Sync + Debug { rng: &mut impl CryptoRngCore, from: &Id, echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, direct_message: DirectMessage, ) -> Result>; @@ -400,6 +440,12 @@ pub trait Round: 'static + Send + Sync + Debug { 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 { diff --git a/manul/src/session/echo.rs b/manul/src/session/echo.rs index 0d294c1..6408633 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, ObjectSafeRound, Payload, Protocol, - ProtocolMessage, ReceiveError, Round, RoundId, + Artifact, DirectMessage, EchoBroadcast, FinalizeError, FinalizeOutcome, NormalBroadcast, ObjectSafeRound, + Payload, Protocol, ProtocolMessage, ReceiveError, Round, RoundId, }, utils::SerializableMap, }; @@ -102,12 +102,8 @@ where &self.destinations } - fn make_direct_message( - &self, - _rng: &mut impl CryptoRngCore, - destination: &SP::Verifier, - ) -> Result { - debug!("{:?}: making echo round message for {:?}", self.verifier, destination); + fn make_normal_broadcast(&self, _rng: &mut impl CryptoRngCore) -> Result { + debug!("{:?}: making an echo round message", self.verifier); // Don't send our own message the second time let mut echo_broadcasts = self.echo_broadcasts.clone(); @@ -121,8 +117,8 @@ where let message = EchoRoundMessage:: { echo_broadcasts: echo_broadcasts.into(), }; - let dm = DirectMessage::new::(&message)?; - Ok(dm) + let bc = NormalBroadcast::new::(&message)?; + Ok(bc) } fn expecting_messages_from(&self) -> &BTreeSet { @@ -134,13 +130,15 @@ where _rng: &mut impl CryptoRngCore, from: &SP::Verifier, echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, direct_message: DirectMessage, ) -> Result> { debug!("{:?}: received an echo message from {:?}", self.verifier, from); echo_broadcast.assert_is_none()?; + direct_message.assert_is_none()?; - let message = direct_message.deserialize::>()?; + let message = normal_broadcast.deserialize::>()?; // Check that the received message contains entries from `destinations` sans `from` // It is an unprovable fault. diff --git a/manul/src/session/evidence.rs b/manul/src/session/evidence.rs index 18c2870..aea0549 100644 --- a/manul/src/session/evidence.rs +++ b/manul/src/session/evidence.rs @@ -12,8 +12,8 @@ use super::{ }; use crate::{ protocol::{ - DirectMessage, DirectMessageError, EchoBroadcast, EchoBroadcastError, MessageValidationError, Protocol, - ProtocolError, ProtocolMessage, ProtocolValidationError, RoundId, + DirectMessage, DirectMessageError, EchoBroadcast, EchoBroadcastError, MessageValidationError, NormalBroadcast, + NormalBroadcastError, Protocol, ProtocolError, ProtocolMessage, ProtocolValidationError, RoundId, }, utils::SerializableMap, }; @@ -45,9 +45,9 @@ impl From for EvidenceError { } } -impl From for EvidenceError { - fn from(error: DirectMessageError) -> Self { - Self::InvalidEvidence(format!("Failed to deserialize direct message: {:?}", error)) +impl From for EvidenceError { + fn from(error: NormalBroadcastError) -> Self { + Self::InvalidEvidence(format!("Failed to deserialize normal brroadcast: {:?}", error)) } } @@ -84,6 +84,7 @@ where pub(crate) fn new_protocol_error( verifier: &SP::Verifier, echo_broadcast: SignedMessage, + normal_broadcast: SignedMessage, direct_message: SignedMessage, error: P::ProtocolError, transcript: &Transcript, @@ -98,6 +99,16 @@ where }) .collect::, _>>()?; + let normal_broadcasts = error + .required_normal_broadcasts() + .iter() + .map(|round_id| { + transcript + .get_normal_broadcast(*round_id, verifier) + .map(|bc| (*round_id, bc)) + }) + .collect::, _>>()?; + let direct_messages = error .required_direct_messages() .iter() @@ -113,7 +124,7 @@ where .iter() .map(|round_id| { transcript - .get_direct_message(round_id.echo(), verifier) + .get_normal_broadcast(round_id.echo(), verifier) .map(|dm| (*round_id, dm)) }) .collect::, _>>()?; @@ -127,8 +138,10 @@ where error, direct_message, echo_broadcast, + normal_broadcast, direct_messages: direct_messages.into(), echo_broadcasts: echo_broadcasts.into(), + normal_broadcasts: normal_broadcasts.into(), combined_echos: combined_echos.into(), }), }) @@ -136,7 +149,7 @@ where pub(crate) fn new_echo_round_error( verifier: &SP::Verifier, - direct_message: SignedMessage, + normal_broadcast: SignedMessage, error: EchoRoundError, transcript: &Transcript, ) -> Result { @@ -146,7 +159,7 @@ where guilty_party: verifier.clone(), description, evidence: EvidenceEnum::InvalidEchoPack(InvalidEchoPackEvidence { - direct_message, + normal_broadcast, invalid_echo_sender: from, phantom: core::marker::PhantomData, }), @@ -155,10 +168,10 @@ where // We could avoid all this if we attached the SignedMessage objects // directly to the error. But then it would have to be generic over `S`, // which the `Round` trait knows nothing about. - let round_id = direct_message.metadata().round_id().non_echo(); + let round_id = normal_broadcast.metadata().round_id().non_echo(); let we_received = transcript.get_echo_broadcast(round_id, &from)?; - let deserialized = direct_message + let deserialized = normal_broadcast .payload() .deserialize::>() .map_err(|error| { @@ -213,6 +226,21 @@ where } } + pub(crate) fn new_invalid_normal_broadcast( + verifier: &SP::Verifier, + normal_broadcast: SignedMessage, + error: NormalBroadcastError, + ) -> Self { + Self { + guilty_party: verifier.clone(), + description: format!("{:?}", error), + evidence: EvidenceEnum::InvalidNormalBroadcast(InvalidNormalBroadcastEvidence { + normal_broadcast, + phantom: core::marker::PhantomData, + }), + } + } + pub fn guilty_party(&self) -> &SP::Verifier { &self.guilty_party } @@ -226,6 +254,7 @@ where EvidenceEnum::Protocol(evidence) => evidence.verify::(party), EvidenceEnum::InvalidDirectMessage(evidence) => evidence.verify::(party), EvidenceEnum::InvalidEchoBroadcast(evidence) => evidence.verify::(party), + EvidenceEnum::InvalidNormalBroadcast(evidence) => evidence.verify::(party), EvidenceEnum::InvalidEchoPack(evidence) => evidence.verify(party), EvidenceEnum::MismatchedBroadcasts(evidence) => evidence.verify::(party), } @@ -237,13 +266,14 @@ enum EvidenceEnum { Protocol(ProtocolEvidence

), InvalidDirectMessage(InvalidDirectMessageEvidence

), InvalidEchoBroadcast(InvalidEchoBroadcastEvidence

), + InvalidNormalBroadcast(InvalidNormalBroadcastEvidence

), InvalidEchoPack(InvalidEchoPackEvidence), MismatchedBroadcasts(MismatchedBroadcastsEvidence

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

, } @@ -254,7 +284,7 @@ where SP: SessionParameters, { fn verify(&self, verifier: &SP::Verifier) -> Result<(), EvidenceError> { - let verified = self.direct_message.clone().verify::(verifier)?; + let verified = self.normal_broadcast.clone().verify::(verifier)?; let deserialized = verified.payload().deserialize::>()?; let invalid_echo = deserialized .echo_broadcasts @@ -276,7 +306,7 @@ where // `from` sent us a correctly signed message but from another round or another session. // Provable fault of `from`. - if verified_echo.metadata() != self.direct_message.metadata() { + if verified_echo.metadata() != self.normal_broadcast.metadata() { return Ok(()); } @@ -358,14 +388,38 @@ where } } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InvalidNormalBroadcastEvidence { + normal_broadcast: SignedMessage, + phantom: core::marker::PhantomData

, +} + +impl

InvalidNormalBroadcastEvidence

+where + P: Protocol, +{ + fn verify(&self, verifier: &SP::Verifier) -> Result<(), EvidenceError> + where + SP: SessionParameters, + { + 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(), + )?) + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] struct ProtocolEvidence { error: P::ProtocolError, direct_message: SignedMessage, echo_broadcast: SignedMessage, + normal_broadcast: SignedMessage, direct_messages: SerializableMap>, echo_broadcasts: SerializableMap>, - combined_echos: SerializableMap>, + normal_broadcasts: SerializableMap>, + combined_echos: SerializableMap>, } impl

ProtocolEvidence

@@ -401,6 +455,20 @@ where )); } + 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() + { + return Err(EvidenceError::InvalidEvidence( + "Invalid attached message metadata".into(), + )); + } + 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)?; @@ -413,6 +481,18 @@ where verified_echo_broadcasts.insert(*round_id, verified_echo_broadcast.payload().clone()); } + 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 metadata = verified_normal_broadcast.metadata(); + if metadata.session_id() != session_id || metadata.round_id() != *round_id { + return Err(EvidenceError::InvalidEvidence( + "Invalid attached message metadata".into(), + )); + } + verified_normal_broadcasts.insert(*round_id, verified_normal_broadcast.payload().clone()); + } + 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)?; @@ -442,8 +522,10 @@ where Ok(self.error.verify_messages_constitute_error( &verified_echo_broadcast, + &verified_normal_broadcast, &verified_direct_message, &verified_echo_broadcasts, + &verified_normal_broadcasts, &verified_direct_messages, &combined_echos, )?) diff --git a/manul/src/session/message.rs b/manul/src/session/message.rs index 2b0d64f..425dccc 100644 --- a/manul/src/session/message.rs +++ b/manul/src/session/message.rs @@ -10,7 +10,7 @@ use super::{ session::{SessionId, SessionParameters}, LocalError, }; -use crate::protocol::{DeserializationError, DirectMessage, EchoBroadcast, Protocol, RoundId}; +use crate::protocol::{DeserializationError, DirectMessage, EchoBroadcast, NormalBroadcast, Protocol, RoundId}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub(crate) struct SerializedSignature(#[serde(with = "SliceLike::")] Box<[u8]>); @@ -170,6 +170,7 @@ impl VerifiedMessage { pub struct MessageBundle { direct_message: SignedMessage, echo_broadcast: SignedMessage, + normal_broadcast: SignedMessage, } impl MessageBundle { @@ -180,6 +181,7 @@ impl MessageBundle { round_id: RoundId, direct_message: DirectMessage, echo_broadcast: SignedMessage, + normal_broadcast: SignedMessage, ) -> Result where P: Protocol, @@ -189,6 +191,7 @@ impl MessageBundle { Ok(Self { direct_message, echo_broadcast, + normal_broadcast, }) } @@ -197,11 +200,16 @@ impl MessageBundle { return None; } + if self.normal_broadcast.metadata() != self.direct_message.metadata() { + return None; + } + let metadata = self.direct_message.message_with_metadata.metadata.clone(); Some(CheckedMessageBundle { metadata, direct_message: self.direct_message, echo_broadcast: self.echo_broadcast, + normal_broadcast: self.normal_broadcast, }) } } @@ -215,6 +223,7 @@ pub(crate) struct CheckedMessageBundle { metadata: MessageMetadata, direct_message: SignedMessage, echo_broadcast: SignedMessage, + normal_broadcast: SignedMessage, } impl CheckedMessageBundle { @@ -229,11 +238,13 @@ impl CheckedMessageBundle { { 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, direct_message, echo_broadcast, + normal_broadcast, }) } } @@ -247,6 +258,7 @@ pub struct VerifiedMessageBundle { metadata: MessageMetadata, direct_message: VerifiedMessage, echo_broadcast: VerifiedMessage, + normal_broadcast: VerifiedMessage, } impl VerifiedMessageBundle @@ -269,11 +281,22 @@ where self.echo_broadcast.payload() } + pub(crate) fn normal_broadcast(&self) -> &NormalBroadcast { + self.normal_broadcast.payload() + } + /// Split the `VerifiedMessageBundle` into its signed constituent parts: /// the echo broadcast and the direct message. - pub(crate) fn into_parts(self) -> (SignedMessage, SignedMessage) { + pub(crate) fn into_parts( + self, + ) -> ( + SignedMessage, + SignedMessage, + SignedMessage, + ) { let direct_message = self.direct_message.into_unverified(); let echo_broadcast = self.echo_broadcast.into_unverified(); - (echo_broadcast, direct_message) + let normal_broadcast = self.normal_broadcast.into_unverified(); + (echo_broadcast, normal_broadcast, direct_message) } } diff --git a/manul/src/session/session.rs b/manul/src/session/session.rs index f916971..c2d9761 100644 --- a/manul/src/session/session.rs +++ b/manul/src/session/session.rs @@ -21,8 +21,9 @@ use super::{ LocalError, RemoteError, }; use crate::protocol::{ - Artifact, DirectMessage, EchoBroadcast, FinalizeError, FinalizeOutcome, FirstRound, ObjectSafeRound, - ObjectSafeRoundWrapper, Payload, Protocol, ProtocolMessage, ReceiveError, ReceiveErrorType, Round, RoundId, + Artifact, DirectMessage, EchoBroadcast, FinalizeError, FinalizeOutcome, FirstRound, NormalBroadcast, + ObjectSafeRound, ObjectSafeRoundWrapper, Payload, Protocol, ProtocolMessage, ReceiveError, ReceiveErrorType, Round, + RoundId, }; /// A set of types needed to execute a session. @@ -108,6 +109,7 @@ pub struct Session { round: Box>, message_destinations: BTreeSet, echo_broadcast: SignedMessage, + normal_broadcast: SignedMessage, possible_next_rounds: BTreeSet, transcript: Transcript, } @@ -159,8 +161,13 @@ where 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 normal = round.make_normal_broadcast(rng)?; + let normal_broadcast = SignedMessage::new::(rng, &signer, &session_id, round.id(), normal)?; + let message_destinations = round.message_destinations().clone(); let possible_next_rounds = if echo_broadcast.payload().is_none() { @@ -175,6 +182,7 @@ where verifier, round, echo_broadcast, + normal_broadcast, possible_next_rounds, message_destinations, transcript, @@ -213,6 +221,7 @@ where self.round.id(), direct_message, self.echo_broadcast.clone(), + self.normal_broadcast.clone(), )?; Ok(( @@ -357,6 +366,7 @@ where rng, message.from(), message.echo_broadcast().clone(), + message.normal_broadcast().clone(), message.direct_message().clone(), ); // We could filter out and return a possible `LocalError` at this stage, @@ -384,6 +394,7 @@ where let transcript = self.transcript.update( round_id, accum.echo_broadcasts, + accum.normal_broadcasts, accum.direct_messages, accum.provable_errors, accum.unprovable_errors, @@ -404,6 +415,7 @@ where let transcript = self.transcript.update( round_id, accum.echo_broadcasts, + accum.normal_broadcasts, accum.direct_messages, accum.provable_errors, accum.unprovable_errors, @@ -496,6 +508,7 @@ pub struct RoundAccumulator { artifacts: BTreeMap, cached: BTreeMap>>, echo_broadcasts: BTreeMap>, + normal_broadcasts: BTreeMap>, direct_messages: BTreeMap>, provable_errors: BTreeMap>, unprovable_errors: BTreeMap, @@ -515,6 +528,7 @@ where artifacts: BTreeMap::new(), cached: BTreeMap::new(), echo_broadcasts: BTreeMap::new(), + normal_broadcasts: BTreeMap::new(), direct_messages: BTreeMap::new(), provable_errors: BTreeMap::new(), unprovable_errors: BTreeMap::new(), @@ -623,10 +637,13 @@ where let error = match processed.processed { Ok(payload) => { // Note: only inserting the messages if they actually have a payload - let (echo_broadcast, direct_message) = processed.message.into_parts(); + let (echo_broadcast, normal_broadcast, direct_message) = processed.message.into_parts(); if !echo_broadcast.payload().is_none() { self.echo_broadcasts.insert(from.clone(), echo_broadcast); } + if !normal_broadcast.payload().is_none() { + self.normal_broadcasts.insert(from.clone(), normal_broadcast); + } if !direct_message.payload().is_none() { self.direct_messages.insert(from.clone(), direct_message); } @@ -638,18 +655,30 @@ where match error.0 { ReceiveErrorType::InvalidDirectMessage(error) => { - let (_echo_broadcast, direct_message) = processed.message.into_parts(); + let (_echo_broadcast, _normal_broadcast, direct_message) = processed.message.into_parts(); let evidence = Evidence::new_invalid_direct_message(&from, direct_message, error); self.register_provable_error(&from, evidence) } ReceiveErrorType::InvalidEchoBroadcast(error) => { - let (echo_broadcast, _direct_message) = processed.message.into_parts(); + let (echo_broadcast, _normal_broadcast, _direct_message) = processed.message.into_parts(); let evidence = Evidence::new_invalid_echo_broadcast(&from, echo_broadcast, error); self.register_provable_error(&from, evidence) } + ReceiveErrorType::InvalidNormalBroadcast(error) => { + let (_echo_broadcast, normal_broadcast, _direct_message) = processed.message.into_parts(); + let evidence = Evidence::new_invalid_normal_broadcast(&from, normal_broadcast, error); + self.register_provable_error(&from, evidence) + } ReceiveErrorType::Protocol(error) => { - let (echo_broadcast, direct_message) = processed.message.into_parts(); - let evidence = Evidence::new_protocol_error(&from, echo_broadcast, direct_message, error, transcript)?; + let (echo_broadcast, normal_broadcast, direct_message) = processed.message.into_parts(); + let evidence = Evidence::new_protocol_error( + &from, + echo_broadcast, + normal_broadcast, + direct_message, + error, + transcript, + )?; self.register_provable_error(&from, evidence) } ReceiveErrorType::Unprovable(error) => { @@ -657,8 +686,8 @@ where Ok(()) } ReceiveErrorType::Echo(error) => { - let (_echo_broadcast, direct_message) = processed.message.into_parts(); - let evidence = Evidence::new_echo_round_error(&from, direct_message, error, transcript)?; + let (_echo_broadcast, normal_broadcast, _direct_message) = processed.message.into_parts(); + let evidence = Evidence::new_echo_round_error(&from, normal_broadcast, error, transcript)?; self.register_provable_error(&from, evidence) } ReceiveErrorType::Local(error) => Err(error), @@ -711,7 +740,7 @@ mod tests { use super::{MessageBundle, ProcessedArtifact, ProcessedMessage, Session, VerifiedMessageBundle}; use crate::{ protocol::{ - DeserializationError, DirectMessage, EchoBroadcast, LocalError, Protocol, ProtocolError, + DeserializationError, DirectMessage, EchoBroadcast, LocalError, NormalBroadcast, Protocol, ProtocolError, ProtocolValidationError, RoundId, }, testing::TestSessionParams, @@ -737,8 +766,10 @@ mod tests { fn verify_messages_constitute_error( &self, _echo_broadcast: &EchoBroadcast, + _normal_broadcast: &NormalBroadcast, _direct_message: &DirectMessage, _echo_broadcasts: &BTreeMap, + _normal_broadcasts: &BTreeMap, _direct_messages: &BTreeMap, _combined_echos: &BTreeMap>, ) -> Result<(), ProtocolValidationError> { diff --git a/manul/src/session/transcript.rs b/manul/src/session/transcript.rs index 3803d2e..edb2c18 100644 --- a/manul/src/session/transcript.rs +++ b/manul/src/session/transcript.rs @@ -5,11 +5,12 @@ use alloc::{ use core::fmt::Debug; use super::{evidence::Evidence, message::SignedMessage, session::SessionParameters, LocalError, RemoteError}; -use crate::protocol::{DirectMessage, EchoBroadcast, Protocol, RoundId}; +use crate::protocol::{DirectMessage, EchoBroadcast, NormalBroadcast, Protocol, RoundId}; #[derive(Debug)] pub(crate) struct Transcript { echo_broadcasts: BTreeMap>>, + normal_broadcasts: BTreeMap>>, direct_messages: BTreeMap>>, provable_errors: BTreeMap>, unprovable_errors: BTreeMap, @@ -24,6 +25,7 @@ where pub fn new() -> Self { Self { echo_broadcasts: BTreeMap::new(), + normal_broadcasts: BTreeMap::new(), direct_messages: BTreeMap::new(), provable_errors: BTreeMap::new(), unprovable_errors: BTreeMap::new(), @@ -31,10 +33,12 @@ where } } + #[allow(clippy::too_many_arguments)] pub fn update( self, round_id: RoundId, echo_broadcasts: BTreeMap>, + normal_broadcasts: BTreeMap>, direct_messages: BTreeMap>, provable_errors: BTreeMap>, unprovable_errors: BTreeMap, @@ -50,6 +54,16 @@ where } }; + let mut all_normal_broadcasts = self.normal_broadcasts; + match all_normal_broadcasts.entry(round_id) { + Entry::Vacant(entry) => entry.insert(normal_broadcasts), + Entry::Occupied(_) => { + return Err(LocalError::new(format!( + "A normal-broadcasts entry for {round_id:?} already exists" + ))) + } + }; + let mut all_direct_messages = self.direct_messages; match all_direct_messages.entry(round_id) { Entry::Vacant(entry) => entry.insert(direct_messages), @@ -90,6 +104,7 @@ where Ok(Self { echo_broadcasts: all_echo_broadcasts, + normal_broadcasts: all_normal_broadcasts, direct_messages: all_direct_messages, provable_errors: all_provable_errors, unprovable_errors: all_unprovable_errors, @@ -110,6 +125,19 @@ where .ok_or_else(|| LocalError::new(format!("No echo broadcasts registered for {from:?} in {round_id:?}"))) } + pub fn get_normal_broadcast( + &self, + round_id: RoundId, + from: &SP::Verifier, + ) -> Result, LocalError> { + self.normal_broadcasts + .get(&round_id) + .ok_or_else(|| LocalError::new(format!("No normal broadcasts registered for {round_id:?}")))? + .get(from) + .cloned() + .ok_or_else(|| LocalError::new(format!("No normal broadcasts registered for {from:?} in {round_id:?}"))) + } + pub fn get_direct_message( &self, round_id: RoundId, diff --git a/manul/src/testing/macros.rs b/manul/src/testing/macros.rs index ce6ddef..5d6f8a0 100644 --- a/manul/src/testing/macros.rs +++ b/manul/src/testing/macros.rs @@ -3,7 +3,7 @@ use alloc::collections::BTreeMap; use rand_core::CryptoRngCore; use crate::protocol::{ - Artifact, DirectMessage, EchoBroadcast, FinalizeError, FinalizeOutcome, LocalError, Payload, Round, + Artifact, DirectMessage, EchoBroadcast, FinalizeError, FinalizeOutcome, LocalError, NormalBroadcast, Payload, Round, }; /// A trait defining a wrapper around an existing type implementing [`Round`]. @@ -44,6 +44,11 @@ pub trait RoundOverride: RoundWrapper { self.inner_round_ref().make_echo_broadcast(rng) } + /// 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) + } + /// An override for [`Round::finalize`]. #[allow(clippy::type_complexity)] fn finalize( @@ -111,15 +116,23 @@ macro_rules! round_override { >::make_echo_broadcast(self, rng) } + fn make_normal_broadcast( + &self, + rng: &mut impl CryptoRngCore, + ) -> Result<$crate::protocol::NormalBroadcast, $crate::protocol::LocalError> { + >::make_normal_broadcast(self, rng) + } + fn receive_message( &self, rng: &mut impl CryptoRngCore, 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, direct_message) + .receive_message(rng, from, echo_broadcast, normal_broadcast, direct_message) } fn finalize(