Replace Coin with ThresholdSign. (#280)

* Avoid redundant hashing in Coin.

* Return the full signature from Coin.

* Rename Coin to ThresholdSign.
This commit is contained in:
Andreas Fackler 2018-10-23 11:49:19 +02:00 committed by Vladimir Komendantskiy
parent 13308906aa
commit 6bcd6bc499
7 changed files with 96 additions and 116 deletions

View File

@ -32,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 = "0.2"
threshold_crypto = "0.2.1"
tiny-keccak = "1.4"
[dev-dependencies]

View File

@ -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

View File

@ -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> {
@ -221,16 +221,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 +263,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 +305,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 +317,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 +353,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)
}

View File

@ -71,17 +71,17 @@ 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")]
@ -101,8 +101,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 {

View File

@ -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)
//!
@ -144,13 +143,13 @@ mod traits;
pub mod binary_agreement;
pub mod broadcast;
pub mod coin;
pub mod dynamic_honey_badger;
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;

View File

@ -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,43 +47,34 @@ 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: Self::Input) -> Result<Step<N>> {
self.get_sig()
}
/// Receives input from a remote node.
@ -99,9 +82,9 @@ where
&mut self,
sender_id: &Self::NodeId,
message: Self::Message,
) -> Result<Step<N, T>> {
) -> Result<Step<N>> {
if !self.terminated {
let CoinMessage(share) = message;
let Message(share) = message;
self.handle_share(sender_id, share)
} else {
Ok(Step::default())
@ -118,35 +101,35 @@ 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> {
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>> {
fn get_sig(&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 share = self.netinfo.secret_key_share().sign_g2(self.msg_hash);
let mut step: Step<_> = Target::All.message(Message(share.clone())).into();
let id = self.netinfo.our_id().clone();
step.extend(self.handle_share(&id, share)?);
Ok(step)
}
fn handle_share(&mut self, sender_id: &N, share: SignatureShare) -> Result<Step<N, T>> {
fn handle_share(&mut self, sender_id: &N, share: SignatureShare) -> Result<Step<N>> {
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 +141,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 +150,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 +172,7 @@ where
.netinfo
.public_key_set()
.public_key()
.verify(&sig, &self.nonce)
.verify_g2(&sig, self.msg_hash)
{
// Abort
error!(

View File

@ -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);
}