2018-07-16 06:31:21 -07:00
|
|
|
#![deny(unused_must_use)]
|
2018-10-23 02:49:19 -07:00
|
|
|
//! Threshold signing tests
|
2018-06-11 09:00:23 -07:00
|
|
|
|
|
|
|
mod network;
|
|
|
|
|
|
|
|
use std::iter::once;
|
|
|
|
|
2018-10-29 07:36:56 -07:00
|
|
|
use log::info;
|
2018-06-11 09:00:23 -07:00
|
|
|
use rand::Rng;
|
|
|
|
|
2018-12-11 05:44:36 -08:00
|
|
|
use hbbft::{crypto::Signature, threshold_sign::ThresholdSign, util};
|
2018-06-11 09:00:23 -07:00
|
|
|
|
2018-12-11 05:44:36 -08:00
|
|
|
use crate::network::{Adversary, MessageScheduler, NodeId, SilentAdversary, TestNetwork, TestNode};
|
2018-06-11 09:00:23 -07:00
|
|
|
|
2018-10-23 02:49:19 -07:00
|
|
|
/// 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
|
2018-06-11 09:00:23 -07:00
|
|
|
where
|
2018-10-23 02:49:19 -07:00
|
|
|
A: Adversary<ThresholdSign<NodeId>>,
|
2018-06-11 09:00:23 -07:00
|
|
|
{
|
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
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
|
2018-06-25 12:09:45 -07:00
|
|
|
network.input_all(());
|
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
|
|
|
|
|
|
|
network.observer.handle_input((), &mut rng); // Observer will only return after `input` was called.
|
2018-06-11 09:00:23 -07:00
|
|
|
|
|
|
|
// Handle messages until all good nodes have terminated.
|
|
|
|
while !network.nodes.values().all(TestNode::terminated) {
|
|
|
|
network.step();
|
|
|
|
}
|
2018-06-25 12:09:45 -07:00
|
|
|
let mut expected = None;
|
2018-06-11 09:00:23 -07:00
|
|
|
// Verify that all instances output the same value.
|
|
|
|
for node in network.nodes.values() {
|
2018-10-23 02:49:19 -07:00
|
|
|
if let Some(ref b) = expected {
|
|
|
|
assert!(once(b).eq(node.outputs()));
|
2018-06-11 09:00:23 -07:00
|
|
|
} else {
|
|
|
|
assert_eq!(1, node.outputs().len());
|
2018-10-23 02:49:19 -07:00
|
|
|
expected = Some(node.outputs()[0].clone());
|
2018-06-11 09:00:23 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Now `expected` is the unique output of all good nodes.
|
2018-06-25 12:09:45 -07:00
|
|
|
assert!(expected.iter().eq(network.observer.outputs()));
|
2018-06-11 09:00:23 -07:00
|
|
|
expected.unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
const GOOD_SAMPLE_SET: f64 = 400.0;
|
|
|
|
|
|
|
|
/// The count of throws of each side of the coin should be approaching 50% with a sufficiently large
|
|
|
|
/// sample set. This check assumes logarithmic growth of the expected number of throws of one coin
|
|
|
|
/// size.
|
|
|
|
fn check_coin_distribution(num_samples: usize, count_true: usize, count_false: usize) {
|
2018-06-14 04:28:38 -07:00
|
|
|
// Maximum 40% expectation in case of 400 samples or more.
|
|
|
|
const EXPECTED_SHARE: f64 = 0.4;
|
2018-06-11 09:00:23 -07:00
|
|
|
let max_gain = GOOD_SAMPLE_SET.log2();
|
2018-06-12 02:24:09 -07:00
|
|
|
let num_samples_f64 = num_samples as f64;
|
|
|
|
let gain = num_samples_f64.log2().min(max_gain);
|
2018-06-11 09:00:23 -07:00
|
|
|
let step = EXPECTED_SHARE / max_gain;
|
2018-06-12 02:24:09 -07:00
|
|
|
let min_throws = (num_samples_f64 * gain * step) as usize;
|
2018-06-11 09:00:23 -07:00
|
|
|
info!(
|
|
|
|
"Expecting a minimum of {} throws for each coin side. Throws of true: {}. Throws of false: {}.",
|
|
|
|
min_throws, count_true, count_false
|
|
|
|
);
|
|
|
|
assert!(count_true > min_throws);
|
|
|
|
assert!(count_false > min_throws);
|
|
|
|
}
|
|
|
|
|
2018-10-23 02:49:19 -07:00
|
|
|
fn test_threshold_sign_different_sizes<A, F>(new_adversary: F, num_samples: usize)
|
2018-06-11 09:00:23 -07:00
|
|
|
where
|
2018-10-23 02:49:19 -07:00
|
|
|
A: Adversary<ThresholdSign<NodeId>>,
|
2018-06-11 09:00:23 -07:00
|
|
|
F: Fn(usize, usize) -> A,
|
|
|
|
{
|
|
|
|
assert!(num_samples > 0);
|
|
|
|
|
|
|
|
// This returns an error in all but the first test.
|
|
|
|
let _ = env_logger::try_init();
|
|
|
|
|
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
|
|
|
|
let mut last_size = 1;
|
|
|
|
let mut sizes = vec![last_size];
|
|
|
|
let num_sizes = (GOOD_SAMPLE_SET.log2() - (num_samples as f64).log2()) as usize;
|
|
|
|
for _ in 0..num_sizes {
|
|
|
|
last_size += rng.gen_range(3, 7);
|
|
|
|
sizes.push(last_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
for size in sizes {
|
2018-11-22 04:00:02 -08:00
|
|
|
let num_faulty_nodes = util::max_faulty(size);
|
2018-06-11 09:00:23 -07:00
|
|
|
let num_good_nodes = size - num_faulty_nodes;
|
|
|
|
info!(
|
|
|
|
"Network size: {} good nodes, {} faulty nodes",
|
|
|
|
num_good_nodes, num_faulty_nodes
|
|
|
|
);
|
|
|
|
let unique_id: u64 = rng.gen();
|
|
|
|
let mut count_true = 0;
|
|
|
|
let mut count_false = 0;
|
|
|
|
for i in 0..num_samples {
|
2018-06-25 11:22:08 -07:00
|
|
|
let adversary = |_| new_adversary(num_good_nodes, num_faulty_nodes);
|
2018-06-11 09:00:23 -07:00
|
|
|
let nonce = format!("My very unique nonce {:x}:{}", unique_id, i);
|
|
|
|
info!("Nonce: {}", nonce);
|
2018-11-06 08:26:48 -08:00
|
|
|
let new_coin = |netinfo: _| {
|
|
|
|
ThresholdSign::new_with_document(netinfo, nonce.clone())
|
|
|
|
.expect("Failed to set the new coin's ID")
|
|
|
|
};
|
2018-08-14 05:56:32 -07:00
|
|
|
let network = TestNetwork::new(num_good_nodes, num_faulty_nodes, adversary, new_coin);
|
2018-10-23 02:49:19 -07:00
|
|
|
let coin = test_threshold_sign(network).parity();
|
2018-06-11 09:00:23 -07:00
|
|
|
if coin {
|
|
|
|
count_true += 1;
|
|
|
|
} else {
|
|
|
|
count_false += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
check_coin_distribution(num_samples, count_true, count_false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2018-10-23 02:49:19 -07:00
|
|
|
fn test_threshold_sign_random_silent_200_samples() {
|
2018-06-11 09:00:23 -07:00
|
|
|
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::Random);
|
2018-10-23 02:49:19 -07:00
|
|
|
test_threshold_sign_different_sizes(new_adversary, 200);
|
2018-06-11 09:00:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2018-10-23 02:49:19 -07:00
|
|
|
fn test_threshold_sign_first_silent_50_samples() {
|
2018-06-11 09:00:23 -07:00
|
|
|
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::First);
|
2018-10-23 02:49:19 -07:00
|
|
|
test_threshold_sign_different_sizes(new_adversary, 50);
|
2018-06-11 09:00:23 -07:00
|
|
|
}
|