mirror of https://github.com/poanetwork/hbbft.git
common coin implementation
This commit is contained in:
parent
b6587a21e8
commit
cf45a4e3cb
|
@ -11,9 +11,15 @@ use std::rc::Rc;
|
|||
use itertools::Itertools;
|
||||
|
||||
use agreement::bin_values::BinValues;
|
||||
use common_coin;
|
||||
use common_coin::{CommonCoin, CommonCoinMessage};
|
||||
use messaging::{DistAlgorithm, NetworkInfo, Target, TargetedMessage};
|
||||
|
||||
error_chain!{
|
||||
links {
|
||||
CommonCoin(common_coin::Error, common_coin::ErrorKind);
|
||||
}
|
||||
|
||||
types {
|
||||
Error, ErrorKind, ResultExt, AgreementResult;
|
||||
}
|
||||
|
@ -24,7 +30,7 @@ error_chain!{
|
|||
}
|
||||
|
||||
#[cfg_attr(feature = "serialization-serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum AgreementContent {
|
||||
/// `BVal` message.
|
||||
BVal(bool),
|
||||
|
@ -34,6 +40,8 @@ pub enum AgreementContent {
|
|||
Conf(BinValues),
|
||||
/// `Term` message.
|
||||
Term(bool),
|
||||
/// Common Coin message,
|
||||
Coin(Box<CommonCoinMessage>),
|
||||
}
|
||||
|
||||
impl AgreementContent {
|
||||
|
@ -48,14 +56,17 @@ impl AgreementContent {
|
|||
|
||||
/// Messages sent during the binary Byzantine agreement stage.
|
||||
#[cfg_attr(feature = "serialization-serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct AgreementMessage {
|
||||
pub epoch: u32,
|
||||
pub content: AgreementContent,
|
||||
}
|
||||
|
||||
/// Binary Agreement instance
|
||||
pub struct Agreement<NodeUid> {
|
||||
pub struct Agreement<NodeUid>
|
||||
where
|
||||
NodeUid: Clone + Debug,
|
||||
{
|
||||
/// Shared network information.
|
||||
netinfo: Rc<NetworkInfo<NodeUid>>,
|
||||
/// Agreement algorithm epoch.
|
||||
|
@ -97,6 +108,11 @@ pub struct Agreement<NodeUid> {
|
|||
messages: VecDeque<AgreementMessage>,
|
||||
/// Whether the `Conf` message round has started in the current epoch.
|
||||
conf_round: bool,
|
||||
/// The subset of `bin_values` contained in received `Conf` messages before invoking the Common
|
||||
/// Coin instance.
|
||||
conf_vals: BinValues,
|
||||
/// A common coin instance. It is reset on epoch update.
|
||||
common_coin: CommonCoin<NodeUid, Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<NodeUid: Clone + Debug + Eq + Hash + Ord> DistAlgorithm for Agreement<NodeUid> {
|
||||
|
@ -129,6 +145,7 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> DistAlgorithm for Agreement<NodeU
|
|||
AgreementContent::Aux(b) => self.handle_aux(sender_id, b),
|
||||
AgreementContent::Conf(v) => self.handle_conf(sender_id, v),
|
||||
AgreementContent::Term(v) => self.handle_term(sender_id, v),
|
||||
AgreementContent::Coin(msg) => self.handle_coin(sender_id, *msg),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,7 +174,7 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> DistAlgorithm for Agreement<NodeU
|
|||
impl<NodeUid: Clone + Debug + Eq + Hash + Ord> Agreement<NodeUid> {
|
||||
pub fn new(netinfo: Rc<NetworkInfo<NodeUid>>) -> Self {
|
||||
Agreement {
|
||||
netinfo,
|
||||
netinfo: netinfo.clone(),
|
||||
epoch: 0,
|
||||
bin_values: BinValues::new(),
|
||||
received_bval: BTreeMap::new(),
|
||||
|
@ -172,6 +189,8 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> Agreement<NodeUid> {
|
|||
terminated: false,
|
||||
messages: VecDeque::new(),
|
||||
conf_round: false,
|
||||
conf_vals: BinValues::None,
|
||||
common_coin: CommonCoin::new(netinfo, vec![0]),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -305,6 +324,40 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> Agreement<NodeUid> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Outputs the optional decision value. The function may start the next epoch. In that case,
|
||||
/// it also returns a message for broadcast.
|
||||
fn handle_coin(&mut self, sender_id: &NodeUid, msg: CommonCoinMessage) -> AgreementResult<()> {
|
||||
self.common_coin.handle_message(sender_id, msg)?;
|
||||
if let Some(coin) = self.common_coin.next_output() {
|
||||
// Check the termination condition: "continue looping until both a value b is output in some
|
||||
// round r, and the value Coin_r' = b for some round r' > r."
|
||||
self.terminated = self.terminated || self.decision == Some(coin);
|
||||
if self.terminated {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.start_next_epoch();
|
||||
|
||||
let b = if let Some(b) = self.conf_vals.definite() {
|
||||
// Outputting a value is allowed only once.
|
||||
if self.decision.is_none() && b == coin {
|
||||
self.decide(b);
|
||||
}
|
||||
b
|
||||
} else {
|
||||
coin
|
||||
};
|
||||
|
||||
self.estimated = Some(b);
|
||||
self.send_bval(b)?;
|
||||
let queued_msgs = replace(&mut self.incoming_queue, Vec::new());
|
||||
for (sender_id, msg) in queued_msgs {
|
||||
self.handle_message(&sender_id, msg)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decides on a value and broadcasts a `Term` message with that value.
|
||||
fn decide(&mut self, b: bool) {
|
||||
// Output the agreement value.
|
||||
|
@ -329,10 +382,11 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> Agreement<NodeUid> {
|
|||
// Continue waiting for (N - f) `Conf` messages
|
||||
return Ok(());
|
||||
}
|
||||
self.invoke_coin(vals)
|
||||
} else {
|
||||
Ok(())
|
||||
self.conf_vals = vals;
|
||||
// Invoke the comon coin.
|
||||
self.common_coin.input(())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_aux(&mut self, b: bool) -> AgreementResult<()> {
|
||||
|
@ -387,53 +441,14 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> Agreement<NodeUid> {
|
|||
self.received_aux.clear();
|
||||
self.received_conf.clear();
|
||||
self.conf_round = false;
|
||||
self.conf_vals = BinValues::None;
|
||||
self.epoch += 1;
|
||||
let nonce = Vec::from(format!("{}", self.epoch));
|
||||
self.common_coin = CommonCoin::new(self.netinfo.clone(), nonce);
|
||||
debug!(
|
||||
"Agreement instance {:?} started epoch {}",
|
||||
self.netinfo.our_uid(),
|
||||
self.epoch
|
||||
);
|
||||
}
|
||||
|
||||
/// Gets a common coin and uses it to compute the next decision estimate and outputs the
|
||||
/// optional decision value. The function may start the next epoch. In that case, it also
|
||||
/// returns a message for broadcast.
|
||||
fn invoke_coin(&mut self, vals: BinValues) -> AgreementResult<()> {
|
||||
debug!(
|
||||
"{:?} invoke_coin in epoch {}",
|
||||
self.netinfo.our_uid(),
|
||||
self.epoch
|
||||
);
|
||||
// FIXME: Implement the Common Coin algorithm. At the moment the
|
||||
// coin value is common across different nodes but not random.
|
||||
let coin = (self.epoch % 2) == 0;
|
||||
|
||||
// Check the termination condition: "continue looping until both a
|
||||
// value b is output in some round r, and the value Coin_r' = b for
|
||||
// some round r' > r."
|
||||
self.terminated = self.terminated || self.decision == Some(coin);
|
||||
if self.terminated {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.start_next_epoch();
|
||||
|
||||
let b = if let Some(b) = vals.definite() {
|
||||
// Outputting a value is allowed only once.
|
||||
if self.decision.is_none() && b == coin {
|
||||
self.decide(b);
|
||||
}
|
||||
b
|
||||
} else {
|
||||
coin
|
||||
};
|
||||
|
||||
self.estimated = Some(b);
|
||||
self.send_bval(b)?;
|
||||
let queued_msgs = replace(&mut self.incoming_queue, Vec::new());
|
||||
for (sender_id, msg) in queued_msgs {
|
||||
self.handle_message(&sender_id, msg)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,72 +1,106 @@
|
|||
//! Common coin from a given set of keys.
|
||||
//! Common coin from a given set of keys based on a `pairing` threshold signature scheme.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Rc;
|
||||
|
||||
use pairing::bls12_381::Bls12;
|
||||
|
||||
use crypto::{PublicKey, PublicKeySet, SecretKey, SecretKeySet, Signature};
|
||||
use crypto::error as cerror;
|
||||
use crypto::Signature;
|
||||
use messaging::{DistAlgorithm, NetworkInfo, Target, TargetedMessage};
|
||||
|
||||
error_chain! {
|
||||
links {
|
||||
Crypto(cerror::Error, cerror::ErrorKind);
|
||||
}
|
||||
|
||||
errors {
|
||||
UnknownSender {
|
||||
description("unknown sender")
|
||||
}
|
||||
NotImplemented {
|
||||
description("not implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CommonCoinMessage {
|
||||
#[cfg_attr(feature = "serialization-serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum CommonCoinMessage {
|
||||
Share(Signature<Bls12>),
|
||||
}
|
||||
|
||||
/// A common coin 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)]
|
||||
struct CommonCoin<N>
|
||||
pub struct CommonCoin<N, T>
|
||||
where
|
||||
N: Debug,
|
||||
{
|
||||
netinfo: Rc<NetworkInfo<N>>,
|
||||
output: Option<Signature<Bls12>>,
|
||||
messages: VecDeque<TargetedMessage<CommonCoinMessage, N>>,
|
||||
/// The name of this common coin. It is required to be unique for each common coin round.
|
||||
nonce: T,
|
||||
/// The result of combination of at least `num_faulty + 1` threshold signature shares.
|
||||
output: Option<bool>,
|
||||
/// Outgoing message queue.
|
||||
messages: VecDeque<CommonCoinMessage>,
|
||||
/// All received threshold signature shares.
|
||||
received_shares: BTreeMap<N, Signature<Bls12>>,
|
||||
/// Termination flag.
|
||||
terminated: bool,
|
||||
}
|
||||
|
||||
impl<N> DistAlgorithm for CommonCoin<N>
|
||||
impl<N, T> DistAlgorithm for CommonCoin<N, T>
|
||||
where
|
||||
N: Clone + Debug + Ord,
|
||||
T: Clone + AsRef<[u8]>,
|
||||
{
|
||||
type NodeUid = N;
|
||||
type Input = ();
|
||||
type Output = Signature<Bls12>;
|
||||
type Output = bool;
|
||||
type Message = CommonCoinMessage;
|
||||
type Error = Error;
|
||||
|
||||
fn input(&mut self, input: Self::Input) -> Result<()> {
|
||||
// FIXME
|
||||
Err(ErrorKind::NotImplemented.into())
|
||||
/// Sends our threshold signature share if not yet sent.
|
||||
fn input(&mut self, _input: Self::Input) -> Result<()> {
|
||||
let share_sent = self.received_shares.keys().fold(false, |result, k| {
|
||||
if !result && k == self.netinfo.our_uid() {
|
||||
true
|
||||
} else {
|
||||
result
|
||||
}
|
||||
});
|
||||
if !share_sent {
|
||||
self.get_coin()
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Receive input from a remote node.
|
||||
/// Receives input from a remote node.
|
||||
fn handle_message(&mut self, sender_id: &Self::NodeUid, message: Self::Message) -> Result<()> {
|
||||
// FIXME
|
||||
Err(ErrorKind::NotImplemented.into())
|
||||
let CommonCoinMessage::Share(share) = message;
|
||||
self.handle_share(sender_id, share)
|
||||
}
|
||||
|
||||
/// Take the next Agreement message for multicast to all other nodes.
|
||||
/// Takes the next share of a threshold signature message for multicasting to all other nodes.
|
||||
fn next_message(&mut self) -> Option<TargetedMessage<Self::Message, Self::NodeUid>> {
|
||||
self.messages.pop_front()
|
||||
self.messages
|
||||
.pop_front()
|
||||
.map(|msg| Target::All.message(msg))
|
||||
}
|
||||
|
||||
/// Consume the output. Once consumed, the output stays `None` forever.
|
||||
/// Consumes the output. Once consumed, the output stays `None` forever.
|
||||
fn next_output(&mut self) -> Option<Self::Output> {
|
||||
self.output.take()
|
||||
}
|
||||
|
||||
/// Whether the algorithm has terminated.
|
||||
fn terminated(&self) -> bool {
|
||||
// FIXME
|
||||
false
|
||||
self.terminated
|
||||
}
|
||||
|
||||
fn our_id(&self) -> &Self::NodeUid {
|
||||
|
@ -74,26 +108,70 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<N> CommonCoin<N>
|
||||
impl<N, T> CommonCoin<N, T>
|
||||
where
|
||||
N: Clone + Debug + Ord,
|
||||
T: Clone + AsRef<[u8]>,
|
||||
{
|
||||
pub fn new(netinfo: Rc<NetworkInfo<N>>) -> Self {
|
||||
pub fn new(netinfo: Rc<NetworkInfo<N>>, nonce: T) -> Self {
|
||||
CommonCoin {
|
||||
netinfo,
|
||||
nonce,
|
||||
output: None,
|
||||
messages: VecDeque::new(),
|
||||
received_shares: BTreeMap::new(),
|
||||
terminated: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_coin<T>(&mut self, nonce: T) -> Result<()>
|
||||
where
|
||||
T: AsRef<[u8]>,
|
||||
{
|
||||
fn get_coin(&mut self) -> Result<()> {
|
||||
let share = self.netinfo.secret_key().sign(&self.nonce);
|
||||
self.messages
|
||||
.push_back(Target::All.message(CommonCoinMessage::Share(
|
||||
self.netinfo.secret_key().sign(nonce),
|
||||
)));
|
||||
Ok(())
|
||||
.push_back(CommonCoinMessage::Share(share.clone()));
|
||||
let id = self.netinfo.our_uid().clone();
|
||||
self.handle_share(&id, share)
|
||||
}
|
||||
|
||||
fn handle_share(&mut self, sender_id: &N, share: Signature<Bls12>) -> Result<()> {
|
||||
let node_indices = self.netinfo.node_indices();
|
||||
if let Some(i) = node_indices.get(sender_id) {
|
||||
let pk_i = self.netinfo.public_key_set().public_key_share(*i);
|
||||
if !pk_i.verify(&share, &self.nonce) {
|
||||
// Silently ignore the invalid share.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.received_shares.insert(sender_id.clone(), share);
|
||||
let received_shares = &self.received_shares;
|
||||
if received_shares.len() > self.netinfo.num_faulty() {
|
||||
// Pass the indices of sender nodes to `combine_signatures`.
|
||||
let shares: BTreeMap<&u64, &Signature<Bls12>> = self
|
||||
.netinfo
|
||||
.all_uids()
|
||||
.iter()
|
||||
.map(|id| (&node_indices[id], received_shares.get(id)))
|
||||
.filter(|(_, share)| share.is_some())
|
||||
.map(|(n, share)| (n, share.unwrap()))
|
||||
.collect();
|
||||
let sig = self.netinfo.public_key_set().combine_signatures(shares)?;
|
||||
// Verify the successfully combined signature with the main public key.
|
||||
if self
|
||||
.netinfo
|
||||
.public_key_set()
|
||||
.public_key()
|
||||
.verify(&sig, &self.nonce)
|
||||
{
|
||||
// Output the parity of the verified signature.
|
||||
self.output = Some(sig.parity());
|
||||
self.terminated = true;
|
||||
} else {
|
||||
// Abort
|
||||
self.terminated = true;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ErrorKind::UnknownSender.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ impl<NodeUid: Clone + Debug + Eq + Hash + Ord> MessageQueue<NodeUid> {
|
|||
/// remaining ones, where we haven't provided input yet.
|
||||
/// * Once all `Agreement` instances have decided, `CommonSubset` returns the set of all proposed
|
||||
/// values for which the decision was "yes".
|
||||
pub struct CommonSubset<NodeUid: Eq + Hash + Ord> {
|
||||
pub struct CommonSubset<NodeUid: Clone + Debug + Eq + Hash + Ord> {
|
||||
/// Shared network information.
|
||||
netinfo: Rc<NetworkInfo<NodeUid>>,
|
||||
broadcast_instances: BTreeMap<NodeUid, Broadcast<NodeUid>>,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
mod error;
|
||||
pub mod error;
|
||||
pub mod keygen;
|
||||
#[cfg(feature = "serialization-serde")]
|
||||
mod serde_impl;
|
||||
|
@ -59,7 +59,7 @@ impl<E: Engine> PublicKey<E> {
|
|||
}
|
||||
|
||||
/// A signature, or a signature share.
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug, PartialOrd)]
|
||||
pub struct Signature<E: Engine>(E::G2);
|
||||
|
||||
impl<E: Engine> PartialEq for Signature<E> {
|
||||
|
@ -68,6 +68,18 @@ impl<E: Engine> PartialEq for Signature<E> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<E: Engine> Signature<E> {
|
||||
pub fn parity(&self) -> bool {
|
||||
2 % self
|
||||
.0
|
||||
.into_affine()
|
||||
.into_uncompressed()
|
||||
.as_ref()
|
||||
.last()
|
||||
.expect("non-empty signature") == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// A secret key, or a secret key share.
|
||||
#[derive(Debug)]
|
||||
pub struct SecretKey<E: Engine>(E::Fr);
|
||||
|
|
|
@ -32,7 +32,7 @@ error_chain!{
|
|||
}
|
||||
|
||||
/// An instance of the Honey Badger Byzantine fault tolerant consensus algorithm.
|
||||
pub struct HoneyBadger<T, N: Eq + Hash + Ord + Clone> {
|
||||
pub struct HoneyBadger<T, N: Clone + Debug + Eq + Hash + Ord + Clone> {
|
||||
/// Shared network data.
|
||||
netinfo: Rc<NetworkInfo<N>>,
|
||||
/// The buffer of transactions that have not yet been included in any output batch.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fmt::Debug;
|
||||
|
||||
use pairing::bls12_381::Bls12;
|
||||
|
@ -189,4 +189,15 @@ impl<NodeUid: Ord> NetworkInfo<NodeUid> {
|
|||
pub fn public_key_set(&self) -> &PublicKeySet<Bls12> {
|
||||
&self.public_key_set
|
||||
}
|
||||
|
||||
/// The canonical numbering of all nodes.
|
||||
///
|
||||
/// FIXME: To avoid multiple computations of the same result, caching should be introduced.
|
||||
pub fn node_indices(&self) -> BTreeMap<&NodeUid, u64> {
|
||||
self.all_uids
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(n, id)| (id, n as u64))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use ring::digest::Algorithm;
|
|||
use agreement::bin_values::BinValues;
|
||||
use agreement::{AgreementContent, AgreementMessage};
|
||||
use broadcast::BroadcastMessage;
|
||||
use common_coin::CommonCoinMessage;
|
||||
use proto::message::*;
|
||||
|
||||
impl From<message::BroadcastProto> for BroadcastMessage {
|
||||
|
|
Loading…
Reference in New Issue