Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
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
31 changes: 30 additions & 1 deletion Cargo.lock

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

92 changes: 77 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,96 @@ multiparty protocols (e.g. threshold signing, random beacons, etc.).
* Simple, configurable \
Protocol can be carried out in a few lines of code: check out examples.
* Independent of networking layer \
We use abstractions `Stream` and `Sink` to receive and send messages.
You may define your own networking layer and you don't need to change anything
in protocol implementation: it's agnostic of networking by default! So you can
use central delivery server, distributed redis nodes, postgres database,
p2p channels, or a public blockchain, or whatever else fits your needs.

## Example
MPC protocol execution typically looks like this:

```rust
// protocol to be executed, takes MPC engine `M`, index of party `i`,
// and number of participants `n`
async fn keygen<M>(mpc: M, i: u16, n: u16) -> Result<KeyShare>
where
M: round_based::Mpc<Msg = KeygenMsg>
{
// ...
}
// establishes network connection(s) to other parties so they may communicate
async fn connect() ->
impl futures::Stream<Item = Result<round_based::Incoming<KeygenMsg>>>
+ futures::Sink<round_based::Outgoing<KeygenMsg>, Error = Error>
+ Unpin
{
// ...
}
let delivery = connect().await;

// constructs an MPC engine, which, primarily, is used to communicate with
// other parties
let mpc = round_based::mpc::connected(delivery);

// execute the protocol
let keyshare = keygen(mpc, i, n).await?;
```

## Networking

In order to run an MPC protocol, transport layer needs to be defined. All you have to do is to
implement `Delivery` trait which is basically a stream and a sink for receiving and sending messages.
provide a channel which implements a stream and a sink for receiving and sending messages.

```rust
async fn connect() ->
impl futures::Stream<Item = Result<round_based::Incoming<Msg>>>
+ futures::Sink<round_based::Outgoing<Msg>, Error = Error>
+ Unpin
{
// ...
}

let delivery = connect().await;
let party = round_based::mpc::connected(delivery);

// run the protocol
```

In order to guarantee the protocol security, it may require:

Message delivery should meet certain criterias that differ from protocol to protocol (refer to
the documentation of the protocol you're using), but usually they are:
* Message Authentication \
Guarantees message source and integrity. If protocol requires it, make sure
message was sent by claimed sender and that it hasn't been tampered with. \
This is typically achieved either through public-key cryptography (e.g.,
signing with a private key) or through symmetric mechanisms like MACs (e.g.,
HMAC) or authenticated encryption (AEAD) in point-to-point scenarios.
* Message Privacy \
When a p2p message is sent, only recipient shall be able to read the content. \
It can be achieved by using symmetric or asymmetric encryption, encryption methods
come with their own trade-offs (e.g. simplicity vs forward secrecy).
* Reliable Broadcast \
When party receives a reliable broadcast message it shall be ensured that
everybody else received the same message. \
Our library provides `echo_broadcast` support out-of-box that enforces broadcast
reliability by adding an extra communication round per each round that requires
reliable broadcast. \
More advanced techniques implement [Byzantine fault](https://en.wikipedia.org/wiki/Byzantine_fault)
tolerant broadcast

* Messages should be authenticated \
Each message should be signed with identity key of the sender. This implies having Public Key
Infrastructure.
* P2P messages should be encrypted \
Only recipient should be able to learn the content of p2p message
* Broadcast channel should be reliable \
Some protocols may require broadcast channel to be reliable. Simply saying, when party receives a
broadcast message over reliable channel it should be ensured that everybody else received the same
message.
## Developing MPC protocol with `round_based`
We plan to write a book guiding through MPC protocol development process, but
while it's not done, you may refer to [random beacon example](https://github.com/LFDT-Lockness/round-based/blob/m/examples/random-generation-protocol/src/lib.rs)
and our well-documented API.

## Features

* `sim` enables protocol execution simulation, see `sim` module
* `sim-async` enables protocol execution simulation with tokio runtime, see `sim::async_env`
module
* `state-machine` provides ability to carry out the protocol, defined as async function, via Sync
API, see `state_machine` module
* `derive` is needed to use `ProtocolMessage` proc macro
API, see `state_machine` module
* `echo-broadcast` adds `echo_broadcast` support
* `derive` is needed to use `ProtocolMsg` proc macro
* `runtime-tokio` enables tokio-specific implementation of async runtime

## Join us in Discord!
Expand Down
5 changes: 5 additions & 0 deletions examples/random-generation-protocol/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ thiserror = { version = "2", default-features = false }
# We don't use it directy, but we need to enable `serde` feature
generic-array = { version = "0.14", features = ["serde"] }

udigest = { version = "0.2", default-features = false, features = ["derive"], optional = true }

[features]
udigest = ["dep:udigest"]

[dev-dependencies]
round-based = { path = "../../round-based", features = ["derive", "sim", "state-machine"] }
tokio = { version = "1.15", features = ["macros", "rt"] }
Expand Down
85 changes: 42 additions & 43 deletions examples/random-generation-protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ use alloc::{vec, vec::Vec};
use serde::{Deserialize, Serialize};
use sha2::{digest::Output, Digest, Sha256};

use round_based::rounds_router::{
simple_store::{RoundInput, RoundInputError},
CompleteRoundError, RoundsRouter,
use round_based::{
mpc::{Mpc, MpcExecution},
MsgId,
};
use round_based::{Delivery, Mpc, MpcParty, MsgId, Outgoing, PartyIndex, ProtocolMessage, SinkExt};

/// Protocol message
#[derive(Clone, Debug, PartialEq, ProtocolMessage, Serialize, Deserialize)]
#[derive(round_based::ProtocolMsg, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "udigest", derive(udigest::Digestable))]
pub enum Msg {
/// Round 1
CommitMsg(CommitMsg),
Expand All @@ -34,38 +34,38 @@ pub enum Msg {
}

/// Message from round 1
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "udigest", derive(udigest::Digestable))]
pub struct CommitMsg {
/// Party commitment
#[cfg_attr(feature = "udigest", udigest(as_bytes))]
pub commitment: Output<Sha256>,
}

/// Message from round 2
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "udigest", derive(udigest::Digestable))]
pub struct DecommitMsg {
/// Randomness generated by party
#[cfg_attr(feature = "udigest", udigest(as_bytes))]
pub randomness: [u8; 32],
}

/// Carries out the randomness generation protocol
pub async fn protocol_of_random_generation<R, M>(
party: M,
i: PartyIndex,
mut mpc: M,
i: u16,
n: u16,
mut rng: R,
) -> Result<[u8; 32], Error<M::ReceiveError, M::SendError>>
) -> Result<[u8; 32], ErrorM<M>>
where
M: Mpc<ProtocolMessage = Msg>,
M: Mpc<Msg = Msg>,
R: rand_core::RngCore,
{
let MpcParty { delivery, .. } = party.into_party();
let (incoming, mut outgoing) = delivery.split();

// Define rounds
let mut rounds = RoundsRouter::<Msg>::builder();
let round1 = rounds.add_round(RoundInput::<CommitMsg>::broadcast(i, n));
let round2 = rounds.add_round(RoundInput::<DecommitMsg>::broadcast(i, n));
let mut rounds = rounds.listen(incoming);
let round1 = mpc.add_round(round_based::round::reliable_broadcast::<CommitMsg>(i, n));
let round2 = mpc.add_round(round_based::round::broadcast::<DecommitMsg>(i, n));
let mut mpc = mpc.finish_setup();

// --- The Protocol ---

Expand All @@ -74,33 +74,26 @@ where
rng.fill_bytes(&mut local_randomness);

// 2. Commit local randomness (broadcast m=sha256(randomness))
// This message must be reliably broadcasted to guarantee protocol security
let commitment = Sha256::digest(local_randomness);
outgoing
.send(Outgoing::broadcast(Msg::CommitMsg(CommitMsg {
commitment,
})))
mpc.reliably_broadcast(Msg::CommitMsg(CommitMsg { commitment }))
.await
.map_err(Error::Round1Send)?;

// 3. Receive committed randomness from other parties
let commitments = rounds
.complete(round1)
.await
.map_err(Error::Round1Receive)?;
let commitments = mpc.complete(round1).await.map_err(Error::Round1Receive)?;

// 4. Open local randomness
outgoing
.send(Outgoing::broadcast(Msg::DecommitMsg(DecommitMsg {
randomness: local_randomness,
})))
.await
.map_err(Error::Round2Send)?;
// This message will be sent to all other participants, but it doesn't require
// a reliable broadcast
mpc.send_to_all(Msg::DecommitMsg(DecommitMsg {
randomness: local_randomness,
}))
.await
.map_err(Error::Round2Send)?;

// 5. Receive opened local randomness from other parties, verify them, and output protocol randomness
let randomness = rounds
.complete(round2)
.await
.map_err(Error::Round2Receive)?;
let randomness = mpc.complete(round2).await.map_err(Error::Round2Receive)?;

let mut guilty_parties = vec![];
let mut output = local_randomness;
Expand Down Expand Up @@ -139,13 +132,13 @@ pub enum Error<RecvErr, SendErr> {
Round1Send(#[source] SendErr),
/// Couldn't receive a message in the first round
#[error("receive messages at round 1")]
Round1Receive(#[source] CompleteRoundError<RoundInputError, RecvErr>),
Round1Receive(#[source] RecvErr),
/// Couldn't send a message in the second round
#[error("send a message at round 2")]
Round2Send(#[source] SendErr),
/// Couldn't receive a message in the second round
#[error("receive messages at round 2")]
Round2Receive(#[source] CompleteRoundError<RoundInputError, RecvErr>),
Round2Receive(#[source] RecvErr),

/// Some of the parties cheated
#[error("malicious parties: {guilty_parties:?}")]
Expand All @@ -155,11 +148,17 @@ pub enum Error<RecvErr, SendErr> {
},
}

/// Error type deduced from `M: Mpc`
pub type ErrorM<M> = Error<
round_based::mpc::CompleteRoundErr<M, round_based::round::RoundInputError>,
<M as Mpc>::SendErr,
>;
Comment on lines +151 to +155
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks useful, maybe we should add this to the crate itself

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Error here is random beacon-specific error type, we can't add it to the crate itself


/// Blames a party in cheating during the protocol
#[derive(Debug)]
pub struct Blame {
/// Index of the cheated party
pub guilty_party: PartyIndex,
pub guilty_party: u16,
/// ID of the message that party sent in the first round
pub commitment_msg: MsgId,
/// ID of the message that party sent in the second round
Expand Down Expand Up @@ -251,7 +250,7 @@ mod tests {
.received_msg(Incoming {
id: 0,
sender: 1,
msg_type: round_based::MessageType::Broadcast,
msg_type: round_based::MessageType::Broadcast { reliable: true },
msg: Msg::CommitMsg(CommitMsg {
commitment: party1_com,
}),
Expand All @@ -265,7 +264,7 @@ mod tests {
.received_msg(Incoming {
id: 1,
sender: 2,
msg_type: round_based::MessageType::Broadcast,
msg_type: round_based::MessageType::Broadcast { reliable: true },
msg: Msg::CommitMsg(CommitMsg {
commitment: party2_com,
}),
Expand Down Expand Up @@ -298,7 +297,7 @@ mod tests {
.received_msg(Incoming {
id: 3,
sender: 1,
msg_type: round_based::MessageType::Broadcast,
msg_type: round_based::MessageType::Broadcast { reliable: false },
msg: Msg::DecommitMsg(DecommitMsg {
randomness: party1_rng,
}),
Expand All @@ -312,7 +311,7 @@ mod tests {
.received_msg(Incoming {
id: 3,
sender: 2,
msg_type: round_based::MessageType::Broadcast,
msg_type: round_based::MessageType::Broadcast { reliable: false },
msg: Msg::DecommitMsg(DecommitMsg {
randomness: party2_rng,
}),
Expand Down
Loading
Loading