Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
edf21ec
Adding test_utils as common.rs
SimonRastikian Nov 11, 2025
ac58781
adding common in dkg
SimonRastikian Nov 11, 2025
f8966a2
Pushing function ecdsa_generate_rerandpresig_args to common.rs
SimonRastikian Nov 11, 2025
d35d317
Isolating MockCryptoRnG
SimonRastikian Nov 11, 2025
11db1da
Adding the run protocol file
SimonRastikian Nov 11, 2025
5bcd605
Separating DKG
SimonRastikian Nov 11, 2025
47f4fed
test_utils/sign.rs
SimonRastikian Nov 11, 2025
fab6283
Presign for robust ECDSA
SimonRastikian Nov 11, 2025
a685499
integrating ecdsa rerandomized args in more test cases
SimonRastikian Nov 11, 2025
63c02be
Switching function calls to test_utils/participants.rs
SimonRastikian Nov 11, 2025
4bee396
cargo fmt and clippy
SimonRastikian Nov 11, 2025
f7a34c0
Snapshotting all
SimonRastikian Nov 11, 2025
3720e8f
Cargo fmt
SimonRastikian Nov 11, 2025
358354d
Dumping a lot of code of simulation
SimonRastikian Nov 13, 2025
1faa085
Cloning given to rng
SimonRastikian Nov 13, 2025
4579ade
Some extra cleanups
SimonRastikian Nov 17, 2025
4d6c869
Adding signing benchmarking
SimonRastikian Nov 17, 2025
d14256d
Advanced Simulated Signing for Robust ECDSA
SimonRastikian Nov 17, 2025
cd35a52
cargo fmt and cargo clippy
SimonRastikian Nov 18, 2025
8b73048
Modifying a couple of functions and cleaning them up for benchmarking
SimonRastikian Nov 18, 2025
2c906ef
Cleaning up code for advanced benchmarking
SimonRastikian Nov 18, 2025
2795722
Improving code for prepare triples
SimonRastikian Nov 18, 2025
2e6172d
Bench simulated ot based presign cleanups
SimonRastikian Nov 18, 2025
fedaae3
Cleaning advanced signing ot ECDSA
SimonRastikian Nov 18, 2025
5ba73f8
Cargo fmt
SimonRastikian Nov 18, 2025
e6942f8
Fixing the triple generation in advanced benchmarks
SimonRastikian Nov 18, 2025
3022772
Naive -> Advanced
SimonRastikian Nov 18, 2025
73144e7
Merging with main
SimonRastikian Nov 27, 2025
d90429c
sort by key
SimonRastikian Dec 1, 2025
70bb1f9
Modifying snaps
SimonRastikian Dec 1, 2025
a745950
Benchmarks with latency
SimonRastikian Dec 1, 2025
224cb01
Solving Conflict
SimonRastikian Dec 7, 2025
1d91be3
Assertion line removed
SimonRastikian Dec 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 21 additions & 18 deletions benches/advanced_ot_based_ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use rand_core::SeedableRng;

mod bench_utils;
use crate::bench_utils::{
ot_ecdsa_prepare_presign, ot_ecdsa_prepare_sign, ot_ecdsa_prepare_triples, PreparedOutputs,
MAX_MALICIOUS,
ot_ecdsa_prepare_presign, ot_ecdsa_prepare_sign, ot_ecdsa_prepare_triples,
run_simulated_protocol, PreparedOutputs, LATENCY, MAX_MALICIOUS, SAMPLE_SIZE,
};

use threshold_signatures::{
Expand All @@ -21,10 +21,7 @@ use threshold_signatures::{
},
participants::Participant,
protocol::Protocol,
test_utils::{
run_protocol, run_protocol_and_take_snapshots, run_simulated_protocol, MockCryptoRng,
Simulator,
},
test_utils::{run_protocol, run_protocol_and_take_snapshots, MockCryptoRng, Simulator},
};

type PreparedSimulatedTriples = PreparedOutputs<Vec<(TripleShare, TriplePub)>>;
Expand All @@ -43,15 +40,18 @@ fn participants_num() -> usize {
fn bench_triples(c: &mut Criterion) {
let num = participants_num();
let max_malicious = *MAX_MALICIOUS;
let latency = *LATENCY;
let rounds = 8;

let mut group = c.benchmark_group("triples");
group.measurement_time(std::time::Duration::from_secs(200));
group.sample_size(*SAMPLE_SIZE);

group.bench_function(
format!("ot_ecdsa_triples_advanced_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}"),
format!("ot_ecdsa_triples_advanced_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}_LATENCY_{latency}"),
|b| {
b.iter_batched(
|| prepare_simulated_triples(num),
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator),
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator, rounds),
criterion::BatchSize::SmallInput,
);
},
Expand All @@ -62,20 +62,22 @@ fn bench_triples(c: &mut Criterion) {
fn bench_presign(c: &mut Criterion) {
let num = participants_num();
let max_malicious = *MAX_MALICIOUS;
let mut group = c.benchmark_group("presign");
group.measurement_time(std::time::Duration::from_secs(300));
let latency = *LATENCY;
let rounds = 2;

let mut rng = MockCryptoRng::seed_from_u64(42);
let preps = ot_ecdsa_prepare_triples(num, threshold(), &mut rng);
let two_triples =
run_protocol(preps.protocols).expect("Running triple preparations should succeed");

let mut group = c.benchmark_group("presign");
group.sample_size(*SAMPLE_SIZE);
group.bench_function(
format!("ot_ecdsa_presign_advanced_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}"),
format!("ot_ecdsa_presign_advanced_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}_LATENCY_{latency}"),
|b| {
b.iter_batched(
|| prepare_simulated_presign(&two_triples),
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator),
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator, rounds),
criterion::BatchSize::SmallInput,
);
},
Expand All @@ -86,9 +88,8 @@ fn bench_presign(c: &mut Criterion) {
fn bench_sign(c: &mut Criterion) {
let num = participants_num();
let max_malicious = *MAX_MALICIOUS;

let mut group = c.benchmark_group("sign");
group.measurement_time(std::time::Duration::from_secs(300));
let latency = *LATENCY;
let rounds = 1;

let mut rng = MockCryptoRng::seed_from_u64(42);
let preps = ot_ecdsa_prepare_triples(num, threshold(), &mut rng);
Expand All @@ -99,12 +100,14 @@ fn bench_sign(c: &mut Criterion) {
let result = run_protocol(preps.protocols).expect("Running presign preparation should succeed");
let pk = preps.key_packages[0].1.public_key;

let mut group = c.benchmark_group("sign");
group.sample_size(*SAMPLE_SIZE);
group.bench_function(
format!("ot_ecdsa_sign_advanced_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}"),
format!("ot_ecdsa_sign_advanced_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}_LATENCY_{latency}"),
|b| {
b.iter_batched(
|| prepare_simulated_sign(&result, pk),
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator),
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator, rounds),
criterion::BatchSize::SmallInput,
);
},
Expand Down
28 changes: 15 additions & 13 deletions benches/advanced_robust_ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,17 @@ use rand_core::SeedableRng;

mod bench_utils;
use crate::bench_utils::{
robust_ecdsa_prepare_presign, robust_ecdsa_prepare_sign, PreparedOutputs, MAX_MALICIOUS,
robust_ecdsa_prepare_presign, robust_ecdsa_prepare_sign, run_simulated_protocol,
PreparedOutputs, LATENCY, MAX_MALICIOUS, SAMPLE_SIZE,
};

use threshold_signatures::{
ecdsa::{
robust_ecdsa::{presign::presign, sign::sign, PresignArguments, PresignOutput},
SignatureOption,
},
participants::Participant,
protocol::Protocol,
test_utils::{
run_protocol, run_protocol_and_take_snapshots, run_simulated_protocol, MockCryptoRng,
Simulator,
},
test_utils::{run_protocol, run_protocol_and_take_snapshots, MockCryptoRng, Simulator},
};

type PreparedPresig = PreparedOutputs<PresignOutput>;
Expand All @@ -32,14 +29,17 @@ fn participants_num() -> usize {
fn bench_presign(c: &mut Criterion) {
let num = participants_num();
let max_malicious = *MAX_MALICIOUS;
let latency = *LATENCY;
let rounds = 3;

let mut group = c.benchmark_group("presign");
group.measurement_time(std::time::Duration::from_secs(300));
group.sample_size(*SAMPLE_SIZE);
group.bench_function(
format!("robust_ecdsa_presign_advanced_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}"),
format!("robust_ecdsa_presign_advanced_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}_LATENCY_{latency}"),
|b| {
b.iter_batched(
|| prepare_simulate_presign(num),
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator),
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator, rounds),
criterion::BatchSize::SmallInput,
);
},
Expand All @@ -50,20 +50,22 @@ fn bench_presign(c: &mut Criterion) {
fn bench_sign(c: &mut Criterion) {
let num = participants_num();
let max_malicious = *MAX_MALICIOUS;
let mut group = c.benchmark_group("sign");
group.measurement_time(std::time::Duration::from_secs(300));
let latency = *LATENCY;
let rounds = 1;

let mut rng = MockCryptoRng::seed_from_u64(42);
let preps = robust_ecdsa_prepare_presign(num, &mut rng);
let result = run_protocol(preps.protocols).expect("Prepare sign should not");
let pk = preps.key_packages[0].1.public_key;

let mut group = c.benchmark_group("sign");
group.sample_size(*SAMPLE_SIZE);
group.bench_function(
format!("robust_ecdsa_sign_advanced_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}"),
format!("robust_ecdsa_sign_advanced_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}_LATENCY_{latency}"),
|b| {
b.iter_batched(
|| prepare_simulated_sign(&result, pk),
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator),
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator, rounds),
criterion::BatchSize::SmallInput,
);
},
Expand Down
30 changes: 30 additions & 0 deletions benches/bench_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,36 @@ pub static MAX_MALICIOUS: LazyLock<usize> = std::sync::LazyLock::new(|| {
.unwrap_or(6)
});

// fix malicious number of participants
pub static LATENCY: LazyLock<u64> = std::sync::LazyLock::new(|| {
env::var("LATENCY")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(0)
});

// fix malicious number of participants
pub static SAMPLE_SIZE: LazyLock<usize> = std::sync::LazyLock::new(|| {
env::var("SAMPLE_SIZE")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(15)
});

pub fn run_simulated_protocol<T>(
rparticipant: Participant,
rprot: Box<dyn Protocol<Output = T>>,
sprot: threshold_signatures::test_utils::Simulator,
rounds_num: u64,
) -> Result<T, threshold_signatures::errors::ProtocolError> {
threshold_signatures::test_utils::run_simulated_protocol::<T>(
rparticipant,
rprot,
sprot,
*LATENCY,
rounds_num,
)
}
/// This helps defining a generic type for the benchmarks prepared outputs
pub struct PreparedOutputs<T> {
pub participant: Participant,
Expand Down
22 changes: 12 additions & 10 deletions benches/naive_ot_based_ecdsa.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use criterion::{criterion_group, Criterion};
mod bench_utils;
use crate::bench_utils::{
ot_ecdsa_prepare_presign, ot_ecdsa_prepare_sign, ot_ecdsa_prepare_triples, MAX_MALICIOUS,
ot_ecdsa_prepare_presign, ot_ecdsa_prepare_sign, ot_ecdsa_prepare_triples, LATENCY,
MAX_MALICIOUS, SAMPLE_SIZE,
};
use rand_core::SeedableRng;
use threshold_signatures::test_utils::{run_protocol, MockCryptoRng};
Expand All @@ -18,12 +19,13 @@ fn participants_num() -> usize {
fn bench_triples(c: &mut Criterion) {
let mut rng = MockCryptoRng::seed_from_u64(42);
let num = participants_num();
let latency = *LATENCY;
let max_malicious = *MAX_MALICIOUS;
let mut group = c.benchmark_group("triples");
group.measurement_time(std::time::Duration::from_secs(200));

let mut group = c.benchmark_group("triples");
group.sample_size(*SAMPLE_SIZE);
group.bench_function(
format!("ot_ecdsa_triples_naive_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}"),
format!("ot_ecdsa_triples_naive_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}_LATENCY_{latency}"),
|b| {
b.iter_batched(
|| ot_ecdsa_prepare_triples(participants_num(), threshold(), &mut rng),
Expand All @@ -38,16 +40,17 @@ fn bench_triples(c: &mut Criterion) {
fn bench_presign(c: &mut Criterion) {
let mut rng = MockCryptoRng::seed_from_u64(42);
let num = participants_num();
let latency = *LATENCY;
let max_malicious = *MAX_MALICIOUS;
let mut group = c.benchmark_group("presign");
group.measurement_time(std::time::Duration::from_secs(300));

let preps = ot_ecdsa_prepare_triples(participants_num(), threshold(), &mut rng);
let two_triples =
run_protocol(preps.protocols).expect("Running triple preparations should succeed");

let mut group = c.benchmark_group("presign");
group.sample_size(*SAMPLE_SIZE);
group.bench_function(
format!("ot_ecdsa_presign_naive_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}"),
format!("ot_ecdsa_presign_naive_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}_LATENCY_{latency}"),
|b| {
b.iter_batched(
|| ot_ecdsa_prepare_presign(&two_triples, threshold(), &mut rng),
Expand All @@ -64,9 +67,6 @@ fn bench_sign(c: &mut Criterion) {
let num = participants_num();
let max_malicious = *MAX_MALICIOUS;

let mut group = c.benchmark_group("sign");
group.measurement_time(std::time::Duration::from_secs(300));

let preps = ot_ecdsa_prepare_triples(participants_num(), threshold(), &mut rng);
let two_triples =
run_protocol(preps.protocols).expect("Running triples preparation should succeed");
Expand All @@ -75,6 +75,8 @@ fn bench_sign(c: &mut Criterion) {
let pk = preps.key_packages[0].1.public_key;
let result = run_protocol(preps.protocols).expect("Running presign preparation should succeed");

let mut group = c.benchmark_group("sign");
group.sample_size(*SAMPLE_SIZE);
group.bench_function(
format!("ot_ecdsa_sign_naive_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}"),
|b| {
Expand Down
15 changes: 10 additions & 5 deletions benches/naive_robust_ecdsa.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use criterion::{criterion_group, Criterion};
mod bench_utils;
use crate::bench_utils::{robust_ecdsa_prepare_presign, robust_ecdsa_prepare_sign, MAX_MALICIOUS};
use crate::bench_utils::{
robust_ecdsa_prepare_presign, robust_ecdsa_prepare_sign, LATENCY, MAX_MALICIOUS, SAMPLE_SIZE,
};
use rand_core::SeedableRng;
use threshold_signatures::test_utils::{run_protocol, MockCryptoRng};

Expand All @@ -12,11 +14,13 @@ fn participants_num() -> usize {
fn bench_presign(c: &mut Criterion) {
let mut rng = MockCryptoRng::seed_from_u64(42);
let num = participants_num();
let latency = *LATENCY;
let max_malicious = *MAX_MALICIOUS;

let mut group = c.benchmark_group("presign");
group.measurement_time(std::time::Duration::from_secs(300));
group.sample_size(*SAMPLE_SIZE);
group.bench_function(
format!("robust_ecdsa_presign_naive_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}"),
format!("robust_ecdsa_presign_naive_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}_LATENCY_{latency}"),
|b| {
b.iter_batched(
|| robust_ecdsa_prepare_presign(num, &mut rng),
Expand All @@ -31,16 +35,17 @@ fn bench_presign(c: &mut Criterion) {
fn bench_sign(c: &mut Criterion) {
let mut rng = MockCryptoRng::seed_from_u64(42);
let num = participants_num();
let latency = *LATENCY;
let max_malicious = *MAX_MALICIOUS;
let mut group = c.benchmark_group("sign");
group.measurement_time(std::time::Duration::from_secs(300));
group.sample_size(*SAMPLE_SIZE);

let preps = robust_ecdsa_prepare_presign(num, &mut rng);
let result = run_protocol(preps.protocols).expect("Prepare sign should not");
let pk = preps.key_packages[0].1.public_key;

group.bench_function(
format!("robust_ecdsa_sign_naive_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}"),
format!("robust_ecdsa_sign_naive_MAX_MALICIOUS_{max_malicious}_PARTICIPANTS_{num}_LATENCY_{latency}"),
Comment on lines +38 to +48
Copy link
Contributor

Choose a reason for hiding this comment

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

Before accepting or refusing this change I need to know what was the idea to include it. Before we had a constant time duration for the benchmark, and now we are letting the users set the sample size. As the parameter input method we use are not great (we are forced to use env variables by criterion), then I would prefer to remain as before (const total time) or move to a constant number of samples, or use criterion's default, instead of making the implementation more verbose.

If we have a good reason to add sample size let me know.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Previously, the benchmark was not constant time. The time we fixed is an estimation. The code would automatically readjust the time it needs if the numbers we gave were smaller than what it actually needs.
Now why changing this? The benchmarkings were taking 40 or 45 min to run 100 iterations each. This was too wasteful for time and resources. Now the user can fix the number of iterations. For large protocols (like the one we have), it is absolutely acceptable to have 15 iterations as the bench numbers are quite exact (we talk of hundreds of milliseconds).

Copy link
Contributor

Choose a reason for hiding this comment

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

I see, I thought the previous group.measurement_time(std::time::Duration::from_secs(300)); referred to maximum allowed total time. From criterion docs:

pub fn measurement_time(&mut self, dur: Duration) -> &mut Self

Criterion will attempt to spent approximately this amount of time measuring each benchmark on a best-effort 
basis. If it is not possible to perform the measurement in the requested time (eg. because each iteration of the 
benchmark is long) then Criterion will spend as long as is needed to collect the desired number of samples. With 
a longer time, the measurement will become more resilient to interference from other programs.

so why instead we put a constant, say 15 as you mention, for the benchmarks that we know otherwise would take too long with the default value, and leave the rest as is? I cannot think of a user that would want a different amount of samples, and worst case they could simply fork, modify and run.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you mean by leave the rest as is? If you're talking about simpler benches like scalar inversion or scalar multiplication, 15 iterations may lead to large variance.
For the rest, it is still giving the choice for the user to decide. A dummy example is me using it with lower number of sampling when I want to debug the bench XD

Copy link
Contributor

Choose a reason for hiding this comment

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

A dummy example is me using it with lower number of sampling when I want to debug the bench XD

Yeah I guess this is really a shortcoming of criterion, not allowing to set the sample size in the command line, which for debugging purposes would make total sense.

EDIT: found a criterion option exactly for this:

cargo bench --profile release -- --profile-time 5 triple

see the docs https://criterion-rs.github.io/book/user_guide/profiling.html?highlight=profile-time#--profile-time, it does exactly what you need if you want to run the benchmark for a short period of time during debug.

I think for general users that will not modify the code, we should just assign proper number of samples/max time per benchmark, as it will certainly differ depending on it

|b| {
b.iter_batched(
|| robust_ecdsa_prepare_sign(&result, pk, &mut rng),
Expand Down
6 changes: 6 additions & 0 deletions src/test_utils/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,16 @@ pub fn run_two_party_protocol<T0: std::fmt::Debug, T1: std::fmt::Debug>(

/// Runs one real participant and one simulation representing the rest of participants
/// The simulation has an internal storage of what to send to the real participant
///
/// Accepts a network latency in milliseconds say 100ms
pub fn run_simulated_protocol<T>(
real_participant: Participant,
mut real_prot: Box<dyn Protocol<Output = T>>,
simulator: Simulator,
network_latency: u64,
round_number: u64,
) -> Result<T, ProtocolError> {
let duration = std::time::Duration::from_millis(network_latency * round_number);
if simulator.real_participant() != real_participant {
return Err(ProtocolError::AssertionFailed(
"The given real participant does not match the simulator's internal real participant"
Expand All @@ -193,5 +198,6 @@ pub fn run_simulated_protocol<T>(
out = Some(output);
}
}
std::thread::sleep(duration);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we sleeping after running the protocol? I was under the impression that we wanted to inject latency in the networking layer, i.e. add a delay between sending and receiving messages.

In what sense does this help us simulate network latency?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In a prior implementation, I tried injecting latency in the networking layer, but this was implying very weird numbers in the triple generation.
Chelsea's idea was to do as done right now to allow some simulation of the latency.
I am still not 100% convinced either and thus I was thinking of investigating more: see #247.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I suggest we merge this version of latency implementation as a simulation. Once I have a clear idea and I hope a better solution than what has been implemented earlier, then I can have a new PR.

Note that the previous implementation was giving good numbers for presign and sign but not for triple gen. So I still prefer to have something right but simulated instead of something not fully right but "real"

out.ok_or_else(|| ProtocolError::Other("out is None".to_string()))
}