Skip to content

Commit f1bf9ba

Browse files
gilcu3nocktoshi
authored andcommitted
fix(bench): make sure benches do not fail by fixing determinism (near#3635)
1 parent 60f1e47 commit f1bf9ba

18 files changed

Lines changed: 159 additions & 75 deletions

crates/threshold-signatures/benches/advanced_dkg.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use rand_core::SeedableRng;
44

55
mod bench_utils;
66
use crate::bench_utils::{
7-
MAX_MALICIOUS, PreparedOutputs, SAMPLE_SIZE, analyze_received_sizes, prepare_dkg,
7+
MAX_MALICIOUS, PreparedOutputs, SAMPLE_SIZE, analyze_received_sizes, participant_rng,
8+
prepare_dkg,
89
};
910

1011
use threshold_signatures::{
@@ -48,7 +49,10 @@ where
4849
|b| {
4950
b.iter_batched(
5051
|| prepare_simulated_dkg::<C>(&setup, threshold()),
51-
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator),
52+
|preps| {
53+
run_simulated_protocol(preps.participant, preps.protocol, preps.simulator)
54+
.expect("simulated replay should complete")
55+
},
5256
criterion::BatchSize::SmallInput,
5357
);
5458
},
@@ -89,22 +93,26 @@ where
8993
{
9094
let mut rng = MockCryptoRng::seed_from_u64(42);
9195
let preps = prepare_dkg::<C, _>(participants_num(), threshold, &mut rng);
92-
let participants: Vec<_> = preps.iter().map(|(p, _)| *p).collect();
93-
let (_, protocol_snapshot) = run_protocol_and_take_snapshots(preps)
96+
let participants: Vec<_> = preps.protocols.iter().map(|(p, _)| *p).collect();
97+
let seeds = preps.seeds;
98+
let (_, protocol_snapshot) = run_protocol_and_take_snapshots(preps.protocols)
9499
.expect("Running protocol with snapshot should not have issues");
95100

96101
// choose the real_participant at random
97102
let real_participant = *participants
98103
.choose(&mut rng)
99104
.expect("participant list is not empty");
100105

106+
// rebuild the exact rng the real participant used during snapshot capture
107+
let rng_for_protocol = participant_rng(&seeds, real_participant);
108+
101109
let cached_simulator = Simulator::new(real_participant, &protocol_snapshot)
102110
.expect("Simulator should not be empty");
103111

104112
DkgSetup {
105113
participants,
106114
real_participant,
107-
rng_for_protocol: rng,
115+
rng_for_protocol,
108116
cached_simulator,
109117
}
110118
}

crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v1.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use rand_core::SeedableRng;
66
mod bench_utils;
77
use crate::bench_utils::{
88
MAX_MALICIOUS, PreparedOutputs, RECONSTRUCTION_LOWER_BOUND, SAMPLE_SIZE,
9-
analyze_received_sizes, ed25519_prepare_sign_v1,
9+
analyze_received_sizes, ed25519_prepare_sign_v1, participant_rng,
1010
};
1111
use threshold_signatures::{
1212
ReconstructionThreshold,
@@ -35,7 +35,10 @@ fn bench_sign(c: &mut Criterion) {
3535
|b| {
3636
b.iter_batched(
3737
|| prepare_simulated_sign(&setup, *RECONSTRUCTION_LOWER_BOUND),
38-
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator),
38+
|preps| {
39+
run_simulated_protocol(preps.participant, preps.protocol, preps.simulator)
40+
.expect("simulated replay should complete")
41+
},
3942
criterion::BatchSize::SmallInput,
4043
);
4144
},
@@ -71,6 +74,9 @@ fn setup_sign_snapshot(threshold: ReconstructionThreshold) -> SignSetup {
7174
// choose the real_participant being the coordinator
7275
let (real_participant, keygen_out) = preps.key_packages[preps.index].clone();
7376

77+
// rebuild the exact rng the real participant used during snapshot capture
78+
let rng_for_protocol = participant_rng(&preps.seeds, real_participant);
79+
7480
let cached_simulator = Simulator::new(real_participant, &protocol_snapshot)
7581
.expect("Simulator should not be empty");
7682

@@ -79,7 +85,7 @@ fn setup_sign_snapshot(threshold: ReconstructionThreshold) -> SignSetup {
7985
real_participant,
8086
keygen_out,
8187
message: preps.message,
82-
rng_for_protocol: rng,
88+
rng_for_protocol,
8389
cached_simulator,
8490
}
8591
}

crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v2.rs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
#![allow(clippy::indexing_slicing)]
22

33
use criterion::{Criterion, criterion_group, criterion_main};
4-
use rand::{Rng, RngCore};
4+
use rand::Rng;
55
use rand_core::SeedableRng;
66

77
mod bench_utils;
88
use crate::bench_utils::{
99
MAX_MALICIOUS, PreparedOutputs, RECONSTRUCTION_LOWER_BOUND, SAMPLE_SIZE,
10-
analyze_received_sizes, ed25519_prepare_presign, ed25519_prepare_sign_v2,
10+
analyze_received_sizes, ed25519_prepare_presign, ed25519_prepare_sign_v2, participant_rng,
1111
};
1212
use threshold_signatures::{
1313
ReconstructionThreshold,
@@ -40,7 +40,10 @@ fn bench_presign(c: &mut Criterion) {
4040
|b| {
4141
b.iter_batched(
4242
|| prepare_simulate_presign(&setup),
43-
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator),
43+
|preps| {
44+
run_simulated_protocol(preps.participant, preps.protocol, preps.simulator)
45+
.expect("simulated replay should complete")
46+
},
4447
criterion::BatchSize::SmallInput,
4548
);
4649
},
@@ -63,7 +66,10 @@ fn bench_sign(c: &mut Criterion) {
6366
|b| {
6467
b.iter_batched(
6568
|| prepare_simulated_sign(&setup, *RECONSTRUCTION_LOWER_BOUND),
66-
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator),
69+
|preps| {
70+
run_simulated_protocol(preps.participant, preps.protocol, preps.simulator)
71+
.expect("simulated replay should complete")
72+
},
6773
criterion::BatchSize::SmallInput,
6874
);
6975
},
@@ -95,16 +101,8 @@ fn setup_presign_snapshot(num_participants: usize) -> PresignSetup {
95101
let index_real_participant = rng.gen_range(0..num_participants);
96102
let (real_participant, keygen_out) = preps.key_packages[index_real_participant].clone();
97103

98-
// recreate rng using by real_participant to generate presignatures
99-
let mut real_participant_rng = MockCryptoRng::seed_from_u64(42);
100-
for (i, _) in preps.key_packages.iter().enumerate() {
101-
let seed = real_participant_rng.next_u64();
102-
103-
if i == index_real_participant {
104-
real_participant_rng = MockCryptoRng::seed_from_u64(seed);
105-
break;
106-
}
107-
}
104+
// rebuild the exact rng the real participant used during snapshot capture
105+
let real_participant_rng = participant_rng(&preps.seeds, real_participant);
108106

109107
let cached_simulator = Simulator::new(real_participant, &protocol_snapshot)
110108
.expect("Simulator should not be empty");

crates/threshold-signatures/benches/advanced_ot_based_ecdsa.rs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33

44
use criterion::{Criterion, criterion_group, criterion_main};
55
use frost_secp256k1::VerifyingKey;
6-
use rand::{Rng, RngCore, seq::SliceRandom as _};
6+
use rand::{Rng, seq::SliceRandom as _};
77
use rand_core::SeedableRng;
88

99
mod bench_utils;
1010
use crate::bench_utils::{
1111
MAX_MALICIOUS, PreparedOutputs, RECONSTRUCTION_LOWER_BOUND, SAMPLE_SIZE,
1212
analyze_received_sizes, ot_ecdsa_prepare_presign, ot_ecdsa_prepare_sign,
13-
ot_ecdsa_prepare_triples,
13+
ot_ecdsa_prepare_triples, participant_rng,
1414
};
1515

1616
use threshold_signatures::{
@@ -58,7 +58,10 @@ fn bench_triples(c: &mut Criterion) {
5858
|b| {
5959
b.iter_batched(
6060
|| prepare_simulated_triples(&setup),
61-
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator),
61+
|preps| {
62+
run_simulated_protocol(preps.participant, preps.protocol, preps.simulator)
63+
.expect("simulated replay should complete")
64+
},
6265
criterion::BatchSize::SmallInput,
6366
);
6467
},
@@ -86,7 +89,10 @@ fn bench_presign(c: &mut Criterion) {
8689
|b| {
8790
b.iter_batched(
8891
|| prepare_simulated_presign(&setup),
89-
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator),
92+
|preps| {
93+
run_simulated_protocol(preps.participant, preps.protocol, preps.simulator)
94+
.expect("simulated replay should complete")
95+
},
9096
criterion::BatchSize::SmallInput,
9197
);
9298
},
@@ -117,7 +123,10 @@ fn bench_sign(c: &mut Criterion) {
117123
|b| {
118124
b.iter_batched(
119125
|| prepare_simulated_sign(&setup, *RECONSTRUCTION_LOWER_BOUND),
120-
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator),
126+
|preps| {
127+
run_simulated_protocol(preps.participant, preps.protocol, preps.simulator)
128+
.expect("simulated replay should complete")
129+
},
121130
criterion::BatchSize::SmallInput,
122131
);
123132
},
@@ -149,15 +158,8 @@ fn setup_triples_snapshot(participant_num: usize) -> TriplesSetup {
149158
.choose(&mut rng)
150159
.expect("participant list is not empty");
151160

152-
// recreate rng using by real_participant to generate triples
153-
let mut rng_copy = MockCryptoRng::seed_from_u64(42);
154-
for p in &preps.participants {
155-
if *p == real_participant {
156-
break;
157-
}
158-
rng_copy.next_u64();
159-
}
160-
let real_participant_rng = MockCryptoRng::seed_from_u64(rng_copy.next_u64());
161+
// rebuild the exact rng the real participant used during snapshot capture
162+
let real_participant_rng = participant_rng(&preps.seeds, real_participant);
161163

162164
let cached_simulator = Simulator::new(real_participant, &protocol_snapshot)
163165
.expect("Simulator should not be empty");

crates/threshold-signatures/benches/advanced_robust_ecdsa.rs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
use criterion::{Criterion, criterion_group, criterion_main};
44
use frost_secp256k1::VerifyingKey;
5-
use rand::{RngCore, seq::SliceRandom as _};
5+
use rand::seq::SliceRandom as _;
66
use rand_core::SeedableRng;
77

88
mod bench_utils;
99
use crate::bench_utils::{
10-
MAX_MALICIOUS, PreparedOutputs, SAMPLE_SIZE, analyze_received_sizes,
10+
MAX_MALICIOUS, PreparedOutputs, SAMPLE_SIZE, analyze_received_sizes, participant_rng,
1111
robust_ecdsa_prepare_presign, robust_ecdsa_prepare_sign,
1212
};
1313
use threshold_signatures::{
@@ -51,7 +51,10 @@ fn bench_presign(c: &mut Criterion) {
5151
|b| {
5252
b.iter_batched(
5353
|| prepare_simulate_presign(&setup),
54-
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator),
54+
|preps| {
55+
run_simulated_protocol(preps.participant, preps.protocol, preps.simulator)
56+
.expect("simulated replay should complete")
57+
},
5558
criterion::BatchSize::SmallInput,
5659
);
5760
},
@@ -79,7 +82,10 @@ fn bench_sign(c: &mut Criterion) {
7982
|b| {
8083
b.iter_batched(
8184
|| prepare_simulated_sign(&setup, max_malicious),
82-
|preps| run_simulated_protocol(preps.participant, preps.protocol, preps.simulator),
85+
|preps| {
86+
run_simulated_protocol(preps.participant, preps.protocol, preps.simulator)
87+
.expect("simulated replay should complete")
88+
},
8389
criterion::BatchSize::SmallInput,
8490
);
8591
},
@@ -114,15 +120,8 @@ fn setup_presign_snapshot(num_participants: usize) -> PresignSetup {
114120
.expect("participant list is not empty")
115121
.clone();
116122

117-
// recreate rng using by real_participant to generate presignatures
118-
let mut rng_copy = MockCryptoRng::seed_from_u64(42);
119-
for p in &preps.participants {
120-
if *p == real_participant {
121-
break;
122-
}
123-
rng_copy.next_u64();
124-
}
125-
let real_participant_rng = MockCryptoRng::seed_from_u64(rng_copy.next_u64());
123+
// rebuild the exact rng the real participant used during snapshot capture
124+
let real_participant_rng = participant_rng(&preps.seeds, real_participant);
126125

127126
let cached_simulator = Simulator::new(real_participant, &protocol_snapshot)
128127
.expect("Simulator should not be empty");

crates/threshold-signatures/benches/bench_utils.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,29 @@ pub use robust_ecdsa::*;
2424

2525
use average::{Estimate, Quantile, Variance};
2626
use k256::AffinePoint;
27-
use std::{env, sync::LazyLock};
27+
use std::{collections::HashMap, env, sync::LazyLock};
2828

29+
use rand_core::SeedableRng;
2930
use threshold_signatures::{
3031
ReconstructionThreshold,
3132
ecdsa::{self, Scalar},
3233
participants::Participant,
3334
protocol::Protocol,
34-
test_utils::Simulator,
35+
test_utils::{MockCryptoRng, Simulator},
3536
};
3637

38+
/// Rebuilds the RNG a participant's protocol was seeded with during snapshot
39+
/// capture, so the simulated replay reproduces the exact recorded run.
40+
pub fn participant_rng<S: std::hash::BuildHasher>(
41+
seeds: &HashMap<Participant, u64, S>,
42+
participant: Participant,
43+
) -> MockCryptoRng {
44+
let seed = *seeds
45+
.get(&participant)
46+
.expect("participant must have a recorded seed");
47+
MockCryptoRng::seed_from_u64(seed)
48+
}
49+
3750
// fix malicious number of participants
3851
pub static MAX_MALICIOUS: LazyLock<usize> = std::sync::LazyLock::new(|| {
3952
env::var("MAX_MALICIOUS")
@@ -63,6 +76,9 @@ pub struct PreparedPresig<PresignOutput, KeygenOutput> {
6376
pub protocols: Vec<(Participant, Box<dyn Protocol<Output = PresignOutput>>)>,
6477
pub key_packages: Vec<(Participant, KeygenOutput)>,
6578
pub participants: Vec<Participant>,
79+
/// Per-participant RNG seed used to build each presign protocol; empty when
80+
/// the protocol is built from deterministic inputs.
81+
pub seeds: HashMap<Participant, u64>,
6682
}
6783

6884
pub struct PreparedSig<RerandomizedPresignOutput> {

crates/threshold-signatures/benches/bench_utils/ckd.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::HashMap;
2+
13
use rand::Rng;
24
use rand_core::{CryptoRngCore, SeedableRng};
35

@@ -28,6 +30,7 @@ pub fn prepare_ckd<R: CryptoRngCore + SeedableRng + Send + 'static>(
2830
let coordinator = participants[coordinator_index];
2931

3032
let mut protocols = Vec::with_capacity(participants.len());
33+
let mut seeds = HashMap::with_capacity(participants.len());
3134

3235
let mut app_id: [u8; 32] = [0u8; 32];
3336
rng.fill_bytes(&mut app_id);
@@ -38,7 +41,8 @@ pub fn prepare_ckd<R: CryptoRngCore + SeedableRng + Send + 'static>(
3841
let app_pk = ckd::ElementG1::generator() * app_sk;
3942

4043
for (p, keygen_out) in &key_packages {
41-
let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64());
44+
let seed = rng.next_u64();
45+
let rng_p = MockCryptoRng::seed_from_u64(seed);
4246
let protocol = ckd::protocol::ckd(
4347
&participants,
4448
coordinator,
@@ -51,6 +55,7 @@ pub fn prepare_ckd<R: CryptoRngCore + SeedableRng + Send + 'static>(
5155
.map(|ckd| Box::new(ckd) as Box<dyn Protocol<Output = ckd::CKDOutputOption>>)
5256
.expect("Ckd should succeed");
5357
protocols.push((*p, protocol));
58+
seeds.insert(*p, seed);
5459
}
5560

5661
PreparedCkdPackage {
@@ -59,6 +64,7 @@ pub fn prepare_ckd<R: CryptoRngCore + SeedableRng + Send + 'static>(
5964
key_packages,
6065
app_id,
6166
app_pk,
67+
seeds,
6268
}
6369
}
6470

@@ -71,4 +77,5 @@ pub struct PreparedCkdPackage {
7177
pub key_packages: Vec<(Participant, ckd::KeygenOutput)>,
7278
pub app_id: ckd::AppId,
7379
pub app_pk: ckd::ElementG1,
80+
pub seeds: HashMap<Participant, u64>,
7481
}

0 commit comments

Comments
 (0)