common coin implementation

This commit is contained in:
Vladimir Komendantskiy 2018-06-08 19:43:27 +01:00
parent b6587a21e8
commit cf45a4e3cb
7 changed files with 201 additions and 84 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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