Add a random_value option to HB and DHB.

This includes a threshold signature in each batch that can be used as a
pseudorandom value.

Also moves `EncryptionSchedule` from `threshold_decrypt` to
`honey_badger`.
This commit is contained in:
Andreas Fackler 2018-11-12 12:24:29 +01:00 committed by Andreas Fackler
parent e4435d5622
commit 30cce9bed8
15 changed files with 187 additions and 40 deletions

View File

@ -1,6 +1,8 @@
use std::collections::BTreeMap;
use std::sync::Arc;
use crypto::Signature;
use super::EncryptionSchedule;
use super::{ChangeState, JoinPlan};
use {NetworkInfo, NodeIdT};
@ -14,6 +16,8 @@ pub struct Batch<C, N> {
pub(super) era: u64,
/// The user contributions committed in this epoch.
pub(super) contributions: BTreeMap<N, C>,
/// The signature that can be used as a pseudorandom value.
pub(super) random_value: Option<Signature>,
/// The current state of adding or removing a node: whether any is in progress, or completed
/// this epoch.
pub(super) change: ChangeState<N>,
@ -102,6 +106,7 @@ impl<C, N: NodeIdT> Batch<C, N> {
pub_key_set: self.netinfo.public_key_set().clone(),
pub_keys: self.netinfo.public_key_map().clone(),
encryption_schedule: self.encryption_schedule,
random_value: self.random_value.is_some(),
})
}
@ -119,4 +124,12 @@ impl<C, N: NodeIdT> Batch<C, N> {
&& self.netinfo.public_key_map() == other.netinfo.public_key_map()
&& self.encryption_schedule == other.encryption_schedule
}
/// Returns the signature that can be used as a pseudorandom value: None of the validators knew
/// its value before the other of the batch were decided.
///
/// If the `random_value` option is `false` (default), this is `None`.
pub fn random_value(&self) -> Option<&Signature> {
self.random_value.as_ref()
}
}

View File

@ -7,9 +7,11 @@ use crypto::{SecretKey, SecretKeySet, SecretKeyShare};
use rand::{self, Rand, Rng};
use serde::{de::DeserializeOwned, Serialize};
use super::{Change, ChangeState, DynamicHoneyBadger, JoinPlan, Result, Step, VoteCounter};
use super::{
Change, ChangeState, DynamicHoneyBadger, EncryptionSchedule, JoinPlan, Result, Step,
VoteCounter,
};
use honey_badger::{HoneyBadger, SubsetHandlingStrategy};
use threshold_decrypt::EncryptionSchedule;
use util::SubRng;
use {Contribution, NetworkInfo, NodeIdT};
@ -25,6 +27,8 @@ pub struct DynamicHoneyBadgerBuilder<C, N> {
rng: Box<dyn rand::Rng>,
/// Strategy used to handle the output of the `Subset` algorithm.
subset_handling_strategy: SubsetHandlingStrategy,
/// Whether to generate a pseudorandom value in each epoch.
random_value: bool,
/// Schedule for adding threshold encryption to some percentage of rounds
encryption_schedule: EncryptionSchedule,
_phantom: PhantomData<(C, N)>,
@ -41,6 +45,7 @@ where
max_future_epochs: 3,
rng: Box::new(rand::thread_rng()),
subset_handling_strategy: SubsetHandlingStrategy::Incremental,
random_value: false,
encryption_schedule: EncryptionSchedule::Always,
_phantom: PhantomData,
}
@ -85,6 +90,12 @@ where
self
}
/// Whether to generate a pseudorandom value in each epoch.
pub fn random_value(&mut self, random_value: bool) -> &mut Self {
self.random_value = random_value;
self
}
/// Sets the schedule to use for threshold encryption.
pub fn encryption_schedule(&mut self, encryption_schedule: EncryptionSchedule) -> &mut Self {
self.encryption_schedule = encryption_schedule;
@ -98,6 +109,7 @@ where
max_future_epochs,
rng,
subset_handling_strategy,
random_value,
encryption_schedule,
_phantom,
} = self;
@ -109,6 +121,7 @@ where
.max_future_epochs(max_future_epochs)
.rng(rng.sub_rng())
.subset_handling_strategy(subset_handling_strategy.clone())
.random_value(*random_value)
.encryption_schedule(*encryption_schedule)
.build();
DynamicHoneyBadger {
@ -153,6 +166,7 @@ where
let honey_badger = HoneyBadger::builder(arc_netinfo.clone())
.max_future_epochs(self.max_future_epochs)
.encryption_schedule(join_plan.encryption_schedule)
.random_value(join_plan.random_value)
.build();
let mut dhb = DynamicHoneyBadger {
netinfo,

View File

@ -1,6 +1,7 @@
use crypto::PublicKey;
use serde_derive::{Deserialize, Serialize};
use threshold_decrypt::EncryptionSchedule;
use super::EncryptionSchedule;
#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash, Debug)]
pub enum NodeChange<N> {

View File

@ -11,15 +11,14 @@ use serde::{de::DeserializeOwned, Serialize};
use super::votes::{SignedVote, VoteCounter};
use super::{
Batch, Change, ChangeState, DynamicHoneyBadgerBuilder, Error, ErrorKind, Input,
InternalContrib, KeyGenMessage, KeyGenState, Message, NodeChange, Result, SignedKeyGenMsg,
Step,
Batch, Change, ChangeState, DynamicHoneyBadgerBuilder, EncryptionSchedule, Error, ErrorKind,
Input, InternalContrib, KeyGenMessage, KeyGenState, Message, NodeChange, Result,
SignedKeyGenMsg, Step,
};
use fault_log::{Fault, FaultKind, FaultLog};
use honey_badger::{self, HoneyBadger, Message as HbMessage};
use sync_key_gen::{Ack, AckOutcome, Part, PartOutcome, SyncKeyGen};
use threshold_decrypt::EncryptionSchedule;
use util::{self, SubRng};
use {Contribution, DistAlgorithm, Epoched, NetworkInfo, NodeIdT, Target};
@ -319,6 +318,7 @@ where
change,
netinfo: Arc::new(self.netinfo.clone()),
contributions: batch_contributions,
random_value: hb_batch.random_value,
encryption_schedule: self.honey_badger.get_encryption_schedule(),
});
}

View File

@ -79,8 +79,7 @@ use rand::Rand;
use serde_derive::{Deserialize, Serialize};
use self::votes::{SignedVote, VoteCounter};
use super::threshold_decrypt::EncryptionSchedule;
use honey_badger::Message as HbMessage;
use honey_badger::{EncryptionSchedule, Message as HbMessage};
use sync_key_gen::{Ack, Part, SyncKeyGen};
use NodeIdT;
@ -148,6 +147,8 @@ pub struct JoinPlan<N: Ord> {
pub_keys: BTreeMap<N, PublicKey>,
/// The current encryption schedule for threshold cryptography.
encryption_schedule: EncryptionSchedule,
/// Whether to generate a pseudorandom value in each epoch.
random_value: bool,
}
/// The ongoing key generation, together with information about the validator change.

View File

@ -22,6 +22,8 @@ pub enum FaultKind {
InvalidCiphertext,
/// `HoneyBadger` received a message with an invalid epoch.
UnexpectedHbMessageEpoch,
/// `HoneyBadger` received a signatures share for the random value even though it is disabled.
UnexpectedSignatureShare,
/// `ThresholdDecrypt` received multiple shares from the same sender.
MultipleDecryptionShares,
/// `Broadcast` received a `Value` from a node other than the proposer.

View File

@ -1,3 +1,4 @@
use crypto::Signature;
use std::collections::BTreeMap;
use NodeIdT;
@ -5,8 +6,15 @@ use NodeIdT;
/// A batch of contributions the algorithm has output.
#[derive(Clone, Debug)]
pub struct Batch<C, N> {
/// This batch's epoch number. Each epoch produces exactly one batch.
pub epoch: u64,
/// The set of agreed contributions, by the contributor's node ID.
pub contributions: BTreeMap<N, C>,
/// The signature that can be used as a pseudorandom value: None of the validators knew
/// its value before the other of the batch were decided.
///
/// If the `random_value` option is `false` (default), this is `None`.
pub random_value: Option<Signature>,
}
impl<C, N: NodeIdT> Batch<C, N> {

View File

@ -5,9 +5,8 @@ use std::sync::Arc;
use rand::{self, Rand, Rng};
use serde::{de::DeserializeOwned, Serialize};
use super::HoneyBadger;
use super::{EncryptionSchedule, HoneyBadger};
use honey_badger::SubsetHandlingStrategy;
use threshold_decrypt::EncryptionSchedule;
use util::SubRng;
use {Contribution, NetworkInfo, NodeIdT};
@ -26,6 +25,8 @@ pub struct HoneyBadgerBuilder<C, N> {
rng: Box<dyn Rng>,
/// Strategy used to handle the output of the `Subset` algorithm.
subset_handling_strategy: SubsetHandlingStrategy,
/// Whether to generate a pseudorandom value in each epoch.
random_value: bool,
/// Schedule for adding threshold encryption to some percentage of rounds
encryption_schedule: EncryptionSchedule,
_phantom: PhantomData<C>,
@ -46,6 +47,7 @@ where
max_future_epochs: 3,
rng: Box::new(rand::thread_rng()),
subset_handling_strategy: SubsetHandlingStrategy::Incremental,
random_value: false,
encryption_schedule: EncryptionSchedule::Always,
_phantom: PhantomData,
}
@ -87,6 +89,12 @@ where
self
}
/// Whether to generate a pseudorandom value in each epoch.
pub fn random_value(&mut self, random_value: bool) -> &mut Self {
self.random_value = random_value;
self
}
/// Sets the schedule to use for threshold encryption.
pub fn encryption_schedule(&mut self, encryption_schedule: EncryptionSchedule) -> &mut Self {
self.encryption_schedule = encryption_schedule;
@ -105,6 +113,7 @@ where
rng: Box::new(self.rng.sub_rng()),
subset_handling_strategy: self.subset_handling_strategy.clone(),
encryption_schedule: self.encryption_schedule,
random_value: self.random_value,
}
}
}

View File

@ -7,7 +7,7 @@ use std::result;
use std::sync::Arc;
use bincode;
use crypto::Ciphertext;
use crypto::{Ciphertext, Signature};
use log::error;
use rand::{Rand, Rng};
use serde::{de::DeserializeOwned, Serialize};
@ -17,10 +17,44 @@ use super::{Batch, ErrorKind, MessageContent, Result, Step};
use fault_log::{Fault, FaultKind, FaultLog};
use subset::{self as cs, Subset, SubsetOutput};
use threshold_decrypt::{self as td, ThresholdDecrypt};
use threshold_sign::{self as ts, ThresholdSign};
use {Contribution, DistAlgorithm, NetworkInfo, NodeIdT};
type CsStep<N> = cs::Step<N>;
/// The status of the threshold signature for the random value.
#[derive(Debug)]
enum SigningState<N> {
/// No random value is required in this epoch.
None,
/// Signing is ongoing.
Ongoing(Box<ThresholdSign<N>>),
/// Signing is complete. This contains the signature.
Complete(Box<Signature>),
}
impl<N: NodeIdT> SigningState<N> {
/// Handles a message containing a decryption share.
fn handle_message(&mut self, sender_id: &N, msg: ts::Message) -> ts::Result<ts::Step<N>> {
match self {
SigningState::None => {
let fault_kind = FaultKind::UnexpectedSignatureShare;
Ok(Fault::new(sender_id.clone(), fault_kind).into())
}
SigningState::Ongoing(ref mut ts) => ts.handle_message(sender_id, msg),
SigningState::Complete(_) => Ok(ts::Step::default()),
}
}
/// Sends the signatures shares.
fn sign(&mut self) -> ts::Result<ts::Step<N>> {
match self {
SigningState::Ongoing(ref mut ts) => ts.sign(),
SigningState::None | SigningState::Complete(_) => Ok(ts::Step::default()),
}
}
}
/// The status of an encrypted contribution.
#[derive(Debug)]
enum DecryptionState<N> {
@ -188,10 +222,13 @@ pub struct EpochState<C, N: Rand> {
subset: SubsetState<N>,
/// The status of threshold decryption, by proposer.
decryption: BTreeMap<N, DecryptionState<N>>,
/// The status of the threshold signature for the random value.
signing: SigningState<N>,
/// Nodes found so far in `Subset` output.
accepted_proposers: BTreeSet<N>,
/// Determines the behavior upon receiving proposals from `subset`.
subset_handler: SubsetHandler<N>,
/// Whether contributions should be encrypted in this epoch.
require_decryption: bool,
_phantom: PhantomData<C>,
}
@ -207,15 +244,26 @@ where
hb_id: u64,
epoch: u64,
subset_handling_strategy: SubsetHandlingStrategy,
random_value: bool,
require_decryption: bool,
) -> Result<Self> {
let epoch_id = EpochId { hb_id, epoch };
let cs = Subset::new(netinfo.clone(), epoch_id).map_err(ErrorKind::CreateSubset)?;
let signing = if random_value {
let rand_id = ("random_value", hb_id, epoch);
let doc = bincode::serialize(&rand_id).map_err(|err| ErrorKind::RandBincode(*err))?;
let ts = ThresholdSign::new_with_document(netinfo.clone(), doc)
.map_err(ErrorKind::CreateThresholdSign)?;
SigningState::Ongoing(Box::new(ts))
} else {
SigningState::None
};
Ok(EpochState {
epoch,
netinfo,
subset: SubsetState::Ongoing(cs),
decryption: BTreeMap::default(),
signing,
accepted_proposers: Default::default(),
subset_handler: subset_handling_strategy.into(),
require_decryption,
@ -273,12 +321,24 @@ where
.map_err(ErrorKind::ThresholdDecrypt)?;
self.process_decryption(proposer_id, td_step)
}
MessageContent::SignatureShare(share) => {
let ts_step = self
.signing
.handle_message(sender_id, share)
.map_err(ErrorKind::ThresholdSign)?;
self.process_signing(ts_step)
}
}
}
/// When contributions of transactions have been decrypted for all valid proposers in this
/// 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 random_value = match &self.signing {
SigningState::None => None,
SigningState::Ongoing(_) => return None,
SigningState::Complete(signature) => Some(*signature.clone()),
};
let proposer_ids = self.subset.accepted_ids()?;
let mut plaintexts = Vec::new();
// Collect accepted plaintexts. Return if some are not decrypted yet.
@ -293,6 +353,7 @@ where
let mut batch = Batch {
epoch: self.epoch,
contributions: BTreeMap::new(),
random_value,
};
// Deserialize the output. If it fails, the proposer of that item is faulty.
for (id, plaintext) in plaintexts {
@ -336,6 +397,8 @@ where
}
if is_done {
let ts_state = self.signing.sign().map_err(ErrorKind::ThresholdSign)?;
step.extend(self.process_signing(ts_state)?);
self.subset = SubsetState::Complete(self.accepted_proposers.clone());
let faulty_shares: Vec<_> = self
.decryption
@ -373,6 +436,18 @@ where
Ok(step)
}
/// Processes a Threshold Sign step.
fn process_signing(&mut self, ts_step: ts::Step<N>) -> Result<Step<C, N>> {
let mut step = Step::default();
let opt_output = step.extend_with(ts_step, |share| {
MessageContent::SignatureShare(share).with_epoch(self.epoch)
});
if let Some(output) = opt_output.into_iter().next() {
self.signing = SigningState::Complete(Box::new(output));
}
Ok(step)
}
/// Given the output of the Subset algorithm, inputs the ciphertexts into the Threshold
/// Decrypt instances and sends our own decryption shares.
fn send_decryption_share(&mut self, proposer_id: N, v: &[u8]) -> Result<Step<C, N>> {

View File

@ -5,20 +5,27 @@ use failure::{Backtrace, Context, Fail};
use subset;
use threshold_decrypt;
use threshold_sign;
/// Honey badger error variants.
#[derive(Debug, Fail)]
pub enum ErrorKind {
#[fail(display = "ProposeBincode error: {}", _0)]
ProposeBincode(bincode::ErrorKind),
#[fail(display = "Error serializing random value document: {}", _0)]
RandBincode(bincode::ErrorKind),
#[fail(display = "Failed to instantiate Subset: {}", _0)]
CreateSubset(subset::Error),
#[fail(display = "Failed to instantiate ThresholdSign: {}", _0)]
CreateThresholdSign(threshold_sign::Error),
#[fail(display = "Failed to input contribution to Subset: {}", _0)]
InputSubset(subset::Error),
#[fail(display = "Failed to handle Subset message: {}", _0)]
HandleSubsetMessage(subset::Error),
#[fail(display = "Threshold decryption error: {}", _0)]
ThresholdDecrypt(threshold_decrypt::Error),
#[fail(display = "Threshold signing error: {}", _0)]
ThresholdSign(threshold_sign::Error),
#[fail(display = "Unknown sender")]
UnknownSender,
}

View File

@ -5,13 +5,13 @@ use std::sync::Arc;
use derivative::Derivative;
use rand::{Rand, Rng};
use serde::{de::DeserializeOwned, Serialize};
use serde_derive::{Deserialize, Serialize};
use super::epoch_state::EpochState;
use super::{Batch, Error, ErrorKind, HoneyBadgerBuilder, Message, Result};
use {util, Contribution, DistAlgorithm, Fault, FaultKind, NetworkInfo, NodeIdT};
pub use super::epoch_state::SubsetHandlingStrategy;
use threshold_decrypt::EncryptionSchedule;
/// An instance of the Honey Badger Byzantine fault tolerant consensus algorithm.
#[derive(Derivative)]
@ -38,6 +38,8 @@ pub struct HoneyBadger<C, N: Rand> {
pub(super) subset_handling_strategy: SubsetHandlingStrategy,
/// The schedule for which rounds we should use threshold encryption.
pub(super) encryption_schedule: EncryptionSchedule,
/// Whether to generate a pseudorandom value in each epoch.
pub(super) random_value: bool,
}
pub type Step<C, N> = ::DaStep<HoneyBadger<C, N>>;
@ -185,6 +187,7 @@ where
self.session_id,
epoch,
self.subset_handling_strategy.clone(),
self.random_value,
self.encryption_schedule.use_on_epoch(epoch),
)?),
})
@ -195,3 +198,24 @@ where
self.max_future_epochs
}
}
/// How frequently Threshold Encryption should be used.
#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash, Debug)]
pub enum EncryptionSchedule {
Always,
Never,
EveryNthEpoch(u32),
/// How many with encryption, followed by how many without encryption.
TickTock(u32, u32),
}
impl EncryptionSchedule {
pub fn use_on_epoch(self, epoch: u64) -> bool {
match self {
EncryptionSchedule::Always => true,
EncryptionSchedule::Never => false,
EncryptionSchedule::EveryNthEpoch(n) => (epoch % u64::from(n)) == 0,
EncryptionSchedule::TickTock(on, off) => (epoch % u64::from(on + off)) <= u64::from(on),
}
}
}

View File

@ -4,9 +4,12 @@ use serde_derive::{Deserialize, Serialize};
use subset;
use threshold_decrypt;
use threshold_sign;
/// The content of a `HoneyBadger` message. It should be further annotated with an epoch.
#[derive(Clone, Debug, Deserialize, Rand, Serialize)]
// `threshold_sign::Message` triggers this Clippy lint, but `Box<T>` doesn't implement `Rand`.
#[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))]
pub enum MessageContent<N: Rand> {
/// A message belonging to the subset algorithm in the given epoch.
Subset(subset::Message<N>),
@ -15,6 +18,8 @@ pub enum MessageContent<N: Rand> {
proposer_id: N,
share: threshold_decrypt::Message,
},
/// A share of the signature used as a pseudorandom value.
SignatureShare(threshold_sign::Message),
}
impl<N: Rand> MessageContent<N> {

View File

@ -32,5 +32,5 @@ mod message;
pub use self::batch::Batch;
pub use self::builder::HoneyBadgerBuilder;
pub use self::error::{Error, ErrorKind, Result};
pub use self::honey_badger::{HoneyBadger, Step, SubsetHandlingStrategy};
pub use self::honey_badger::{EncryptionSchedule, HoneyBadger, Step, SubsetHandlingStrategy};
pub use self::message::{Message, MessageContent};

View File

@ -22,27 +22,6 @@ use serde_derive::{Deserialize, Serialize};
use fault_log::{Fault, FaultKind, FaultLog};
use {DistAlgorithm, NetworkInfo, NodeIdT, Target};
/// How frequently Threshold Encryption should be used.
#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash, Debug)]
pub enum EncryptionSchedule {
Always,
Never,
EveryNthEpoch(u32),
/// How many with encryption, followed by how many without encryption.
TickTock(u32, u32),
}
impl EncryptionSchedule {
pub fn use_on_epoch(self, epoch: u64) -> bool {
match self {
EncryptionSchedule::Always => true,
EncryptionSchedule::Never => false,
EncryptionSchedule::EveryNthEpoch(n) => (epoch % u64::from(n)) == 0,
EncryptionSchedule::TickTock(on, off) => (epoch % u64::from(on + off)) <= u64::from(on),
}
}
}
/// A threshold decryption error.
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
pub enum Error {

View File

@ -21,7 +21,8 @@ use itertools::Itertools;
use log::info;
use rand::Rng;
use hbbft::honey_badger::{Batch, HoneyBadger, MessageContent};
use hbbft::crypto::Signature;
use hbbft::honey_badger::{Batch, EncryptionSchedule, HoneyBadger, MessageContent};
use hbbft::sender_queue::{self, SenderQueue, Step};
use hbbft::transaction_queue::TransactionQueue;
use hbbft::{threshold_decrypt, DistAlgorithm, NetworkInfo, Target, TargetedMessage};
@ -163,17 +164,21 @@ fn verify_output_sequence<A>(network: &TestNetwork<A, UsizeHoneyBadger>)
where
A: Adversary<UsizeHoneyBadger>,
{
let mut expected: Option<BTreeMap<&_, &_>> = None;
let mut expected: Option<BTreeMap<&_, (&_, &_)>> = None;
for node in network.nodes.values() {
assert!(!node.outputs().is_empty());
let outputs: BTreeMap<&u64, &BTreeMap<NodeId, Vec<usize>>> = node
let outputs: BTreeMap<&u64, (&BTreeMap<NodeId, Vec<usize>>, &Signature)> = node
.outputs()
.iter()
.map(
|Batch {
epoch,
contributions,
}| (epoch, contributions),
random_value,
}| {
let random_value = random_value.as_ref().expect("missing random value");
(epoch, (contributions, random_value))
},
).collect();
if expected.is_none() {
expected = Some(outputs);
@ -194,7 +199,11 @@ fn new_honey_badger(
.filter(|&&them| them != our_id)
.cloned()
.chain(iter::once(observer));
SenderQueue::builder(HoneyBadger::builder(netinfo).build(), peer_ids).build(our_id)
let hb = HoneyBadger::builder(netinfo)
.encryption_schedule(EncryptionSchedule::EveryNthEpoch(2))
.random_value(true)
.build();
SenderQueue::builder(hb, peer_ids).build(our_id)
}
fn test_honey_badger_different_sizes<A, F>(new_adversary: F, num_txs: usize)