mirror of https://github.com/poanetwork/hbbft.git
merge from upstream
This commit is contained in:
commit
40e611c824
26
Cargo.toml
26
Cargo.toml
|
@ -1,18 +1,20 @@
|
|||
[package]
|
||||
name = "hbbft"
|
||||
version = "0.1.0"
|
||||
authors = ["Vladimir Komendantskiy <komendantsky@gmail.com>",
|
||||
"Andreas Fackler <AndreasFackler@gmx.de>",
|
||||
"Peter van Nostrand <jnz@riseup.net>",
|
||||
"Andrew Gross <andogro@gmail.com>",
|
||||
"Nick Sanders <nsan1129@gmail.com>",
|
||||
"Marc Brinkmann <git@marcbrinkmann.de>"]
|
||||
description = "Honey Badger Byzantine fault tolerant consensus algorithm"
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/poanetwork/hbbft"
|
||||
readme = "README.md"
|
||||
keywords = ["consensus", "asynchronous", "threshold"]
|
||||
authors = [
|
||||
"Vladimir Komendantskiy <komendantsky@gmail.com>",
|
||||
"Andreas Fackler <AndreasFackler@gmx.de>",
|
||||
"Peter van Nostrand <jnz@riseup.net>",
|
||||
"Andrew Gross <andogro@gmail.com>",
|
||||
"Nick Sanders <nsan1129@gmail.com>",
|
||||
"Marc Brinkmann <git@marcbrinkmann.de>",
|
||||
]
|
||||
categories = ["algorithms", "asynchronous", "cryptography", "network-programming"]
|
||||
keywords = ["consensus", "asynchronous", "threshold"]
|
||||
license = "MIT/Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/poanetwork/hbbft"
|
||||
description = "The Honey Badger of Byzantine Fault Tolerant Consensus Protocols"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "poanetwork/hbbft" }
|
||||
|
@ -30,7 +32,7 @@ rand_derive = "0.3.1"
|
|||
reed-solomon-erasure = "3.1.0"
|
||||
serde = "1.0.55"
|
||||
serde_derive = "1.0.55"
|
||||
threshold_crypto = { git = "https://github.com/poanetwork/threshold_crypto", tag = "0.2.0-beta2" }
|
||||
threshold_crypto = "0.2.1"
|
||||
tiny-keccak = "1.4"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -37,7 +37,8 @@ In addition to **validators**, the algorithms support **observers**: These don't
|
|||
|
||||
- **[Binary Agreement](src/binary_agreement/binary_agreement.rs):** Each node inputs a binary value. The nodes agree on a value that was input by at least one correct node.
|
||||
|
||||
- **[Coin](src/coin.rs):** A pseudorandom binary value used by the Binary Agreement protocol.
|
||||
- **[Threshold Sign](src/threshold_sign.rs):**
|
||||
Each node inputs the same data to be signed, and outputs the unique valid signature matching the public master key. It is used as a pseudorandom value in the Binary Agreement protocol.
|
||||
|
||||
- **[Threshold Decryption](src/threshold_decryption.rs):**
|
||||
Each node inputs the same ciphertext, encrypted to the public master key, and outputs the decrypted data.
|
||||
|
@ -147,7 +148,6 @@ We have simplified algorithm naming conventions from the original paper.
|
|||
| Subset | Asynchronous Common Subset (ACS) |
|
||||
| Broadcast | Reliable Broadcast (RBC) |
|
||||
| Binary Agreement | Binary Byzantine Agreement (BBA) |
|
||||
| Coin | Common Coin |
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use super::bool_multimap::BoolMultimap;
|
|||
use super::bool_set::BoolSet;
|
||||
use super::sbv_broadcast::{self, SbvBroadcast};
|
||||
use super::{Error, Message, MessageContent, Nonce, Result, Step};
|
||||
use coin::{self, Coin, CoinMessage};
|
||||
use threshold_sign::{self, ThresholdSign};
|
||||
use {DistAlgorithm, NetworkInfo, NodeIdT, Target};
|
||||
|
||||
/// The state of the current epoch's coin. In some epochs this is fixed, in others it starts
|
||||
|
@ -15,7 +15,7 @@ enum CoinState<N> {
|
|||
/// The value was fixed in the current epoch, or the coin has already terminated.
|
||||
Decided(bool),
|
||||
/// The coin value is not known yet.
|
||||
InProgress(Coin<N, Nonce>),
|
||||
InProgress(Box<ThresholdSign<N>>),
|
||||
}
|
||||
|
||||
impl<N> CoinState<N> {
|
||||
|
@ -82,18 +82,7 @@ impl<N: NodeIdT> DistAlgorithm for BinaryAgreement<N> {
|
|||
|
||||
/// Receive input from a remote node.
|
||||
fn handle_message(&mut self, sender_id: &Self::NodeId, msg: Message) -> Result<Step<N>> {
|
||||
let Message { epoch, content } = msg;
|
||||
if self.decision.is_some() || (epoch < self.epoch && content.can_expire()) {
|
||||
// Message is obsolete: We are already in a later epoch or terminated.
|
||||
Ok(Step::default())
|
||||
} else if epoch > self.epoch {
|
||||
// Message is for a later epoch. We can't handle that yet.
|
||||
let queue = self.incoming_queue.entry(epoch).or_insert_with(Vec::new);
|
||||
queue.push((sender_id.clone(), content));
|
||||
Ok(Step::default())
|
||||
} else {
|
||||
self.handle_message_content(sender_id, content)
|
||||
}
|
||||
self.handle_message(sender_id, msg)
|
||||
}
|
||||
|
||||
/// Whether the algorithm has terminated.
|
||||
|
@ -107,6 +96,10 @@ impl<N: NodeIdT> DistAlgorithm for BinaryAgreement<N> {
|
|||
}
|
||||
|
||||
impl<N: NodeIdT> BinaryAgreement<N> {
|
||||
/// Creates a new `BinaryAgreement` instance. The `session_id` and `proposer_id` are used to
|
||||
/// uniquely identify this instance: its messages cannot be replayed in an instance with
|
||||
/// different values.
|
||||
// TODO: Use a generic type argument for that instead of something `Subset`-specific.
|
||||
pub fn new(netinfo: Arc<NetworkInfo<N>>, session_id: u64, proposer_id: N) -> Result<Self> {
|
||||
if !netinfo.is_node_validator(&proposer_id) {
|
||||
return Err(Error::UnknownProposer);
|
||||
|
@ -128,9 +121,9 @@ impl<N: NodeIdT> BinaryAgreement<N> {
|
|||
}
|
||||
|
||||
/// Sets the input value for Binary Agreement.
|
||||
fn handle_input(&mut self, input: bool) -> Result<Step<N>> {
|
||||
if self.epoch != 0 || self.estimated.is_some() {
|
||||
return Err(Error::InputNotAccepted);
|
||||
pub fn handle_input(&mut self, input: bool) -> Result<Step<N>> {
|
||||
if !self.can_input() {
|
||||
return Ok(Step::default());
|
||||
}
|
||||
// Set the initial estimated value to the input value.
|
||||
self.estimated = Some(input);
|
||||
|
@ -139,8 +132,25 @@ impl<N: NodeIdT> BinaryAgreement<N> {
|
|||
self.handle_sbvb_step(sbvb_step)
|
||||
}
|
||||
|
||||
/// Acceptance check to be performed before setting the input value.
|
||||
pub fn accepts_input(&self) -> bool {
|
||||
/// Handles an incoming message.
|
||||
pub fn handle_message(&mut self, sender_id: &N, msg: Message) -> Result<Step<N>> {
|
||||
let Message { epoch, content } = msg;
|
||||
if self.decision.is_some() || (epoch < self.epoch && content.can_expire()) {
|
||||
// Message is obsolete: We are already in a later epoch or terminated.
|
||||
Ok(Step::default())
|
||||
} else if epoch > self.epoch {
|
||||
// Message is for a later epoch. We can't handle that yet.
|
||||
let queue = self.incoming_queue.entry(epoch).or_insert_with(Vec::new);
|
||||
queue.push((sender_id.clone(), content));
|
||||
Ok(Step::default())
|
||||
} else {
|
||||
self.handle_message_content(sender_id, content)
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether we can still input a value. It is not an error to input if this returns `false`,
|
||||
/// but it will have no effect on the outcome.
|
||||
pub fn can_input(&self) -> bool {
|
||||
self.epoch == 0 && self.estimated.is_none()
|
||||
}
|
||||
|
||||
|
@ -221,16 +231,16 @@ impl<N: NodeIdT> BinaryAgreement<N> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Handles a Coin message. If there is output from Coin, starts the next
|
||||
/// epoch. The function may output a decision value.
|
||||
fn handle_coin(&mut self, sender_id: &N, msg: CoinMessage) -> Result<Step<N>> {
|
||||
let coin_step = match self.coin_state {
|
||||
/// Handles a `ThresholdSign` message. If there is output, starts the next epoch. The function
|
||||
/// may output a decision value.
|
||||
fn handle_coin(&mut self, sender_id: &N, msg: threshold_sign::Message) -> Result<Step<N>> {
|
||||
let ts_step = match self.coin_state {
|
||||
CoinState::Decided(_) => return Ok(Step::default()), // Coin value is already decided.
|
||||
CoinState::InProgress(ref mut coin) => coin
|
||||
CoinState::InProgress(ref mut ts) => ts
|
||||
.handle_message(sender_id, msg)
|
||||
.map_err(Error::HandleCoin)?,
|
||||
.map_err(Error::HandleThresholdSign)?,
|
||||
};
|
||||
self.on_coin_step(coin_step)
|
||||
self.on_coin_step(ts_step)
|
||||
}
|
||||
|
||||
/// Multicasts a `Conf(values)` message, and handles it.
|
||||
|
@ -263,14 +273,15 @@ impl<N: NodeIdT> BinaryAgreement<N> {
|
|||
Ok(step)
|
||||
}
|
||||
|
||||
/// Handles a step returned from the `Coin`.
|
||||
fn on_coin_step(&mut self, coin_step: coin::Step<N, Nonce>) -> Result<Step<N>> {
|
||||
/// Handles a step returned from the `ThresholdSign`.
|
||||
fn on_coin_step(&mut self, ts_step: threshold_sign::Step<N>) -> Result<Step<N>> {
|
||||
let mut step = Step::default();
|
||||
let epoch = self.epoch;
|
||||
let to_msg = |c_msg| MessageContent::Coin(Box::new(c_msg)).with_epoch(epoch);
|
||||
let coin_output = step.extend_with(coin_step, to_msg);
|
||||
if let Some(coin) = coin_output.into_iter().next() {
|
||||
self.coin_state = coin.into();
|
||||
let ts_output = step.extend_with(ts_step, to_msg);
|
||||
if let Some(sig) = ts_output.into_iter().next() {
|
||||
// Take the parity of the signature as the coin value.
|
||||
self.coin_state = sig.parity().into();
|
||||
step.extend(self.try_update_epoch()?);
|
||||
}
|
||||
Ok(step)
|
||||
|
@ -304,7 +315,7 @@ impl<N: NodeIdT> BinaryAgreement<N> {
|
|||
}
|
||||
|
||||
/// Creates the initial coin state for the current epoch, i.e. sets it to the predetermined
|
||||
/// value, or initializes a `Coin` instance.
|
||||
/// value, or initializes a `ThresholdSign` instance.
|
||||
fn coin_state(&self) -> CoinState<N> {
|
||||
match self.epoch % 3 {
|
||||
0 => CoinState::Decided(true),
|
||||
|
@ -316,7 +327,7 @@ impl<N: NodeIdT> BinaryAgreement<N> {
|
|||
self.netinfo.node_index(&self.proposer_id).unwrap(),
|
||||
self.epoch,
|
||||
);
|
||||
CoinState::InProgress(Coin::new(self.netinfo.clone(), nonce))
|
||||
CoinState::InProgress(Box::new(ThresholdSign::new(self.netinfo.clone(), nonce)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -352,13 +363,11 @@ impl<N: NodeIdT> BinaryAgreement<N> {
|
|||
}
|
||||
|
||||
// Invoke the coin.
|
||||
let coin_step = match self.coin_state {
|
||||
let ts_step = match self.coin_state {
|
||||
CoinState::Decided(_) => return Ok(Step::default()), // Coin has already decided.
|
||||
CoinState::InProgress(ref mut coin) => coin
|
||||
.handle_input(())
|
||||
.map_err(Error::TryFinishConfRoundCoin)?,
|
||||
CoinState::InProgress(ref mut ts) => ts.handle_input(()).map_err(Error::InvokeCoin)?,
|
||||
};
|
||||
let mut step = self.on_coin_step(coin_step)?;
|
||||
let mut step = self.on_coin_step(ts_step)?;
|
||||
step.extend(self.try_update_epoch()?);
|
||||
Ok(step)
|
||||
}
|
||||
|
|
|
@ -71,21 +71,19 @@ mod sbv_broadcast;
|
|||
use rand;
|
||||
|
||||
use self::bool_set::BoolSet;
|
||||
use coin::{self, CoinMessage};
|
||||
use threshold_sign;
|
||||
|
||||
pub use self::binary_agreement::BinaryAgreement;
|
||||
|
||||
/// An Binary Agreement error.
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "HandleCoin error: {}", _0)]
|
||||
HandleCoin(coin::Error),
|
||||
#[fail(display = "TryFinishConfRoundCoin error: {}", _0)]
|
||||
TryFinishConfRoundCoin(coin::Error),
|
||||
#[fail(display = "Error handling threshold sign message: {}", _0)]
|
||||
HandleThresholdSign(threshold_sign::Error),
|
||||
#[fail(display = "Error invoking the common coin: {}", _0)]
|
||||
InvokeCoin(threshold_sign::Error),
|
||||
#[fail(display = "Unknown proposer")]
|
||||
UnknownProposer,
|
||||
#[fail(display = "Input not accepted")]
|
||||
InputNotAccepted,
|
||||
}
|
||||
|
||||
/// An Binary Agreement result.
|
||||
|
@ -101,8 +99,8 @@ pub enum MessageContent {
|
|||
Conf(BoolSet),
|
||||
/// `Term` message.
|
||||
Term(bool),
|
||||
/// Coin message,
|
||||
Coin(Box<CoinMessage>),
|
||||
/// `ThresholdSign` message used for the common coin,
|
||||
Coin(Box<threshold_sign::Message>),
|
||||
}
|
||||
|
||||
impl MessageContent {
|
||||
|
|
|
@ -45,27 +45,11 @@ impl<N: NodeIdT> DistAlgorithm for Broadcast<N> {
|
|||
type Error = Error;
|
||||
|
||||
fn handle_input(&mut self, input: Self::Input) -> Result<Step<N>> {
|
||||
if *self.netinfo.our_id() != self.proposer_id {
|
||||
return Err(Error::InstanceCannotPropose);
|
||||
}
|
||||
// Split the value into chunks/shards, encode them with erasure codes.
|
||||
// Assemble a Merkle tree from data and parity shards. Take all proofs
|
||||
// from this tree and send them, each to its own node.
|
||||
let (proof, mut step) = self.send_shards(input)?;
|
||||
let our_id = &self.netinfo.our_id().clone();
|
||||
step.extend(self.handle_value(our_id, proof)?);
|
||||
Ok(step)
|
||||
self.broadcast(input)
|
||||
}
|
||||
|
||||
fn handle_message(&mut self, sender_id: &N, message: Self::Message) -> Result<Step<N>> {
|
||||
if !self.netinfo.is_node_validator(sender_id) {
|
||||
return Err(Error::UnknownSender);
|
||||
}
|
||||
match message {
|
||||
Message::Value(p) => self.handle_value(sender_id, p),
|
||||
Message::Echo(p) => self.handle_echo(sender_id, p),
|
||||
Message::Ready(ref hash) => self.handle_ready(sender_id, hash),
|
||||
}
|
||||
self.handle_message(sender_id, message)
|
||||
}
|
||||
|
||||
fn terminated(&self) -> bool {
|
||||
|
@ -98,6 +82,32 @@ impl<N: NodeIdT> Broadcast<N> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Initiates the broadcast. This must only be called in the proposer node.
|
||||
pub fn broadcast(&mut self, input: Vec<u8>) -> Result<Step<N>> {
|
||||
if *self.netinfo.our_id() != self.proposer_id {
|
||||
return Err(Error::InstanceCannotPropose);
|
||||
}
|
||||
// Split the value into chunks/shards, encode them with erasure codes.
|
||||
// Assemble a Merkle tree from data and parity shards. Take all proofs
|
||||
// from this tree and send them, each to its own node.
|
||||
let (proof, mut step) = self.send_shards(input)?;
|
||||
let our_id = &self.netinfo.our_id().clone();
|
||||
step.extend(self.handle_value(our_id, proof)?);
|
||||
Ok(step)
|
||||
}
|
||||
|
||||
/// Handles an incoming message.
|
||||
pub fn handle_message(&mut self, sender_id: &N, message: Message) -> Result<Step<N>> {
|
||||
if !self.netinfo.is_node_validator(sender_id) {
|
||||
return Err(Error::UnknownSender);
|
||||
}
|
||||
match message {
|
||||
Message::Value(p) => self.handle_value(sender_id, p),
|
||||
Message::Echo(p) => self.handle_echo(sender_id, p),
|
||||
Message::Ready(ref hash) => self.handle_ready(sender_id, hash),
|
||||
}
|
||||
}
|
||||
|
||||
/// Breaks the input value into shards of equal length and encodes them --
|
||||
/// and some extra parity shards -- with a Reed-Solomon erasure coding
|
||||
/// scheme. The returned value contains the shard assigned to this
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
//! extern crate rand;
|
||||
//!
|
||||
//! use hbbft::broadcast::{Broadcast, Error, Step};
|
||||
//! use hbbft::{DistAlgorithm, NetworkInfo, SourcedMessage, Target, TargetedMessage};
|
||||
//! use hbbft::{NetworkInfo, SourcedMessage, Target, TargetedMessage};
|
||||
//! use rand::{thread_rng, Rng};
|
||||
//! use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
//! use std::iter::once;
|
||||
|
@ -111,7 +111,7 @@
|
|||
//! // Now we can start the algorithm, its input is the payload.
|
||||
//! let initial_step = {
|
||||
//! let proposer = nodes.get_mut(&PROPOSER_ID).unwrap();
|
||||
//! proposer.handle_input(payload.clone()).unwrap()
|
||||
//! proposer.broadcast(payload.clone()).unwrap()
|
||||
//! };
|
||||
//! on_step(
|
||||
//! PROPOSER_ID,
|
||||
|
|
|
@ -82,29 +82,7 @@ where
|
|||
}
|
||||
|
||||
fn handle_message(&mut self, sender_id: &N, message: Self::Message) -> Result<Step<C, N>> {
|
||||
let epoch = message.start_epoch();
|
||||
if epoch < self.start_epoch {
|
||||
// Obsolete message.
|
||||
Ok(Step::default())
|
||||
} else if epoch > self.start_epoch {
|
||||
// Message cannot be handled yet. Save it for later.
|
||||
let entry = (sender_id.clone(), message);
|
||||
self.incoming_queue.push(entry);
|
||||
Ok(Step::default())
|
||||
} else {
|
||||
match message {
|
||||
Message::HoneyBadger(_, hb_msg) => {
|
||||
self.handle_honey_badger_message(sender_id, hb_msg)
|
||||
}
|
||||
Message::KeyGen(_, kg_msg, sig) => self
|
||||
.handle_key_gen_message(sender_id, kg_msg, *sig)
|
||||
.map(FaultLog::into),
|
||||
Message::SignedVote(signed_vote) => self
|
||||
.vote_counter
|
||||
.add_pending_vote(sender_id, signed_vote)
|
||||
.map(FaultLog::into),
|
||||
}
|
||||
}
|
||||
self.handle_message(sender_id, message)
|
||||
}
|
||||
|
||||
fn terminated(&self) -> bool {
|
||||
|
@ -133,17 +111,23 @@ where
|
|||
|
||||
/// Proposes a contribution in the current epoch.
|
||||
pub fn propose(&mut self, contrib: C) -> Result<Step<C, N>> {
|
||||
let key_gen_messages = self
|
||||
.key_gen_msg_buffer
|
||||
.iter()
|
||||
.filter(|kg_msg| kg_msg.epoch() == self.start_epoch)
|
||||
.cloned()
|
||||
.collect();
|
||||
let step = self
|
||||
.honey_badger
|
||||
.handle_input(InternalContrib {
|
||||
contrib,
|
||||
key_gen_messages: self.key_gen_msg_buffer.clone(),
|
||||
key_gen_messages,
|
||||
votes: self.vote_counter.pending_votes().cloned().collect(),
|
||||
}).map_err(ErrorKind::ProposeHoneyBadger)?;
|
||||
self.process_output(step)
|
||||
}
|
||||
|
||||
/// Cast a vote to change the set of validators.
|
||||
/// Casts a vote to change the set of validators.
|
||||
pub fn vote_for(&mut self, change: Change<N>) -> Result<Step<C, N>> {
|
||||
if !self.netinfo.is_validator() {
|
||||
return Ok(Step::default()); // TODO: Return an error?
|
||||
|
@ -153,6 +137,33 @@ where
|
|||
Ok(Target::All.message(msg).into())
|
||||
}
|
||||
|
||||
/// Handles an incoming message.
|
||||
pub fn handle_message(&mut self, sender_id: &N, message: Message<N>) -> Result<Step<C, N>> {
|
||||
let epoch = message.start_epoch();
|
||||
if epoch < self.start_epoch {
|
||||
// Obsolete message.
|
||||
Ok(Step::default())
|
||||
} else if epoch > self.start_epoch {
|
||||
// Message cannot be handled yet. Save it for later.
|
||||
let entry = (sender_id.clone(), message);
|
||||
self.incoming_queue.push(entry);
|
||||
Ok(Step::default())
|
||||
} else {
|
||||
match message {
|
||||
Message::HoneyBadger(_, hb_msg) => {
|
||||
self.handle_honey_badger_message(sender_id, hb_msg)
|
||||
}
|
||||
Message::KeyGen(_, kg_msg, sig) => self
|
||||
.handle_key_gen_message(sender_id, kg_msg, *sig)
|
||||
.map(FaultLog::into),
|
||||
Message::SignedVote(signed_vote) => self
|
||||
.vote_counter
|
||||
.add_pending_vote(sender_id, signed_vote)
|
||||
.map(FaultLog::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the information about the node IDs in the network, and the cryptographic keys.
|
||||
pub fn netinfo(&self) -> &NetworkInfo<N> {
|
||||
&self.netinfo
|
||||
|
@ -228,19 +239,11 @@ where
|
|||
}
|
||||
};
|
||||
|
||||
// If the joining node is correct, it will send at most (N + 1)² + 1 key generation
|
||||
// messages.
|
||||
if Some(sender_id) == kgs.change.candidate() {
|
||||
let n = self.netinfo.num_nodes() + 1;
|
||||
if kgs.candidate_msg_count > n * n {
|
||||
info!(
|
||||
"Too many key gen messages from candidate {:?}: {:?}.",
|
||||
sender_id, kg_msg
|
||||
);
|
||||
let fault_kind = FaultKind::TooManyCandidateKeyGenMessages;
|
||||
return Ok(Fault::new(sender_id.clone(), fault_kind).into());
|
||||
}
|
||||
kgs.candidate_msg_count += 1;
|
||||
// If the sender is correct, it will send at most _N + 1_ key generation messages:
|
||||
// one `Part`, and for each validator an `Ack`. _N_ is the node number _after_ the change.
|
||||
if kgs.count_messages(sender_id) > kgs.key_gen.num_nodes() + 1 {
|
||||
let fault_kind = FaultKind::TooManyKeyGenMessages;
|
||||
return Ok(Fault::new(sender_id.clone(), fault_kind).into());
|
||||
}
|
||||
|
||||
let tx = SignedKeyGenMsg(self.start_epoch, sender_id.clone(), kg_msg, sig);
|
||||
|
@ -273,23 +276,18 @@ where
|
|||
self.key_gen_msg_buffer
|
||||
.retain(|skgm| !key_gen_messages.contains(skgm));
|
||||
for SignedKeyGenMsg(epoch, s_id, kg_msg, sig) in key_gen_messages {
|
||||
if epoch < self.start_epoch {
|
||||
info!("Obsolete key generation message: {:?}.", kg_msg);
|
||||
continue;
|
||||
}
|
||||
if !self.verify_signature(&s_id, &sig, &kg_msg)? {
|
||||
info!(
|
||||
"Invalid signature in {:?}'s batch from {:?} for: {:?}.",
|
||||
id, s_id, kg_msg
|
||||
);
|
||||
if epoch != self.start_epoch {
|
||||
let fault_kind = FaultKind::InvalidKeyGenMessageEpoch;
|
||||
step.fault_log.append(id.clone(), fault_kind);
|
||||
} else if !self.verify_signature(&s_id, &sig, &kg_msg)? {
|
||||
let fault_kind = FaultKind::InvalidKeyGenMessageSignature;
|
||||
step.fault_log.append(id.clone(), fault_kind);
|
||||
continue;
|
||||
} else {
|
||||
step.extend(match kg_msg {
|
||||
KeyGenMessage::Part(part) => self.handle_part(&s_id, part)?,
|
||||
KeyGenMessage::Ack(ack) => self.handle_ack(&s_id, ack)?.into(),
|
||||
});
|
||||
}
|
||||
step.extend(match kg_msg {
|
||||
KeyGenMessage::Part(part) => self.handle_part(&s_id, part)?,
|
||||
KeyGenMessage::Ack(ack) => self.handle_ack(&s_id, ack)?.into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -369,8 +367,7 @@ where
|
|||
self.start_epoch = epoch;
|
||||
self.key_gen_msg_buffer.retain(|kg_msg| kg_msg.0 >= epoch);
|
||||
let netinfo = Arc::new(self.netinfo.clone());
|
||||
let counter = VoteCounter::new(netinfo.clone(), epoch);
|
||||
mem::replace(&mut self.vote_counter, counter);
|
||||
self.vote_counter = VoteCounter::new(netinfo.clone(), epoch);
|
||||
self.honey_badger = HoneyBadger::builder(netinfo)
|
||||
.max_future_epochs(self.max_future_epochs)
|
||||
.rng(self.rng.sub_rng())
|
||||
|
@ -383,8 +380,9 @@ where
|
|||
let outcome = if let Some(kgs) = self.key_gen_state.as_mut() {
|
||||
kgs.key_gen.handle_part(&mut self.rng, &sender_id, part)
|
||||
} else {
|
||||
// No key generation ongoing. Return early.
|
||||
return Ok(Step::default());
|
||||
// No key generation ongoing.
|
||||
let fault_kind = FaultKind::UnexpectedKeyGenPart;
|
||||
return Ok(Fault::new(sender_id.clone(), fault_kind).into());
|
||||
};
|
||||
|
||||
match outcome {
|
||||
|
@ -399,7 +397,9 @@ where
|
|||
if let Some(kgs) = self.key_gen_state.as_mut() {
|
||||
Ok(kgs.key_gen.handle_ack(sender_id, ack))
|
||||
} else {
|
||||
Ok(FaultLog::new())
|
||||
// No key generation ongoing.
|
||||
let fault_kind = FaultKind::UnexpectedKeyGenAck;
|
||||
return Ok(Fault::new(sender_id.clone(), fault_kind).into());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@ struct KeyGenState<N> {
|
|||
change: NodeChange<N>,
|
||||
/// The number of key generation messages received from the candidate. At most _N² + 1_ are
|
||||
/// accepted.
|
||||
candidate_msg_count: usize,
|
||||
msg_count: BTreeMap<N, usize>,
|
||||
}
|
||||
|
||||
impl<N: NodeIdT> KeyGenState<N> {
|
||||
|
@ -160,7 +160,7 @@ impl<N: NodeIdT> KeyGenState<N> {
|
|||
KeyGenState {
|
||||
key_gen,
|
||||
change,
|
||||
candidate_msg_count: 0,
|
||||
msg_count: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,6 +178,13 @@ impl<N: NodeIdT> KeyGenState<N> {
|
|||
NodeChange::Add(_, _) | NodeChange::Remove(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Increments the message count for the given node, and returns the new count.
|
||||
fn count_messages(&mut self, node_id: &N) -> usize {
|
||||
let count = self.msg_count.entry(node_id.clone()).or_insert(0);
|
||||
*count += 1;
|
||||
*count
|
||||
}
|
||||
}
|
||||
|
||||
/// The contribution for the internal `HoneyBadger` instance: this includes a user-defined
|
||||
|
@ -195,3 +202,10 @@ struct InternalContrib<C, N> {
|
|||
/// A signed internal message.
|
||||
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Hash, Clone)]
|
||||
struct SignedKeyGenMsg<N>(u64, N, KeyGenMessage, Signature);
|
||||
|
||||
impl<N> SignedKeyGenMsg<N> {
|
||||
/// Returns the start epoch of the ongoing key generation.
|
||||
fn epoch(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,6 @@ pub enum AckMessageFault {
|
|||
NodeCount,
|
||||
#[fail(display = "Sender does not exist")]
|
||||
SenderExist,
|
||||
#[fail(display = "Duplicate ack")]
|
||||
DuplicateAck,
|
||||
#[fail(display = "Value decryption failed")]
|
||||
ValueDecryption,
|
||||
#[fail(display = "Value deserialization failed")]
|
||||
|
@ -44,14 +42,19 @@ pub enum FaultKind {
|
|||
/// `HoneyBadger` could not deserialize bytes (i.e. a serialized Batch)
|
||||
/// from a given proposer into a vector of transactions.
|
||||
BatchDeserializationFailed,
|
||||
/// `DynamicHoneyBadger` received a key generation message with an invalid
|
||||
/// signature.
|
||||
/// `DynamicHoneyBadger` received a key generation message with an invalid signature.
|
||||
InvalidKeyGenMessageSignature,
|
||||
/// `DynamicHoneyBadger` received a key generation message with an invalid epoch.
|
||||
InvalidKeyGenMessageEpoch,
|
||||
/// `DynamicHoneyBadger` received a key generation message when there was no key generation in
|
||||
/// progress.
|
||||
UnexpectedKeyGenMessage,
|
||||
/// `DynamicHoneyBadger` received more key generation messages from the candidate than expected.
|
||||
TooManyCandidateKeyGenMessages,
|
||||
/// `DynamicHoneyBadger` received a signed `Ack` when no key generation in progress.
|
||||
UnexpectedKeyGenAck,
|
||||
/// `DynamicHoneyBadger` received a signed `Part` when no key generation in progress.
|
||||
UnexpectedKeyGenPart,
|
||||
/// `DynamicHoneyBadger` received more key generation messages from the peer than expected.
|
||||
TooManyKeyGenMessages,
|
||||
/// `DynamicHoneyBadger` received a message (Accept, Propose, or Change)
|
||||
/// with an invalid signature.
|
||||
IncorrectPayloadSignature,
|
||||
|
@ -59,6 +62,8 @@ pub enum FaultKind {
|
|||
AckMessage(AckMessageFault),
|
||||
/// `DynamicHoneyBadger`/`SyncKeyGen` received an invalid Part message.
|
||||
InvalidPartMessage,
|
||||
/// `DynamicHoneyBadger`/`SyncKeyGen` received multiple Part messages from a node.
|
||||
MultiplePartMessages,
|
||||
/// `DynamicHoneyBadger` received a change vote with an invalid signature.
|
||||
InvalidVoteSignature,
|
||||
/// A validator committed an invalid vote in `DynamicHoneyBadger`.
|
||||
|
|
|
@ -48,14 +48,6 @@ where
|
|||
DecryptionState::Complete(_) => Ok(td::Step::default()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the plaintext, if it has already been decrypted.
|
||||
fn plaintext(&self) -> Option<&[u8]> {
|
||||
match self {
|
||||
DecryptionState::Ongoing(_) => None,
|
||||
DecryptionState::Complete(ref plaintext) => Some(&plaintext[..]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The status of the subset algorithm.
|
||||
|
@ -266,13 +258,13 @@ where
|
|||
/// epoch, moves those contributions into a batch, outputs the batch and updates the epoch.
|
||||
pub fn try_output_batch(&self) -> Option<(Batch<C, N>, FaultLog<N>)> {
|
||||
let proposer_ids = self.subset.accepted_ids()?;
|
||||
let plaintexts: BTreeMap<N, &[u8]> = self
|
||||
.decryption
|
||||
.iter()
|
||||
.flat_map(|(id, dec_state)| dec_state.plaintext().map(|pt| (id.clone(), pt)))
|
||||
.collect();
|
||||
if !proposer_ids.iter().eq(plaintexts.keys()) {
|
||||
return None; // Not all accepted contributions are decrypted yet.
|
||||
let mut plaintexts = Vec::new();
|
||||
// Collect accepted plaintexts. Return if some are not decrypted yet.
|
||||
for id in proposer_ids {
|
||||
match self.decryption.get(id) {
|
||||
None | Some(DecryptionState::Ongoing(_)) => return None,
|
||||
Some(DecryptionState::Complete(ref pt)) => plaintexts.push((id.clone(), pt)),
|
||||
}
|
||||
}
|
||||
|
||||
let mut fault_log = FaultLog::default();
|
||||
|
|
14
src/lib.rs
14
src/lib.rs
|
@ -85,12 +85,11 @@
|
|||
//! This is used in Subset to decide whether each node's proposal should be included in the subset
|
||||
//! or not.
|
||||
//!
|
||||
//! [**Coin**](coin/index.html)
|
||||
//! [**Threshold Sign**](threshold_sign/index.html)
|
||||
//!
|
||||
//! Each node inputs `()` to initiate a coin flip. Once _f + 1_ nodes have input, either all nodes
|
||||
//! receive `true` or all nodes receive `false`. The outcome cannot be known by the adversary
|
||||
//! before at least one correct node has provided input, and is uniformly distributed and
|
||||
//! pseudorandom.
|
||||
//! Each node inputs `()` to broadcast signature shares. Once _f + 1_ nodes have input, all nodes
|
||||
//! receive a valid signature. The outcome cannot be known by the adversary before at least one
|
||||
//! correct node has provided input, and can be used as a source of pseudorandomness.
|
||||
//!
|
||||
//! [**Threshold Decryption**](threshold_decryption/index.html)
|
||||
//!
|
||||
|
@ -137,24 +136,25 @@ extern crate tiny_keccak;
|
|||
|
||||
pub extern crate threshold_crypto as crypto;
|
||||
|
||||
mod fault_log;
|
||||
mod messaging;
|
||||
mod network_info;
|
||||
mod traits;
|
||||
|
||||
pub mod binary_agreement;
|
||||
pub mod broadcast;
|
||||
pub mod coin;
|
||||
pub mod dynamic_honey_badger;
|
||||
pub mod fault_log;
|
||||
pub mod honey_badger;
|
||||
pub mod queueing_honey_badger;
|
||||
pub mod subset;
|
||||
pub mod sync_key_gen;
|
||||
pub mod threshold_decryption;
|
||||
pub mod threshold_sign;
|
||||
pub mod transaction_queue;
|
||||
pub mod util;
|
||||
|
||||
pub use crypto::pairing;
|
||||
pub use fault_log::{AckMessageFault, Fault, FaultKind, FaultLog};
|
||||
pub use messaging::{SourcedMessage, Target, TargetedMessage};
|
||||
pub use network_info::NetworkInfo;
|
||||
pub use traits::{Contribution, DistAlgorithm, Message, NodeIdT, Step};
|
||||
|
|
|
@ -222,32 +222,14 @@ where
|
|||
fn handle_input(&mut self, input: Self::Input) -> Result<Step<T, N, Q>> {
|
||||
// User transactions are forwarded to `HoneyBadger` right away. Internal messages are
|
||||
// in addition signed and broadcast.
|
||||
let mut step = match input {
|
||||
Input::User(tx) => {
|
||||
self.queue.extend(iter::once(tx));
|
||||
Step::default()
|
||||
}
|
||||
Input::Change(change) => self
|
||||
.dyn_hb
|
||||
.handle_input(Input::Change(change))
|
||||
.map_err(ErrorKind::Input)?
|
||||
.convert(),
|
||||
};
|
||||
step.extend(self.propose()?);
|
||||
Ok(step)
|
||||
match input {
|
||||
Input::User(tx) => self.push_transaction(tx),
|
||||
Input::Change(change) => self.vote_for(change),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_message(&mut self, sender_id: &N, message: Self::Message) -> Result<Step<T, N, Q>> {
|
||||
let mut step = self
|
||||
.dyn_hb
|
||||
.handle_message(sender_id, message)
|
||||
.map_err(ErrorKind::HandleMessage)?
|
||||
.convert::<Self>();
|
||||
for batch in &step.output {
|
||||
self.queue.remove_multiple(batch.iter());
|
||||
}
|
||||
step.extend(self.propose()?);
|
||||
Ok(step)
|
||||
self.handle_message(sender_id, message)
|
||||
}
|
||||
|
||||
fn terminated(&self) -> bool {
|
||||
|
@ -271,6 +253,37 @@ where
|
|||
QueueingHoneyBadgerBuilder::new(dyn_hb)
|
||||
}
|
||||
|
||||
/// Adds a transaction to the queue.
|
||||
pub fn push_transaction(&mut self, tx: T) -> Result<Step<T, N, Q>> {
|
||||
self.queue.extend(iter::once(tx));
|
||||
self.propose()
|
||||
}
|
||||
|
||||
/// Casts a vote to change the set of validators.
|
||||
pub fn vote_for(&mut self, change: Change<N>) -> Result<Step<T, N, Q>> {
|
||||
let mut step = self
|
||||
.dyn_hb
|
||||
.handle_input(Input::Change(change))
|
||||
.map_err(ErrorKind::Input)?
|
||||
.convert();
|
||||
step.extend(self.propose()?);
|
||||
Ok(step)
|
||||
}
|
||||
|
||||
/// Handles an incoming message.
|
||||
pub fn handle_message(&mut self, sender_id: &N, message: Message<N>) -> Result<Step<T, N, Q>> {
|
||||
let mut step = self
|
||||
.dyn_hb
|
||||
.handle_message(sender_id, message)
|
||||
.map_err(ErrorKind::HandleMessage)?
|
||||
.convert::<Self>();
|
||||
for batch in &step.output {
|
||||
self.queue.remove_multiple(batch.iter());
|
||||
}
|
||||
step.extend(self.propose()?);
|
||||
Ok(step)
|
||||
}
|
||||
|
||||
/// Returns a reference to the internal `DynamicHoneyBadger` instance.
|
||||
pub fn dyn_hb(&self) -> &DynamicHoneyBadger<Vec<T>, N> {
|
||||
&self.dyn_hb
|
||||
|
|
|
@ -95,25 +95,11 @@ impl<N: NodeIdT + Rand> DistAlgorithm for Subset<N> {
|
|||
type Error = Error;
|
||||
|
||||
fn handle_input(&mut self, input: Self::Input) -> Result<Step<N>> {
|
||||
debug!(
|
||||
"{:?} Proposing {:0.10}",
|
||||
self.netinfo.our_id(),
|
||||
HexFmt(&input)
|
||||
);
|
||||
self.send_proposed_value(input)
|
||||
}
|
||||
|
||||
fn handle_message(
|
||||
&mut self,
|
||||
sender_id: &Self::NodeId,
|
||||
message: Self::Message,
|
||||
) -> Result<Step<N>> {
|
||||
match message {
|
||||
Message::Broadcast(p_id, b_msg) => self.handle_broadcast(sender_id, &p_id, b_msg),
|
||||
Message::BinaryAgreement(p_id, a_msg) => {
|
||||
self.handle_binary_agreement(sender_id, &p_id, a_msg)
|
||||
}
|
||||
}
|
||||
fn handle_message(&mut self, sender_id: &N, message: Message<N>) -> Result<Step<N>> {
|
||||
self.handle_message(sender_id, message)
|
||||
}
|
||||
|
||||
fn terminated(&self) -> bool {
|
||||
|
@ -170,10 +156,20 @@ impl<N: NodeIdT + Rand> Subset<N> {
|
|||
return Ok(Step::default());
|
||||
}
|
||||
let id = self.netinfo.our_id().clone();
|
||||
// Upon receiving input v_i , input v_i to RBC_i. See Figure 2.
|
||||
debug!("{:?} Proposing {:0.10}", id, HexFmt(&value));
|
||||
self.process_broadcast(&id, |bc| bc.handle_input(value))
|
||||
}
|
||||
|
||||
/// Handles an incoming message.
|
||||
pub fn handle_message(&mut self, sender_id: &N, message: Message<N>) -> Result<Step<N>> {
|
||||
match message {
|
||||
Message::Broadcast(p_id, b_msg) => self.handle_broadcast(sender_id, &p_id, b_msg),
|
||||
Message::BinaryAgreement(p_id, a_msg) => {
|
||||
self.handle_binary_agreement(sender_id, &p_id, a_msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of validators from which we have already received a proposal.
|
||||
pub(crate) fn received_proposals(&self) -> usize {
|
||||
self.broadcast_results.len()
|
||||
|
@ -243,13 +239,7 @@ impl<N: NodeIdT + Rand> Subset<N> {
|
|||
{
|
||||
error!("Duplicate insert in broadcast_results: {:?}", inval)
|
||||
}
|
||||
let set_binary_agreement_input = |ba: &mut BinaryAgreement<N>| {
|
||||
if ba.accepts_input() {
|
||||
ba.handle_input(true)
|
||||
} else {
|
||||
Ok(binary_agreement::Step::default())
|
||||
}
|
||||
};
|
||||
let set_binary_agreement_input = |ba: &mut BinaryAgreement<N>| ba.handle_input(true);
|
||||
step.extend(self.process_binary_agreement(proposer_id, set_binary_agreement_input)?);
|
||||
Ok(step)
|
||||
}
|
||||
|
@ -301,17 +291,15 @@ impl<N: NodeIdT + Rand> Subset<N> {
|
|||
// Upon delivery of value 1 from at least N − f instances of BA, provide
|
||||
// input 0 to each instance of BA that has not yet been provided input.
|
||||
for (id, binary_agreement) in &mut self.ba_instances {
|
||||
if binary_agreement.accepts_input() {
|
||||
let to_msg = |a_msg| Message::BinaryAgreement(id.clone(), a_msg);
|
||||
for output in step.extend_with(
|
||||
binary_agreement
|
||||
.handle_input(false)
|
||||
.map_err(Error::ProcessBinaryAgreement1)?,
|
||||
to_msg,
|
||||
) {
|
||||
if self.ba_results.insert(id.clone(), output).is_some() {
|
||||
return Err(Error::MultipleBinaryAgreementResults);
|
||||
}
|
||||
let to_msg = |a_msg| Message::BinaryAgreement(id.clone(), a_msg);
|
||||
for output in step.extend_with(
|
||||
binary_agreement
|
||||
.handle_input(false)
|
||||
.map_err(Error::ProcessBinaryAgreement1)?,
|
||||
to_msg,
|
||||
) {
|
||||
if self.ba_results.insert(id.clone(), output).is_some() {
|
||||
return Err(Error::MultipleBinaryAgreementResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@
|
|||
//! secret_key_shares.insert(id, sks);
|
||||
//! }
|
||||
//!
|
||||
//! // Three out of four nodes can now sign a message. Each share can be verified individually.
|
||||
//! // Two out of four nodes can now sign a message. Each share can be verified individually.
|
||||
//! let msg = "Nodes 0 and 1 does not agree with this.";
|
||||
//! let mut sig_shares: BTreeMap<usize, SignatureShare> = BTreeMap::new();
|
||||
//! for (&id, sks) in &secret_key_shares {
|
||||
|
@ -226,7 +226,7 @@ impl Debug for Ack {
|
|||
}
|
||||
|
||||
/// The information needed to track a single proposer's secret sharing process.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct ProposalState {
|
||||
/// The proposer's commitment.
|
||||
commit: BivarCommitment,
|
||||
|
@ -275,7 +275,7 @@ pub struct SyncKeyGen<N> {
|
|||
our_idx: Option<u64>,
|
||||
/// Our secret key.
|
||||
sec_key: SecretKey,
|
||||
/// The public keys of all nodes, by node index.
|
||||
/// The public keys of all nodes, by node ID.
|
||||
pub_keys: BTreeMap<N, PublicKey>,
|
||||
/// Proposed bivariate polynomials.
|
||||
parts: BTreeMap<u64, ProposalState>,
|
||||
|
@ -340,11 +340,16 @@ impl<N: NodeIdT> SyncKeyGen<N> {
|
|||
sender_id: &N,
|
||||
Part(commit, rows): Part,
|
||||
) -> Option<PartOutcome<N>> {
|
||||
let sender_idx = self.node_index(sender_id)?;
|
||||
let sender_idx = self.node_index(sender_id)?; // TODO: Return an error.
|
||||
let opt_commit_row = self.our_idx.map(|idx| commit.row(idx + 1));
|
||||
match self.parts.entry(sender_idx) {
|
||||
Entry::Occupied(_) => {
|
||||
debug!("Received multiple parts from node {:?}.", sender_id);
|
||||
Entry::Occupied(entry) => {
|
||||
if *entry.get() != ProposalState::new(commit) {
|
||||
debug!("Received multiple parts from node {:?}.", sender_id);
|
||||
let fault_log =
|
||||
FaultLog::init(sender_id.clone(), FaultKind::MultiplePartMessages);
|
||||
return Some(PartOutcome::Invalid(fault_log));
|
||||
}
|
||||
return None;
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
|
@ -456,6 +461,11 @@ impl<N: NodeIdT> SyncKeyGen<N> {
|
|||
Ok(netinfo)
|
||||
}
|
||||
|
||||
/// Returns the number of nodes participating in the key generation.
|
||||
pub fn num_nodes(&self) -> usize {
|
||||
self.pub_keys.len()
|
||||
}
|
||||
|
||||
/// Handles an `Ack` message or returns an error string.
|
||||
fn handle_ack_or_err(
|
||||
&mut self,
|
||||
|
@ -470,7 +480,7 @@ impl<N: NodeIdT> SyncKeyGen<N> {
|
|||
.get_mut(&proposer_idx)
|
||||
.ok_or_else(|| Fault::SenderExist)?;
|
||||
if !part.acks.insert(sender_idx) {
|
||||
return Err(Fault::DuplicateAck);
|
||||
return Ok(());
|
||||
}
|
||||
let our_idx = match self.our_idx {
|
||||
Some(our_idx) => our_idx,
|
||||
|
|
|
@ -110,6 +110,7 @@ impl<N: NodeIdT> ThresholdDecryption<N> {
|
|||
|
||||
/// Sets the ciphertext, sends the decryption share, and tries to decrypt it.
|
||||
/// This must be called exactly once, with the same ciphertext in all participating nodes.
|
||||
/// If we have enough shares, outputs the plaintext.
|
||||
pub fn set_ciphertext(&mut self, ct: Ciphertext) -> Result<Step<N>> {
|
||||
if self.ciphertext.is_some() {
|
||||
return Err(Error::MultipleInputs(Box::new(ct)));
|
||||
|
@ -136,7 +137,8 @@ impl<N: NodeIdT> ThresholdDecryption<N> {
|
|||
self.shares.keys()
|
||||
}
|
||||
|
||||
fn handle_message(&mut self, sender_id: &N, message: Message) -> Result<Step<N>> {
|
||||
/// Handles an incoming message. If we have collected enough shares, outputs the plaintext.
|
||||
pub fn handle_message(&mut self, sender_id: &N, message: Message) -> Result<Step<N>> {
|
||||
if self.terminated {
|
||||
return Ok(Step::default()); // Don't waste time on redundant shares.
|
||||
}
|
||||
|
|
|
@ -1,34 +1,26 @@
|
|||
//! # A Cryptographic Coin
|
||||
//! # Collaborative Threshold Signing
|
||||
//!
|
||||
//! The Coin produces a pseudorandom binary value that the correct nodes agree on, and that
|
||||
//! cannot be known beforehand.
|
||||
//! The algorithm is instantiated with data to sign, and waits for the input (no data, just `()`),
|
||||
//! then sends a signature share to the others. When at least _f + 1_ correct validators have done
|
||||
//! so, each node outputs the same, valid signature of the data.
|
||||
//!
|
||||
//! Every Coin instance has a _nonce_ that determines the value, without giving it away: It
|
||||
//! is not feasible to compute the output from the nonce alone, and the output is uniformly
|
||||
//! distributed.
|
||||
//!
|
||||
//! The nodes input a signal (no data, just `()`), and after _2 f + 1_ nodes have provided input,
|
||||
//! everyone receives the output value. In particular, the adversary cannot know the output value
|
||||
//! before at least one correct node has provided input.
|
||||
//! In addition to signing, this can also be used as a source of pseudorandomness: The signature
|
||||
//! cannot be known until more than _f_ validators have contributed their shares.
|
||||
//!
|
||||
//! ## How it works
|
||||
//!
|
||||
//! The algorithm uses a threshold signature scheme with the uniqueness property: For each public
|
||||
//! key and message, there is exactly one valid signature. This group signature is produced using
|
||||
//! signature shares from any combination of _2 f + 1_ secret key share holders.
|
||||
//!
|
||||
//! * On input, a node signs the nonce and sends its signature share to everyone else.
|
||||
//! * When a node has received _2 f + 1_ shares, it computes the main signature and outputs the XOR
|
||||
//! of its bits.
|
||||
//! signature shares from any combination of _f + 1_ secret key share holders.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crypto::{self, Signature, SignatureShare};
|
||||
use crypto::{self, hash_g2, Signature, SignatureShare, G2};
|
||||
use fault_log::{Fault, FaultKind};
|
||||
use {DistAlgorithm, NetworkInfo, NodeIdT, Target};
|
||||
|
||||
/// A coin error.
|
||||
/// A threshold signing error.
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
|
||||
pub enum Error {
|
||||
#[fail(display = "CombineAndVerifySigCrypto error: {}", _0)]
|
||||
|
@ -39,15 +31,15 @@ pub enum Error {
|
|||
VerificationFailed,
|
||||
}
|
||||
|
||||
/// A coin result.
|
||||
/// A threshold signing result.
|
||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Rand)]
|
||||
pub struct CoinMessage(SignatureShare);
|
||||
pub struct Message(SignatureShare);
|
||||
|
||||
impl CoinMessage {
|
||||
impl Message {
|
||||
pub fn new(sig: SignatureShare) -> Self {
|
||||
CoinMessage(sig)
|
||||
Message(sig)
|
||||
}
|
||||
|
||||
pub fn to_sig(&self) -> &SignatureShare {
|
||||
|
@ -55,57 +47,39 @@ impl CoinMessage {
|
|||
}
|
||||
}
|
||||
|
||||
/// A coin algorithm instance. On input, broadcasts our threshold signature share. Upon
|
||||
/// A threshold signing algorithm instance. On input, broadcasts our threshold signature share. Upon
|
||||
/// receiving at least `num_faulty + 1` shares, attempts to combine them into a signature. If that
|
||||
/// signature is valid, the instance outputs it and terminates; otherwise the instance aborts.
|
||||
#[derive(Debug)]
|
||||
pub struct Coin<N, T> {
|
||||
pub struct ThresholdSign<N> {
|
||||
netinfo: Arc<NetworkInfo<N>>,
|
||||
/// The name of this coin. It is required to be unique for each coin round.
|
||||
nonce: T,
|
||||
/// The hash of the data to be signed.
|
||||
msg_hash: G2,
|
||||
/// All received threshold signature shares.
|
||||
received_shares: BTreeMap<N, SignatureShare>,
|
||||
/// Whether we provided input to the coin.
|
||||
/// Whether we already sent our shares.
|
||||
had_input: bool,
|
||||
/// Termination flag.
|
||||
terminated: bool,
|
||||
}
|
||||
|
||||
pub type Step<N, T> = ::Step<Coin<N, T>>;
|
||||
pub type Step<N> = ::Step<ThresholdSign<N>>;
|
||||
|
||||
impl<N, T> DistAlgorithm for Coin<N, T>
|
||||
where
|
||||
N: NodeIdT,
|
||||
T: Clone + AsRef<[u8]> + Send + Sync,
|
||||
{
|
||||
impl<N: NodeIdT> DistAlgorithm for ThresholdSign<N> {
|
||||
type NodeId = N;
|
||||
type Input = ();
|
||||
type Output = bool;
|
||||
type Message = CoinMessage;
|
||||
type Output = Signature;
|
||||
type Message = Message;
|
||||
type Error = Error;
|
||||
|
||||
/// Sends our threshold signature share if not yet sent.
|
||||
fn handle_input(&mut self, _input: Self::Input) -> Result<Step<N, T>> {
|
||||
if !self.had_input {
|
||||
self.had_input = true;
|
||||
self.get_coin()
|
||||
} else {
|
||||
Ok(Step::default())
|
||||
}
|
||||
fn handle_input(&mut self, _input: ()) -> Result<Step<N>> {
|
||||
self.sign()
|
||||
}
|
||||
|
||||
/// Receives input from a remote node.
|
||||
fn handle_message(
|
||||
&mut self,
|
||||
sender_id: &Self::NodeId,
|
||||
message: Self::Message,
|
||||
) -> Result<Step<N, T>> {
|
||||
if !self.terminated {
|
||||
let CoinMessage(share) = message;
|
||||
self.handle_share(sender_id, share)
|
||||
} else {
|
||||
Ok(Step::default())
|
||||
}
|
||||
fn handle_message(&mut self, sender_id: &N, message: Message) -> Result<Step<N>> {
|
||||
self.handle_message(sender_id, message)
|
||||
}
|
||||
|
||||
/// Whether the algorithm has terminated.
|
||||
|
@ -118,35 +92,42 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<N, T> Coin<N, T>
|
||||
where
|
||||
N: NodeIdT,
|
||||
T: Clone + AsRef<[u8]> + Send + Sync,
|
||||
{
|
||||
pub fn new(netinfo: Arc<NetworkInfo<N>>, nonce: T) -> Self {
|
||||
Coin {
|
||||
impl<N: NodeIdT> ThresholdSign<N> {
|
||||
/// Creates a new instance of `ThresholdSign`, with the goal to collaboratively sign `msg`.
|
||||
pub fn new<M: AsRef<[u8]>>(netinfo: Arc<NetworkInfo<N>>, msg: M) -> Self {
|
||||
ThresholdSign {
|
||||
netinfo,
|
||||
nonce,
|
||||
msg_hash: hash_g2(msg),
|
||||
received_shares: BTreeMap::new(),
|
||||
had_input: false,
|
||||
terminated: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_coin(&mut self) -> Result<Step<N, T>> {
|
||||
/// Sends our signature shares, and if we have collected enough, returns the full signature.
|
||||
pub fn sign(&mut self) -> Result<Step<N>> {
|
||||
if self.had_input {
|
||||
return Ok(Step::default());
|
||||
}
|
||||
self.had_input = true;
|
||||
if !self.netinfo.is_validator() {
|
||||
return self.try_output();
|
||||
}
|
||||
let share = self.netinfo.secret_key_share().sign(&self.nonce);
|
||||
let mut step: Step<_, _> = Target::All.message(CoinMessage(share.clone())).into();
|
||||
let msg = Message(self.netinfo.secret_key_share().sign_g2(self.msg_hash));
|
||||
let mut step: Step<_> = Target::All.message(msg.clone()).into();
|
||||
let id = self.netinfo.our_id().clone();
|
||||
step.extend(self.handle_share(&id, share)?);
|
||||
step.extend(self.handle_message(&id, msg)?);
|
||||
Ok(step)
|
||||
}
|
||||
|
||||
fn handle_share(&mut self, sender_id: &N, share: SignatureShare) -> Result<Step<N, T>> {
|
||||
/// Handles an incoming share. If we have collected enough, returns the full signature.
|
||||
pub fn handle_message(&mut self, sender_id: &N, message: Message) -> Result<Step<N>> {
|
||||
if self.terminated {
|
||||
return Ok(Step::default());
|
||||
}
|
||||
let Message(share) = message;
|
||||
if let Some(pk_i) = self.netinfo.public_key_share(sender_id) {
|
||||
if !pk_i.verify(&share, &self.nonce) {
|
||||
if !pk_i.verify_g2(&share, self.msg_hash) {
|
||||
// Log the faulty node and ignore the invalid share.
|
||||
let fault_kind = FaultKind::UnverifiedSignatureShareSender;
|
||||
return Ok(Fault::new(sender_id.clone(), fault_kind).into());
|
||||
|
@ -158,7 +139,7 @@ where
|
|||
self.try_output()
|
||||
}
|
||||
|
||||
fn try_output(&mut self) -> Result<Step<N, T>> {
|
||||
fn try_output(&mut self) -> Result<Step<N>> {
|
||||
debug!(
|
||||
"{:?} received {} shares, had_input = {}",
|
||||
self.netinfo.our_id(),
|
||||
|
@ -167,12 +148,10 @@ where
|
|||
);
|
||||
if self.had_input && self.received_shares.len() > self.netinfo.num_faulty() {
|
||||
let sig = self.combine_and_verify_sig()?;
|
||||
// Output the parity of the verified signature.
|
||||
let parity = sig.parity();
|
||||
debug!("{:?} output {}", self.netinfo.our_id(), parity);
|
||||
debug!("{:?} output {:?}", self.netinfo.our_id(), sig);
|
||||
self.terminated = true;
|
||||
let step = self.handle_input(())?; // Before terminating, make sure we sent our share.
|
||||
Ok(step.with_output(parity))
|
||||
Ok(step.with_output(sig))
|
||||
} else {
|
||||
Ok(Step::default())
|
||||
}
|
||||
|
@ -191,7 +170,7 @@ where
|
|||
.netinfo
|
||||
.public_key_set()
|
||||
.public_key()
|
||||
.verify(&sig, &self.nonce)
|
||||
.verify_g2(&sig, self.msg_hash)
|
||||
{
|
||||
// Abort
|
||||
error!(
|
|
@ -102,7 +102,7 @@ Adversaries can be introduced through the `.adversary` method on the constructor
|
|||
|
||||
By default, all network tests write traces of every network message into logfiles, named `net-trace_*.txt` in the current working directory. Each log stores one message per line, in the format of `[SENDER] -> [RECEIVER]: MSG`.
|
||||
|
||||
This behavior can be controlled using the `HBBFT_TEST_TRACE` environment variable; if set and equal to `0` or `false`, this functionality is disabled. Tracing is enabled by default.
|
||||
This behavior can be controlled using the `HBBFT_TEST_TRACE` environment variable; if set and equal to `1` or `true`, this functionality is enabled. Tracing is disabled by default.
|
||||
|
||||
The `NetBuilder` allows hard-coding the trace setting, any value passed will override environment settings:
|
||||
|
||||
|
|
|
@ -500,10 +500,12 @@ where
|
|||
net.adversary = self.adversary;
|
||||
}
|
||||
|
||||
// If the trace setting is not overriden, we use the setting from the environment.
|
||||
let trace = self.trace.unwrap_or_else(|| {
|
||||
// If the trace setting is not overriden, we use the setting from the environment.
|
||||
let setting = env::var("HBBFT_TEST_TRACE").unwrap_or_else(|_| "true".to_string());
|
||||
!(setting == "false" || setting == "0")
|
||||
match env::var("HBBFT_TEST_TRACE").as_ref().map(|s| s.as_str()) {
|
||||
Ok("true") | Ok("1") => true,
|
||||
_ => false,
|
||||
}
|
||||
});
|
||||
|
||||
if trace {
|
||||
|
|
|
@ -7,7 +7,7 @@ use crypto::SecretKeyShare;
|
|||
use rand::{self, Rng};
|
||||
|
||||
use hbbft::dynamic_honey_badger::Batch;
|
||||
use hbbft::{Contribution, DistAlgorithm, NetworkInfo, Step, Target, TargetedMessage};
|
||||
use hbbft::{Contribution, DistAlgorithm, Fault, NetworkInfo, Step, Target, TargetedMessage};
|
||||
|
||||
/// A node identifier. In the tests, nodes are simply numbered.
|
||||
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Clone, Copy, Serialize, Deserialize, Rand)]
|
||||
|
@ -25,6 +25,8 @@ pub struct TestNode<D: DistAlgorithm> {
|
|||
outputs: Vec<D::Output>,
|
||||
/// Outgoing messages to be sent to other nodes.
|
||||
messages: VecDeque<TargetedMessage<D::Message, D::NodeId>>,
|
||||
/// Collected fault logs.
|
||||
faults: Vec<Fault<D::NodeId>>,
|
||||
}
|
||||
|
||||
impl<D: DistAlgorithm> TestNode<D> {
|
||||
|
@ -44,6 +46,7 @@ impl<D: DistAlgorithm> TestNode<D> {
|
|||
let step = self.algo.handle_input(input).expect("input");
|
||||
self.outputs.extend(step.output);
|
||||
self.messages.extend(step.messages);
|
||||
self.faults.extend(step.fault_log.0);
|
||||
}
|
||||
|
||||
/// Returns the internal algorithm's instance.
|
||||
|
@ -60,6 +63,7 @@ impl<D: DistAlgorithm> TestNode<D> {
|
|||
queue: VecDeque::new(),
|
||||
outputs: step.output.into_iter().collect(),
|
||||
messages: step.messages,
|
||||
faults: step.fault_log.0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,9 +77,10 @@ impl<D: DistAlgorithm> TestNode<D> {
|
|||
.expect("handling message");
|
||||
self.outputs.extend(step.output);
|
||||
self.messages.extend(step.messages);
|
||||
self.faults.extend(step.fault_log.0);
|
||||
}
|
||||
|
||||
/// Checks whether the node has messages to process
|
||||
/// Checks whether the node has messages to process.
|
||||
fn is_idle(&self) -> bool {
|
||||
self.queue.is_empty()
|
||||
}
|
||||
|
@ -480,6 +485,8 @@ where
|
|||
}
|
||||
while !self.observer.queue.is_empty() {
|
||||
self.observer.handle_message();
|
||||
let faults: Vec<_> = self.observer.faults.drain(..).collect();
|
||||
self.check_faults(faults);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -501,7 +508,7 @@ where
|
|||
let id = self.adversary.pick_node(&self.nodes);
|
||||
|
||||
// The node handles the incoming message and creates new outgoing ones to be dispatched.
|
||||
let msgs: Vec<_> = {
|
||||
let (msgs, faults): (Vec<_>, Vec<_>) = {
|
||||
let mut node = self.nodes.get_mut(&id).unwrap();
|
||||
|
||||
// Ensure the adversary is playing fair by selecting a node that will result in actual
|
||||
|
@ -513,8 +520,12 @@ where
|
|||
);
|
||||
|
||||
node.handle_message();
|
||||
node.messages.drain(..).collect()
|
||||
(
|
||||
node.messages.drain(..).collect(),
|
||||
node.faults.drain(..).collect(),
|
||||
)
|
||||
};
|
||||
self.check_faults(faults);
|
||||
self.dispatch_messages(id, msgs);
|
||||
|
||||
id
|
||||
|
@ -522,11 +533,15 @@ where
|
|||
|
||||
/// Inputs a value in node `id`.
|
||||
pub fn input(&mut self, id: NodeId, value: D::Input) {
|
||||
let msgs: Vec<_> = {
|
||||
let (msgs, faults): (Vec<_>, Vec<_>) = {
|
||||
let mut node = self.nodes.get_mut(&id).expect("input instance");
|
||||
node.handle_input(value);
|
||||
node.messages.drain(..).collect()
|
||||
(
|
||||
node.messages.drain(..).collect(),
|
||||
node.faults.drain(..).collect(),
|
||||
)
|
||||
};
|
||||
self.check_faults(faults);
|
||||
self.dispatch_messages(id, msgs);
|
||||
}
|
||||
|
||||
|
@ -541,6 +556,15 @@ where
|
|||
self.input(id, value.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies that no correct node is reported as faulty.
|
||||
fn check_faults<I: IntoIterator<Item = Fault<D::NodeId>>>(&self, faults: I) {
|
||||
for fault in faults {
|
||||
if self.nodes.contains_key(&fault.node_id) {
|
||||
panic!("Unexpected fault: {:?}", fault);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Adversary<D>, C, D> TestNetwork<A, D>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#![deny(unused_must_use)]
|
||||
//! Coin tests
|
||||
//! Threshold signing tests
|
||||
|
||||
extern crate env_logger;
|
||||
extern crate hbbft;
|
||||
|
@ -18,15 +18,16 @@ use std::iter::once;
|
|||
|
||||
use rand::Rng;
|
||||
|
||||
use hbbft::coin::Coin;
|
||||
use crypto::Signature;
|
||||
use hbbft::threshold_sign::ThresholdSign;
|
||||
|
||||
use network::{Adversary, MessageScheduler, NodeId, SilentAdversary, TestNetwork, TestNode};
|
||||
|
||||
/// Tests a network of Coin instances with an optional expected value. Outputs the computed
|
||||
/// coin value if the test is successful.
|
||||
fn test_coin<A>(mut network: TestNetwork<A, Coin<NodeId, String>>) -> bool
|
||||
/// Tests a network of threshold signing instances with an optional expected value. Outputs the
|
||||
/// computed signature if the test is successful.
|
||||
fn test_threshold_sign<A>(mut network: TestNetwork<A, ThresholdSign<NodeId>>) -> Signature
|
||||
where
|
||||
A: Adversary<Coin<NodeId, String>>,
|
||||
A: Adversary<ThresholdSign<NodeId>>,
|
||||
{
|
||||
network.input_all(());
|
||||
network.observer.handle_input(()); // Observer will only return after `input` was called.
|
||||
|
@ -38,11 +39,11 @@ where
|
|||
let mut expected = None;
|
||||
// Verify that all instances output the same value.
|
||||
for node in network.nodes.values() {
|
||||
if let Some(b) = expected {
|
||||
assert!(once(&b).eq(node.outputs()));
|
||||
if let Some(ref b) = expected {
|
||||
assert!(once(b).eq(node.outputs()));
|
||||
} else {
|
||||
assert_eq!(1, node.outputs().len());
|
||||
expected = Some(node.outputs()[0]);
|
||||
expected = Some(node.outputs()[0].clone());
|
||||
}
|
||||
}
|
||||
// Now `expected` is the unique output of all good nodes.
|
||||
|
@ -71,9 +72,9 @@ fn check_coin_distribution(num_samples: usize, count_true: usize, count_false: u
|
|||
assert!(count_false > min_throws);
|
||||
}
|
||||
|
||||
fn test_coin_different_sizes<A, F>(new_adversary: F, num_samples: usize)
|
||||
fn test_threshold_sign_different_sizes<A, F>(new_adversary: F, num_samples: usize)
|
||||
where
|
||||
A: Adversary<Coin<NodeId, String>>,
|
||||
A: Adversary<ThresholdSign<NodeId>>,
|
||||
F: Fn(usize, usize) -> A,
|
||||
{
|
||||
assert!(num_samples > 0);
|
||||
|
@ -105,9 +106,9 @@ where
|
|||
let adversary = |_| new_adversary(num_good_nodes, num_faulty_nodes);
|
||||
let nonce = format!("My very unique nonce {:x}:{}", unique_id, i);
|
||||
info!("Nonce: {}", nonce);
|
||||
let new_coin = |netinfo: _| Coin::new(netinfo, nonce.clone());
|
||||
let new_coin = |netinfo: _| ThresholdSign::new(netinfo, nonce.clone());
|
||||
let network = TestNetwork::new(num_good_nodes, num_faulty_nodes, adversary, new_coin);
|
||||
let coin = test_coin(network);
|
||||
let coin = test_threshold_sign(network).parity();
|
||||
if coin {
|
||||
count_true += 1;
|
||||
} else {
|
||||
|
@ -119,13 +120,13 @@ where
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_coin_random_silent_200_samples() {
|
||||
fn test_threshold_sign_random_silent_200_samples() {
|
||||
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::Random);
|
||||
test_coin_different_sizes(new_adversary, 200);
|
||||
test_threshold_sign_different_sizes(new_adversary, 200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_coin_first_silent_50_samples() {
|
||||
fn test_threshold_sign_first_silent_50_samples() {
|
||||
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::First);
|
||||
test_coin_different_sizes(new_adversary, 50);
|
||||
test_threshold_sign_different_sizes(new_adversary, 50);
|
||||
}
|
Loading…
Reference in New Issue