Skip to content
5 changes: 5 additions & 0 deletions crates/threshold-signatures/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion crates/threshold-signatures/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
150 changes: 150 additions & 0 deletions crates/threshold-signatures/benches/advanced_dkg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#![allow(clippy::indexing_slicing)]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, fine for benches

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used though?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find it, but the clippy has the last word


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
}

Comment on lines +24 to +31
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm I think it would be nice to make these lazy loaded constants like MAX_MALICIOUS. But not super important.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

type PreparedSimulatedDkg<C> = PreparedOutputs<KeygenOutput<C>>;

/// 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::<Secp256K1Sha256>(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);
Comment on lines +35 to +56
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we could reuse code for all 3 implementations

}

/// 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::<Ed25519Sha512>(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::<BLS12381SHA256>(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 ******************************/
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of these comments. I find it nicer to create sub modules if there's a natural division of the code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will create a new issue for this. It's gonna make things simpler to review.

/// Used to simulate DKG keygen for benchmarking
fn prepare_simulated_dkg<C: Ciphersuite>(
threshold: ReconstructionLowerBound,
) -> PreparedSimulatedDkg<C>
where
Element<C>: Send,
Scalar<C>: Send,
{
let mut rng = MockCryptoRng::seed_from_u64(42);
let preps = prepare_dkg::<C, _>(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::<C>(&participants, real_participant, threshold, rng)
.map(|p| Box::new(p) as Box<dyn Protocol<Output = KeygenOutput<C>>>)
.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,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Participant> = preps
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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<Participant> = preps
Expand All @@ -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,
Expand Down
12 changes: 6 additions & 6 deletions crates/threshold-signatures/benches/advanced_ot_based_ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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,
Expand Down
8 changes: 4 additions & 4 deletions crates/threshold-signatures/benches/advanced_robust_ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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,
Expand Down
Loading
Loading