-
Notifications
You must be signed in to change notification settings - Fork 1
Add StaticRound
to eliminate some boilerplate when writing protocols
#117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
9edb13a
e93bbfc
e8d23b1
11620e5
fa2028b
c172784
959da23
5ac9729
a2ab642
9ff447c
32499ef
8659991
7b742a4
6952e51
e8d2fca
fb07279
5757b16
1a05741
fe8e2b8
bc066c6
247ea03
af3d288
e6856a5
56fbe74
9df80d0
6edb7b0
e2a2e3e
d85e47b
81e411c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,9 +55,9 @@ use std::collections::{BTreeMap, BTreeSet}; | |
use manul::{ | ||
dev::{run_sync, BinaryFormat, TestHasher, TestSignature, TestSigner, TestVerifier}, | ||
protocol::{ | ||
Artifact, BoxedFormat, BoxedRound, CommunicationInfo, DirectMessage, EchoBroadcast, EchoRoundParticipation, | ||
EntryPoint, FinalizeOutcome, LocalError, MessageValidationError, NoProtocolErrors, NormalBroadcast, Payload, | ||
Protocol, ProtocolMessage, ProtocolMessagePart, ReceiveError, Round, RoundId, TransitionInfo, | ||
BoxedRound, CommunicationInfo, EchoRoundParticipation, EntryPoint, FinalizeOutcome, LocalError, NoArtifact, | ||
NoMessage, NoProtocolErrors, Protocol, ProtocolMessage, ReceiveError, Round, RoundId, RoundInfo, | ||
TransitionInfo, | ||
}, | ||
session::SessionParameters, | ||
}; | ||
|
@@ -73,35 +73,18 @@ use tracing::{debug, info, trace}; | |
#[derive(Debug)] | ||
pub struct DiningCryptographersProtocol; | ||
|
||
impl<Id> Protocol<Id> for DiningCryptographersProtocol { | ||
impl Protocol<DinerId> for DiningCryptographersProtocol { | ||
// XOR/¬XOR of the two bits of each of the three diners (one is their own cointoss, the other shared with their | ||
// neighbour). | ||
type Result = (bool, bool, bool); | ||
type SharedData = (); | ||
|
||
type ProtocolError = NoProtocolErrors; | ||
|
||
fn verify_direct_message_is_invalid( | ||
_format: &BoxedFormat, | ||
_round_id: &RoundId, | ||
_message: &DirectMessage, | ||
) -> Result<(), MessageValidationError> { | ||
Ok(()) | ||
} | ||
|
||
fn verify_echo_broadcast_is_invalid( | ||
_format: &BoxedFormat, | ||
_round_id: &RoundId, | ||
_message: &EchoBroadcast, | ||
) -> Result<(), MessageValidationError> { | ||
Ok(()) | ||
} | ||
|
||
fn verify_normal_broadcast_is_invalid( | ||
_format: &BoxedFormat, | ||
_round_id: &RoundId, | ||
_message: &NormalBroadcast, | ||
) -> Result<(), MessageValidationError> { | ||
Ok(()) | ||
fn round_info(round_id: &RoundId) -> Option<RoundInfo<DinerId, Self>> { | ||
match round_id { | ||
_ if round_id == 1 => Some(RoundInfo::new::<Round1>()), | ||
_ if round_id == 2 => Some(RoundInfo::new::<Round2>()), | ||
_ => None, | ||
} | ||
} | ||
} | ||
|
||
|
@@ -125,6 +108,14 @@ pub struct Round2 { | |
|
||
impl Round<DinerId> for Round1 { | ||
type Protocol = DiningCryptographersProtocol; | ||
type ProtocolError = NoProtocolErrors<Self>; | ||
fjarri marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
type DirectMessage = Round1Message; | ||
type EchoBroadcast = NoMessage; | ||
type NormalBroadcast = NoMessage; | ||
|
||
type Payload = bool; | ||
type Artifact = (); | ||
fjarri marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Used to define the possible paths to and from this round. This protocol is very simple, it's simply Round 1 -> | ||
// Round 2, so we can use the "linear" utility method to set this up. | ||
|
@@ -157,44 +148,37 @@ impl Round<DinerId> for Round1 { | |
// This is called when this diner prepares to share a random bit with their neighbour. | ||
fn make_direct_message( | ||
&self, | ||
_rng: &mut dyn CryptoRngCore, | ||
format: &BoxedFormat, | ||
_rng: &mut impl CryptoRngCore, | ||
destination: &DinerId, | ||
) -> Result<(DirectMessage, Option<Artifact>), LocalError> { | ||
) -> Result<(Self::DirectMessage, Self::Artifact), LocalError> { | ||
info!( | ||
"[Round1, make_direct_message] from {:?} to {destination:?}", | ||
self.diner_id | ||
); | ||
let msg = Round1Message { toss: self.own_toss }; | ||
let dm = DirectMessage::new(format, msg)?; | ||
|
||
Ok((dm, None)) | ||
Ok((Round1Message { toss: self.own_toss }, ())) | ||
fjarri marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// This is called when this diner receives a bit from their neighbour. | ||
fn receive_message( | ||
&self, | ||
format: &BoxedFormat, | ||
from: &DinerId, | ||
message: ProtocolMessage, | ||
) -> Result<Payload, ReceiveError<DinerId, Self::Protocol>> { | ||
let dm = message.direct_message.deserialize::<Round1Message>(format)?; | ||
message: ProtocolMessage<DinerId, Self>, | ||
) -> Result<Self::Payload, ReceiveError<DinerId, Self>> { | ||
let dm = message.direct_message; | ||
debug!( | ||
"[Round1, receive_message] {:?} was dm'd by {from:?}: {dm:?}", | ||
self.diner_id | ||
); | ||
let payload = Payload::new(dm.toss); | ||
Ok(payload) | ||
Ok(dm.toss) | ||
} | ||
|
||
// At the end of round 1 we construct the next one, Round 2, and return a [`FinalizeOutcome::AnotherRound`]. | ||
fn finalize( | ||
self: Box<Self>, | ||
_rng: &mut dyn CryptoRngCore, | ||
payloads: BTreeMap<DinerId, Payload>, | ||
_artifacts: BTreeMap<DinerId, Artifact>, | ||
self, | ||
_rng: &mut impl CryptoRngCore, | ||
payloads: BTreeMap<DinerId, Self::Payload>, | ||
_artifacts: BTreeMap<DinerId, Self::Artifact>, | ||
) -> Result<FinalizeOutcome<DinerId, Self::Protocol>, LocalError> { | ||
let payloads = downcast_payloads::<bool>(payloads)?; | ||
debug!("[Round1, finalize] {:?} sees payloads: {payloads:?}", self.diner_id); | ||
|
||
let neighbour_toss = *payloads | ||
|
@@ -206,7 +190,7 @@ impl Round<DinerId> for Round1 { | |
"[Round1, finalize] {:?} is finalizing to Round 2. Own cointoss: {}, neighbour cointoss: {neighbour_toss}", | ||
self.diner_id, self.own_toss | ||
); | ||
Ok(FinalizeOutcome::AnotherRound(BoxedRound::new_dynamic(Round2 { | ||
Ok(FinalizeOutcome::AnotherRound(BoxedRound::new(Round2 { | ||
diner_id: self.diner_id, | ||
own_toss: self.own_toss, | ||
neighbour_toss, | ||
|
@@ -217,6 +201,14 @@ impl Round<DinerId> for Round1 { | |
|
||
impl Round<DinerId> for Round2 { | ||
type Protocol = DiningCryptographersProtocol; | ||
type ProtocolError = NoProtocolErrors<Self>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Naming nitpick: the associated type is in the singular, so it's a bit odd to assign a value whose name is plural. OTOH calling it "NoProtocolError" isn't great either. Not sure there is a great solution to be found here (idiomatically it should really be Maybe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A random thought I had: I read through rust-lang/rust#99301 which seems to be about ways to access data from nested errors in a generic way. Seems a bit stuck though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can instead rename the associated type to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But wouldn't the plural on a type name imply that it is a collection of types of protocol errors? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to impl Anyway, all of this is nitpicking. The code is fine as-is, modulo perhaps the name. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is better. I still think it's awkward that there isn't a better way to do this but that has nothing to do with this PR. |
||
|
||
type DirectMessage = NoMessage; | ||
type EchoBroadcast = NoMessage; | ||
type NormalBroadcast = Round2Message; | ||
|
||
type Payload = bool; | ||
type Artifact = NoArtifact; | ||
|
||
// This round is the last in the protocol so we can terminate here. | ||
fn transition_info(&self) -> TransitionInfo { | ||
|
@@ -247,11 +239,7 @@ impl Round<DinerId> for Round2 { | |
} | ||
|
||
// Implementing this method means that Round 2 will make a broadcast (without echoes). | ||
fn make_normal_broadcast( | ||
&self, | ||
_rng: &mut dyn CryptoRngCore, | ||
format: &BoxedFormat, | ||
) -> Result<NormalBroadcast, LocalError> { | ||
fn make_normal_broadcast(&self, _rng: &mut impl CryptoRngCore) -> Result<Self::NormalBroadcast, LocalError> { | ||
debug!( | ||
"[Round2, make_normal_broadcast] {:?} broadcasts to everyone else", | ||
self.diner_id | ||
|
@@ -262,37 +250,33 @@ impl Round<DinerId> for Round2 { | |
} else { | ||
self.own_toss ^ self.neighbour_toss | ||
}; | ||
let msg = Round2Message { reveal }; | ||
let bcast = NormalBroadcast::new(format, msg)?; | ||
Ok(bcast) | ||
Ok(Round2Message { reveal }) | ||
} | ||
|
||
// Called once for each diner as messages are delivered to it. Here we deserialize the message using the configured | ||
// [`SessionParameters::WireFormat`] and construct the [`Payload`] that we want to make available to the `finalize` | ||
// method below. | ||
fn receive_message( | ||
&self, | ||
format: &BoxedFormat, | ||
from: &DinerId, | ||
message: ProtocolMessage, | ||
) -> Result<Payload, ReceiveError<DinerId, Self::Protocol>> { | ||
message: ProtocolMessage<DinerId, Self>, | ||
) -> Result<Self::Payload, ReceiveError<DinerId, Self>> { | ||
debug!("[Round2, receive_message] from {from:?} to {:?}", self.diner_id); | ||
let bcast = message.normal_broadcast.deserialize::<Round2Message>(format)?; | ||
let bcast = message.normal_broadcast; | ||
trace!("[Round2, receive_message] message (deserialized bcast): {:?}", bcast); | ||
// The payload is kept and delivered in the `finalize` method. | ||
let payload = Payload::new(bcast.reveal); | ||
Ok(payload) | ||
Ok(bcast.reveal) | ||
} | ||
|
||
// The `finalize` method has access to all the [`Payload`]s that were sent to this diner. This protocol does not use | ||
// [`Artifact`]s, but when used, they are also available here. | ||
// This is the last round in the protocol, so we return a [`FinalizeOutcome::Result`] with the result of the | ||
// protocol from this participant's point of view. | ||
fn finalize( | ||
self: Box<Self>, | ||
_rng: &mut dyn CryptoRngCore, | ||
payloads: BTreeMap<DinerId, Payload>, | ||
_artifacts: BTreeMap<DinerId, Artifact>, | ||
self, | ||
_rng: &mut impl CryptoRngCore, | ||
payloads: BTreeMap<DinerId, Self::Payload>, | ||
_artifacts: BTreeMap<DinerId, Self::Artifact>, | ||
) -> Result<FinalizeOutcome<DinerId, Self::Protocol>, LocalError> { | ||
// XOR/¬XOR the two bits of this diner, depending on whether they paid or not. | ||
let mut own_reveal = self.own_toss ^ self.neighbour_toss; | ||
|
@@ -301,8 +285,7 @@ impl Round<DinerId> for Round2 { | |
} | ||
// Extract the payloads from the other participants so we can produce a [`Protocol::Result`]. In this case it is | ||
// a tuple of 3 booleans. | ||
let payloads_d = downcast_payloads::<bool>(payloads)?; | ||
let bits = payloads_d.values().cloned().collect::<Vec<_>>(); | ||
let bits = payloads.into_values().collect::<Vec<_>>(); | ||
Ok(FinalizeOutcome::Result((bits[0], bits[1], own_reveal))) | ||
} | ||
} | ||
|
@@ -337,7 +320,7 @@ impl EntryPoint<DinerId> for DiningEntryPoint { | |
// Each `EntryPoint` creates one `Session`. | ||
fn make_round( | ||
self, | ||
rng: &mut dyn CryptoRngCore, | ||
rng: &mut impl CryptoRngCore, | ||
_shared_randomness: &[u8], | ||
id: &DinerId, | ||
) -> Result<BoxedRound<DinerId, Self::Protocol>, LocalError> { | ||
|
@@ -351,7 +334,7 @@ impl EntryPoint<DinerId> for DiningEntryPoint { | |
"[DiningEntryPoint, make_round] diner {id:?} tossed: {:?} (paid? {paid})", | ||
round.own_toss | ||
); | ||
let round = BoxedRound::new_dynamic(round); | ||
let round = BoxedRound::new(round); | ||
fjarri marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Ok(round) | ||
} | ||
} | ||
|
@@ -376,13 +359,6 @@ impl SessionParameters for DiningSessionParams { | |
type WireFormat = BinaryFormat; | ||
} | ||
|
||
// Just a utility method to help us convert a [`Payload`] to, for example, a `bool`. | ||
fn downcast_payloads<T: 'static>(map: BTreeMap<DinerId, Payload>) -> Result<BTreeMap<DinerId, T>, LocalError> { | ||
map.into_iter() | ||
.map(|(id, payload)| payload.downcast::<T>().map(|p| (id, p))) | ||
.collect() | ||
} | ||
|
||
fn main() { | ||
tracing_subscriber::fmt::init(); | ||
info!("Dining Cryptographers Protocol Example"); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,4 +4,4 @@ pub mod simple; | |
pub mod simple_chain; | ||
|
||
#[cfg(test)] | ||
mod simple_malicious; | ||
mod simple_test; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_ if round_id == 1
is a bit awkward looking, and I think thePartialEq
impl betweenRoundId
andRoundNum
actually makes it a bit worse. I tinkered a bit and came up with a few alternatives.PartialEq
impl, add around()
method toRoundId
to make it clear what we're matching on:to_info()
method on theRound
trait so we can emphasize the round itself:Adding the
to_info
method to the Round-trait requires adding+ Sized
to the bounds, which might be problematic, otherwise I think 3) is the version I prefer. Regardless of which version you prefer going with, I think the PartialEq impl between RoundNum and RoundId is "too clever" and forces the reader to go deeper into the internals than what is actually useful.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, the comparison part would be fixed by #120. I like the
to_info()
approach, but it's an orthogonal issue.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Options 2 and 3 implicitly drop the groups from round ID, which can lead to silent errors. Yes, technically at that point round IDs should not have groups, but if there's some bug in the code, they might.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to_info()
would require a method with a default implementation in theRound
trait, which I would really like to make non-overridable, but that involves some sealed trait magic.