Skip to content
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

Protocol combinators #60

Merged
merged 9 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `SessionId::new()` renamed to `from_seed()`. ([#41])
- `FirstRound::new()` takes a `&[u8]` instead of a `SessionId` object. ([#41])
- The signatures of `Round::make_echo_broadcast()`, `Round::make_direct_message()`, and `Round::receive_message()`, take messages without `Option`s. ([#46])
- `Round::make_direct_message_with_artifact()` is the method returning an artifact now; `Round::make_direct_message()` is a shortcut for cases where no artifact is returned. ([#46])
- `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])
- Renamed `(Verified)MessageBundle` to `(Verified)Message`. Both are now generic over `Verifier`. ([#56])
- `Session::preprocess_message()` now returns a `PreprocessOutcome` instead of just an `Option`. ([#57])
- `Session::terminate_due_to_errors()` replaces `terminate()`; `terminate()` now signals user interrupt. ([#58])
- Renamed `FirstRound` trait to `EntryPoint`. ([#60])
- Added `Protocol` type to `EntryPoint`. ([#60])
- `EntryPoint` and `FinalizeOutcome::AnotherRound` now use a new `BoxedRound` wrapper type. ([#60])
- `PartyId` and `ProtocolError` are now bound on `Serialize`/`Deserialize`. ([#60])


### Added
Expand All @@ -31,6 +34,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Re-export `digest` from the `session` module. ([#56])
- Added `Message::destination()`. ([#56])
- `PartyId` trait alias for the combination of bounds needed for a party identifier. ([#59])
- An impl of `ProtocolError` for `()` for protocols that don't use errors. ([#60])
- A dummy `CorrectnessProof` trait. ([#60])
- A `misbehave` combinator, intended primarily for testing. ([#60])
- A `chain` combinator for chaining two protocols. ([#60])
- `EntryPoint::ENTRY_ROUND` constant. ([#60])
dvdplm marked this conversation as resolved.
Show resolved Hide resolved


[#32]: https://github.com/entropyxyz/manul/pull/32
Expand All @@ -45,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#57]: https://github.com/entropyxyz/manul/pull/57
[#58]: https://github.com/entropyxyz/manul/pull/58
[#59]: https://github.com/entropyxyz/manul/pull/59
[#60]: https://github.com/entropyxyz/manul/pull/60


## [0.0.1] - 2024-10-12
Expand Down
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
extern crate alloc;

pub mod simple;
pub mod simple_chain;
dvdplm marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(test)]
mod simple_malicious;
25 changes: 14 additions & 11 deletions examples/src/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,15 @@ struct Round1Payload {
x: u8,
}

impl<Id: PartyId> FirstRound<Id> for Round1<Id> {
impl<Id: PartyId> EntryPoint<Id> for Round1<Id> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming ideas:

  • Init
  • Start
  • Gateway
  • ProtocolStart
  • InitRound
  • Kickoff

Can you elaborate a bit on why "FirstRound" is a bad name? I kind of liked it because it's very descriptive.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was logical when FirstRound was bound on Round, but it's not anymore. Now it's more like a Round factory.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be especially true if we make it stateful (see the note in the PR description).

type Inputs = Inputs<Id>;
type Protocol = SimpleProtocol;
fn new(
_rng: &mut impl CryptoRngCore,
_shared_randomness: &[u8],
id: Id,
inputs: Self::Inputs,
) -> Result<Self, LocalError> {
) -> Result<BoxedRound<Id, Self::Protocol>, LocalError> {
// Just some numbers associated with IDs to use in the dummy protocol.
// They will be the same on each node since IDs are ordered.
let ids_to_positions = inputs
Expand All @@ -169,13 +170,13 @@ impl<Id: PartyId> FirstRound<Id> for Round1<Id> {
let mut ids = inputs.all_ids;
ids.remove(&id);

Ok(Self {
Ok(BoxedRound::new_dynamic(Self {
context: Context {
id,
other_ids: ids,
ids_to_positions,
},
})
}))
}
}

Expand Down Expand Up @@ -228,14 +229,15 @@ impl<Id: PartyId> Round<Id> for Round1<Id> {
_rng: &mut impl CryptoRngCore,
serializer: &Serializer,
destination: &Id,
) -> Result<DirectMessage, LocalError> {
) -> Result<(DirectMessage, Option<Artifact>), LocalError> {
debug!("{:?}: making direct message for {:?}", self.context.id, destination);

let message = Round1Message {
my_position: self.context.ids_to_positions[&self.context.id],
your_position: self.context.ids_to_positions[destination],
};
DirectMessage::new(serializer, message)
let dm = DirectMessage::new(serializer, message)?;
Ok((dm, None))
}

fn receive_message(
Expand Down Expand Up @@ -281,11 +283,11 @@ impl<Id: PartyId> Round<Id> for Round1<Id> {
let sum = self.context.ids_to_positions[&self.context.id]
+ typed_payloads.iter().map(|payload| payload.x).sum::<u8>();

let round2 = Round2 {
let round2 = BoxedRound::new_dynamic(Round2 {
round1_sum: sum,
context: self.context,
};
Ok(FinalizeOutcome::another_round(round2))
});
Ok(FinalizeOutcome::AnotherRound(round2))
}

fn expecting_messages_from(&self) -> &BTreeSet<Id> {
Expand Down Expand Up @@ -325,14 +327,15 @@ impl<Id: PartyId> Round<Id> for Round2<Id> {
_rng: &mut impl CryptoRngCore,
serializer: &Serializer,
destination: &Id,
) -> Result<DirectMessage, LocalError> {
) -> Result<(DirectMessage, Option<Artifact>), LocalError> {
debug!("{:?}: making direct message for {:?}", self.context.id, destination);

let message = Round2Message {
my_position: self.context.ids_to_positions[&self.context.id],
your_position: self.context.ids_to_positions[destination],
};
DirectMessage::new(serializer, message)
let dm = DirectMessage::new(serializer, message)?;
Ok((dm, None))
}

fn receive_message(
Expand Down
85 changes: 85 additions & 0 deletions examples/src/simple_chain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use core::fmt::Debug;

use manul::{
combinators::chain::{Chained, ChainedEntryPoint},
protocol::PartyId,
};

use super::simple::{Inputs, Round1};

pub struct ChainedSimple;
dvdplm marked this conversation as resolved.
Show resolved Hide resolved

#[derive(Debug)]
pub struct NewInputs<Id>(Inputs<Id>);

impl<'a, Id: PartyId> From<&'a NewInputs<Id>> for Inputs<Id> {
fn from(source: &'a NewInputs<Id>) -> Self {
source.0.clone()
}
}

impl<Id: PartyId> From<(NewInputs<Id>, u8)> for Inputs<Id> {
fn from(source: (NewInputs<Id>, u8)) -> Self {
let (inputs, _result) = source;
inputs.0
}
}

impl<Id: PartyId> Chained<Id> for ChainedSimple {
type Inputs = NewInputs<Id>;
type EntryPoint1 = Round1<Id>;
type EntryPoint2 = Round1<Id>;
}

pub type DoubleSimpleEntryPoint<Id> = ChainedEntryPoint<Id, ChainedSimple>;

#[cfg(test)]
mod tests {
use alloc::collections::BTreeSet;

use manul::{
session::{signature::Keypair, SessionOutcome},
testing::{run_sync, BinaryFormat, TestSessionParams, TestSigner, TestVerifier},
};
use rand_core::OsRng;
use tracing_subscriber::EnvFilter;

use super::{DoubleSimpleEntryPoint, NewInputs};
use crate::simple::Inputs;

#[test]
fn round() {
let signers = (0..3).map(TestSigner::new).collect::<Vec<_>>();
let all_ids = signers
.iter()
.map(|signer| signer.verifying_key())
.collect::<BTreeSet<_>>();
let inputs = signers
.into_iter()
.map(|signer| {
(
signer,
NewInputs(Inputs {
all_ids: all_ids.clone(),
}),
)
})
.collect::<Vec<_>>();

let my_subscriber = tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.finish();
let reports = tracing::subscriber::with_default(my_subscriber, || {
run_sync::<DoubleSimpleEntryPoint<TestVerifier>, TestSessionParams<BinaryFormat>>(&mut OsRng, inputs)
.unwrap()
});

for (_id, report) in reports {
if let SessionOutcome::Result(result) = report.outcome {
assert_eq!(result, 3); // 0 + 1 + 2
} else {
panic!("Session did not finish successfully");
}
}
}
}
Loading
Loading