diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index 936ed6b..2795fa2 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -30,6 +30,7 @@ zeroize = { version = "1.5.4", default-features = false, features = ["derive"] } proptest = { version = "1.0", optional = true } proptest-derive = { version = "0.3", optional = true } serde_json = { version = "1.0", optional = true } +criterion = { version = "0.4", optional = true } [dev-dependencies] curve25519-dalek = { version = "=4.0.0-pre.1", features = ["serde"] } @@ -43,6 +44,9 @@ sha2 = "0.10.2" [features] nightly = [] default = [] -# Exposes ciphersuite-generic tests for other crates to use -test-impl = ["proptest", "proptest-derive", "serde_json"] internals = [] +# Exposes ciphersuite-generic tests for other crates to use +test-impl = ["proptest", "proptest-derive", "serde_json", "criterion"] + +[lib] +bench = false diff --git a/frost-core/src/benches.rs b/frost-core/src/benches.rs new file mode 100644 index 0000000..d086c6d --- /dev/null +++ b/frost-core/src/benches.rs @@ -0,0 +1,191 @@ +//! Ciphersuite-generic benchmark functions. + +use std::collections::HashMap; + +use criterion::{BenchmarkId, Criterion, Throughput}; +use rand_core::{CryptoRng, RngCore}; + +use crate::{batch, frost, Ciphersuite, Signature, SigningKey, VerifyingKey}; + +struct Item { + vk: VerifyingKey, + sig: Signature, +} + +fn sigs_with_distinct_keys( + rng: &mut R, +) -> impl Iterator> { + let mut rng = rng.clone(); + std::iter::repeat_with(move || { + let msg = b"Bench"; + let sk = SigningKey::new(&mut rng); + let vk = VerifyingKey::from(&sk); + let sig = sk.sign(&mut rng, &msg[..]); + Item { vk, sig } + }) +} + +/// Benchmark batched signature verification with the specified ciphersuite. +pub fn bench_batch_verify( + c: &mut Criterion, + name: &str, + rng: &mut R, +) { + let mut group = c.benchmark_group(format!("Batch Verification {}", name)); + for &n in [8usize, 16, 24, 32, 40, 48, 56, 64].iter() { + group.throughput(Throughput::Elements(n as u64)); + + let sigs = sigs_with_distinct_keys::(rng) + .take(n) + .collect::>(); + + group.bench_with_input( + BenchmarkId::new("Unbatched verification", n), + &sigs, + |b, sigs| { + b.iter(|| { + for item in sigs.iter() { + let msg = b"Bench"; + + let Item { vk, sig } = item; + let _ = vk.verify(msg, sig); + } + }) + }, + ); + + group.bench_with_input( + BenchmarkId::new("Batched verification", n), + &sigs, + |b, sigs| { + let mut rng = rng.clone(); + b.iter(|| { + let mut batch = batch::Verifier::new(); + for item in sigs.iter() { + let msg = b"Bench"; + + let Item { vk, sig } = item; + batch.queue((*vk, *sig, msg)); + } + batch.verify(&mut rng) + }) + }, + ); + } + group.finish(); +} + +/// Benchmark FROST signing with the specified ciphersuite. +pub fn bench_sign( + c: &mut Criterion, + name: &str, + rng: &mut R, +) { + let mut group = c.benchmark_group(format!("FROST Signing {}", name)); + for &n in [3u16, 10, 100, 1000].iter() { + let max_signers = n; + let min_signers = (n * 2 + 2) / 3; + + group.bench_with_input( + BenchmarkId::new("Key Generation with Dealer", max_signers), + &(max_signers, min_signers), + |b, (max_signers, min_signers)| { + let mut rng = rng.clone(); + b.iter(|| { + frost::keys::keygen_with_dealer::(*max_signers, *min_signers, &mut rng) + .unwrap(); + }) + }, + ); + + let (shares, pubkeys) = + frost::keys::keygen_with_dealer::(max_signers, min_signers, rng).unwrap(); + + // Verifies the secret shares from the dealer + let key_packages: HashMap<_, _> = shares + .into_iter() + .map(|share| { + ( + share.identifier, + frost::keys::KeyPackage::try_from(share).unwrap(), + ) + }) + .collect(); + + group.bench_with_input( + BenchmarkId::new("Round 1", min_signers), + &key_packages, + |b, key_packages| { + b.iter(|| { + let participant_identifier = 1u16.try_into().expect("should be nonzero"); + frost::round1::commit( + participant_identifier, + key_packages + .get(&participant_identifier) + .unwrap() + .secret_share(), + rng, + ); + }) + }, + ); + + let mut nonces: HashMap<_, _> = HashMap::new(); + let mut commitments: HashMap<_, _> = HashMap::new(); + + for participant_index in 1..=min_signers { + let participant_identifier = participant_index.try_into().expect("should be nonzero"); + let (nonce, commitment) = frost::round1::commit( + participant_identifier, + key_packages + .get(&participant_identifier) + .unwrap() + .secret_share(), + rng, + ); + nonces.insert(participant_identifier, nonce); + commitments.insert(participant_identifier, commitment); + } + + let message = "message to sign".as_bytes(); + let comms = commitments.clone().into_values().collect(); + let signing_package = frost::SigningPackage::new(comms, message.to_vec()); + + group.bench_with_input( + BenchmarkId::new("Round 2", min_signers), + &( + key_packages.clone(), + nonces.clone(), + signing_package.clone(), + ), + |b, (key_packages, nonces, signing_package)| { + b.iter(|| { + let participant_identifier = 1u16.try_into().expect("should be nonzero"); + let key_package = key_packages.get(&participant_identifier).unwrap(); + let nonces_to_use = &nonces.get(&participant_identifier).unwrap(); + frost::round2::sign(signing_package, nonces_to_use, key_package).unwrap(); + }) + }, + ); + + let mut signature_shares = Vec::new(); + for participant_identifier in nonces.keys() { + let key_package = key_packages.get(participant_identifier).unwrap(); + let nonces_to_use = &nonces.get(participant_identifier).unwrap(); + let signature_share = + frost::round2::sign(&signing_package, nonces_to_use, key_package).unwrap(); + signature_shares.push(signature_share); + } + + group.bench_with_input( + BenchmarkId::new("Aggregate", min_signers), + &(signing_package.clone(), signature_shares.clone(), pubkeys), + |b, (signing_package, signature_shares, pubkeys)| { + b.iter(|| { + frost::aggregate(signing_package, &signature_shares[..], pubkeys).unwrap(); + }) + }, + ); + } + group.finish(); +} diff --git a/frost-core/src/frost.rs b/frost-core/src/frost.rs index e246deb..c62f624 100644 --- a/frost-core/src/frost.rs +++ b/frost-core/src/frost.rs @@ -181,6 +181,7 @@ fn derive_lagrange_coeff( /// Generated by the coordinator of the signing operation and distributed to /// each signing party +#[derive(Clone)] pub struct SigningPackage { /// The set of commitments participants published in the first round of the /// protocol. diff --git a/frost-core/src/frost/keys.rs b/frost-core/src/frost/keys.rs index f2c42c1..f07aeb4 100644 --- a/frost-core/src/frost/keys.rs +++ b/frost-core/src/frost/keys.rs @@ -60,11 +60,11 @@ where /// Generates a new uniformly random secret value using the provided RNG. // TODO: should this only be behind test? - pub fn random(mut rng: R) -> Self + pub fn random(rng: &mut R) -> Self where R: CryptoRng + RngCore, { - Self(random_nonzero::(&mut rng)) + Self(random_nonzero::(rng)) } } @@ -322,15 +322,15 @@ where pub fn keygen_with_dealer( max_signers: u16, min_signers: u16, - mut rng: R, + rng: &mut R, ) -> Result<(Vec>, PublicKeyPackage), Error> { let mut bytes = [0; 64]; rng.fill_bytes(&mut bytes); - let secret = SharedSecret::random(&mut rng); + let secret = SharedSecret::random(rng); let group_public = VerifyingKey::from(&secret); - let coefficients = generate_coefficients::(min_signers as usize - 1, &mut rng); + let coefficients = generate_coefficients::(min_signers as usize - 1, rng); let secret_shares = generate_secret_shares(&secret, max_signers, min_signers, coefficients)?; let mut signer_pubkeys: HashMap, VerifyingShare> = diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 0493273..a1cbed7 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -14,6 +14,8 @@ use std::{ use rand_core::{CryptoRng, RngCore}; pub mod batch; +#[cfg(any(test, feature = "test-impl"))] +pub mod benches; mod error; pub mod frost; mod scalar_mul; diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index 636badf..55daf86 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -44,3 +44,12 @@ serde_json = "1.0" [features] nightly = [] default = [] + +[lib] +# Disables non-criterion benchmark which is not used; prevents errors +# when using criterion-specific flags +bench = false + +[[bench]] +name = "bench" +harness = false diff --git a/frost-ed25519/benches/bench.rs b/frost-ed25519/benches/bench.rs new file mode 100644 index 0000000..04cfbfb --- /dev/null +++ b/frost-ed25519/benches/bench.rs @@ -0,0 +1,19 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use rand::thread_rng; + +use frost_ed25519::*; + +fn bench_ed25519_batch_verify(c: &mut Criterion) { + let mut rng = thread_rng(); + + frost_core::benches::bench_batch_verify::(c, "ed25519", &mut rng); +} + +fn bench_ed25519_sign(c: &mut Criterion) { + let mut rng = thread_rng(); + + frost_core::benches::bench_sign::(c, "ed25519", &mut rng); +} + +criterion_group!(benches, bench_ed25519_batch_verify, bench_ed25519_sign); +criterion_main!(benches); diff --git a/frost-ed448/Cargo.toml b/frost-ed448/Cargo.toml index aea64f4..425bf11 100644 --- a/frost-ed448/Cargo.toml +++ b/frost-ed448/Cargo.toml @@ -42,3 +42,12 @@ serde_json = "1.0" [features] nightly = [] default = [] + +[lib] +# Disables non-criterion benchmark which is not used; prevents errors +# when using criterion-specific flags +bench = false + +[[bench]] +name = "bench" +harness = false diff --git a/frost-ed448/benches/bench.rs b/frost-ed448/benches/bench.rs new file mode 100644 index 0000000..343f72f --- /dev/null +++ b/frost-ed448/benches/bench.rs @@ -0,0 +1,21 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use rand::thread_rng; + +use frost_ed448::*; + +// bench_ed448_batch_verify not included until batch verification is fixed for Ed448 +#[allow(unused)] +fn bench_ed448_batch_verify(c: &mut Criterion) { + let mut rng = thread_rng(); + + frost_core::benches::bench_batch_verify::(c, "ed448", &mut rng); +} + +fn bench_ed448_sign(c: &mut Criterion) { + let mut rng = thread_rng(); + + frost_core::benches::bench_sign::(c, "ed448", &mut rng); +} + +criterion_group!(benches, bench_ed448_sign); +criterion_main!(benches); diff --git a/frost-p256/Cargo.toml b/frost-p256/Cargo.toml index 2dc907d..5b58924 100644 --- a/frost-p256/Cargo.toml +++ b/frost-p256/Cargo.toml @@ -44,6 +44,11 @@ serde_json = "1.0" nightly = [] default = [] -# [[bench]] -# name = "bench" -# harness = false +[lib] +# Disables non-criterion benchmark which is not used; prevents errors +# when using criterion-specific flags +bench = false + +[[bench]] +name = "bench" +harness = false diff --git a/frost-p256/benches/bench.rs b/frost-p256/benches/bench.rs new file mode 100644 index 0000000..1a4d835 --- /dev/null +++ b/frost-p256/benches/bench.rs @@ -0,0 +1,19 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use rand::thread_rng; + +use frost_p256::*; + +fn bench_p256_batch_verify(c: &mut Criterion) { + let mut rng = thread_rng(); + + frost_core::benches::bench_batch_verify::(c, "p256", &mut rng); +} + +fn bench_p256_sign(c: &mut Criterion) { + let mut rng = thread_rng(); + + frost_core::benches::bench_sign::(c, "p256", &mut rng); +} + +criterion_group!(benches, bench_p256_batch_verify, bench_p256_sign); +criterion_main!(benches); diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index f195e43..da07986 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -25,7 +25,7 @@ sha2 = "0.10.2" [dev-dependencies] bincode = "1" -criterion = "0.4" +criterion = { version = "0.4", features = ["html_reports"] } ed25519-dalek = "1.0.1" ed25519-zebra = "3.0.0" lazy_static = "1.4" @@ -39,6 +39,11 @@ serde_json = "1.0" nightly = [] default = [] +[lib] +# Disables non-criterion benchmark which is not used; prevents errors +# when using criterion-specific flags +bench = false + [[bench]] name = "bench" harness = false diff --git a/frost-ristretto255/benches/bench.rs b/frost-ristretto255/benches/bench.rs index a079ed8..b53e560 100644 --- a/frost-ristretto255/benches/bench.rs +++ b/frost-ristretto255/benches/bench.rs @@ -1,72 +1,23 @@ -// use std::convert::TryFrom; +use criterion::{criterion_group, criterion_main, Criterion}; +use rand::thread_rng; -// use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; -// use rand::thread_rng; +use frost_ristretto255::*; -// use frost_ristretto255::*; +fn bench_ristretto255_batch_verify(c: &mut Criterion) { + let mut rng = thread_rng(); -// struct Item { -// vk: VerifyingKey, -// sig: Signature, -// } + frost_core::benches::bench_batch_verify::(c, "ristretto255", &mut rng); +} -// fn sigs_with_distinct_keys() -> impl Iterator { -// std::iter::repeat_with(|| { -// let msg = b"Bench"; -// let sk = SigningKey::new(thread_rng()); -// let vk = VerifyingKey::from(&sk).into(); -// let sig = sk.sign(thread_rng(), &msg[..]); -// Item { vk, sig } -// }) -// } +fn bench_ristretto255_sign(c: &mut Criterion) { + let mut rng = thread_rng(); -// fn bench_batch_verify(c: &mut Criterion) { -// let mut group = c.benchmark_group("Batch Verification"); -// for &n in [8usize, 16, 24, 32, 40, 48, 56, 64].iter() { -// group.throughput(Throughput::Elements(n as u64)); + frost_core::benches::bench_sign::(c, "ristretto255", &mut rng); +} -// let sigs = sigs_with_distinct_keys().take(n).collect::>(); - -// group.bench_with_input( -// BenchmarkId::new("Unbatched verification", n), -// &sigs, -// |b, sigs| { -// b.iter(|| { -// for item in sigs.iter() { -// let msg = b"Bench"; - -// let Item { vk, sig } = item; -// { -// vk.verify(msg, sig); -// } -// } -// }) -// }, -// ); - -// group.bench_with_input( -// BenchmarkId::new("Batched verification", n), -// &sigs, -// |b, sigs| { -// b.iter(|| { -// let mut batch = batch::Verifier::new(); -// for item in sigs.iter() { -// let msg = b"Bench"; - -// let Item { vk, sig } = item; -// { -// batch.queue((*vk, *sig, msg)); -// } -// } -// batch.verify(thread_rng()) -// }) -// }, -// ); -// } -// group.finish(); -// } - -// criterion_group!(benches, bench_batch_verify); -// criterion_main!(benches); - -fn main() {} +criterion_group!( + benches, + bench_ristretto255_batch_verify, + bench_ristretto255_sign +); +criterion_main!(benches); diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index bb1cb38..d31eb0d 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -44,3 +44,12 @@ serde_json = "1.0" [features] nightly = [] default = [] + +[lib] +# Disables non-criterion benchmark which is not used; prevents errors +# when using criterion-specific flags +bench = false + +[[bench]] +name = "bench" +harness = false diff --git a/frost-secp256k1/benches/bench.rs b/frost-secp256k1/benches/bench.rs new file mode 100644 index 0000000..c577363 --- /dev/null +++ b/frost-secp256k1/benches/bench.rs @@ -0,0 +1,19 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use rand::thread_rng; + +use frost_secp256k1::*; + +fn bench_secp256k1_batch_verify(c: &mut Criterion) { + let mut rng = thread_rng(); + + frost_core::benches::bench_batch_verify::(c, "secp256k1", &mut rng); +} + +fn bench_secp256k1_sign(c: &mut Criterion) { + let mut rng = thread_rng(); + + frost_core::benches::bench_sign::(c, "secp256k1", &mut rng); +} + +criterion_group!(benches, bench_secp256k1_batch_verify, bench_secp256k1_sign); +criterion_main!(benches); diff --git a/gendoc/Cargo.toml b/gendoc/Cargo.toml index 10efb1e..bd20efc 100644 --- a/gendoc/Cargo.toml +++ b/gendoc/Cargo.toml @@ -7,3 +7,10 @@ edition = "2021" [dependencies] regex = "1.6.0" + +[[bin]] +name = "gendoc" +path = "src/main.rs" +# Disables non-criterion benchmark which is not used; prevents errors +# when using criterion-specific flags +bench = false