diff --git a/crates/threshold-signatures/Cargo.toml b/crates/threshold-signatures/Cargo.toml index 4ea405681..e89ba8c31 100644 --- a/crates/threshold-signatures/Cargo.toml +++ b/crates/threshold-signatures/Cargo.toml @@ -103,6 +103,11 @@ name = "ckd" harness = false required-features = ["test-utils"] +[[bench]] +name = "advanced_dkg" +harness = false +required-features = ["test-utils"] + [lints.clippy] mod_module_files = "deny" # We might revisit these exceptions in the future diff --git a/crates/threshold-signatures/README.md b/crates/threshold-signatures/README.md index cc9b5d11a..1ef2ad1ea 100644 --- a/crates/threshold-signatures/README.md +++ b/crates/threshold-signatures/README.md @@ -219,7 +219,7 @@ MAX_MALICIOUS=15 LATENCY=100 SAMPLE_SIZE=20 cargo bench -- robust_ecdsa_presign_ ``` By default, the maximum number of malicious parties is 6, the latency is 0 milliseconds and the number of iterations is 15. -The detailed numbers and analysis can be found in the [docs/benches/model.md](docs/benches/model.md) documentation. +The detailed numbers and analysis can be found in the [docs/benches/results.md](docs/benches/results.md) documentation. In a nutshell, our results show that the Robust ECDSA scheme is better to deploy than the OT based ECDSA in terms of efficiency and network bandwidth. In fact, with 15 maximum malicious parties and 100 ms of latency, the Robust ECDSA offline phase is roughly **4.7 times** faster than the OT based ECDSA offline phase and transmits **130 times** less bytes over the network before completing. As for Ed25519 the online phase is relatively slow with the current implementation (which does not split the scheme into presign and sign) compared to the ECDSA. With 100ms of latency, the current implementation (no presigning) is roughly 3 times slower to serve a message signing request (online phase) than both of the ECDSA schemes due to the fact that it has 3 times more rounds. diff --git a/crates/threshold-signatures/benches/advanced_dkg.rs b/crates/threshold-signatures/benches/advanced_dkg.rs new file mode 100644 index 000000000..094fdccbd --- /dev/null +++ b/crates/threshold-signatures/benches/advanced_dkg.rs @@ -0,0 +1,150 @@ +#![allow(clippy::indexing_slicing)] + +use criterion::{criterion_group, criterion_main, Criterion}; +use rand::seq::SliceRandom as _; +use rand_core::SeedableRng; + +mod bench_utils; +use crate::bench_utils::{ + analyze_received_sizes, prepare_dkg, PreparedOutputs, MAX_MALICIOUS, SAMPLE_SIZE, +}; + +use threshold_signatures::{ + confidential_key_derivation::ciphersuite::BLS12381SHA256, + frost_ed25519::Ed25519Sha512, + frost_secp256k1::Secp256K1Sha256, + keygen, + protocol::Protocol, + test_utils::{ + run_protocol_and_take_snapshots, run_simulated_protocol, MockCryptoRng, Simulator, + }, + Ciphersuite, Element, KeygenOutput, ReconstructionLowerBound, Scalar, +}; + +fn threshold() -> ReconstructionLowerBound { + ReconstructionLowerBound::from(*MAX_MALICIOUS + 1) +} + +fn participants_num() -> usize { + *MAX_MALICIOUS + 1 +} + +type PreparedSimulatedDkg = PreparedOutputs>; + +/// Benches the DKG protocol for Secp256k1 +fn bench_dkg_secp256k1(c: &mut Criterion) { + let num = participants_num(); + let max_malicious = *MAX_MALICIOUS; + let mut sizes = Vec::with_capacity(*SAMPLE_SIZE); + + let mut group = c.benchmark_group("dkg"); + group.sample_size(*SAMPLE_SIZE); + group.bench_function( + format!("dkg_secp256k1_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}"), + |b| { + b.iter_batched( + || { + let preps = prepare_simulated_dkg::(threshold()); + sizes.push(preps.simulator.get_view_size()); + preps + }, + |preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator), + criterion::BatchSize::SmallInput, + ); + }, + ); + analyze_received_sizes(&sizes, true); +} + +/// Benches the DKG protocol for Ed25519 +fn bench_dkg_ed25519(c: &mut Criterion) { + let num = participants_num(); + let max_malicious = *MAX_MALICIOUS; + let mut sizes = Vec::with_capacity(*SAMPLE_SIZE); + + let mut group = c.benchmark_group("dkg"); + group.sample_size(*SAMPLE_SIZE); + group.bench_function( + format!("dkg_ed25519_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}"), + |b| { + b.iter_batched( + || { + let preps = prepare_simulated_dkg::(threshold()); + sizes.push(preps.simulator.get_view_size()); + preps + }, + |preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator), + criterion::BatchSize::SmallInput, + ); + }, + ); + analyze_received_sizes(&sizes, true); +} + +/// Benches the DKG protocol for BLS12-381 +fn bench_dkg_bls12381(c: &mut Criterion) { + let num = participants_num(); + let max_malicious = *MAX_MALICIOUS; + let mut sizes = Vec::with_capacity(*SAMPLE_SIZE); + + let mut group = c.benchmark_group("dkg"); + group.sample_size(*SAMPLE_SIZE); + group.bench_function( + format!("dkg_bls12381_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}"), + |b| { + b.iter_batched( + || { + let preps = prepare_simulated_dkg::(threshold()); + sizes.push(preps.simulator.get_view_size()); + preps + }, + |preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator), + criterion::BatchSize::SmallInput, + ); + }, + ); + analyze_received_sizes(&sizes, true); +} + +criterion_group!( + benches, + bench_dkg_secp256k1, + bench_dkg_ed25519, + bench_dkg_bls12381 +); +criterion_main!(benches); + +/****************************** Helpers ******************************/ +/// Used to simulate DKG keygen for benchmarking +fn prepare_simulated_dkg( + threshold: ReconstructionLowerBound, +) -> PreparedSimulatedDkg +where + Element: Send, + Scalar: Send, +{ + let mut rng = MockCryptoRng::seed_from_u64(42); + let preps = prepare_dkg::(participants_num(), threshold, &mut rng); + let participants: Vec<_> = preps.iter().map(|(p, _)| *p).collect(); + let (_, protocol_snapshot) = run_protocol_and_take_snapshots(preps) + .expect("Running protocol with snapshot should not have issues"); + + // choose the real_participant at random + let real_participant = *participants + .choose(&mut rng) + .expect("participant list is not empty"); + + let real_protocol = keygen::(&participants, real_participant, threshold, rng) + .map(|p| Box::new(p) as Box>>) + .expect("Keygen should succeed"); + + // now preparing the simulator + let simulated_protocol = + Simulator::new(real_participant, protocol_snapshot).expect("Simulator should not be empty"); + + PreparedSimulatedDkg { + participant: real_participant, + protocol: real_protocol, + simulator: simulated_protocol, + } +} diff --git a/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v1.rs b/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v1.rs index c870e2518..6fd4fd562 100644 --- a/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v1.rs +++ b/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v1.rs @@ -54,7 +54,7 @@ criterion_main!(benches); fn prepare_simulated_sign(threshold: ReconstructionLowerBound) -> PreparedSimulatedSig { let mut rng = MockCryptoRng::seed_from_u64(41); let preps = ed25519_prepare_sign_v1(threshold, &mut rng); - let (_, protocolsnapshot) = run_protocol_and_take_snapshots(preps.protocols) + let (_, protocol_snapshot) = run_protocol_and_take_snapshots(preps.protocols) .expect("Running protocol with snapshot should not have issues"); let participants: Vec = preps @@ -79,7 +79,7 @@ fn prepare_simulated_sign(threshold: ReconstructionLowerBound) -> PreparedSimula // now preparing the simulator let simulated_protocol = - Simulator::new(real_participant, protocolsnapshot).expect("Simulator should not be empty"); + Simulator::new(real_participant, protocol_snapshot).expect("Simulator should not be empty"); PreparedSimulatedSig { participant: real_participant, diff --git a/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v2.rs b/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v2.rs index d7e29cdca..cdbf367d9 100644 --- a/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v2.rs +++ b/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v2.rs @@ -85,7 +85,7 @@ fn prepare_simulate_presign(num_participants: usize) -> PreparedPresig { let mut rng = MockCryptoRng::seed_from_u64(42); let preps = ed25519_prepare_presign(num_participants, &mut rng); - let (_, protocolsnapshot) = run_protocol_and_take_snapshots(preps.protocols) + let (_, protocol_snapshot) = run_protocol_and_take_snapshots(preps.protocols) .expect("Running protocol with snapshot should not have issues"); // choose the real_participant at random @@ -117,7 +117,7 @@ fn prepare_simulate_presign(num_participants: usize) -> PreparedPresig { // now preparing the simulator let simulated_protocol = - Simulator::new(real_participant, protocolsnapshot).expect("Simulator should not be empty"); + Simulator::new(real_participant, protocol_snapshot).expect("Simulator should not be empty"); PreparedPresig { participant: real_participant, @@ -132,7 +132,7 @@ fn prepare_simulated_sign(threshold: ReconstructionLowerBound) -> PreparedSimula let preps = ed25519_prepare_presign(threshold.value(), &mut rng); let result = run_protocol(preps.protocols).expect("Prepare sign should not fail"); let preps = ed25519_prepare_sign_v2(&result, preps.key_packages, threshold, &mut rng); - let (_, protocolsnapshot) = run_protocol_and_take_snapshots(preps.protocols) + let (_, protocol_snapshot) = run_protocol_and_take_snapshots(preps.protocols) .expect("Running protocol with snapshot should not have issues"); let participants: Vec = preps @@ -157,7 +157,7 @@ fn prepare_simulated_sign(threshold: ReconstructionLowerBound) -> PreparedSimula // now preparing the simulator let simulated_protocol = - Simulator::new(real_participant, protocolsnapshot).expect("Simulator should not be empty"); + Simulator::new(real_participant, protocol_snapshot).expect("Simulator should not be empty"); PreparedSimulatedSig { participant: real_participant, diff --git a/crates/threshold-signatures/benches/advanced_ot_based_ecdsa.rs b/crates/threshold-signatures/benches/advanced_ot_based_ecdsa.rs index 7e1b3443d..1e1c0b101 100644 --- a/crates/threshold-signatures/benches/advanced_ot_based_ecdsa.rs +++ b/crates/threshold-signatures/benches/advanced_ot_based_ecdsa.rs @@ -140,7 +140,7 @@ fn prepare_simulated_triples(participant_num: usize) -> PreparedSimulatedTriples let mut rng = MockCryptoRng::seed_from_u64(42); let preps = ot_ecdsa_prepare_triples(participant_num, *RECONSTRUCTION_LOWER_BOUND, &mut rng); - let (_, protocolsnapshot) = run_protocol_and_take_snapshots(preps.protocols) + let (_, protocol_snapshot) = run_protocol_and_take_snapshots(preps.protocols) .expect("Running protocol with snapshot should not have issues"); // choose the real_participant at random @@ -170,7 +170,7 @@ fn prepare_simulated_triples(participant_num: usize) -> PreparedSimulatedTriples // now preparing the simulator let simulated_protocol = - Simulator::new(real_participant, protocolsnapshot).expect("Simulator should not be empty"); + Simulator::new(real_participant, protocol_snapshot).expect("Simulator should not be empty"); PreparedSimulatedTriples { participant: real_participant, protocol: real_protocol, @@ -184,7 +184,7 @@ fn prepare_simulated_presign( ) -> PreparedSimulatedPresig { let mut rng = MockCryptoRng::seed_from_u64(40); let preps = ot_ecdsa_prepare_presign(two_triples, *RECONSTRUCTION_LOWER_BOUND, &mut rng); - let (_, protocolsnapshot) = run_protocol_and_take_snapshots(preps.protocols) + let (_, protocol_snapshot) = run_protocol_and_take_snapshots(preps.protocols) .expect("Running protocol with snapshot should not have issues"); let mut rng = MockCryptoRng::seed_from_u64(41); @@ -211,7 +211,7 @@ fn prepare_simulated_presign( // now preparing the simulator let simulated_protocol = - Simulator::new(real_participant, protocolsnapshot).expect("Simulator should not be empty"); + Simulator::new(real_participant, protocol_snapshot).expect("Simulator should not be empty"); PreparedSimulatedPresig { participant: real_participant, @@ -228,7 +228,7 @@ pub fn prepare_simulated_sign( ) -> PreparedSimulatedSig { let mut rng = MockCryptoRng::seed_from_u64(40); let preps = ot_ecdsa_prepare_sign(result, threshold, pk, &mut rng); - let (_, protocolsnapshot) = run_protocol_and_take_snapshots(preps.protocols) + let (_, protocol_snapshot) = run_protocol_and_take_snapshots(preps.protocols) .expect("Running protocol with snapshot should not have issues"); // choose the real_participant at random @@ -251,7 +251,7 @@ pub fn prepare_simulated_sign( // now preparing the being the coordinator let simulated_protocol = - Simulator::new(real_participant, protocolsnapshot).expect("Simulator should not be empty"); + Simulator::new(real_participant, protocol_snapshot).expect("Simulator should not be empty"); PreparedSimulatedSig { participant: real_participant, protocol: real_protocol, diff --git a/crates/threshold-signatures/benches/advanced_robust_ecdsa.rs b/crates/threshold-signatures/benches/advanced_robust_ecdsa.rs index 9eb277fce..7980f235c 100644 --- a/crates/threshold-signatures/benches/advanced_robust_ecdsa.rs +++ b/crates/threshold-signatures/benches/advanced_robust_ecdsa.rs @@ -97,7 +97,7 @@ fn prepare_simulate_presign(num_participants: usize) -> PreparedPresig { let mut rng = MockCryptoRng::seed_from_u64(42); let preps = robust_ecdsa_prepare_presign(num_participants, &mut rng); - let (_, protocolsnapshot) = run_protocol_and_take_snapshots(preps.protocols) + let (_, protocol_snapshot) = run_protocol_and_take_snapshots(preps.protocols) .expect("Running protocol with snapshot should not have issues"); // choose the real_participant at random @@ -131,7 +131,7 @@ fn prepare_simulate_presign(num_participants: usize) -> PreparedPresig { // now preparing the simulator let simulated_protocol = - Simulator::new(real_participant, protocolsnapshot).expect("Simulator should not be empty"); + Simulator::new(real_participant, protocol_snapshot).expect("Simulator should not be empty"); PreparedPresig { participant: real_participant, @@ -148,7 +148,7 @@ fn prepare_simulated_sign( ) -> PreparedSimulatedSig { let mut rng = MockCryptoRng::seed_from_u64(41); let preps = robust_ecdsa_prepare_sign(result, max_malicious.into(), pk, &mut rng); - let (_, protocolsnapshot) = run_protocol_and_take_snapshots(preps.protocols) + let (_, protocol_snapshot) = run_protocol_and_take_snapshots(preps.protocols) .expect("Running protocol with snapshot should not have issues"); // collect all participants @@ -170,7 +170,7 @@ fn prepare_simulated_sign( // now preparing the simulator let simulated_protocol = - Simulator::new(real_participant, protocolsnapshot).expect("Simulator should not be empty"); + Simulator::new(real_participant, protocol_snapshot).expect("Simulator should not be empty"); PreparedSimulatedSig { participant: real_participant, diff --git a/crates/threshold-signatures/benches/bench_utils.rs b/crates/threshold-signatures/benches/bench_utils.rs index 0ddcbc84e..ebd67939d 100644 --- a/crates/threshold-signatures/benches/bench_utils.rs +++ b/crates/threshold-signatures/benches/bench_utils.rs @@ -21,13 +21,14 @@ use threshold_signatures::{ robust_ecdsa, Scalar, }, frost::eddsa, + keygen, participants::Participant, protocol::Protocol, test_utils::{ ecdsa_generate_rerandpresig_args, generate_participants_with_random_ids, run_keygen, MockCryptoRng, Simulator, }, - MaxMalicious, ReconstructionLowerBound, + Ciphersuite, KeygenOutput, MaxMalicious, ReconstructionLowerBound, }; // fix malicious number of participants @@ -164,10 +165,8 @@ pub fn ot_ecdsa_prepare_presign let key_packages = run_keygen(&participants, threshold, rng); - let mut protocols: Vec<( - Participant, - Box>, - )> = Vec::with_capacity(participants.len()); + let mut protocols: Vec<(_, Box>)> = + Vec::with_capacity(participants.len()); for (((p, keygen_out), share0), share1) in key_packages.clone().into_iter().zip(shares0).zip(shares1) @@ -226,10 +225,7 @@ pub fn ot_ecdsa_prepare_sign( }) .collect::>(); - let mut protocols: Vec<( - Participant, - Box>, - )> = Vec::with_capacity(result.len()); + let mut protocols = Vec::with_capacity(result.len()); for (p, presignature) in result.clone() { let protocol = ot_based_ecdsa::sign::sign( @@ -287,10 +283,7 @@ pub fn robust_ecdsa_prepare_presign RobustECDSAPreparedPresig { let participants = generate_participants_with_random_ids(num_participants, rng); let key_packages = run_keygen(&participants, *MAX_MALICIOUS + 1, rng); - let mut protocols: Vec<( - Participant, - Box>, - )> = Vec::with_capacity(participants.len()); + let mut protocols: Vec<_> = Vec::with_capacity(participants.len()); for (p, keygen_out) in &key_packages { let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); @@ -348,10 +341,7 @@ pub fn robust_ecdsa_prepare_sign( }) .collect::>(); - let mut protocols: Vec<( - Participant, - Box>, - )> = Vec::with_capacity(result.len()); + let mut protocols = Vec::with_capacity(result.len()); for (p, presignature) in result.clone() { let protocol = robust_ecdsa::sign::sign( @@ -388,10 +378,7 @@ pub fn ed25519_prepare_presign( ) -> FrostEd25519PreparedPresig { let participants = generate_participants_with_random_ids(num_participants, rng); let key_packages = run_keygen(&participants, *MAX_MALICIOUS + 1, rng); - let mut protocols: Vec<( - Participant, - Box>, - )> = Vec::with_capacity(participants.len()); + let mut protocols: Vec<_> = Vec::with_capacity(participants.len()); for (p, keygen_out) in &key_packages { let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); @@ -428,10 +415,7 @@ pub fn ed25519_prepare_sign_v1( let coordinator_index = rng.gen_range(0..num_participants); let coordinator = participants[coordinator_index]; - let mut protocols: Vec<( - Participant, - Box>, - )> = Vec::with_capacity(participants.len()); + let mut protocols = Vec::with_capacity(participants.len()); let mut message: [u8; 32] = [0u8; 32]; rng.fill_bytes(&mut message); @@ -469,17 +453,13 @@ pub fn ed25519_prepare_sign_v2( ) -> FrostEd25519SigV2 { let num_participants = threshold.value(); // collect all participants - let participants: Vec = - result.iter().map(|(participant, _)| *participant).collect(); + let participants: Vec<_> = result.iter().map(|(participant, _)| *participant).collect(); // choose a coordinator at random let coordinator_index = rng.gen_range(0..num_participants); let coordinator = participants[coordinator_index]; - let mut protocols: Vec<( - Participant, - Box>, - )> = Vec::with_capacity(participants.len()); + let mut protocols = Vec::with_capacity(participants.len()); let mut message: [u8; 32] = [0u8; 32]; rng.fill_bytes(&mut message); @@ -545,10 +525,7 @@ pub fn prepare_ckd( let coordinator_index = rng.gen_range(0..num_participants); let coordinator = participants[coordinator_index]; - let mut protocols: Vec<( - Participant, - Box>, - )> = Vec::with_capacity(participants.len()); + let mut protocols = Vec::with_capacity(participants.len()); let mut app_id: [u8; 32] = [0u8; 32]; rng.fill_bytes(&mut app_id); @@ -593,3 +570,30 @@ pub struct PreparedCkdPackage { pub app_id: ckd::AppId, pub app_pk: ckd::ElementG1, } + +/********************* DKG *********************/ +/// Used to prepare DKG keygen protocols for benchmarking +pub fn prepare_dkg( + num_participants: usize, + threshold: ReconstructionLowerBound, + rng: &mut R, +) -> PreparedDkgPackage +where + threshold_signatures::Element: Send, + threshold_signatures::Scalar: Send, +{ + let participants = generate_participants_with_random_ids(num_participants, rng); + let mut protocols = Vec::with_capacity(num_participants); + + for p in &participants { + let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); + let protocol = keygen::(&participants, *p, threshold, rng_p) + .map(|p| Box::new(p) as Box>>) + .expect("Keygen should succeed"); + protocols.push((*p, protocol)); + } + + protocols +} + +pub type PreparedDkgPackage = Vec<(Participant, Box>>)>; diff --git a/crates/threshold-signatures/benches/ckd.rs b/crates/threshold-signatures/benches/ckd.rs index cf164a4ef..05e1fdc68 100644 --- a/crates/threshold-signatures/benches/ckd.rs +++ b/crates/threshold-signatures/benches/ckd.rs @@ -54,7 +54,7 @@ criterion_main!(benches); fn prepare_simulated_ckd(threshold: ReconstructionLowerBound) -> PreparedSimulatedCkd { let mut rng = MockCryptoRng::seed_from_u64(41); let preps = prepare_ckd(threshold, &mut rng); - let (_, protocolsnapshot) = run_protocol_and_take_snapshots(preps.protocols) + let (_, protocol_snapshot) = run_protocol_and_take_snapshots(preps.protocols) .expect("Running protocol with snapshot should not have issues"); let participants: Vec = preps @@ -79,7 +79,7 @@ fn prepare_simulated_ckd(threshold: ReconstructionLowerBound) -> PreparedSimulat // now preparing the simulator let simulated_protocol = - Simulator::new(real_participant, protocolsnapshot).expect("Simulator should not be empty"); + Simulator::new(real_participant, protocol_snapshot).expect("Simulator should not be empty"); PreparedSimulatedCkd { participant: real_participant, diff --git a/crates/threshold-signatures/docs/benches/model.md b/crates/threshold-signatures/docs/benches/results.md similarity index 100% rename from crates/threshold-signatures/docs/benches/model.md rename to crates/threshold-signatures/docs/benches/results.md diff --git a/crates/threshold-signatures/src/ecdsa/README.md b/crates/threshold-signatures/src/ecdsa/README.md index 5508436da..b6314667b 100644 --- a/crates/threshold-signatures/src/ecdsa/README.md +++ b/crates/threshold-signatures/src/ecdsa/README.md @@ -36,7 +36,7 @@ See [`robust_ecdsa/README.md`](robust_ecdsa/README.md) for details. | **Threshold parameter** | `ReconstructionLowerBound` | `MaxMalicious` | | **Scaling** | Less efficient with many participants | Better efficiency and bandwidth | -See the [benchmark analysis](../../docs/benches/model.md) for detailed performance comparisons. +See the [benchmark analysis](../../docs/benches/results.md) for detailed performance comparisons. ## DKG