From a6442a95cb8ecf80bf0b94c78546d56016cf2e73 Mon Sep 17 00:00:00 2001 From: SimonRastikian <43679791+SimonRastikian@users.noreply.github.com> Date: Thu, 26 Mar 2026 09:18:48 +0100 Subject: [PATCH 1/8] DKG benchmarks implementations --- crates/threshold-signatures/Cargo.toml | 5 + .../benches/advanced_dkg.rs | 150 ++++++++++++++++++ .../benches/bench_utils.rs | 37 +++++ 3 files changed, 192 insertions(+) create mode 100644 crates/threshold-signatures/benches/advanced_dkg.rs 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/benches/advanced_dkg.rs b/crates/threshold-signatures/benches/advanced_dkg.rs new file mode 100644 index 000000000..d87def372 --- /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, Ciphersuite, Element, KeygenOutput, Scalar, + protocol::Protocol, + test_utils::{ + run_protocol_and_take_snapshots, run_simulated_protocol, MockCryptoRng, Simulator, + }, + ReconstructionLowerBound, +}; + +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 (_, protocolsnapshot) = run_protocol_and_take_snapshots(preps.protocols) + .expect("Running protocol with snapshot should not have issues"); + + // choose the real_participant at random + let real_participant = *preps + .participants + .choose(&mut rng) + .expect("participant list is not empty"); + + let real_protocol = keygen::( + &preps.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, protocolsnapshot).expect("Simulator should not be empty"); + + PreparedSimulatedDkg { + participant: real_participant, + protocol: real_protocol, + simulator: simulated_protocol, + } +} \ No newline at end of file diff --git a/crates/threshold-signatures/benches/bench_utils.rs b/crates/threshold-signatures/benches/bench_utils.rs index 0ddcbc84e..68a0df1fb 100644 --- a/crates/threshold-signatures/benches/bench_utils.rs +++ b/crates/threshold-signatures/benches/bench_utils.rs @@ -8,6 +8,7 @@ use rand_core::{CryptoRngCore, SeedableRng}; use std::{env, sync::LazyLock}; use threshold_signatures::{ + keygen, Ciphersuite, KeygenOutput, confidential_key_derivation::{ self as ckd, ciphersuite::{Field as _, Group as _}, @@ -593,3 +594,39 @@ 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<( + Participant, + Box>>, + )> = 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)); + } + + PreparedDkgPackage { + protocols, + participants, + } +} + +pub struct PreparedDkgPackage { + pub protocols: Vec<(Participant, Box>>)>, + pub participants: Vec, +} From 52b0f4fe60b231065b85a299fc1b306ec546aa05 Mon Sep 17 00:00:00 2001 From: SimonRastikian <43679791+SimonRastikian@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:06:11 +0100 Subject: [PATCH 2/8] cargo fmt --- .../benches/advanced_dkg.rs | 24 +++++++++---------- .../benches/bench_utils.rs | 10 ++++---- .../docs/benches/{model.md => results.md} | 0 3 files changed, 16 insertions(+), 18 deletions(-) rename crates/threshold-signatures/docs/benches/{model.md => results.md} (100%) diff --git a/crates/threshold-signatures/benches/advanced_dkg.rs b/crates/threshold-signatures/benches/advanced_dkg.rs index d87def372..a6a3b1ee3 100644 --- a/crates/threshold-signatures/benches/advanced_dkg.rs +++ b/crates/threshold-signatures/benches/advanced_dkg.rs @@ -13,12 +13,12 @@ use threshold_signatures::{ confidential_key_derivation::ciphersuite::BLS12381SHA256, frost_ed25519::Ed25519Sha512, frost_secp256k1::Secp256K1Sha256, - keygen, Ciphersuite, Element, KeygenOutput, Scalar, + keygen, protocol::Protocol, test_utils::{ run_protocol_and_take_snapshots, run_simulated_protocol, MockCryptoRng, Simulator, }, - ReconstructionLowerBound, + Ciphersuite, Element, KeygenOutput, ReconstructionLowerBound, Scalar, }; fn threshold() -> ReconstructionLowerBound { @@ -106,7 +106,12 @@ fn bench_dkg_bls12381(c: &mut Criterion) { analyze_received_sizes(&sizes, true); } -criterion_group!(benches, bench_dkg_secp256k1, bench_dkg_ed25519, bench_dkg_bls12381); +criterion_group!( + benches, + bench_dkg_secp256k1, + bench_dkg_ed25519, + bench_dkg_bls12381 +); criterion_main!(benches); /****************************** Helpers ******************************/ @@ -129,14 +134,9 @@ where .choose(&mut rng) .expect("participant list is not empty"); - let real_protocol = keygen::( - &preps.participants, - real_participant, - threshold, - rng, - ) - .map(|p| Box::new(p) as Box>>) - .expect("Keygen should succeed"); + let real_protocol = keygen::(&preps.participants, real_participant, threshold, rng) + .map(|p| Box::new(p) as Box>>) + .expect("Keygen should succeed"); // now preparing the simulator let simulated_protocol = @@ -147,4 +147,4 @@ where protocol: real_protocol, simulator: simulated_protocol, } -} \ No newline at end of file +} diff --git a/crates/threshold-signatures/benches/bench_utils.rs b/crates/threshold-signatures/benches/bench_utils.rs index 68a0df1fb..4abde0ea1 100644 --- a/crates/threshold-signatures/benches/bench_utils.rs +++ b/crates/threshold-signatures/benches/bench_utils.rs @@ -8,7 +8,6 @@ use rand_core::{CryptoRngCore, SeedableRng}; use std::{env, sync::LazyLock}; use threshold_signatures::{ - keygen, Ciphersuite, KeygenOutput, confidential_key_derivation::{ self as ckd, ciphersuite::{Field as _, Group as _}, @@ -22,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 @@ -607,10 +607,8 @@ where threshold_signatures::Scalar: Send, { let participants = generate_participants_with_random_ids(num_participants, rng); - let mut protocols: Vec<( - Participant, - Box>>, - )> = Vec::with_capacity(num_participants); + let mut protocols: Vec<(Participant, Box>>)> = + Vec::with_capacity(num_participants); for p in &participants { let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); 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 From f6ca1769fe01e0d27cfb5f3976a1f971e9ef6c4c Mon Sep 17 00:00:00 2001 From: SimonRastikian <43679791+SimonRastikian@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:15:51 +0100 Subject: [PATCH 3/8] cargo shear fix --- crates/threshold-signatures/README.md | 2 +- crates/threshold-signatures/src/ecdsa/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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 From f12512a297554f3984597c4fdf536df422130a5c Mon Sep 17 00:00:00 2001 From: SimonRastikian <43679791+SimonRastikian@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:45:03 +0100 Subject: [PATCH 4/8] Relaxing code --- .../benches/advanced_dkg.rs | 8 +-- .../benches/bench_utils.rs | 53 +++++-------------- 2 files changed, 17 insertions(+), 44 deletions(-) diff --git a/crates/threshold-signatures/benches/advanced_dkg.rs b/crates/threshold-signatures/benches/advanced_dkg.rs index a6a3b1ee3..6e4fec2b5 100644 --- a/crates/threshold-signatures/benches/advanced_dkg.rs +++ b/crates/threshold-signatures/benches/advanced_dkg.rs @@ -125,16 +125,16 @@ where { let mut rng = MockCryptoRng::seed_from_u64(42); let preps = prepare_dkg::(participants_num(), threshold, &mut rng); - let (_, protocolsnapshot) = run_protocol_and_take_snapshots(preps.protocols) + let participants: Vec<_> = preps.iter().map(|(p, _)| *p).collect(); + let (_, protocolsnapshot) = run_protocol_and_take_snapshots(preps) .expect("Running protocol with snapshot should not have issues"); // choose the real_participant at random - let real_participant = *preps - .participants + let real_participant = *participants .choose(&mut rng) .expect("participant list is not empty"); - let real_protocol = keygen::(&preps.participants, real_participant, threshold, rng) + let real_protocol = keygen::(&participants, real_participant, threshold, rng) .map(|p| Box::new(p) as Box>>) .expect("Keygen should succeed"); diff --git a/crates/threshold-signatures/benches/bench_utils.rs b/crates/threshold-signatures/benches/bench_utils.rs index 4abde0ea1..00d858675 100644 --- a/crates/threshold-signatures/benches/bench_utils.rs +++ b/crates/threshold-signatures/benches/bench_utils.rs @@ -166,8 +166,8 @@ pub fn ot_ecdsa_prepare_presign let key_packages = run_keygen(&participants, threshold, rng); let mut protocols: Vec<( - Participant, - Box>, + _, + Box>, )> = Vec::with_capacity(participants.len()); for (((p, keygen_out), share0), share1) in @@ -227,10 +227,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( @@ -288,10 +285,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()); @@ -349,10 +343,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( @@ -389,10 +380,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()); @@ -429,10 +417,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); @@ -470,17 +455,14 @@ pub fn ed25519_prepare_sign_v2( ) -> FrostEd25519SigV2 { let num_participants = threshold.value(); // collect all participants - let participants: Vec = + 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); @@ -546,10 +528,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); @@ -607,7 +586,7 @@ where threshold_signatures::Scalar: Send, { let participants = generate_participants_with_random_ids(num_participants, rng); - let mut protocols: Vec<(Participant, Box>>)> = + let mut protocols = Vec::with_capacity(num_participants); for p in &participants { @@ -618,13 +597,7 @@ where protocols.push((*p, protocol)); } - PreparedDkgPackage { - protocols, - participants, - } + protocols } -pub struct PreparedDkgPackage { - pub protocols: Vec<(Participant, Box>>)>, - pub participants: Vec, -} +pub type PreparedDkgPackage = Vec<(Participant, Box>>)>; From 007ea065b20477831c2100d023fb7fe0cc43a0d0 Mon Sep 17 00:00:00 2001 From: SimonRastikian <43679791+SimonRastikian@users.noreply.github.com> Date: Fri, 27 Mar 2026 13:22:24 +0100 Subject: [PATCH 5/8] cargo fmt --- crates/threshold-signatures/benches/bench_utils.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/crates/threshold-signatures/benches/bench_utils.rs b/crates/threshold-signatures/benches/bench_utils.rs index 00d858675..ebd67939d 100644 --- a/crates/threshold-signatures/benches/bench_utils.rs +++ b/crates/threshold-signatures/benches/bench_utils.rs @@ -165,10 +165,8 @@ pub fn ot_ecdsa_prepare_presign let key_packages = run_keygen(&participants, threshold, rng); - let mut protocols: Vec<( - _, - 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) @@ -455,8 +453,7 @@ 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); @@ -586,8 +583,7 @@ where threshold_signatures::Scalar: Send, { let participants = generate_participants_with_random_ids(num_participants, rng); - let mut protocols = - Vec::with_capacity(num_participants); + let mut protocols = Vec::with_capacity(num_participants); for p in &participants { let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); From 99c5be1216748bf49e337d9b83a184cd7f490255 Mon Sep 17 00:00:00 2001 From: SimonRastikian <43679791+SimonRastikian@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:10:29 +0100 Subject: [PATCH 6/8] Improving syntax --- crates/threshold-signatures/benches/advanced_dkg.rs | 4 ++-- .../benches/advanced_eddsa_frost_sign_v1.rs | 4 ++-- .../benches/advanced_eddsa_frost_sign_v2.rs | 8 ++++---- .../benches/advanced_ot_based_ecdsa.rs | 12 ++++++------ .../benches/advanced_robust_ecdsa.rs | 8 ++++---- crates/threshold-signatures/benches/ckd.rs | 4 ++-- crates/threshold-signatures/src/test_utils.rs | 2 +- .../src/test_utils/participant_simulation.rs | 4 ++-- .../threshold-signatures/src/test_utils/protocol.rs | 8 ++++---- .../threshold-signatures/src/test_utils/snapshot.rs | 4 ++-- 10 files changed, 29 insertions(+), 29 deletions(-) diff --git a/crates/threshold-signatures/benches/advanced_dkg.rs b/crates/threshold-signatures/benches/advanced_dkg.rs index 6e4fec2b5..094fdccbd 100644 --- a/crates/threshold-signatures/benches/advanced_dkg.rs +++ b/crates/threshold-signatures/benches/advanced_dkg.rs @@ -126,7 +126,7 @@ where 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 (_, protocolsnapshot) = run_protocol_and_take_snapshots(preps) + let (_, protocol_snapshot) = run_protocol_and_take_snapshots(preps) .expect("Running protocol with snapshot should not have issues"); // choose the real_participant at random @@ -140,7 +140,7 @@ where // 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"); PreparedSimulatedDkg { participant: real_participant, 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/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/src/test_utils.rs b/crates/threshold-signatures/src/test_utils.rs index 89e55cbb5..e9896f627 100644 --- a/crates/threshold-signatures/src/test_utils.rs +++ b/crates/threshold-signatures/src/test_utils.rs @@ -45,7 +45,7 @@ pub use protocol::{ run_protocol, run_protocol_and_take_snapshots, run_simulated_protocol, run_two_party_protocol, }; pub use sign::{check_one_coordinator_output, run_sign}; -pub use snapshot::ProtocolSnapshot; +pub use snapshot::protocol_snapshot; pub use test_generators::*; /// Checks that the list contains all None but one element diff --git a/crates/threshold-signatures/src/test_utils/participant_simulation.rs b/crates/threshold-signatures/src/test_utils/participant_simulation.rs index 6ef573c10..21faa5fed 100644 --- a/crates/threshold-signatures/src/test_utils/participant_simulation.rs +++ b/crates/threshold-signatures/src/test_utils/participant_simulation.rs @@ -1,6 +1,6 @@ use crate::participants::Participant; use crate::protocol::MessageData; -use crate::test_utils::snapshot::ProtocolSnapshot; +use crate::test_utils::snapshot::protocol_snapshot; pub struct Simulator { /// the `real_participant` we are simulating for @@ -10,7 +10,7 @@ pub struct Simulator { } impl Simulator { - pub fn new(real_participant: Participant, protocol_snap: ProtocolSnapshot) -> Option { + pub fn new(real_participant: Participant, protocol_snap: protocol_snapshot) -> Option { if protocol_snap.number_of_participants() <= 1 { return None; } diff --git a/crates/threshold-signatures/src/test_utils/protocol.rs b/crates/threshold-signatures/src/test_utils/protocol.rs index ce182472a..05db53c7f 100644 --- a/crates/threshold-signatures/src/test_utils/protocol.rs +++ b/crates/threshold-signatures/src/test_utils/protocol.rs @@ -1,7 +1,7 @@ use crate::errors::ProtocolError; use crate::participants::Participant; use crate::protocol::{Action, Protocol}; -use crate::test_utils::{ProtocolSnapshot, Simulator}; +use crate::test_utils::{protocol_snapshot, Simulator}; use std::collections::HashMap; use crate::participants::ParticipantList; @@ -27,7 +27,7 @@ pub fn run_protocol( /// Like [`run_protocol()`], except that it snapshots all the communication. pub fn run_protocol_and_take_snapshots( ps: Vec<(Participant, Box>)>, -) -> Result<(Vec<(Participant, T)>, ProtocolSnapshot), ProtocolError> { +) -> Result<(Vec<(Participant, T)>, protocol_snapshot), ProtocolError> { run_protocol_common(ps, true).map(|(v, snapshot)| (v, snapshot.unwrap())) } @@ -118,14 +118,14 @@ pub fn run_two_party_protocol( fn run_protocol_common( mut ps: Vec<(Participant, Box>)>, take_snapshots: bool, -) -> Result<(Vec<(Participant, T)>, Option), ProtocolError> { +) -> Result<(Vec<(Participant, T)>, Option), ProtocolError> { let indices: HashMap = ps.iter().enumerate().map(|(i, (p, _))| (*p, i)).collect(); let mut protocol_snapshots = { if take_snapshots { let participants: Vec<_> = ps.iter().map(|(p, _)| *p).collect(); - Some(ProtocolSnapshot::new_empty(participants)) + Some(protocol_snapshot::new_empty(participants)) } else { None } diff --git a/crates/threshold-signatures/src/test_utils/snapshot.rs b/crates/threshold-signatures/src/test_utils/snapshot.rs index 31b11d2b6..d8876af32 100644 --- a/crates/threshold-signatures/src/test_utils/snapshot.rs +++ b/crates/threshold-signatures/src/test_utils/snapshot.rs @@ -59,11 +59,11 @@ impl ParticipantSnapshot { /// Used to store the snapshot of all the messages sent during /// the communication rounds of a certain protocol -pub struct ProtocolSnapshot { +pub struct protocol_snapshot { snapshots: HashMap, } -impl ProtocolSnapshot { +impl protocol_snapshot { /// Creates an empty snapshot pub fn new_empty(participants: Vec) -> Self { let snapshots = participants From 67bb740c14a8808e4445eb81328fec7d4060dcaf Mon Sep 17 00:00:00 2001 From: SimonRastikian <43679791+SimonRastikian@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:34:03 +0100 Subject: [PATCH 7/8] Fix: very stupid blind replace --- crates/threshold-signatures/src/test_utils.rs | 2 +- .../src/test_utils/participant_simulation.rs | 4 ++-- crates/threshold-signatures/src/test_utils/protocol.rs | 8 ++++---- crates/threshold-signatures/src/test_utils/snapshot.rs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/threshold-signatures/src/test_utils.rs b/crates/threshold-signatures/src/test_utils.rs index e9896f627..89e55cbb5 100644 --- a/crates/threshold-signatures/src/test_utils.rs +++ b/crates/threshold-signatures/src/test_utils.rs @@ -45,7 +45,7 @@ pub use protocol::{ run_protocol, run_protocol_and_take_snapshots, run_simulated_protocol, run_two_party_protocol, }; pub use sign::{check_one_coordinator_output, run_sign}; -pub use snapshot::protocol_snapshot; +pub use snapshot::ProtocolSnapshot; pub use test_generators::*; /// Checks that the list contains all None but one element diff --git a/crates/threshold-signatures/src/test_utils/participant_simulation.rs b/crates/threshold-signatures/src/test_utils/participant_simulation.rs index 21faa5fed..6ef573c10 100644 --- a/crates/threshold-signatures/src/test_utils/participant_simulation.rs +++ b/crates/threshold-signatures/src/test_utils/participant_simulation.rs @@ -1,6 +1,6 @@ use crate::participants::Participant; use crate::protocol::MessageData; -use crate::test_utils::snapshot::protocol_snapshot; +use crate::test_utils::snapshot::ProtocolSnapshot; pub struct Simulator { /// the `real_participant` we are simulating for @@ -10,7 +10,7 @@ pub struct Simulator { } impl Simulator { - pub fn new(real_participant: Participant, protocol_snap: protocol_snapshot) -> Option { + pub fn new(real_participant: Participant, protocol_snap: ProtocolSnapshot) -> Option { if protocol_snap.number_of_participants() <= 1 { return None; } diff --git a/crates/threshold-signatures/src/test_utils/protocol.rs b/crates/threshold-signatures/src/test_utils/protocol.rs index 05db53c7f..ce182472a 100644 --- a/crates/threshold-signatures/src/test_utils/protocol.rs +++ b/crates/threshold-signatures/src/test_utils/protocol.rs @@ -1,7 +1,7 @@ use crate::errors::ProtocolError; use crate::participants::Participant; use crate::protocol::{Action, Protocol}; -use crate::test_utils::{protocol_snapshot, Simulator}; +use crate::test_utils::{ProtocolSnapshot, Simulator}; use std::collections::HashMap; use crate::participants::ParticipantList; @@ -27,7 +27,7 @@ pub fn run_protocol( /// Like [`run_protocol()`], except that it snapshots all the communication. pub fn run_protocol_and_take_snapshots( ps: Vec<(Participant, Box>)>, -) -> Result<(Vec<(Participant, T)>, protocol_snapshot), ProtocolError> { +) -> Result<(Vec<(Participant, T)>, ProtocolSnapshot), ProtocolError> { run_protocol_common(ps, true).map(|(v, snapshot)| (v, snapshot.unwrap())) } @@ -118,14 +118,14 @@ pub fn run_two_party_protocol( fn run_protocol_common( mut ps: Vec<(Participant, Box>)>, take_snapshots: bool, -) -> Result<(Vec<(Participant, T)>, Option), ProtocolError> { +) -> Result<(Vec<(Participant, T)>, Option), ProtocolError> { let indices: HashMap = ps.iter().enumerate().map(|(i, (p, _))| (*p, i)).collect(); let mut protocol_snapshots = { if take_snapshots { let participants: Vec<_> = ps.iter().map(|(p, _)| *p).collect(); - Some(protocol_snapshot::new_empty(participants)) + Some(ProtocolSnapshot::new_empty(participants)) } else { None } diff --git a/crates/threshold-signatures/src/test_utils/snapshot.rs b/crates/threshold-signatures/src/test_utils/snapshot.rs index d8876af32..31b11d2b6 100644 --- a/crates/threshold-signatures/src/test_utils/snapshot.rs +++ b/crates/threshold-signatures/src/test_utils/snapshot.rs @@ -59,11 +59,11 @@ impl ParticipantSnapshot { /// Used to store the snapshot of all the messages sent during /// the communication rounds of a certain protocol -pub struct protocol_snapshot { +pub struct ProtocolSnapshot { snapshots: HashMap, } -impl protocol_snapshot { +impl ProtocolSnapshot { /// Creates an empty snapshot pub fn new_empty(participants: Vec) -> Self { let snapshots = participants From b56fe024d64fe54ca97a9fdb95198900f7195237 Mon Sep 17 00:00:00 2001 From: SimonRastikian <43679791+SimonRastikian@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:31:28 +0100 Subject: [PATCH 8/8] Cleanups --- .../benches/advanced_dkg.rs | 1 - .../benches/advanced_eddsa_frost_sign_v1.rs | 1 - .../benches/advanced_eddsa_frost_sign_v2.rs | 1 - .../benches/advanced_ot_based_ecdsa.rs | 1 - .../benches/advanced_robust_ecdsa.rs | 1 - .../benches/bench_utils.rs | 599 ------------------ .../benches/bench_utils/dkg.rs | 35 + .../benches/bench_utils/frost_eddsa.rs | 216 +++++++ .../benches/bench_utils/mod.rs | 116 ++++ .../benches/bench_utils/ot_based_ecdsa.rs | 176 +++++ .../benches/bench_utils/robust_ecdsa.rs | 109 ++++ .../src/crypto/polynomials/commitment.rs | 193 ++++++ .../{polynomials.rs => polynomials/mod.rs} | 398 +----------- .../src/crypto/polynomials/polynomial.rs | 208 ++++++ 14 files changed, 1063 insertions(+), 992 deletions(-) delete mode 100644 crates/threshold-signatures/benches/bench_utils.rs create mode 100644 crates/threshold-signatures/benches/bench_utils/dkg.rs create mode 100644 crates/threshold-signatures/benches/bench_utils/frost_eddsa.rs create mode 100644 crates/threshold-signatures/benches/bench_utils/mod.rs create mode 100644 crates/threshold-signatures/benches/bench_utils/ot_based_ecdsa.rs create mode 100644 crates/threshold-signatures/benches/bench_utils/robust_ecdsa.rs create mode 100644 crates/threshold-signatures/src/crypto/polynomials/commitment.rs rename crates/threshold-signatures/src/crypto/{polynomials.rs => polynomials/mod.rs} (71%) create mode 100644 crates/threshold-signatures/src/crypto/polynomials/polynomial.rs diff --git a/crates/threshold-signatures/benches/advanced_dkg.rs b/crates/threshold-signatures/benches/advanced_dkg.rs index 094fdccbd..567b8eae8 100644 --- a/crates/threshold-signatures/benches/advanced_dkg.rs +++ b/crates/threshold-signatures/benches/advanced_dkg.rs @@ -114,7 +114,6 @@ criterion_group!( ); criterion_main!(benches); -/****************************** Helpers ******************************/ /// Used to simulate DKG keygen for benchmarking fn prepare_simulated_dkg( threshold: ReconstructionLowerBound, 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 6fd4fd562..16b9789e2 100644 --- a/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v1.rs +++ b/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v1.rs @@ -49,7 +49,6 @@ fn bench_sign(c: &mut Criterion) { criterion_group!(benches, bench_sign); criterion_main!(benches); -/****************************** Helpers ******************************/ /// Used to simulate robust ecdsa signatures for benchmarking fn prepare_simulated_sign(threshold: ReconstructionLowerBound) -> PreparedSimulatedSig { let mut rng = MockCryptoRng::seed_from_u64(41); 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 cdbf367d9..f7c130c4f 100644 --- a/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v2.rs +++ b/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v2.rs @@ -78,7 +78,6 @@ fn bench_sign(c: &mut Criterion) { criterion_group!(benches, bench_presign, bench_sign); criterion_main!(benches); -/****************************** Helpers ******************************/ /// Used to simulate Frost Ed25519 presignatures for benchmarking fn prepare_simulate_presign(num_participants: usize) -> PreparedPresig { // Running presign a first time with snapshots diff --git a/crates/threshold-signatures/benches/advanced_ot_based_ecdsa.rs b/crates/threshold-signatures/benches/advanced_ot_based_ecdsa.rs index 1e1c0b101..96a55fb74 100644 --- a/crates/threshold-signatures/benches/advanced_ot_based_ecdsa.rs +++ b/crates/threshold-signatures/benches/advanced_ot_based_ecdsa.rs @@ -134,7 +134,6 @@ fn bench_sign(c: &mut Criterion) { criterion_group!(benches, bench_triples, bench_presign, bench_sign); criterion_main!(benches); -/****************************** Helpers ******************************/ /// Used to simulate ot based ecdsa triples for benchmarking fn prepare_simulated_triples(participant_num: usize) -> PreparedSimulatedTriples { let mut rng = MockCryptoRng::seed_from_u64(42); diff --git a/crates/threshold-signatures/benches/advanced_robust_ecdsa.rs b/crates/threshold-signatures/benches/advanced_robust_ecdsa.rs index 7980f235c..1de4d60a3 100644 --- a/crates/threshold-signatures/benches/advanced_robust_ecdsa.rs +++ b/crates/threshold-signatures/benches/advanced_robust_ecdsa.rs @@ -90,7 +90,6 @@ fn bench_sign(c: &mut Criterion) { criterion_group!(benches, bench_presign, bench_sign); criterion_main!(benches); -/****************************** Helpers ******************************/ /// Used to simulate robust ecdsa presignatures for benchmarking fn prepare_simulate_presign(num_participants: usize) -> PreparedPresig { // Running presign a first time with snapshots diff --git a/crates/threshold-signatures/benches/bench_utils.rs b/crates/threshold-signatures/benches/bench_utils.rs deleted file mode 100644 index ebd67939d..000000000 --- a/crates/threshold-signatures/benches/bench_utils.rs +++ /dev/null @@ -1,599 +0,0 @@ -#![allow(dead_code, clippy::missing_panics_doc, clippy::indexing_slicing)] - -use average::{Estimate, Quantile, Variance}; -use frost_secp256k1::VerifyingKey; -use k256::AffinePoint; -use rand::Rng; -use rand_core::{CryptoRngCore, SeedableRng}; -use std::{env, sync::LazyLock}; - -use threshold_signatures::{ - confidential_key_derivation::{ - self as ckd, - ciphersuite::{Field as _, Group as _}, - }, - ecdsa::{ - self, - ot_based_ecdsa::{ - self, - triples::{generate_triple_many, TriplePub, TripleShare}, - }, - 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, - }, - Ciphersuite, KeygenOutput, MaxMalicious, ReconstructionLowerBound, -}; - -// fix malicious number of participants -pub static MAX_MALICIOUS: LazyLock = std::sync::LazyLock::new(|| { - env::var("MAX_MALICIOUS") - .ok() - .and_then(|v| v.parse().ok()) - .unwrap_or(6) -}); - -// fix number of samples -pub static SAMPLE_SIZE: LazyLock = std::sync::LazyLock::new(|| { - env::var("SAMPLE_SIZE") - .ok() - .and_then(|v| v.parse().ok()) - .unwrap_or(15) -}); - -pub static RECONSTRUCTION_LOWER_BOUND: LazyLock = - LazyLock::new(|| ReconstructionLowerBound::from(*MAX_MALICIOUS + 1)); - -/// This helps defining a generic type for the benchmarks prepared outputs -pub struct PreparedOutputs { - pub participant: Participant, - pub protocol: Box>, - pub simulator: Simulator, -} -pub struct PreparedPresig { - pub protocols: Vec<(Participant, Box>)>, - pub key_packages: Vec<(Participant, KeygenOutput)>, - pub participants: Vec, -} - -pub struct PreparedSig { - pub protocols: Vec<( - Participant, - Box>, - )>, - pub index: usize, - pub presig: RerandomizedPresignOutput, - pub derived_pk: AffinePoint, - pub msg_hash: Scalar, -} - -#[allow(clippy::cast_precision_loss)] -/// Analyzes the size of the received data by a participant across the entire protocol -pub fn analyze_received_sizes( - sizes: &[usize], - is_print: bool, -) -> (usize, usize, f64, f64, f64, f64) { - if sizes.len() <= 1 { - return (0, 0, 0.0, 0.0, 0.0, 0.0); - } - let min = *sizes.iter().min().expect("Minimum should exist"); - let max = *sizes.iter().max().expect("Maximum should exist"); - let avg = sizes.iter().sum::() as f64 / sizes.len() as f64; - - let data = sizes.iter().map(|&x| x as f64).collect::>(); - - // Median (0.5 quantile) - let mut quantile = Quantile::new(0.5); - // Variance + Std Dev - let mut variance_est = Variance::new(); - - for &x in &data { - variance_est.add(x); - quantile.add(x); - } - - let median = quantile.quantile(); - let variance = variance_est.sample_variance(); - let std_dev = variance.sqrt(); - - if is_print { - println!("Analysis for received messages:"); - println!( - "\ - min:{min}B\t\ - max:{max}B\t\ - average:{avg}B\t\ - median:{median}B\t\ - variance:{variance}B\t\ - standard deviation:{std_dev}B - " - ); - } - - (min, max, avg, median, variance, std_dev) -} - -/********************* OT Based ECDSA *********************/ -/// Used to prepare ot based ecdsa triples for benchmarking -pub fn ot_ecdsa_prepare_triples( - participant_num: usize, - threshold: ReconstructionLowerBound, - rng: &mut R, -) -> OTECDSAPreparedTriples { - let mut protocols: Vec<(_, Box>)> = - Vec::with_capacity(participant_num); - let participants = generate_participants_with_random_ids(participant_num, rng); - - for p in &participants { - let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); - let protocol = generate_triple_many::<2>(&participants, *p, threshold, rng_p) - .expect("Triple generation should succeed"); - protocols.push((*p, Box::new(protocol))); - } - OTECDSAPreparedTriples { - protocols, - participants, - } -} - -/// Used to prepare ot based ecdsa presignatures for benchmarking -pub fn ot_ecdsa_prepare_presign( - two_triples: &[(Participant, Vec<(TripleShare, TriplePub)>)], - threshold: ReconstructionLowerBound, - rng: &mut R, -) -> OTECDSAPreparedPresig { - let mut two_triples = two_triples.to_owned(); - two_triples.sort_by_key(|(p, _)| *p); - - // collect all participants - let participants: Vec = two_triples - .iter() - .map(|(participant, _)| *participant) - .collect(); - - let (shares, pubs): (Vec<_>, Vec<_>) = two_triples.into_iter().flat_map(|(_, vec)| vec).unzip(); - // split shares into shares0 and shares 1 and pubs into pubs0 and pubs1 - let (shares0, shares1) = split_even_odd(shares); - // split shares into shares0 and shares 1 and pubs into pubs0 and pubs1 - let (pub0, pub1) = split_even_odd(pubs); - - let key_packages = run_keygen(&participants, threshold, rng); - - 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) - { - let protocol = ot_based_ecdsa::presign::presign( - &participants, - p, - ot_based_ecdsa::PresignArguments { - triple0: (share0, pub0[0].clone()), - triple1: (share1, pub1[0].clone()), - keygen_out, - threshold, - }, - ) - .expect("Presigning should succeed"); - protocols.push((p, Box::new(protocol))); - } - OTECDSAPreparedPresig { - protocols, - key_packages, - participants, - } -} - -/// Used to prepare ot based ecdsa signatures for benchmarking -pub fn ot_ecdsa_prepare_sign( - result: &[(Participant, ot_based_ecdsa::PresignOutput)], - threshold: ReconstructionLowerBound, - pk: VerifyingKey, - rng: &mut R, -) -> OTECDSAPreparedSig { - // collect all participants - let participants: Vec = - result.iter().map(|(participant, _)| *participant).collect(); - - // choose a coordinator at random - let index = rng.gen_range(0..result.len()); - let coordinator = result[index].0; - - let (args, msg_hash) = - ecdsa_generate_rerandpresig_args(rng, &participants, pk, result[0].1.big_r); - let derived_pk = args - .tweak - .derive_verifying_key(&pk) - .to_element() - .to_affine(); - - let result = result - .iter() - .map(|(p, presig)| { - ( - *p, - ot_based_ecdsa::RerandomizedPresignOutput::rerandomize_presign(presig, &args) - .expect("Rerandomizing presignature should succeed"), - ) - }) - .collect::>(); - - let mut protocols = Vec::with_capacity(result.len()); - - for (p, presignature) in result.clone() { - let protocol = ot_based_ecdsa::sign::sign( - args.participants.participants(), - coordinator, - threshold, - p, - derived_pk, - presignature, - msg_hash, - ) - .map(|sig| Box::new(sig) as Box>) - .expect("Signing should succeed"); - protocols.push((p, protocol)); - } - OTECDSAPreparedSig { - protocols, - index, - presig: result[index].1.clone(), - derived_pk, - msg_hash, - } -} - -pub fn split_even_odd(v: Vec) -> (Vec, Vec) { - let mut even = Vec::with_capacity(v.len() / 2 + 1); - let mut odd = Vec::with_capacity(v.len() / 2); - for (i, x) in v.into_iter().enumerate() { - if i % 2 == 0 { - even.push(x); - } else { - odd.push(x); - } - } - (even, odd) -} - -type TriplesProtocols = Vec<( - Participant, - Box>>, -)>; -pub struct OTECDSAPreparedTriples { - pub protocols: TriplesProtocols, - pub participants: Vec, -} - -pub type OTECDSAPreparedPresig = PreparedPresig; -pub type OTECDSAPreparedSig = PreparedSig; - -/********************* Robust ECDSA *********************/ -/// Used to prepare robust ecdsa presignatures for benchmarking -pub fn robust_ecdsa_prepare_presign( - num_participants: usize, - rng: &mut R, -) -> 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<_> = Vec::with_capacity(participants.len()); - - for (p, keygen_out) in &key_packages { - let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); - let protocol = robust_ecdsa::presign::presign( - &participants, - *p, - robust_ecdsa::PresignArguments { - keygen_out: keygen_out.clone(), - max_malicious: (*MAX_MALICIOUS).into(), - }, - rng_p, - ) - .map(|presig| Box::new(presig) as Box>) - .expect("Presignature should succeed"); - protocols.push((*p, protocol)); - } - RobustECDSAPreparedPresig { - protocols, - key_packages, - participants, - } -} - -/// Used to prepare robust ecdsa signatures for benchmarking -pub fn robust_ecdsa_prepare_sign( - result: &[(Participant, robust_ecdsa::PresignOutput)], - max_malicious: MaxMalicious, - pk: VerifyingKey, - rng: &mut R, -) -> RobustECDSASig { - // collect all participants - let participants: Vec = - result.iter().map(|(participant, _)| *participant).collect(); - - // choose a coordinator at random - let coordinator_index = rng.gen_range(0..result.len()); - let coordinator = result[coordinator_index].0; - - let (args, msg_hash) = - ecdsa_generate_rerandpresig_args(rng, &participants, pk, result[0].1.big_r); - let derived_pk = args - .tweak - .derive_verifying_key(&pk) - .to_element() - .to_affine(); - - let result = result - .iter() - .map(|(p, presig)| { - ( - *p, - robust_ecdsa::RerandomizedPresignOutput::rerandomize_presign(presig, &args) - .expect("Rerandomizing presignature should succeed"), - ) - }) - .collect::>(); - - let mut protocols = Vec::with_capacity(result.len()); - - for (p, presignature) in result.clone() { - let protocol = robust_ecdsa::sign::sign( - &participants, - coordinator, - max_malicious, - p, - derived_pk, - presignature, - msg_hash, - ) - .map(|sig| Box::new(sig) as Box>) - .expect("Signing should succeed"); - protocols.push((p, protocol)); - } - RobustECDSASig { - protocols, - index: coordinator_index, - presig: result[coordinator_index].1.clone(), - derived_pk, - msg_hash, - } -} - -pub type RobustECDSAPreparedPresig = - PreparedPresig; -pub type RobustECDSASig = PreparedSig; - -/********************* Frost EdDSA *********************/ -/// Used to prepare ed25519 presignatures for benchmarking -pub fn ed25519_prepare_presign( - num_participants: usize, - rng: &mut R, -) -> 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<_> = Vec::with_capacity(participants.len()); - - for (p, keygen_out) in &key_packages { - let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); - let protocol = eddsa::presign( - &participants, - *p, - &eddsa::PresignArguments { - keygen_out: keygen_out.clone(), - threshold: (*MAX_MALICIOUS + 1).into(), - }, - rng_p, - ) - .map(|presig| Box::new(presig) as Box>) - .expect("Presignature should succeed"); - protocols.push((*p, protocol)); - } - FrostEd25519PreparedPresig { - protocols, - key_packages, - participants, - } -} - -/// Used to prepare ed25519 signatures for benchmarking -pub fn ed25519_prepare_sign_v1( - threshold: ReconstructionLowerBound, - rng: &mut R, -) -> FrostEd25519SigV1 { - let num_participants = threshold.value(); - let participants = generate_participants_with_random_ids(num_participants, rng); - let key_packages = run_keygen(&participants, *MAX_MALICIOUS + 1, rng); - - // choose a coordinator at random - let coordinator_index = rng.gen_range(0..num_participants); - let coordinator = participants[coordinator_index]; - - let mut protocols = Vec::with_capacity(participants.len()); - - let mut message: [u8; 32] = [0u8; 32]; - rng.fill_bytes(&mut message); - let message = message.to_vec(); - - for (p, keygen_out) in &key_packages { - let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); - let protocol = eddsa::sign::sign_v1( - &participants, - threshold, - *p, - coordinator, - keygen_out.clone(), - message.clone(), - rng_p, - ) - .map(|sig| Box::new(sig) as Box>) - .expect("Signing should succeed"); - protocols.push((*p, protocol)); - } - - FrostEd25519SigV1 { - protocols, - index: coordinator_index, - key_packages, - message, - } -} - -pub fn ed25519_prepare_sign_v2( - result: &[(Participant, eddsa::PresignOutput)], - key_packages: Vec<(Participant, eddsa::KeygenOutput)>, - threshold: ReconstructionLowerBound, - rng: &mut R, -) -> FrostEd25519SigV2 { - let num_participants = threshold.value(); - // collect all participants - 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::with_capacity(participants.len()); - - let mut message: [u8; 32] = [0u8; 32]; - rng.fill_bytes(&mut message); - let message = message.to_vec(); - - for ((p, keygen_out), (p_redundancy, presign)) in key_packages.iter().zip(result) { - assert_eq!(p, p_redundancy); - let protocol = eddsa::sign::sign_v2( - &participants, - threshold, - *p, - coordinator, - keygen_out.clone(), - presign.clone(), - message.clone(), - ) - .map(|sig| Box::new(sig) as Box>) - .expect("Signing should succeed"); - protocols.push((*p, protocol)); - } - - FrostEd25519SigV2 { - protocols, - index: coordinator_index, - presig: result[coordinator_index].1.clone(), - key_packages, - message, - } -} - -pub struct FrostEd25519SigV1 { - pub protocols: Vec<( - Participant, - Box>, - )>, - pub index: usize, - pub key_packages: Vec<(Participant, eddsa::KeygenOutput)>, - pub message: Vec, -} - -pub type FrostEd25519PreparedPresig = PreparedPresig; -pub struct FrostEd25519SigV2 { - pub protocols: Vec<( - Participant, - Box>, - )>, - pub index: usize, - pub presig: eddsa::PresignOutput, - pub key_packages: Vec<(Participant, eddsa::KeygenOutput)>, - pub message: Vec, -} - -pub fn prepare_ckd( - threshold: ReconstructionLowerBound, - rng: &mut R, -) -> PreparedCkdPackage { - let num_participants = threshold.value(); - // collect all participants - let participants = generate_participants_with_random_ids(num_participants, rng); - let key_packages = run_keygen(&participants, *MAX_MALICIOUS + 1, rng); - - // choose a coordinator at random - let coordinator_index = rng.gen_range(0..num_participants); - let coordinator = participants[coordinator_index]; - - let mut protocols = Vec::with_capacity(participants.len()); - - let mut app_id: [u8; 32] = [0u8; 32]; - rng.fill_bytes(&mut app_id); - let app_id = ckd::AppId::try_new(app_id).expect("cannot fail"); - - let scalar_rng = MockCryptoRng::seed_from_u64(rng.next_u64()); - let app_sk = ckd::Scalar::random(scalar_rng); - let app_pk = ckd::ElementG1::generator() * app_sk; - - for (p, keygen_out) in &key_packages { - let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); - let protocol = ckd::protocol::ckd( - &participants, - coordinator, - *p, - keygen_out.clone(), - app_id.clone(), - app_pk, - rng_p, - ) - .map(|ckd| Box::new(ckd) as Box>) - .expect("Ckd should succeed"); - protocols.push((*p, protocol)); - } - - PreparedCkdPackage { - protocols, - index: coordinator_index, - key_packages, - app_id, - app_pk, - } -} - -pub struct PreparedCkdPackage { - pub protocols: Vec<( - Participant, - Box>, - )>, - pub index: usize, - pub key_packages: Vec<(Participant, ckd::KeygenOutput)>, - 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/bench_utils/dkg.rs b/crates/threshold-signatures/benches/bench_utils/dkg.rs new file mode 100644 index 000000000..68e7bfc52 --- /dev/null +++ b/crates/threshold-signatures/benches/bench_utils/dkg.rs @@ -0,0 +1,35 @@ +use rand_core::{CryptoRngCore, SeedableRng}; + +use threshold_signatures::{ + keygen, + participants::Participant, + protocol::Protocol, + test_utils::{generate_participants_with_random_ids, MockCryptoRng}, + Ciphersuite, KeygenOutput, ReconstructionLowerBound, +}; + +/// 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/bench_utils/frost_eddsa.rs b/crates/threshold-signatures/benches/bench_utils/frost_eddsa.rs new file mode 100644 index 000000000..271472bd1 --- /dev/null +++ b/crates/threshold-signatures/benches/bench_utils/frost_eddsa.rs @@ -0,0 +1,216 @@ +use rand::Rng; +use rand_core::{CryptoRngCore, SeedableRng}; + +use threshold_signatures::{ + confidential_key_derivation::{ + self as ckd, + ciphersuite::{Field as _, Group as _}, + }, + frost::eddsa, + participants::Participant, + protocol::Protocol, + test_utils::{generate_participants_with_random_ids, run_keygen, MockCryptoRng}, + ReconstructionLowerBound, +}; + +use super::{PreparedPresig, MAX_MALICIOUS}; + +/// Used to prepare ed25519 presignatures for benchmarking +pub fn ed25519_prepare_presign( + num_participants: usize, + rng: &mut R, +) -> 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<_> = Vec::with_capacity(participants.len()); + + for (p, keygen_out) in &key_packages { + let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); + let protocol = eddsa::presign( + &participants, + *p, + &eddsa::PresignArguments { + keygen_out: keygen_out.clone(), + threshold: (*MAX_MALICIOUS + 1).into(), + }, + rng_p, + ) + .map(|presig| Box::new(presig) as Box>) + .expect("Presignature should succeed"); + protocols.push((*p, protocol)); + } + FrostEd25519PreparedPresig { + protocols, + key_packages, + participants, + } +} + +/// Used to prepare ed25519 signatures for benchmarking +pub fn ed25519_prepare_sign_v1( + threshold: ReconstructionLowerBound, + rng: &mut R, +) -> FrostEd25519SigV1 { + let num_participants = threshold.value(); + let participants = generate_participants_with_random_ids(num_participants, rng); + let key_packages = run_keygen(&participants, *MAX_MALICIOUS + 1, rng); + + // choose a coordinator at random + let coordinator_index = rng.gen_range(0..num_participants); + let coordinator = participants[coordinator_index]; + + let mut protocols = Vec::with_capacity(participants.len()); + + let mut message: [u8; 32] = [0u8; 32]; + rng.fill_bytes(&mut message); + let message = message.to_vec(); + + for (p, keygen_out) in &key_packages { + let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); + let protocol = eddsa::sign::sign_v1( + &participants, + threshold, + *p, + coordinator, + keygen_out.clone(), + message.clone(), + rng_p, + ) + .map(|sig| Box::new(sig) as Box>) + .expect("Signing should succeed"); + protocols.push((*p, protocol)); + } + + FrostEd25519SigV1 { + protocols, + index: coordinator_index, + key_packages, + message, + } +} + +pub fn ed25519_prepare_sign_v2( + result: &[(Participant, eddsa::PresignOutput)], + key_packages: Vec<(Participant, eddsa::KeygenOutput)>, + threshold: ReconstructionLowerBound, + rng: &mut R, +) -> FrostEd25519SigV2 { + let num_participants = threshold.value(); + // collect all participants + 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::with_capacity(participants.len()); + + let mut message: [u8; 32] = [0u8; 32]; + rng.fill_bytes(&mut message); + let message = message.to_vec(); + + for ((p, keygen_out), (p_redundancy, presign)) in key_packages.iter().zip(result) { + assert_eq!(p, p_redundancy); + let protocol = eddsa::sign::sign_v2( + &participants, + threshold, + *p, + coordinator, + keygen_out.clone(), + presign.clone(), + message.clone(), + ) + .map(|sig| Box::new(sig) as Box>) + .expect("Signing should succeed"); + protocols.push((*p, protocol)); + } + + FrostEd25519SigV2 { + protocols, + index: coordinator_index, + presig: result[coordinator_index].1.clone(), + key_packages, + message, + } +} + +pub struct FrostEd25519SigV1 { + pub protocols: Vec<( + Participant, + Box>, + )>, + pub index: usize, + pub key_packages: Vec<(Participant, eddsa::KeygenOutput)>, + pub message: Vec, +} + +pub type FrostEd25519PreparedPresig = PreparedPresig; +pub struct FrostEd25519SigV2 { + pub protocols: Vec<( + Participant, + Box>, + )>, + pub index: usize, + pub presig: eddsa::PresignOutput, + pub key_packages: Vec<(Participant, eddsa::KeygenOutput)>, + pub message: Vec, +} + +pub fn prepare_ckd( + threshold: ReconstructionLowerBound, + rng: &mut R, +) -> PreparedCkdPackage { + let num_participants = threshold.value(); + // collect all participants + let participants = generate_participants_with_random_ids(num_participants, rng); + let key_packages = run_keygen(&participants, *MAX_MALICIOUS + 1, rng); + + // choose a coordinator at random + let coordinator_index = rng.gen_range(0..num_participants); + let coordinator = participants[coordinator_index]; + + let mut protocols = Vec::with_capacity(participants.len()); + + let mut app_id: [u8; 32] = [0u8; 32]; + rng.fill_bytes(&mut app_id); + let app_id = ckd::AppId::try_new(app_id).expect("cannot fail"); + + let scalar_rng = MockCryptoRng::seed_from_u64(rng.next_u64()); + let app_sk = ckd::Scalar::random(scalar_rng); + let app_pk = ckd::ElementG1::generator() * app_sk; + + for (p, keygen_out) in &key_packages { + let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); + let protocol = ckd::protocol::ckd( + &participants, + coordinator, + *p, + keygen_out.clone(), + app_id.clone(), + app_pk, + rng_p, + ) + .map(|ckd| Box::new(ckd) as Box>) + .expect("Ckd should succeed"); + protocols.push((*p, protocol)); + } + + PreparedCkdPackage { + protocols, + index: coordinator_index, + key_packages, + app_id, + app_pk, + } +} + +pub struct PreparedCkdPackage { + pub protocols: Vec<( + Participant, + Box>, + )>, + pub index: usize, + pub key_packages: Vec<(Participant, ckd::KeygenOutput)>, + pub app_id: ckd::AppId, + pub app_pk: ckd::ElementG1, +} diff --git a/crates/threshold-signatures/benches/bench_utils/mod.rs b/crates/threshold-signatures/benches/bench_utils/mod.rs new file mode 100644 index 000000000..bd803f7ce --- /dev/null +++ b/crates/threshold-signatures/benches/bench_utils/mod.rs @@ -0,0 +1,116 @@ +#![allow( + dead_code, + unused_imports, + clippy::missing_panics_doc, + clippy::indexing_slicing +)] + +mod dkg; +mod frost_eddsa; +mod ot_based_ecdsa; +mod robust_ecdsa; + +pub use dkg::*; +pub use frost_eddsa::*; +pub use ot_based_ecdsa::*; +pub use robust_ecdsa::*; + +use average::{Estimate, Quantile, Variance}; +use k256::AffinePoint; +use std::{env, sync::LazyLock}; + +use threshold_signatures::{ + ecdsa::{self, Scalar}, + participants::Participant, + protocol::Protocol, + test_utils::Simulator, + ReconstructionLowerBound, +}; + +// fix malicious number of participants +pub static MAX_MALICIOUS: LazyLock = std::sync::LazyLock::new(|| { + env::var("MAX_MALICIOUS") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(6) +}); + +// fix number of samples +pub static SAMPLE_SIZE: LazyLock = std::sync::LazyLock::new(|| { + env::var("SAMPLE_SIZE") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(15) +}); + +pub static RECONSTRUCTION_LOWER_BOUND: LazyLock = + LazyLock::new(|| ReconstructionLowerBound::from(*MAX_MALICIOUS + 1)); + +/// This helps defining a generic type for the benchmarks prepared outputs +pub struct PreparedOutputs { + pub participant: Participant, + pub protocol: Box>, + pub simulator: Simulator, +} +pub struct PreparedPresig { + pub protocols: Vec<(Participant, Box>)>, + pub key_packages: Vec<(Participant, KeygenOutput)>, + pub participants: Vec, +} + +pub struct PreparedSig { + pub protocols: Vec<( + Participant, + Box>, + )>, + pub index: usize, + pub presig: RerandomizedPresignOutput, + pub derived_pk: AffinePoint, + pub msg_hash: Scalar, +} + +#[allow(clippy::cast_precision_loss)] +/// Analyzes the size of the received data by a participant across the entire protocol +pub fn analyze_received_sizes( + sizes: &[usize], + is_print: bool, +) -> (usize, usize, f64, f64, f64, f64) { + if sizes.len() <= 1 { + return (0, 0, 0.0, 0.0, 0.0, 0.0); + } + let min = *sizes.iter().min().expect("Minimum should exist"); + let max = *sizes.iter().max().expect("Maximum should exist"); + let avg = sizes.iter().sum::() as f64 / sizes.len() as f64; + + let data = sizes.iter().map(|&x| x as f64).collect::>(); + + // Median (0.5 quantile) + let mut quantile = Quantile::new(0.5); + // Variance + Std Dev + let mut variance_est = Variance::new(); + + for &x in &data { + variance_est.add(x); + quantile.add(x); + } + + let median = quantile.quantile(); + let variance = variance_est.sample_variance(); + let std_dev = variance.sqrt(); + + if is_print { + println!("Analysis for received messages:"); + println!( + "\ + min:{min}B\t\ + max:{max}B\t\ + average:{avg}B\t\ + median:{median}B\t\ + variance:{variance}B\t\ + standard deviation:{std_dev}B + " + ); + } + + (min, max, avg, median, variance, std_dev) +} diff --git a/crates/threshold-signatures/benches/bench_utils/ot_based_ecdsa.rs b/crates/threshold-signatures/benches/bench_utils/ot_based_ecdsa.rs new file mode 100644 index 000000000..22bca5f32 --- /dev/null +++ b/crates/threshold-signatures/benches/bench_utils/ot_based_ecdsa.rs @@ -0,0 +1,176 @@ +use rand::Rng; +use rand_core::{CryptoRngCore, SeedableRng}; + +use threshold_signatures::{ + ecdsa::{ + self, + ot_based_ecdsa::{ + self, + triples::{generate_triple_many, TriplePub, TripleShare}, + }, + }, + participants::Participant, + protocol::Protocol, + test_utils::{ + ecdsa_generate_rerandpresig_args, generate_participants_with_random_ids, run_keygen, + MockCryptoRng, + }, + ReconstructionLowerBound, +}; + +use super::{PreparedPresig, PreparedSig}; + +/// Used to prepare ot based ecdsa triples for benchmarking +pub fn ot_ecdsa_prepare_triples( + participant_num: usize, + threshold: ReconstructionLowerBound, + rng: &mut R, +) -> OTECDSAPreparedTriples { + let mut protocols: Vec<(_, Box>)> = + Vec::with_capacity(participant_num); + let participants = generate_participants_with_random_ids(participant_num, rng); + + for p in &participants { + let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); + let protocol = generate_triple_many::<2>(&participants, *p, threshold, rng_p) + .expect("Triple generation should succeed"); + protocols.push((*p, Box::new(protocol))); + } + OTECDSAPreparedTriples { + protocols, + participants, + } +} + +/// Used to prepare ot based ecdsa presignatures for benchmarking +pub fn ot_ecdsa_prepare_presign( + two_triples: &[(Participant, Vec<(TripleShare, TriplePub)>)], + threshold: ReconstructionLowerBound, + rng: &mut R, +) -> OTECDSAPreparedPresig { + let mut two_triples = two_triples.to_owned(); + two_triples.sort_by_key(|(p, _)| *p); + + // collect all participants + let participants: Vec = two_triples + .iter() + .map(|(participant, _)| *participant) + .collect(); + + let (shares, pubs): (Vec<_>, Vec<_>) = two_triples.into_iter().flat_map(|(_, vec)| vec).unzip(); + // split shares into shares0 and shares 1 and pubs into pubs0 and pubs1 + let (shares0, shares1) = split_even_odd(shares); + // split shares into shares0 and shares 1 and pubs into pubs0 and pubs1 + let (pub0, pub1) = split_even_odd(pubs); + + let key_packages = run_keygen(&participants, threshold, rng); + + 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) + { + let protocol = ot_based_ecdsa::presign::presign( + &participants, + p, + ot_based_ecdsa::PresignArguments { + triple0: (share0, pub0[0].clone()), + triple1: (share1, pub1[0].clone()), + keygen_out, + threshold, + }, + ) + .expect("Presigning should succeed"); + protocols.push((p, Box::new(protocol))); + } + OTECDSAPreparedPresig { + protocols, + key_packages, + participants, + } +} + +/// Used to prepare ot based ecdsa signatures for benchmarking +pub fn ot_ecdsa_prepare_sign( + result: &[(Participant, ot_based_ecdsa::PresignOutput)], + threshold: ReconstructionLowerBound, + pk: frost_secp256k1::VerifyingKey, + rng: &mut R, +) -> OTECDSAPreparedSig { + // collect all participants + let participants: Vec = + result.iter().map(|(participant, _)| *participant).collect(); + + // choose a coordinator at random + let index = rng.gen_range(0..result.len()); + let coordinator = result[index].0; + + let (args, msg_hash) = + ecdsa_generate_rerandpresig_args(rng, &participants, pk, result[0].1.big_r); + let derived_pk = args + .tweak + .derive_verifying_key(&pk) + .to_element() + .to_affine(); + + let result = result + .iter() + .map(|(p, presig)| { + ( + *p, + ot_based_ecdsa::RerandomizedPresignOutput::rerandomize_presign(presig, &args) + .expect("Rerandomizing presignature should succeed"), + ) + }) + .collect::>(); + + let mut protocols = Vec::with_capacity(result.len()); + + for (p, presignature) in result.clone() { + let protocol = ot_based_ecdsa::sign::sign( + args.participants.participants(), + coordinator, + threshold, + p, + derived_pk, + presignature, + msg_hash, + ) + .map(|sig| Box::new(sig) as Box>) + .expect("Signing should succeed"); + protocols.push((p, protocol)); + } + OTECDSAPreparedSig { + protocols, + index, + presig: result[index].1.clone(), + derived_pk, + msg_hash, + } +} + +pub fn split_even_odd(v: Vec) -> (Vec, Vec) { + let mut even = Vec::with_capacity(v.len() / 2 + 1); + let mut odd = Vec::with_capacity(v.len() / 2); + for (i, x) in v.into_iter().enumerate() { + if i % 2 == 0 { + even.push(x); + } else { + odd.push(x); + } + } + (even, odd) +} + +type TriplesProtocols = Vec<( + Participant, + Box>>, +)>; +pub struct OTECDSAPreparedTriples { + pub protocols: TriplesProtocols, + pub participants: Vec, +} + +pub type OTECDSAPreparedPresig = PreparedPresig; +pub type OTECDSAPreparedSig = PreparedSig; diff --git a/crates/threshold-signatures/benches/bench_utils/robust_ecdsa.rs b/crates/threshold-signatures/benches/bench_utils/robust_ecdsa.rs new file mode 100644 index 000000000..02e323321 --- /dev/null +++ b/crates/threshold-signatures/benches/bench_utils/robust_ecdsa.rs @@ -0,0 +1,109 @@ +use rand::Rng; +use rand_core::{CryptoRngCore, SeedableRng}; + +use threshold_signatures::{ + ecdsa::{self, robust_ecdsa}, + participants::Participant, + protocol::Protocol, + test_utils::{ + ecdsa_generate_rerandpresig_args, generate_participants_with_random_ids, run_keygen, + MockCryptoRng, + }, + MaxMalicious, +}; + +use super::{PreparedPresig, PreparedSig, MAX_MALICIOUS}; + +/// Used to prepare robust ecdsa presignatures for benchmarking +pub fn robust_ecdsa_prepare_presign( + num_participants: usize, + rng: &mut R, +) -> 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<_> = Vec::with_capacity(participants.len()); + + for (p, keygen_out) in &key_packages { + let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); + let protocol = robust_ecdsa::presign::presign( + &participants, + *p, + robust_ecdsa::PresignArguments { + keygen_out: keygen_out.clone(), + max_malicious: (*MAX_MALICIOUS).into(), + }, + rng_p, + ) + .map(|presig| Box::new(presig) as Box>) + .expect("Presignature should succeed"); + protocols.push((*p, protocol)); + } + RobustECDSAPreparedPresig { + protocols, + key_packages, + participants, + } +} + +/// Used to prepare robust ecdsa signatures for benchmarking +pub fn robust_ecdsa_prepare_sign( + result: &[(Participant, robust_ecdsa::PresignOutput)], + max_malicious: MaxMalicious, + pk: frost_secp256k1::VerifyingKey, + rng: &mut R, +) -> RobustECDSASig { + // collect all participants + let participants: Vec = + result.iter().map(|(participant, _)| *participant).collect(); + + // choose a coordinator at random + let coordinator_index = rng.gen_range(0..result.len()); + let coordinator = result[coordinator_index].0; + + let (args, msg_hash) = + ecdsa_generate_rerandpresig_args(rng, &participants, pk, result[0].1.big_r); + let derived_pk = args + .tweak + .derive_verifying_key(&pk) + .to_element() + .to_affine(); + + let result = result + .iter() + .map(|(p, presig)| { + ( + *p, + robust_ecdsa::RerandomizedPresignOutput::rerandomize_presign(presig, &args) + .expect("Rerandomizing presignature should succeed"), + ) + }) + .collect::>(); + + let mut protocols = Vec::with_capacity(result.len()); + + for (p, presignature) in result.clone() { + let protocol = robust_ecdsa::sign::sign( + &participants, + coordinator, + max_malicious, + p, + derived_pk, + presignature, + msg_hash, + ) + .map(|sig| Box::new(sig) as Box>) + .expect("Signing should succeed"); + protocols.push((p, protocol)); + } + RobustECDSASig { + protocols, + index: coordinator_index, + presig: result[coordinator_index].1.clone(), + derived_pk, + msg_hash, + } +} + +pub type RobustECDSAPreparedPresig = + PreparedPresig; +pub type RobustECDSASig = PreparedSig; diff --git a/crates/threshold-signatures/src/crypto/polynomials/commitment.rs b/crates/threshold-signatures/src/crypto/polynomials/commitment.rs new file mode 100644 index 000000000..7538dc6ae --- /dev/null +++ b/crates/threshold-signatures/src/crypto/polynomials/commitment.rs @@ -0,0 +1,193 @@ +use frost_core::{keys::CoefficientCommitment, Group, Scalar}; +use subtle::ConstantTimeEq; + +use crate::crypto::ciphersuite::Ciphersuite; +use crate::errors::ProtocolError; +use crate::participants::Participant; + +use serde::{Deserialize, Deserializer, Serialize}; + +use super::batch_compute_lagrange_coefficients; + +/// Contains the committed coefficients of a polynomial i.e. coeff * G +#[derive(Clone, Debug, PartialEq)] +pub struct PolynomialCommitment { + /// The committed coefficients which are group elements + /// (elliptic curve points) + coefficients: Vec>, +} + +impl PolynomialCommitment { + /// Creates a `PolynomialCommitment` out of a vector of `CoefficientCommitment` + /// This function raises Error if the vector is empty or if it is the all identity vector + pub fn new(coefcommitments: &[CoefficientCommitment]) -> Result { + // count the number of zero coeffs before spotting the first non-zero from the back + let count = coefcommitments + .iter() + .rposition(|x| x.value() != C::Group::identity()) + .map_or(0, |i| i + 1); + + if count == 0 { + return Err(ProtocolError::EmptyOrZeroCoefficients); + } + + let new_coefficients: Vec<_> = coefcommitments.iter().take(count).copied().collect(); + + Ok(Self { + coefficients: new_coefficients, + }) + } + + /// Returns the coefficients of the + pub fn get_coefficients(&self) -> Vec> { + self.coefficients.clone() + } + + /// Outputs the degree of the committed polynomial + pub fn degree(&self) -> usize { + self.coefficients.len() - 1 + } + + /// Adds two `PolynomialCommitment` together + /// and raises an error if the result is the identity + pub fn add(&self, rhs: &Self) -> Result { + let max_len = self.coefficients.len().max(rhs.coefficients.len()); + let mut coefficients: Vec> = Vec::with_capacity(max_len); + + // add polynomials even if they have different lengths + for i in 0..max_len { + let a = self.coefficients.get(i); + let b = rhs.coefficients.get(i); + + let sum = match (a, b) { + (Some(a), Some(b)) => CoefficientCommitment::new(a.value() + b.value()), + (Some(a), None) => *a, + (None, Some(b)) => *b, + (None, None) => + // should be unreachable + { + return Err(ProtocolError::EmptyOrZeroCoefficients) + } + }; + coefficients.push(sum); + } + + // raises error in the case that the two polynomials are opposite + Self::new(&coefficients) + } + + /// Evaluates the committed polynomial on zero (outputs the constant term) + /// Returns error if the polynomial is empty + pub fn eval_at_zero(&self) -> Result, ProtocolError> { + self.coefficients + .first() + .copied() + .ok_or(ProtocolError::EmptyOrZeroCoefficients) + } + + /// Evaluates the committed polynomial at a specific value + pub fn eval_at_point( + &self, + point: Scalar, + ) -> Result, ProtocolError> { + if self.coefficients.is_empty() { + return Err(ProtocolError::EmptyOrZeroCoefficients); + } + let mut out = C::Group::identity(); + for c in self.coefficients.iter().rev() { + out = out * point + c.value(); + } + Ok(CoefficientCommitment::new(out)) + } + + /// Evaluates the committed polynomial at a participant identifier. + pub fn eval_at_participant( + &self, + participant: Participant, + ) -> Result, ProtocolError> { + let id = participant.scalar::(); + self.eval_at_point(id) + } + + /// Computes polynomial interpolation on the exponent at a specific point + /// using a sequence of sorted coefficient commitments. + /// Input requirements: + /// * identifiers MUST be pairwise distinct and of length greater than 1 + /// * shares and identifiers must be of same length + /// * identifier[i] corresponds to share[i] + // Returns error if shares' and identifiers' lengths are distinct or less than or equals to 1. + pub fn eval_exponent_interpolation( + identifiers: &[Scalar], + shares: &[CoefficientCommitment], + point: Option<&Scalar>, + ) -> Result, ProtocolError> + where + Scalar: ConstantTimeEq, + { + let mut interpolation = C::Group::identity(); + // raise Error if the lengths are not the same + // or the number of identifiers (<= 1) + if identifiers.len() != shares.len() || identifiers.len() <= 1 { + return Err(ProtocolError::InvalidInterpolationArguments); + } + + // Compute the Lagrange coefficients in batch + let lagrange_coefficients = batch_compute_lagrange_coefficients::(identifiers, point)?; + + // Compute y = g^f(point) via polynomial interpolation of these points of f + for (lagrange_coefficient, share) in lagrange_coefficients.iter().zip(shares) { + interpolation = interpolation + (share.value() * lagrange_coefficient.0); + } + + Ok(CoefficientCommitment::new(interpolation)) + } + + /// Extends the Commited Polynomial with an extra value as a constant + /// Used usually after sending a smaller polynomial to prevent serialization from + /// failing if the constant term is the identity + pub fn extend_with_identity(&self) -> Result { + let mut coeffcommitment = vec![CoefficientCommitment::::new(C::Group::identity())]; + coeffcommitment.extend(self.get_coefficients()); + Self::new(&coeffcommitment) + } + + /// Set the constant value of this polynomial to a new group element + /// Aborts if the output polynomial would be the identity or empty + pub fn set_non_identity_constant( + &mut self, + v: CoefficientCommitment, + ) -> Result<(), ProtocolError> { + let coefficients_len = self.coefficients.len(); + self.coefficients + .first_mut() + .map_or(Err(ProtocolError::EmptyOrZeroCoefficients), |first| { + if v.value() == C::Group::identity() && coefficients_len == 1 { + Err(ProtocolError::EmptyOrZeroCoefficients) + } else { + *first = v; + Ok(()) + } + }) + } +} + +impl Serialize for PolynomialCommitment { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.coefficients.serialize(serializer) + } +} + +// Deserialization enforcing non-empty vecs and non all-identity PolynomialCommitments +impl<'de, C: Ciphersuite> Deserialize<'de> for PolynomialCommitment { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let coefficients = Vec::>::deserialize(deserializer)?; + Self::new(&coefficients) + .map_err(|err| serde::de::Error::custom(format!("ProtocolError: {err}"))) + } +} diff --git a/crates/threshold-signatures/src/crypto/polynomials.rs b/crates/threshold-signatures/src/crypto/polynomials/mod.rs similarity index 71% rename from crates/threshold-signatures/src/crypto/polynomials.rs rename to crates/threshold-signatures/src/crypto/polynomials/mod.rs index f884b2b92..3ba7dad8f 100644 --- a/crates/threshold-signatures/src/crypto/polynomials.rs +++ b/crates/threshold-signatures/src/crypto/polynomials/mod.rs @@ -1,394 +1,14 @@ -use frost_core::{ - keys::CoefficientCommitment, serialization::SerializableScalar, Field, Group, Scalar, -}; -use rand_core::CryptoRngCore; -use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; - -use super::ciphersuite::Ciphersuite; -use crate::{errors::ProtocolError, participants::Participant}; - -use serde::{Deserialize, Deserializer, Serialize}; - -/// Polynomial structure of non-empty or non-zero coefficients -/// Represents a polynomial with coefficients in the scalar field of the curve. -pub struct Polynomial { - /// The coefficients of our polynomial, - /// The 0 term being the constant term of the polynomial - coefficients: Vec>, -} - -impl Polynomial { - /// Constructs the polynomial out of scalars - /// The first scalar (coefficients[0]) is the constant term - /// The highest degree null coefficients are dropped - pub fn new(coefficients: &[Scalar]) -> Result { - if coefficients.is_empty() { - return Err(ProtocolError::EmptyOrZeroCoefficients); - } - // count the number of zero coeffs before spotting the first non-zero - let count = coefficients - .iter() - .rev() - .take_while(|x| *x == &::Field::zero()) - .count(); - - // get the degree + 1 of the polynomial - let last_non_null = coefficients.len() - count; - - let new_coefficients = coefficients - .get(..last_non_null) - .ok_or(ProtocolError::EmptyOrZeroCoefficients)? - .to_vec(); - if new_coefficients.is_empty() { - Err(ProtocolError::EmptyOrZeroCoefficients) - } else { - Ok(Self { - coefficients: new_coefficients, - }) - } - } - - /// Returns the coefficients of the polynomial - pub fn get_coefficients(&self) -> Vec> { - self.coefficients.clone() - } - - /// Creates a random polynomial p of the given degree - /// and sets p(0) = secret - /// if the secret is not given then it is picked at random - pub fn generate_polynomial( - secret: Option>, - degree: usize, - rng: &mut impl CryptoRngCore, - ) -> Result { - let poly_size = degree - .checked_add(1) - .ok_or(ProtocolError::IntegerOverflow)?; - - let poly_alloc_size = poly_size - .checked_mul(Self::coefficient_size()) - .ok_or(ProtocolError::IntegerOverflow)?; - // @dev why not usize::MAX? https://github.com/near/threshold-signatures/pull/163#discussion_r2447505305 - // Allocations must fit within isize range. - if poly_alloc_size > isize::MAX as usize { - return Err(ProtocolError::IntegerOverflow); - } - - let mut coefficients = Vec::with_capacity(poly_size); - // insert the secret share if exists - let secret = secret.unwrap_or_else(|| ::Field::random(rng)); - - coefficients.push(secret); - for _ in 1..poly_size { - coefficients.push(::Field::random(rng)); - } - // fails only if: - // * polynomial is of degree 0 and the constant term is 0 - // * polynomial degree is the max of usize, and so degree + 1 is 0 - // such cases never happen in a classic (non-malicious) implementations - Self::new(&coefficients) - } - - /// Returns the constant term or error in case the polynomial is empty - pub fn eval_at_zero(&self) -> Result, ProtocolError> { - let result = self - .coefficients - .first() - .copied() - .ok_or(ProtocolError::EmptyOrZeroCoefficients)?; - Ok(SerializableScalar(result)) - } +mod commitment; +mod polynomial; - /// Evaluates a polynomial at a certain scalar - /// Evaluate the polynomial with the given coefficients - /// at the point using Horner's method. - /// Implements [`polynomial_evaluate`] from the spec: - /// - /// Returns error if the polynomial is empty - pub fn eval_at_point(&self, point: Scalar) -> Result, ProtocolError> { - if self.coefficients.is_empty() { - return Err(ProtocolError::EmptyOrZeroCoefficients); - } - if point == ::Field::zero() { - self.eval_at_zero() - } else { - let mut value = ::Field::zero(); - for coeff in self.coefficients.iter().rev() { - value = value * point + *coeff; - } - Ok(SerializableScalar(value)) - } - } - - /// Evaluates a polynomial at the identifier of a participant - pub fn eval_at_participant( - &self, - participant: Participant, - ) -> Result, ProtocolError> { - let id = participant.scalar::(); - self.eval_at_point(id) - } - - /// Computes polynomial interpolation at a specific point - /// using a sequence of sorted elements - /// Input requirements: - /// * identifiers MUST be pairwise distinct and of length greater than 1 - /// * shares and identifiers must be of same length - /// * identifier[i] corresponds to share[i] - // Returns error if shares' and identifiers' lengths are distinct or less than or equals to 1 - pub fn eval_interpolation( - identifiers: &[Scalar], - shares: &[SerializableScalar], - point: Option<&Scalar>, - ) -> Result, ProtocolError> - where - Scalar: ConstantTimeEq, - { - let mut interpolation = ::Field::zero(); - // raise Error if the lengths are not the same - // or the number of identifiers (<= 1) - if identifiers.len() != shares.len() || identifiers.len() <= 1 { - return Err(ProtocolError::InvalidInterpolationArguments); - } - - // Compute the Lagrange coefficients in batch - let lagrange_coefficients = batch_compute_lagrange_coefficients::(identifiers, point)?; - - // Compute y = f(point) via polynomial interpolation of these points of f - for (lagrange_coefficient, share) in lagrange_coefficients.iter().zip(shares) { - interpolation = interpolation + (lagrange_coefficient.0 * share.0); - } - - Ok(SerializableScalar(interpolation)) - } - - /// Commits to a polynomial returning a sequence of group coefficients - /// Creates a commitment vector of coefficients * G - pub fn commit_polynomial(&self) -> Result, ProtocolError> { - // Computes the multiplication of every coefficient of p with the generator G - let coef_commitment = self - .coefficients - .iter() - .map(|c| CoefficientCommitment::new(C::Group::generator() * *c)) - .collect::>(); - // self cannot be the zero polynomial because there is no way - // to create such a polynomial using this library. This implies the panic never occurs. - PolynomialCommitment::new(&coef_commitment) - } - - /// Set the constant value of this polynomial to a new scalar - /// Abort if the output polynomial would be zero or empty - pub fn set_nonzero_constant(&mut self, v: Scalar) -> Result<(), ProtocolError> { - let coefficients_len = self.coefficients.len(); - self.coefficients - .first_mut() - .map_or(Err(ProtocolError::EmptyOrZeroCoefficients), |first| { - if v == ::Field::zero() && coefficients_len == 1 { - Err(ProtocolError::EmptyOrZeroCoefficients) - } else { - *first = v; - Ok(()) - } - }) - } - - /// Extends the Polynomial with an extra value as a constant - /// Used usually after sending a smaller polynomial to prevent serialization from - /// failing if the constant term is the identity - pub fn extend_with_zero(&self) -> Result { - let mut coeffcommitment = vec![::Field::zero()]; - coeffcommitment.extend(self.get_coefficients()); - Self::new(&coeffcommitment) - } - - fn coefficient_size() -> usize { - core::mem::size_of::<<::Field as Field>::Scalar>() - } -} +pub use commitment::PolynomialCommitment; +pub use polynomial::Polynomial; -/******************* Polynomial Commitment *******************/ -/// Contains the committed coefficients of a polynomial i.e. coeff * G -#[derive(Clone, Debug, PartialEq)] -pub struct PolynomialCommitment { - /// The committed coefficients which are group elements - /// (elliptic curve points) - coefficients: Vec>, -} - -impl PolynomialCommitment { - /// Creates a `PolynomialCommitment` out of a vector of `CoefficientCommitment` - /// This function raises Error if the vector is empty or if it is the all identity vector - pub fn new(coefcommitments: &[CoefficientCommitment]) -> Result { - // count the number of zero coeffs before spotting the first non-zero from the back - let count = coefcommitments - .iter() - .rposition(|x| x.value() != C::Group::identity()) - .map_or(0, |i| i + 1); - - if count == 0 { - return Err(ProtocolError::EmptyOrZeroCoefficients); - } - - let new_coefficients: Vec<_> = coefcommitments.iter().take(count).copied().collect(); - - Ok(Self { - coefficients: new_coefficients, - }) - } - - /// Returns the coefficients of the - pub fn get_coefficients(&self) -> Vec> { - self.coefficients.clone() - } - - /// Outputs the degree of the committed polynomial - pub fn degree(&self) -> usize { - self.coefficients.len() - 1 - } - - /// Adds two `PolynomialCommitment` together - /// and raises an error if the result is the identity - pub fn add(&self, rhs: &Self) -> Result { - let max_len = self.coefficients.len().max(rhs.coefficients.len()); - let mut coefficients: Vec> = Vec::with_capacity(max_len); - - // add polynomials even if they have different lengths - for i in 0..max_len { - let a = self.coefficients.get(i); - let b = rhs.coefficients.get(i); - - let sum = match (a, b) { - (Some(a), Some(b)) => CoefficientCommitment::new(a.value() + b.value()), - (Some(a), None) => *a, - (None, Some(b)) => *b, - (None, None) => - // should be unreachable - { - return Err(ProtocolError::EmptyOrZeroCoefficients) - } - }; - coefficients.push(sum); - } - - // raises error in the case that the two polynomials are opposite - Self::new(&coefficients) - } - - /// Evaluates the committed polynomial on zero (outputs the constant term) - /// Returns error if the polynomial is empty - pub fn eval_at_zero(&self) -> Result, ProtocolError> { - self.coefficients - .first() - .copied() - .ok_or(ProtocolError::EmptyOrZeroCoefficients) - } - - /// Evaluates the committed polynomial at a specific value - pub fn eval_at_point( - &self, - point: Scalar, - ) -> Result, ProtocolError> { - if self.coefficients.is_empty() { - return Err(ProtocolError::EmptyOrZeroCoefficients); - } - let mut out = C::Group::identity(); - for c in self.coefficients.iter().rev() { - out = out * point + c.value(); - } - Ok(CoefficientCommitment::new(out)) - } - - /// Evaluates the committed polynomial at a participant identifier. - pub fn eval_at_participant( - &self, - participant: Participant, - ) -> Result, ProtocolError> { - let id = participant.scalar::(); - self.eval_at_point(id) - } - - /// Computes polynomial interpolation on the exponent at a specific point - /// using a sequence of sorted coefficient commitments. - /// Input requirements: - /// * identifiers MUST be pairwise distinct and of length greater than 1 - /// * shares and identifiers must be of same length - /// * identifier[i] corresponds to share[i] - // Returns error if shares' and identifiers' lengths are distinct or less than or equals to 1. - pub fn eval_exponent_interpolation( - identifiers: &[Scalar], - shares: &[CoefficientCommitment], - point: Option<&Scalar>, - ) -> Result, ProtocolError> - where - Scalar: ConstantTimeEq, - { - let mut interpolation = C::Group::identity(); - // raise Error if the lengths are not the same - // or the number of identifiers (<= 1) - if identifiers.len() != shares.len() || identifiers.len() <= 1 { - return Err(ProtocolError::InvalidInterpolationArguments); - } - - // Compute the Lagrange coefficients in batch - let lagrange_coefficients = batch_compute_lagrange_coefficients::(identifiers, point)?; - - // Compute y = g^f(point) via polynomial interpolation of these points of f - for (lagrange_coefficient, share) in lagrange_coefficients.iter().zip(shares) { - interpolation = interpolation + (share.value() * lagrange_coefficient.0); - } - - Ok(CoefficientCommitment::new(interpolation)) - } - - /// Extends the Commited Polynomial with an extra value as a constant - /// Used usually after sending a smaller polynomial to prevent serialization from - /// failing if the constant term is the identity - pub fn extend_with_identity(&self) -> Result { - let mut coeffcommitment = vec![CoefficientCommitment::::new(C::Group::identity())]; - coeffcommitment.extend(self.get_coefficients()); - Self::new(&coeffcommitment) - } - - /// Set the constant value of this polynomial to a new group element - /// Aborts if the output polynomial would be the identity or empty - pub fn set_non_identity_constant( - &mut self, - v: CoefficientCommitment, - ) -> Result<(), ProtocolError> { - let coefficients_len = self.coefficients.len(); - self.coefficients - .first_mut() - .map_or(Err(ProtocolError::EmptyOrZeroCoefficients), |first| { - if v.value() == C::Group::identity() && coefficients_len == 1 { - Err(ProtocolError::EmptyOrZeroCoefficients) - } else { - *first = v; - Ok(()) - } - }) - } -} - -impl Serialize for PolynomialCommitment { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.coefficients.serialize(serializer) - } -} +use frost_core::{serialization::SerializableScalar, Field, Group, Scalar}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; -// Deserialization enforcing non-empty vecs and non all-identity PolynomialCommitments -impl<'de, C: Ciphersuite> Deserialize<'de> for PolynomialCommitment { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let coefficients = Vec::>::deserialize(deserializer)?; - Self::new(&coefficients) - .map_err(|err| serde::de::Error::custom(format!("ProtocolError: {err}"))) - } -} +use super::ciphersuite::Ciphersuite; +use crate::errors::ProtocolError; /// Computes the Lagrange coefficient (a.k.a. Lagrange basis polynomial) /// evaluated at point x. @@ -627,9 +247,11 @@ mod test { use super::*; use crate::errors::ProtocolError; + use crate::participants::Participant; use crate::test_utils::{ generate_participants, generate_participants_with_random_ids, MockCryptoRng, }; + use frost_core::keys::CoefficientCommitment; use frost_core::Field; use frost_secp256k1::{Secp256K1Group, Secp256K1ScalarField, Secp256K1Sha256}; use k256::Scalar; diff --git a/crates/threshold-signatures/src/crypto/polynomials/polynomial.rs b/crates/threshold-signatures/src/crypto/polynomials/polynomial.rs new file mode 100644 index 000000000..aa840fa57 --- /dev/null +++ b/crates/threshold-signatures/src/crypto/polynomials/polynomial.rs @@ -0,0 +1,208 @@ +use frost_core::{ + keys::CoefficientCommitment, serialization::SerializableScalar, Field, Group, Scalar, +}; +use rand_core::CryptoRngCore; +use subtle::ConstantTimeEq; + +use crate::crypto::ciphersuite::Ciphersuite; +use crate::errors::ProtocolError; +use crate::participants::Participant; + +use super::{batch_compute_lagrange_coefficients, PolynomialCommitment}; + +/// Polynomial structure of non-empty or non-zero coefficients +/// Represents a polynomial with coefficients in the scalar field of the curve. +pub struct Polynomial { + /// The coefficients of our polynomial, + /// The 0 term being the constant term of the polynomial + coefficients: Vec>, +} + +impl Polynomial { + /// Constructs the polynomial out of scalars + /// The first scalar (coefficients[0]) is the constant term + /// The highest degree null coefficients are dropped + pub fn new(coefficients: &[Scalar]) -> Result { + if coefficients.is_empty() { + return Err(ProtocolError::EmptyOrZeroCoefficients); + } + // count the number of zero coeffs before spotting the first non-zero + let count = coefficients + .iter() + .rev() + .take_while(|x| *x == &::Field::zero()) + .count(); + + // get the degree + 1 of the polynomial + let last_non_null = coefficients.len() - count; + + let new_coefficients = coefficients + .get(..last_non_null) + .ok_or(ProtocolError::EmptyOrZeroCoefficients)? + .to_vec(); + if new_coefficients.is_empty() { + Err(ProtocolError::EmptyOrZeroCoefficients) + } else { + Ok(Self { + coefficients: new_coefficients, + }) + } + } + + /// Returns the coefficients of the polynomial + pub fn get_coefficients(&self) -> Vec> { + self.coefficients.clone() + } + + /// Creates a random polynomial p of the given degree + /// and sets p(0) = secret + /// if the secret is not given then it is picked at random + pub fn generate_polynomial( + secret: Option>, + degree: usize, + rng: &mut impl CryptoRngCore, + ) -> Result { + let poly_size = degree + .checked_add(1) + .ok_or(ProtocolError::IntegerOverflow)?; + + let poly_alloc_size = poly_size + .checked_mul(Self::coefficient_size()) + .ok_or(ProtocolError::IntegerOverflow)?; + // @dev why not usize::MAX? https://github.com/near/threshold-signatures/pull/163#discussion_r2447505305 + // Allocations must fit within isize range. + if poly_alloc_size > isize::MAX as usize { + return Err(ProtocolError::IntegerOverflow); + } + + let mut coefficients = Vec::with_capacity(poly_size); + // insert the secret share if exists + let secret = secret.unwrap_or_else(|| ::Field::random(rng)); + + coefficients.push(secret); + for _ in 1..poly_size { + coefficients.push(::Field::random(rng)); + } + // fails only if: + // * polynomial is of degree 0 and the constant term is 0 + // * polynomial degree is the max of usize, and so degree + 1 is 0 + // such cases never happen in a classic (non-malicious) implementations + Self::new(&coefficients) + } + + /// Returns the constant term or error in case the polynomial is empty + pub fn eval_at_zero(&self) -> Result, ProtocolError> { + let result = self + .coefficients + .first() + .copied() + .ok_or(ProtocolError::EmptyOrZeroCoefficients)?; + Ok(SerializableScalar(result)) + } + + /// Evaluates a polynomial at a certain scalar + /// Evaluate the polynomial with the given coefficients + /// at the point using Horner's method. + /// Implements [`polynomial_evaluate`] from the spec: + /// + /// Returns error if the polynomial is empty + pub fn eval_at_point(&self, point: Scalar) -> Result, ProtocolError> { + if self.coefficients.is_empty() { + return Err(ProtocolError::EmptyOrZeroCoefficients); + } + if point == ::Field::zero() { + self.eval_at_zero() + } else { + let mut value = ::Field::zero(); + for coeff in self.coefficients.iter().rev() { + value = value * point + *coeff; + } + Ok(SerializableScalar(value)) + } + } + + /// Evaluates a polynomial at the identifier of a participant + pub fn eval_at_participant( + &self, + participant: Participant, + ) -> Result, ProtocolError> { + let id = participant.scalar::(); + self.eval_at_point(id) + } + + /// Computes polynomial interpolation at a specific point + /// using a sequence of sorted elements + /// Input requirements: + /// * identifiers MUST be pairwise distinct and of length greater than 1 + /// * shares and identifiers must be of same length + /// * identifier[i] corresponds to share[i] + // Returns error if shares' and identifiers' lengths are distinct or less than or equals to 1 + pub fn eval_interpolation( + identifiers: &[Scalar], + shares: &[SerializableScalar], + point: Option<&Scalar>, + ) -> Result, ProtocolError> + where + Scalar: ConstantTimeEq, + { + let mut interpolation = ::Field::zero(); + // raise Error if the lengths are not the same + // or the number of identifiers (<= 1) + if identifiers.len() != shares.len() || identifiers.len() <= 1 { + return Err(ProtocolError::InvalidInterpolationArguments); + } + + // Compute the Lagrange coefficients in batch + let lagrange_coefficients = batch_compute_lagrange_coefficients::(identifiers, point)?; + + // Compute y = f(point) via polynomial interpolation of these points of f + for (lagrange_coefficient, share) in lagrange_coefficients.iter().zip(shares) { + interpolation = interpolation + (lagrange_coefficient.0 * share.0); + } + + Ok(SerializableScalar(interpolation)) + } + + /// Commits to a polynomial returning a sequence of group coefficients + /// Creates a commitment vector of coefficients * G + pub fn commit_polynomial(&self) -> Result, ProtocolError> { + // Computes the multiplication of every coefficient of p with the generator G + let coef_commitment = self + .coefficients + .iter() + .map(|c| CoefficientCommitment::new(C::Group::generator() * *c)) + .collect::>(); + // self cannot be the zero polynomial because there is no way + // to create such a polynomial using this library. This implies the panic never occurs. + PolynomialCommitment::new(&coef_commitment) + } + + /// Set the constant value of this polynomial to a new scalar + /// Abort if the output polynomial would be zero or empty + pub fn set_nonzero_constant(&mut self, v: Scalar) -> Result<(), ProtocolError> { + let coefficients_len = self.coefficients.len(); + self.coefficients + .first_mut() + .map_or(Err(ProtocolError::EmptyOrZeroCoefficients), |first| { + if v == ::Field::zero() && coefficients_len == 1 { + Err(ProtocolError::EmptyOrZeroCoefficients) + } else { + *first = v; + Ok(()) + } + }) + } + + /// Extends the Polynomial with an extra value as a constant + /// Used usually after sending a smaller polynomial to prevent serialization from + /// failing if the constant term is the identity + pub fn extend_with_zero(&self) -> Result { + let mut coeffcommitment = vec![::Field::zero()]; + coeffcommitment.extend(self.get_coefficients()); + Self::new(&coeffcommitment) + } + + fn coefficient_size() -> usize { + core::mem::size_of::<<::Field as Field>::Scalar>() + } +}