manul/protocol/
round.rs

1use alloc::{
2    boxed::Box,
3    collections::{BTreeMap, BTreeSet},
4    format,
5};
6use core::{
7    any::Any,
8    fmt::{Debug, Display},
9};
10
11use rand_core::CryptoRngCore;
12use serde::{Deserialize, Serialize};
13
14use super::{
15    boxed_format::BoxedFormat,
16    boxed_round::BoxedRound,
17    errors::{LocalError, MessageValidationError, ProtocolValidationError, ReceiveError},
18    message::{DirectMessage, EchoBroadcast, NormalBroadcast, ProtocolMessage, ProtocolMessagePart},
19    round_id::{RoundId, TransitionInfo},
20};
21
22/// Describes what other parties this rounds sends messages to, and what other parties it expects messages from.
23#[derive(Debug, Clone)]
24pub struct CommunicationInfo<Id> {
25    /// The destinations of the messages to be sent out by this round.
26    ///
27    /// The way it is interpreted by the execution layer is
28    /// - An echo broadcast (if any) is sent to all of these destinations;
29    /// - A direct message is sent to each of these destinations,
30    ///   which means [`make_direct_message`](`Round::make_direct_message`) may be called
31    ///   for each element of the returned set.
32    pub message_destinations: BTreeSet<Id>,
33
34    /// Returns the set of node IDs from which this round expects messages.
35    ///
36    /// The execution layer will not call [`finalize`](`Round::finalize`) until all these nodes have responded
37    /// (and the corresponding [`receive_message`](`Round::receive_message`) finished successfully).
38    pub expecting_messages_from: BTreeSet<Id>,
39
40    /// Returns the specific way the node participates in the echo round following this round.
41    ///
42    /// Returns [`EchoRoundParticipation::Default`] by default; this works fine when every node
43    /// sends messages to every other one, or do not send or receive any echo broadcasts.
44    /// Otherwise, review the options in [`EchoRoundParticipation`] and pick the appropriate one.
45    pub echo_round_participation: EchoRoundParticipation<Id>,
46}
47
48impl<Id: PartyId> CommunicationInfo<Id> {
49    /// A regular round that sends messages to all `other_parties`, and expects messages back from them.
50    pub fn regular(other_parties: &BTreeSet<Id>) -> Self {
51        Self {
52            message_destinations: other_parties.clone(),
53            expecting_messages_from: other_parties.clone(),
54            echo_round_participation: EchoRoundParticipation::Default,
55        }
56    }
57}
58
59/// Possible successful outcomes of [`Round::finalize`].
60#[derive(Debug)]
61pub enum FinalizeOutcome<Id: PartyId, P: Protocol<Id>> {
62    /// Transition to a new round.
63    AnotherRound(BoxedRound<Id, P>),
64    /// The protocol reached a result.
65    Result(P::Result),
66}
67
68/// A distributed protocol.
69pub trait Protocol<Id>: 'static {
70    /// The successful result of an execution of this protocol.
71    type Result: Debug;
72
73    /// An object of this type will be returned when a provable error happens during [`Round::receive_message`].
74    type ProtocolError: ProtocolError<Id>;
75
76    /// Returns `Ok(())` if the given direct message cannot be deserialized
77    /// assuming it is a direct message from the round `round_id`.
78    ///
79    /// Normally one would use [`ProtocolMessagePart::verify_is_not`] and [`ProtocolMessagePart::verify_is_some`]
80    /// when implementing this.
81    fn verify_direct_message_is_invalid(
82        format: &BoxedFormat,
83        round_id: &RoundId,
84        message: &DirectMessage,
85    ) -> Result<(), MessageValidationError>;
86
87    /// Returns `Ok(())` if the given echo broadcast cannot be deserialized
88    /// assuming it is an echo broadcast from the round `round_id`.
89    ///
90    /// Normally one would use [`ProtocolMessagePart::verify_is_not`] and [`ProtocolMessagePart::verify_is_some`]
91    /// when implementing this.
92    fn verify_echo_broadcast_is_invalid(
93        format: &BoxedFormat,
94        round_id: &RoundId,
95        message: &EchoBroadcast,
96    ) -> Result<(), MessageValidationError>;
97
98    /// Returns `Ok(())` if the given echo broadcast cannot be deserialized
99    /// assuming it is an echo broadcast from the round `round_id`.
100    ///
101    /// Normally one would use [`ProtocolMessagePart::verify_is_not`] and [`ProtocolMessagePart::verify_is_some`]
102    /// when implementing this.
103    fn verify_normal_broadcast_is_invalid(
104        format: &BoxedFormat,
105        round_id: &RoundId,
106        message: &NormalBroadcast,
107    ) -> Result<(), MessageValidationError>;
108}
109
110/// Declares which parts of the message from a round have to be stored to serve as the evidence of malicious behavior.
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub struct RequiredMessageParts {
113    pub(crate) echo_broadcast: bool,
114    pub(crate) normal_broadcast: bool,
115    pub(crate) direct_message: bool,
116}
117
118impl RequiredMessageParts {
119    fn new(echo_broadcast: bool, normal_broadcast: bool, direct_message: bool) -> Self {
120        // We must require at least one part, otherwise this struct doesn't need to be created.
121        debug_assert!(echo_broadcast || normal_broadcast || direct_message);
122        Self {
123            echo_broadcast,
124            normal_broadcast,
125            direct_message,
126        }
127    }
128
129    /// Store echo broadcast
130    pub fn echo_broadcast() -> Self {
131        Self::new(true, false, false)
132    }
133
134    /// Store normal broadcast
135    pub fn normal_broadcast() -> Self {
136        Self::new(false, true, false)
137    }
138
139    /// Store direct message
140    pub fn direct_message() -> Self {
141        Self::new(false, false, true)
142    }
143
144    /// Store echo broadcast in addition to what is already stored.
145    pub fn and_echo_broadcast(&self) -> Self {
146        Self::new(true, self.normal_broadcast, self.direct_message)
147    }
148
149    /// Store normal broadcast in addition to what is already stored.
150    pub fn and_normal_broadcast(&self) -> Self {
151        Self::new(self.echo_broadcast, true, self.direct_message)
152    }
153
154    /// Store direct message in addition to what is already stored.
155    pub fn and_direct_message(&self) -> Self {
156        Self::new(self.echo_broadcast, self.normal_broadcast, true)
157    }
158}
159
160/// Declares which messages from this and previous rounds
161/// have to be stored to serve as the evidence of malicious behavior.
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct RequiredMessages {
164    pub(crate) this_round: RequiredMessageParts,
165    pub(crate) previous_rounds: Option<BTreeMap<RoundId, RequiredMessageParts>>,
166    pub(crate) combined_echos: Option<BTreeSet<RoundId>>,
167}
168
169impl RequiredMessages {
170    /// The general case constructor.
171    ///
172    /// `this_round` specifies the message parts to be stored from the message that triggered the error.
173    ///
174    /// `previous_rounds` specifies, optionally, if any message parts from the previous rounds need to be included.
175    ///
176    /// `combined_echos` specifies, optionally, if any echoed broadcasts need to be included.
177    /// The combined echos are echo broadcasts sent by a party during the echo round,
178    /// where it bundles all the received broadcasts and sends them back to everyone.
179    /// That is, they will include the echo broadcasts from all other nodes signed by the guilty party.
180    pub fn new(
181        this_round: RequiredMessageParts,
182        previous_rounds: Option<BTreeMap<RoundId, RequiredMessageParts>>,
183        combined_echos: Option<BTreeSet<RoundId>>,
184    ) -> Self {
185        Self {
186            this_round,
187            previous_rounds,
188            combined_echos,
189        }
190    }
191}
192
193/// Describes provable errors originating during protocol execution.
194///
195/// Provable here means that we can create an evidence object entirely of messages signed by some party,
196/// which, in combination, prove the party's malicious actions.
197pub trait ProtocolError<Id>: Display + Debug + Clone + Serialize + for<'de> Deserialize<'de> {
198    /// Additional data that cannot be derived from the node's messages alone
199    /// and therefore has to be supplied externally during evidence verification.
200    type AssociatedData: Debug;
201
202    /// Specifies the messages of the guilty party that need to be stored as the evidence
203    /// to prove its malicious behavior.
204    fn required_messages(&self) -> RequiredMessages;
205
206    /// Returns `Ok(())` if the attached messages indeed prove that a malicious action happened.
207    ///
208    /// The signatures and metadata of the messages will be checked by the calling code,
209    /// the responsibility of this method is just to check the message contents.
210    ///
211    /// `message` contain the message parts that triggered the error
212    /// during [`Round::receive_message`].
213    ///
214    /// `previous_messages` are message parts from the previous rounds, as requested by
215    /// [`required_messages`](Self::required_messages).
216    ///
217    /// Note that if some message part was not requested by above methods, it will be set to an empty one
218    /// in the [`ProtocolMessage`], even if it was present originally.
219    ///
220    /// `combined_echos` are bundled echos from other parties from the previous rounds,
221    /// as requested by [`required_messages`](Self::required_messages).
222    #[allow(clippy::too_many_arguments)]
223    fn verify_messages_constitute_error(
224        &self,
225        format: &BoxedFormat,
226        guilty_party: &Id,
227        shared_randomness: &[u8],
228        associated_data: &Self::AssociatedData,
229        message: ProtocolMessage,
230        previous_messages: BTreeMap<RoundId, ProtocolMessage>,
231        combined_echos: BTreeMap<RoundId, BTreeMap<Id, EchoBroadcast>>,
232    ) -> Result<(), ProtocolValidationError>;
233}
234
235#[derive(displaydoc::Display, Debug, Clone, Copy, Serialize, Deserialize)]
236/// A stub type indicating that this protocol does not generate any provable errors.
237pub struct NoProtocolErrors;
238
239impl<Id> ProtocolError<Id> for NoProtocolErrors {
240    type AssociatedData = ();
241
242    fn required_messages(&self) -> RequiredMessages {
243        panic!("Attempt to use an empty error type in an evidence. This is a bug in the protocol implementation.")
244    }
245
246    fn verify_messages_constitute_error(
247        &self,
248        _format: &BoxedFormat,
249        _guilty_party: &Id,
250        _shared_randomness: &[u8],
251        _associated_data: &Self::AssociatedData,
252        _message: ProtocolMessage,
253        _previous_messages: BTreeMap<RoundId, ProtocolMessage>,
254        _combined_echos: BTreeMap<RoundId, BTreeMap<Id, EchoBroadcast>>,
255    ) -> Result<(), ProtocolValidationError> {
256        panic!("Attempt to use an empty error type in an evidence. This is a bug in the protocol implementation.")
257    }
258}
259
260/// Message payload created in [`Round::receive_message`].
261#[derive(Debug)]
262pub struct Payload(pub Box<dyn Any + Send + Sync>);
263
264impl Payload {
265    /// Creates a new payload.
266    ///
267    /// Would be normally called in [`Round::receive_message`].
268    pub fn new<T: 'static + Send + Sync>(payload: T) -> Self {
269        Self(Box::new(payload))
270    }
271
272    /// Creates an empty payload.
273    ///
274    /// Use it in [`Round::receive_message`] if it does not need to create payloads.
275    pub fn empty() -> Self {
276        Self::new(())
277    }
278
279    /// Attempts to downcast back to the concrete type.
280    ///
281    /// Would be normally called in [`Round::finalize`].
282    pub fn downcast<T: 'static>(self) -> Result<T, LocalError> {
283        Ok(*(self
284            .0
285            .downcast::<T>()
286            .map_err(|_| LocalError::new(format!("Failed to downcast into {}", core::any::type_name::<T>())))?))
287    }
288}
289
290/// Associated data created alongside a message in [`Round::make_direct_message`].
291#[derive(Debug)]
292pub struct Artifact(pub Box<dyn Any + Send + Sync>);
293
294impl Artifact {
295    /// Creates a new artifact.
296    ///
297    /// Would be normally called in [`Round::make_direct_message`].
298    pub fn new<T: 'static + Send + Sync>(artifact: T) -> Self {
299        Self(Box::new(artifact))
300    }
301
302    /// Attempts to downcast back to the concrete type.
303    ///
304    /// Would be normally called in [`Round::finalize`].
305    pub fn downcast<T: 'static>(self) -> Result<T, LocalError> {
306        Ok(*(self
307            .0
308            .downcast::<T>()
309            .map_err(|_| LocalError::new(format!("Failed to downcast into {}", core::any::type_name::<T>())))?))
310    }
311}
312
313/// A round that initiates a protocol.
314///
315/// This is a round that can be created directly;
316/// all the others are only reachable throud [`Round::finalize`] by the execution layer.
317pub trait EntryPoint<Id: PartyId> {
318    /// The protocol implemented by the round this entry points returns.
319    type Protocol: Protocol<Id>;
320
321    /// Returns the ID of the round returned by [`Self::make_round`].
322    fn entry_round_id() -> RoundId;
323
324    /// Creates the round.
325    ///
326    /// `session_id` can be assumed to be the same for each node participating in a session.
327    /// `id` is the ID of this node.
328    fn make_round(
329        self,
330        rng: &mut dyn CryptoRngCore,
331        shared_randomness: &[u8],
332        id: &Id,
333    ) -> Result<BoxedRound<Id, Self::Protocol>, LocalError>;
334}
335
336/// A trait alias for the combination of traits needed for a party identifier.
337pub trait PartyId: 'static + Debug + Clone + Ord + Send + Sync + Serialize + for<'de> Deserialize<'de> {}
338
339impl<T> PartyId for T where T: 'static + Debug + Clone + Ord + Send + Sync + Serialize + for<'de> Deserialize<'de> {}
340
341/// The specific way the node participates in the echo round (if any).
342#[derive(Debug, Clone)]
343pub enum EchoRoundParticipation<Id> {
344    /// The default behavior: sends broadcasts and receives echoed messages, or does neither.
345    ///
346    /// That is, this node will be a part of the echo round if [`Round::make_echo_broadcast`] generates a message.
347    Default,
348
349    /// This node sends broadcasts that will be echoed, but does not receive any.
350    Send,
351
352    /// This node receives broadcasts that it needs to echo, but does not send any itself.
353    Receive {
354        /// The other participants of the echo round
355        /// (that is, the nodes to which echoed messages will be sent).
356        echo_targets: BTreeSet<Id>,
357    },
358}
359
360mod sealed {
361    /// A dyn safe trait to get the type's ID.
362    pub trait DynTypeId: 'static {
363        /// Returns the type ID of the implementing type.
364        fn get_type_id(&self) -> core::any::TypeId {
365            core::any::TypeId::of::<Self>()
366        }
367    }
368
369    impl<T: 'static> DynTypeId for T {}
370}
371
372use sealed::DynTypeId;
373
374/**
375A type representing a single round of a protocol.
376
377The way a round will be used by an external caller:
378- create messages to send out (by calling [`make_direct_message`](`Self::make_direct_message`)
379  and [`make_echo_broadcast`](`Self::make_echo_broadcast`));
380- process received messages from other nodes (by calling [`receive_message`](`Self::receive_message`));
381- attempt to finalize (by calling [`finalize`](`Self::finalize`)) to produce the next round, or return a result.
382*/
383pub trait Round<Id: PartyId>: 'static + Debug + Send + Sync + DynTypeId {
384    /// The protocol this round is a part of.
385    type Protocol: Protocol<Id>;
386
387    /// Returns the information about the position of this round in the state transition graph.
388    ///
389    /// See [`TransitionInfo`] documentation for more details.
390    fn transition_info(&self) -> TransitionInfo;
391
392    /// Returns the information about the communication this rounds engages in with other nodes.
393    ///
394    /// See [`CommunicationInfo`] documentation for more details.
395    fn communication_info(&self) -> CommunicationInfo<Id>;
396
397    /// Returns the direct message to the given destination and (maybe) an accompanying artifact.
398    ///
399    /// Return [`DirectMessage::none`] if this round does not send direct messages.
400    ///
401    /// In some protocols, when a message to another node is created, there is some associated information
402    /// that needs to be retained for later (randomness, proofs of knowledge, and so on).
403    /// These should be put in an [`Artifact`] and will be available at the time of [`finalize`](`Self::finalize`).
404    fn make_direct_message(
405        &self,
406        #[allow(unused_variables)] rng: &mut dyn CryptoRngCore,
407        #[allow(unused_variables)] format: &BoxedFormat,
408        #[allow(unused_variables)] destination: &Id,
409    ) -> Result<(DirectMessage, Option<Artifact>), LocalError> {
410        Ok((DirectMessage::none(), None))
411    }
412
413    /// Returns the echo broadcast for this round.
414    ///
415    /// Return [`EchoBroadcast::none`] if this round does not send echo-broadcast messages.
416    /// This is also the blanket implementation.
417    ///
418    /// The execution layer will guarantee that all the destinations are sure they all received the same broadcast.
419    /// This also means that a message with the broadcasts from all nodes signed by each node is available
420    /// if an evidence of malicious behavior has to be constructed.
421    fn make_echo_broadcast(
422        &self,
423        #[allow(unused_variables)] rng: &mut dyn CryptoRngCore,
424        #[allow(unused_variables)] format: &BoxedFormat,
425    ) -> Result<EchoBroadcast, LocalError> {
426        Ok(EchoBroadcast::none())
427    }
428
429    /// Returns the normal broadcast for this round.
430    ///
431    /// Return [`NormalBroadcast::none`] if this round does not send normal broadcast messages.
432    /// This is also the blanket implementation.
433    ///
434    /// Unlike the echo broadcasts, these will be just sent to every node defined in [`Self::communication_info`]
435    /// without any confirmation required.
436    fn make_normal_broadcast(
437        &self,
438        #[allow(unused_variables)] rng: &mut dyn CryptoRngCore,
439        #[allow(unused_variables)] format: &BoxedFormat,
440    ) -> Result<NormalBroadcast, LocalError> {
441        Ok(NormalBroadcast::none())
442    }
443
444    /// Processes the received message and generates the payload that will be used in [`finalize`](`Self::finalize`).
445    ///
446    /// Note that there is no need to authenticate the message at this point;
447    /// it has already been done by the execution layer.
448    fn receive_message(
449        &self,
450        format: &BoxedFormat,
451        from: &Id,
452        message: ProtocolMessage,
453    ) -> Result<Payload, ReceiveError<Id, Self::Protocol>>;
454
455    /// Attempts to finalize the round, producing the next round or the result.
456    ///
457    /// `payloads` here are the ones previously generated by [`receive_message`](`Self::receive_message`),
458    /// and `artifacts` are the ones previously generated by
459    /// [`make_direct_message`](`Self::make_direct_message`).
460    fn finalize(
461        self: Box<Self>,
462        rng: &mut dyn CryptoRngCore,
463        payloads: BTreeMap<Id, Payload>,
464        artifacts: BTreeMap<Id, Artifact>,
465    ) -> Result<FinalizeOutcome<Id, Self::Protocol>, LocalError>;
466}