diff --git a/src/honey_badger/batch.rs b/src/honey_badger/batch.rs new file mode 100644 index 0000000..47fee74 --- /dev/null +++ b/src/honey_badger/batch.rs @@ -0,0 +1,49 @@ +use std::collections::BTreeMap; + +/// A batch of contributions the algorithm has output. +#[derive(Clone, Debug)] +pub struct Batch { + pub epoch: u64, + pub contributions: BTreeMap, +} + +impl Batch { + /// Returns an iterator over references to all transactions included in the batch. + pub fn iter<'a>(&'a self) -> impl Iterator::Item> + where + &'a C: IntoIterator, + { + self.contributions.values().flat_map(|item| item) + } + + /// Returns an iterator over all transactions included in the batch. Consumes the batch. + pub fn into_tx_iter(self) -> impl Iterator::Item> + where + C: IntoIterator, + { + self.contributions.into_iter().flat_map(|(_, vec)| vec) + } + + /// Returns the number of transactions in the batch (without detecting duplicates). + pub fn len(&self) -> usize + where + C: AsRef<[Tx]>, + { + self.contributions + .values() + .map(C::as_ref) + .map(<[Tx]>::len) + .sum() + } + + /// Returns `true` if the batch contains no transactions. + pub fn is_empty(&self) -> bool + where + C: AsRef<[Tx]>, + { + self.contributions + .values() + .map(C::as_ref) + .all(<[Tx]>::is_empty) + } +} diff --git a/src/honey_badger/builder.rs b/src/honey_badger/builder.rs new file mode 100644 index 0000000..4b906a3 --- /dev/null +++ b/src/honey_badger/builder.rs @@ -0,0 +1,58 @@ +use std::collections::BTreeMap; +use std::fmt::Debug; +use std::hash::Hash; +use std::marker::PhantomData; +use std::sync::Arc; + +use rand::Rand; +use serde::{Deserialize, Serialize}; + +use super::HoneyBadger; +use messaging::NetworkInfo; + +/// A Honey Badger builder, to configure the parameters and create new instances of `HoneyBadger`. +pub struct HoneyBadgerBuilder { + /// Shared network data. + netinfo: Arc>, + /// The maximum number of future epochs for which we handle messages simultaneously. + max_future_epochs: usize, + _phantom: PhantomData, +} + +impl HoneyBadgerBuilder +where + C: Serialize + for<'r> Deserialize<'r> + Debug + Hash + Eq, + NodeUid: Ord + Clone + Debug + Rand, +{ + /// Returns a new `HoneyBadgerBuilder` configured to use the node IDs and cryptographic keys + /// specified by `netinfo`. + pub fn new(netinfo: Arc>) -> Self { + HoneyBadgerBuilder { + netinfo, + max_future_epochs: 3, + _phantom: PhantomData, + } + } + + /// Sets the maximum number of future epochs for which we handle messages simultaneously. + pub fn max_future_epochs(&mut self, max_future_epochs: usize) -> &mut Self { + self.max_future_epochs = max_future_epochs; + self + } + + /// Creates a new Honey Badger instance. + pub fn build(&self) -> HoneyBadger { + HoneyBadger { + netinfo: self.netinfo.clone(), + epoch: 0, + has_input: false, + common_subsets: BTreeMap::new(), + max_future_epochs: self.max_future_epochs as u64, + incoming_queue: BTreeMap::new(), + received_shares: BTreeMap::new(), + decrypted_contributions: BTreeMap::new(), + ciphertexts: BTreeMap::new(), + _phantom: PhantomData, + } + } +} diff --git a/src/honey_badger/error.rs b/src/honey_badger/error.rs new file mode 100644 index 0000000..f5b1e26 --- /dev/null +++ b/src/honey_badger/error.rs @@ -0,0 +1,67 @@ +use std::fmt::{self, Display, Formatter}; + +use bincode; +use failure::{Backtrace, Context, Fail}; + +use common_subset; + +/// Honey badger error variants. +#[derive(Debug, Fail)] +pub enum ErrorKind { + #[fail(display = "ProposeBincode error: {}", _0)] + ProposeBincode(bincode::ErrorKind), + #[fail(display = "ProposeCommonSubset0 error: {}", _0)] + ProposeCommonSubset0(common_subset::Error), + #[fail(display = "ProposeCommonSubset1 error: {}", _0)] + ProposeCommonSubset1(common_subset::Error), + #[fail(display = "HandleCommonMessageCommonSubset0 error: {}", _0)] + HandleCommonMessageCommonSubset0(common_subset::Error), + #[fail(display = "HandleCommonMessageCommonSubset1 error: {}", _0)] + HandleCommonMessageCommonSubset1(common_subset::Error), + #[fail(display = "Unknown sender")] + UnknownSender, +} + +/// A honey badger error. +#[derive(Debug)] +pub struct Error { + inner: Context, +} + +impl Fail for Error { + fn cause(&self) -> Option<&Fail> { + self.inner.cause() + } + + fn backtrace(&self) -> Option<&Backtrace> { + self.inner.backtrace() + } +} + +impl Error { + pub fn kind(&self) -> &ErrorKind { + self.inner.get_context() + } +} + +impl From for Error { + fn from(kind: ErrorKind) -> Error { + Error { + inner: Context::new(kind), + } + } +} + +impl From> for Error { + fn from(inner: Context) -> Error { + Error { inner } + } +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(&self.inner, f) + } +} + +pub type Result = ::std::result::Result; diff --git a/src/honey_badger.rs b/src/honey_badger/honey_badger.rs similarity index 70% rename from src/honey_badger.rs rename to src/honey_badger/honey_badger.rs index 8bf1873..7848a40 100644 --- a/src/honey_badger.rs +++ b/src/honey_badger/honey_badger.rs @@ -1,31 +1,6 @@ -//! # Honey Badger -//! -//! Honey Badger allows a network of _N_ nodes with at most _f_ faulty ones, -//! where _3 f < N_, to input "contributions" - any kind of data -, and to agree on a sequence of -//! _batches_ of contributions. The protocol proceeds in _epochs_, starting at number 0, and outputs -//! one batch in each epoch. It never terminates: It handles a continuous stream of incoming -//! contributions and keeps producing new batches from them. All correct nodes will output the same -//! batch for each epoch. Each validator proposes one contribution per epoch, and every batch will -//! contain the contributions of at least _N - f_ validators. -//! -//! ## How it works -//! -//! In every epoch, every validator encrypts their contribution and proposes it to the others. -//! A `CommonSubset` instance determines which proposals are accepted and will be part of the new -//! batch. Using threshold encryption, the nodes collaboratively decrypt all accepted -//! contributions. Invalid contributions (that e.g. cannot be deserialized) are discarded - their -//! proposers must be faulty -, and the remaining ones are output as the new batch. The next epoch -//! begins as soon as the validators propose new contributions again. -//! -//! So it is essentially an endlessly repeating `CommonSubset`, but with the proposed values -//! encrypted. The encryption makes it harder for an attacker to try and censor a particular value -//! by influencing the set of proposals that make it into the common subset, because they don't -//! know the decrypted values before the subset is determined. - -use rand::Rand; use std::collections::btree_map::Entry; use std::collections::{BTreeMap, BTreeSet}; -use std::fmt::{self, Debug, Display}; +use std::fmt::Debug; use std::hash::Hash; use std::marker::PhantomData; use std::mem; @@ -33,147 +8,41 @@ use std::sync::Arc; use bincode; use crypto::{Ciphertext, DecryptionShare}; -use failure::{Backtrace, Context, Fail}; use itertools::Itertools; +use rand::Rand; use serde::{Deserialize, Serialize}; +use super::{Batch, Error, ErrorKind, HoneyBadgerBuilder, Message, MessageContent, Result}; use common_subset::{self, CommonSubset}; use fault_log::{Fault, FaultKind, FaultLog}; use messaging::{self, DistAlgorithm, NetworkInfo, Target}; -/// Honey badger error variants. -#[derive(Debug, Fail)] -pub enum ErrorKind { - #[fail(display = "ProposeBincode error: {}", _0)] - ProposeBincode(bincode::ErrorKind), - #[fail(display = "ProposeCommonSubset0 error: {}", _0)] - ProposeCommonSubset0(common_subset::Error), - #[fail(display = "ProposeCommonSubset1 error: {}", _0)] - ProposeCommonSubset1(common_subset::Error), - #[fail(display = "HandleCommonMessageCommonSubset0 error: {}", _0)] - HandleCommonMessageCommonSubset0(common_subset::Error), - #[fail(display = "HandleCommonMessageCommonSubset1 error: {}", _0)] - HandleCommonMessageCommonSubset1(common_subset::Error), - #[fail(display = "Unknown sender")] - UnknownSender, -} - -/// A honey badger error. -#[derive(Debug)] -pub struct Error { - inner: Context, -} - -impl Fail for Error { - fn cause(&self) -> Option<&Fail> { - self.inner.cause() - } - - fn backtrace(&self) -> Option<&Backtrace> { - self.inner.backtrace() - } -} - -impl Error { - pub fn kind(&self) -> &ErrorKind { - self.inner.get_context() - } -} - -impl From for Error { - fn from(kind: ErrorKind) -> Error { - Error { - inner: Context::new(kind), - } - } -} - -impl From> for Error { - fn from(inner: Context) -> Error { - Error { inner } - } -} - -impl Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Display::fmt(&self.inner, f) - } -} - -pub type Result = ::std::result::Result; - -/// A Honey Badger builder, to configure the parameters and create new instances of `HoneyBadger`. -pub struct HoneyBadgerBuilder { - /// Shared network data. - netinfo: Arc>, - /// The maximum number of future epochs for which we handle messages simultaneously. - max_future_epochs: usize, - _phantom: PhantomData, -} - -impl HoneyBadgerBuilder -where - C: Serialize + for<'r> Deserialize<'r> + Debug + Hash + Eq, - NodeUid: Ord + Clone + Debug + Rand, -{ - /// Returns a new `HoneyBadgerBuilder` configured to use the node IDs and cryptographic keys - /// specified by `netinfo`. - pub fn new(netinfo: Arc>) -> Self { - HoneyBadgerBuilder { - netinfo, - max_future_epochs: 3, - _phantom: PhantomData, - } - } - - /// Sets the maximum number of future epochs for which we handle messages simultaneously. - pub fn max_future_epochs(&mut self, max_future_epochs: usize) -> &mut Self { - self.max_future_epochs = max_future_epochs; - self - } - - /// Creates a new Honey Badger instance. - pub fn build(&self) -> HoneyBadger { - HoneyBadger { - netinfo: self.netinfo.clone(), - epoch: 0, - has_input: false, - common_subsets: BTreeMap::new(), - max_future_epochs: self.max_future_epochs as u64, - incoming_queue: BTreeMap::new(), - received_shares: BTreeMap::new(), - decrypted_contributions: BTreeMap::new(), - ciphertexts: BTreeMap::new(), - _phantom: PhantomData, - } - } -} - /// An instance of the Honey Badger Byzantine fault tolerant consensus algorithm. #[derive(Debug)] pub struct HoneyBadger { /// Shared network data. - netinfo: Arc>, + pub(super) netinfo: Arc>, /// The earliest epoch from which we have not yet received output. - epoch: u64, + pub(super) epoch: u64, /// Whether we have already submitted a proposal for the current epoch. - has_input: bool, + pub(super) has_input: bool, /// The Asynchronous Common Subset instance that decides which nodes' transactions to include, /// indexed by epoch. - common_subsets: BTreeMap>, + pub(super) common_subsets: BTreeMap>, /// The maximum number of `CommonSubset` instances that we run simultaneously. - max_future_epochs: u64, + pub(super) max_future_epochs: u64, /// Messages for future epochs that couldn't be handled yet. - incoming_queue: BTreeMap)>>, + pub(super) incoming_queue: BTreeMap)>>, /// Received decryption shares for an epoch. Each decryption share has a sender and a /// proposer. The outer `BTreeMap` has epochs as its key. The next `BTreeMap` has proposers as /// its key. The inner `BTreeMap` has the sender as its key. - received_shares: BTreeMap>>, + pub(super) received_shares: + BTreeMap>>, /// Decoded accepted proposals. - decrypted_contributions: BTreeMap>, + pub(super) decrypted_contributions: BTreeMap>, /// Ciphertexts output by Common Subset in an epoch. - ciphertexts: BTreeMap>, - _phantom: PhantomData, + pub(super) ciphertexts: BTreeMap>, + pub(super) _phantom: PhantomData, } pub type Step = messaging::Step>; @@ -631,85 +500,3 @@ where } } } - -/// A batch of contributions the algorithm has output. -#[derive(Clone, Debug)] -pub struct Batch { - pub epoch: u64, - pub contributions: BTreeMap, -} - -impl Batch { - /// Returns an iterator over references to all transactions included in the batch. - pub fn iter<'a>(&'a self) -> impl Iterator::Item> - where - &'a C: IntoIterator, - { - self.contributions.values().flat_map(|item| item) - } - - /// Returns an iterator over all transactions included in the batch. Consumes the batch. - pub fn into_tx_iter(self) -> impl Iterator::Item> - where - C: IntoIterator, - { - self.contributions.into_iter().flat_map(|(_, vec)| vec) - } - - /// Returns the number of transactions in the batch (without detecting duplicates). - pub fn len(&self) -> usize - where - C: AsRef<[Tx]>, - { - self.contributions - .values() - .map(C::as_ref) - .map(<[Tx]>::len) - .sum() - } - - /// Returns `true` if the batch contains no transactions. - pub fn is_empty(&self) -> bool - where - C: AsRef<[Tx]>, - { - self.contributions - .values() - .map(C::as_ref) - .all(<[Tx]>::is_empty) - } -} - -/// The content of a `HoneyBadger` message. It should be further annotated with an epoch. -#[derive(Clone, Debug, Deserialize, Rand, Serialize)] -pub enum MessageContent { - /// A message belonging to the common subset algorithm in the given epoch. - CommonSubset(common_subset::Message), - /// A decrypted share of the output of `proposer_id`. - DecryptionShare { - proposer_id: NodeUid, - share: DecryptionShare, - }, -} - -impl MessageContent { - pub fn with_epoch(self, epoch: u64) -> Message { - Message { - epoch, - content: self, - } - } -} - -/// A message sent to or received from another node's Honey Badger instance. -#[derive(Clone, Debug, Deserialize, Rand, Serialize)] -pub struct Message { - epoch: u64, - content: MessageContent, -} - -impl Message { - pub fn epoch(&self) -> u64 { - self.epoch - } -} diff --git a/src/honey_badger/message.rs b/src/honey_badger/message.rs new file mode 100644 index 0000000..fb7b88d --- /dev/null +++ b/src/honey_badger/message.rs @@ -0,0 +1,38 @@ +use crypto::DecryptionShare; +use rand::Rand; + +use common_subset; + +/// The content of a `HoneyBadger` message. It should be further annotated with an epoch. +#[derive(Clone, Debug, Deserialize, Rand, Serialize)] +pub enum MessageContent { + /// A message belonging to the common subset algorithm in the given epoch. + CommonSubset(common_subset::Message), + /// A decrypted share of the output of `proposer_id`. + DecryptionShare { + proposer_id: NodeUid, + share: DecryptionShare, + }, +} + +impl MessageContent { + pub fn with_epoch(self, epoch: u64) -> Message { + Message { + epoch, + content: self, + } + } +} + +/// A message sent to or received from another node's Honey Badger instance. +#[derive(Clone, Debug, Deserialize, Rand, Serialize)] +pub struct Message { + pub(super) epoch: u64, + pub(super) content: MessageContent, +} + +impl Message { + pub fn epoch(&self) -> u64 { + self.epoch + } +} diff --git a/src/honey_badger/mod.rs b/src/honey_badger/mod.rs new file mode 100644 index 0000000..26fd192 --- /dev/null +++ b/src/honey_badger/mod.rs @@ -0,0 +1,35 @@ +//! # Honey Badger +//! +//! Honey Badger allows a network of _N_ nodes with at most _f_ faulty ones, +//! where _3 f < N_, to input "contributions" - any kind of data -, and to agree on a sequence of +//! _batches_ of contributions. The protocol proceeds in _epochs_, starting at number 0, and outputs +//! one batch in each epoch. It never terminates: It handles a continuous stream of incoming +//! contributions and keeps producing new batches from them. All correct nodes will output the same +//! batch for each epoch. Each validator proposes one contribution per epoch, and every batch will +//! contain the contributions of at least _N - f_ validators. +//! +//! ## How it works +//! +//! In every epoch, every validator encrypts their contribution and proposes it to the others. +//! A `CommonSubset` instance determines which proposals are accepted and will be part of the new +//! batch. Using threshold encryption, the nodes collaboratively decrypt all accepted +//! contributions. Invalid contributions (that e.g. cannot be deserialized) are discarded - their +//! proposers must be faulty -, and the remaining ones are output as the new batch. The next epoch +//! begins as soon as the validators propose new contributions again. +//! +//! So it is essentially an endlessly repeating `CommonSubset`, but with the proposed values +//! encrypted. The encryption makes it harder for an attacker to try and censor a particular value +//! by influencing the set of proposals that make it into the common subset, because they don't +//! know the decrypted values before the subset is determined. + +mod batch; +mod builder; +mod error; +mod honey_badger; +mod message; + +pub use self::batch::Batch; +pub use self::builder::HoneyBadgerBuilder; +pub use self::error::{Error, ErrorKind, Result}; +pub use self::honey_badger::{HoneyBadger, Step}; +pub use self::message::{Message, MessageContent}; diff --git a/src/lib.rs b/src/lib.rs index 9937bec..6cdf606 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,6 +100,8 @@ // TODO: Remove this once https://github.com/rust-lang-nursery/error-chain/issues/245 is resolved. #![allow(renamed_and_removed_lints)] +// We put algorithm structs in `src/algorithm/algorithm.rs`. +#![cfg_attr(feature = "cargo-clippy", allow(module_inception))] extern crate bincode; extern crate byteorder;