hbbft/src/threshold_sign.rs

278 lines
10 KiB
Rust
Raw Normal View History

//! # Collaborative Threshold Signing
//!
//! The algorithm is instantiated and waits to recieve a document to sign, as well as signature
//! shares from threshold signature validation peers. Once `set_document` is successfully called,
//! then `handle_input(())` or `sign()` is called, which then sends a signature share to each
//! threshold signature validation peer. When at least _f + 1_ validators have shared their
//! signatures in this manner, each node outputs the same, valid signature of the data.
//!
//! 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
2018-07-02 23:53:08 -07:00
//! key and message, there is exactly one valid signature. This group signature is produced using
//! signature shares from any combination of _f + 1_ secret key share holders.
use std::collections::BTreeMap;
use std::sync::Arc;
use std::{fmt, result};
use crate::crypto::{self, hash_g2, Signature, SignatureShare, G2};
2018-10-29 07:36:56 -07:00
use failure::Fail;
use log::debug;
OsRng / external RNG Refactoring (#357) * Use `OsRng` in place of `thread_rng`. This changes the defaults of any builder by instantiating an `OsRng` instead of a `thread_rng`, the former being much more secure than the latter. Additionally, all the unit tests that still instantiate RNGs manually used `OsRng`s as well; while there is no actual need for this level of security in tests, the performance overhead is very small and random number generation complexity has such a small impact on these tests that the convenience of being able to ban `thread_rng` from the codebase altogether, setting a good example and avoid issues when refactoring later greatly outweigh the negatives. * Instead of storing random number generators in the various consensus algorithm instances, pass them in from the outside whenever they are needed. This changes a large amount of interfaces (and in this commit is only partially done, since `DistAlgorithm` needs to be fundamentally altered as well. It also obsoletes parts of the `util` module. * Added an `R: Rng` type parameter to both methods of `DistAlgorithm`, forcing callers to pass in their own Rngs. * Fixed documentation grammar and spelling in some of the altered interfaces due to RNG refactoring. * Move `rng` argument to the end of the argument for most functions. Also includes a reformatting due to Rust 1.30. * Updated tests, accomodate `rng`-API changes. * Fixed remaining compilation issues with new RNG code. * Fix illegal `self` import outside curly braces. * Cleaned up comments and fixed broken definition of `broadcast_input`. * Updated existing test cases to properly work with static dispatch randomness. * Do not use boxed `Rng`s for key generation in test networks. * Use the passed-in `Rng` in `ReorderingAdversary`, instead of storing a boxed one. * Fixed clippy lints after refactoring. * Removed some no-longer necessary manual `fmt::Debug` implementations in test framework. * Use `OsRng` even in tests in `binary_agreement_mitm`. * Use a proper deterministic RNG in tests `binary_agreement_mitm`. * Refactor `examples/simulation.rs` by not using `ThreadRng`, passing generic `Rng` parameters throughout and using a type alias instead of a newtype as the `Transaction`. * Remove `thread_rng` use from `examples/node.rs`. * Explicitly construct `InternalContrib` in `DynamicHoneyBadger::propose`. * Fixed typo in description of `DistAlgorithm` trait.
2018-12-14 04:51:09 -08:00
use rand::Rng;
2018-10-29 07:36:56 -07:00
use rand_derive::Rand;
2019-04-01 05:37:14 -07:00
use serde::{Deserialize, Serialize};
2018-10-29 07:36:56 -07:00
2018-12-18 07:17:46 -08:00
use crate::fault_log::{Fault, FaultLog};
use crate::{ConsensusProtocol, NetworkInfo, NodeIdT, Target};
/// A threshold signing error.
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
pub enum Error {
/// Redundant input provided.
#[fail(display = "Redundant input provided")]
MultipleMessagesToSign,
/// Error combining and verifying signature shares.
#[fail(display = "Error combining and verifying signature shares: {}", _0)]
2018-10-10 07:11:27 -07:00
CombineAndVerifySigCrypto(crypto::error::Error),
/// Unknown sender
#[fail(display = "Unknown sender")]
UnknownSender,
/// Signature verification failed.
#[fail(display = "Signature verification failed")]
VerificationFailed,
/// Document hash is not set, cannot sign or verify signatures.
#[fail(display = "Document hash is not set, cannot sign or verify signatures")]
DocumentHashIsNone,
}
/// A threshold signing result.
pub type Result<T> = ::std::result::Result<T, Error>;
2018-12-18 07:17:46 -08:00
/// A threshold sign message fault
#[derive(Clone, Debug, Fail, PartialEq)]
pub enum FaultKind {
/// `ThresholdSign` (`Coin`) received a signature share from an unverified sender.
#[fail(
display = "`ThresholdSign` (`Coin`) received a signature share from an unverified sender."
)]
UnverifiedSignatureShareSender,
/// `HoneyBadger` received a signatures share for the random value even though it is disabled.
#[fail(
display = "`HoneyBadger` received a signatures share for the random value even though it
is disabled."
)]
UnexpectedSignatureShare,
}
/// A threshold signing message, containing a signature share.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Rand)]
pub struct Message(pub SignatureShare);
2018-06-09 13:50:36 -07:00
/// A threshold signing algorithm instance. On input, broadcasts our threshold signature share. Upon
2018-06-08 11:43:27 -07:00
/// 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 ThresholdSign<N> {
netinfo: Arc<NetworkInfo<N>>,
/// The hash of the document to be signed.
doc_hash: Option<G2>,
/// All received threshold signature shares, together with the node index.
received_shares: BTreeMap<N, (usize, SignatureShare)>,
/// Whether we already sent our shares.
had_input: bool,
2018-06-08 11:43:27 -07:00
/// Termination flag.
terminated: bool,
}
/// A step returned from `ThresholdSign`. It contains at most one output.
pub type Step<N> = crate::CpStep<ThresholdSign<N>>;
2018-07-09 04:35:26 -07:00
impl<N: NodeIdT> ConsensusProtocol for ThresholdSign<N> {
2018-08-29 09:08:35 -07:00
type NodeId = N;
type Input = ();
type Output = Signature;
type Message = Message;
type Error = Error;
2018-12-18 07:17:46 -08:00
type FaultKind = FaultKind;
2018-06-08 11:43:27 -07:00
/// Sends our threshold signature share if not yet sent.
OsRng / external RNG Refactoring (#357) * Use `OsRng` in place of `thread_rng`. This changes the defaults of any builder by instantiating an `OsRng` instead of a `thread_rng`, the former being much more secure than the latter. Additionally, all the unit tests that still instantiate RNGs manually used `OsRng`s as well; while there is no actual need for this level of security in tests, the performance overhead is very small and random number generation complexity has such a small impact on these tests that the convenience of being able to ban `thread_rng` from the codebase altogether, setting a good example and avoid issues when refactoring later greatly outweigh the negatives. * Instead of storing random number generators in the various consensus algorithm instances, pass them in from the outside whenever they are needed. This changes a large amount of interfaces (and in this commit is only partially done, since `DistAlgorithm` needs to be fundamentally altered as well. It also obsoletes parts of the `util` module. * Added an `R: Rng` type parameter to both methods of `DistAlgorithm`, forcing callers to pass in their own Rngs. * Fixed documentation grammar and spelling in some of the altered interfaces due to RNG refactoring. * Move `rng` argument to the end of the argument for most functions. Also includes a reformatting due to Rust 1.30. * Updated tests, accomodate `rng`-API changes. * Fixed remaining compilation issues with new RNG code. * Fix illegal `self` import outside curly braces. * Cleaned up comments and fixed broken definition of `broadcast_input`. * Updated existing test cases to properly work with static dispatch randomness. * Do not use boxed `Rng`s for key generation in test networks. * Use the passed-in `Rng` in `ReorderingAdversary`, instead of storing a boxed one. * Fixed clippy lints after refactoring. * Removed some no-longer necessary manual `fmt::Debug` implementations in test framework. * Use `OsRng` even in tests in `binary_agreement_mitm`. * Use a proper deterministic RNG in tests `binary_agreement_mitm`. * Refactor `examples/simulation.rs` by not using `ThreadRng`, passing generic `Rng` parameters throughout and using a type alias instead of a newtype as the `Transaction`. * Remove `thread_rng` use from `examples/node.rs`. * Explicitly construct `InternalContrib` in `DynamicHoneyBadger::propose`. * Fixed typo in description of `DistAlgorithm` trait.
2018-12-14 04:51:09 -08:00
fn handle_input<R: Rng>(&mut self, _input: (), _rng: &mut R) -> Result<Step<N>> {
self.sign()
}
2018-06-08 11:43:27 -07:00
/// Receives input from a remote node.
OsRng / external RNG Refactoring (#357) * Use `OsRng` in place of `thread_rng`. This changes the defaults of any builder by instantiating an `OsRng` instead of a `thread_rng`, the former being much more secure than the latter. Additionally, all the unit tests that still instantiate RNGs manually used `OsRng`s as well; while there is no actual need for this level of security in tests, the performance overhead is very small and random number generation complexity has such a small impact on these tests that the convenience of being able to ban `thread_rng` from the codebase altogether, setting a good example and avoid issues when refactoring later greatly outweigh the negatives. * Instead of storing random number generators in the various consensus algorithm instances, pass them in from the outside whenever they are needed. This changes a large amount of interfaces (and in this commit is only partially done, since `DistAlgorithm` needs to be fundamentally altered as well. It also obsoletes parts of the `util` module. * Added an `R: Rng` type parameter to both methods of `DistAlgorithm`, forcing callers to pass in their own Rngs. * Fixed documentation grammar and spelling in some of the altered interfaces due to RNG refactoring. * Move `rng` argument to the end of the argument for most functions. Also includes a reformatting due to Rust 1.30. * Updated tests, accomodate `rng`-API changes. * Fixed remaining compilation issues with new RNG code. * Fix illegal `self` import outside curly braces. * Cleaned up comments and fixed broken definition of `broadcast_input`. * Updated existing test cases to properly work with static dispatch randomness. * Do not use boxed `Rng`s for key generation in test networks. * Use the passed-in `Rng` in `ReorderingAdversary`, instead of storing a boxed one. * Fixed clippy lints after refactoring. * Removed some no-longer necessary manual `fmt::Debug` implementations in test framework. * Use `OsRng` even in tests in `binary_agreement_mitm`. * Use a proper deterministic RNG in tests `binary_agreement_mitm`. * Refactor `examples/simulation.rs` by not using `ThreadRng`, passing generic `Rng` parameters throughout and using a type alias instead of a newtype as the `Transaction`. * Remove `thread_rng` use from `examples/node.rs`. * Explicitly construct `InternalContrib` in `DynamicHoneyBadger::propose`. * Fixed typo in description of `DistAlgorithm` trait.
2018-12-14 04:51:09 -08:00
fn handle_message<R: Rng>(
&mut self,
sender_id: &Self::NodeId,
message: Message,
_rng: &mut R,
) -> Result<Step<N>> {
self.handle_message(sender_id, message)
}
/// Whether the algorithm has terminated.
fn terminated(&self) -> bool {
2018-06-08 11:43:27 -07:00
self.terminated
}
2018-08-29 09:08:35 -07:00
fn our_id(&self) -> &Self::NodeId {
self.netinfo.our_id()
}
}
impl<N: NodeIdT> ThresholdSign<N> {
/// Creates a new instance of `ThresholdSign`, with the goal to collaboratively sign `doc`.
pub fn new(netinfo: Arc<NetworkInfo<N>>) -> Self {
ThresholdSign {
netinfo,
doc_hash: None,
2018-06-08 11:43:27 -07:00
received_shares: BTreeMap::new(),
had_input: false,
2018-06-08 11:43:27 -07:00
terminated: false,
}
}
2018-06-07 09:38:27 -07:00
/// Creates a new instance of `ThresholdSign`, including setting the document to sign.
pub fn new_with_document<M: AsRef<[u8]>>(netinfo: Arc<NetworkInfo<N>>, doc: M) -> Result<Self> {
let mut ts = ThresholdSign::new(netinfo);
ts.set_document(doc)?;
Ok(ts)
}
/// Sets doc_hash. Signature shares can only be sent after this function is completed.
pub fn set_document<M: AsRef<[u8]>>(&mut self, doc: M) -> Result<()> {
if self.doc_hash.is_some() {
return Err(Error::MultipleMessagesToSign);
}
self.doc_hash = Some(hash_g2(doc));
Ok(())
}
/// Sends our signature shares, and if we have collected enough, returns the full signature.
/// Returns an error if the message to sign hasn't been received yet.
pub fn sign(&mut self) -> Result<Step<N>> {
if self.had_input {
// Don't waste time on redundant shares.
return Ok(Step::default());
}
let hash = self.doc_hash.ok_or(Error::DocumentHashIsNone)?;
self.had_input = true;
let mut step = Step::default();
step.fault_log.extend(self.remove_invalid_shares());
let msg = match self.netinfo.secret_key_share() {
Some(sks) => Message(sks.sign_g2(hash)),
None => return Ok(step.join(self.try_output()?)), // Not a validator.
};
step.messages.push(Target::All.message(msg.clone()));
let id = self.our_id().clone();
step.extend(self.handle_message(&id, msg)?);
Ok(step)
2018-06-08 11:43:27 -07:00
}
/// Handles a message with a signature share received from `sender_id`.
///
/// This must be called with every message we receive from another node.
///
/// 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;
// Before checking the share, ensure the sender is a known validator
let idx = self
.netinfo
.node_index(sender_id)
.ok_or(Error::UnknownSender)?;
if !self.is_share_valid(sender_id, &share) {
let fault_kind = FaultKind::UnverifiedSignatureShareSender;
return Ok(Fault::new(sender_id.clone(), fault_kind).into());
}
self.received_shares.insert(sender_id.clone(), (idx, share));
self.try_output()
}
/// Removes all shares that are invalid, and returns faults for their senders.
2018-12-18 07:17:46 -08:00
fn remove_invalid_shares(&mut self) -> FaultLog<N, FaultKind> {
let faulty_senders: Vec<N> = self
.received_shares
.iter()
.filter(|(id, (_, ref share))| !self.is_share_valid(id, share))
.map(|(id, _)| id.clone())
.collect();
let mut fault_log = FaultLog::default();
for id in faulty_senders {
self.received_shares.remove(&id);
fault_log.append(id, FaultKind::UnverifiedSignatureShareSender);
}
fault_log
}
/// Returns `true` if the share is valid, or if we don't have the message data yet.
fn is_share_valid(&self, id: &N, share: &SignatureShare) -> bool {
let hash = match self.doc_hash {
None => return true, // No document yet. Verification postponed.
Some(ref doc_hash) => doc_hash,
};
match self.netinfo.public_key_share(id) {
None => false, // Unknown sender.
Some(pk_i) => pk_i.verify_g2(&share, *hash),
}
}
fn try_output(&mut self) -> Result<Step<N>> {
let hash = match self.doc_hash {
Some(hash) => hash,
None => return Ok(Step::default()),
};
if !self.terminated && self.received_shares.len() > self.netinfo.num_faulty() {
let sig = self.combine_and_verify_sig(hash)?;
self.terminated = true;
let step = self.sign()?; // Before terminating, make sure we sent our share.
debug!("{} output {:?}", self, sig);
Ok(step.with_output(sig))
} else {
debug!(
"{} received {} shares, {}",
self,
self.received_shares.len(),
if self.had_input { ", had input" } else { "" }
);
Ok(Step::default())
2018-06-08 11:43:27 -07:00
}
2018-06-07 09:38:27 -07:00
}
2018-06-14 04:28:38 -07:00
fn combine_and_verify_sig(&self, hash: G2) -> Result<Signature> {
2018-06-14 04:28:38 -07:00
// Pass the indices of sender nodes to `combine_signatures`.
let shares_itr = self
.received_shares
.values()
.map(|&(ref idx, ref share)| (idx, share));
let sig = self
.netinfo
.public_key_set()
.combine_signatures(shares_itr)
.map_err(Error::CombineAndVerifySigCrypto)?;
2018-06-14 04:28:38 -07:00
if !self
.netinfo
.public_key_set()
.public_key()
.verify_g2(&sig, hash)
2018-06-14 04:28:38 -07:00
{
Err(Error::VerificationFailed)
2018-06-14 04:28:38 -07:00
} else {
Ok(sig)
}
}
}
impl<N: NodeIdT> fmt::Display for ThresholdSign<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> result::Result<(), fmt::Error> {
write!(f, "{:?} TS({:?})", self.our_id(), self.doc_hash)
}
}