From 063f021576d5e8b6cf138f5b1ad6a62e5972240a Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Tue, 20 Jun 2023 14:07:54 +0000 Subject: [PATCH 1/3] Add DKG benchmarks --- cggmp21/src/keygen.rs | 14 +++++ cggmp21/src/keygen/non_threshold.rs | 50 ++++++++++++++-- cggmp21/src/keygen/threshold.rs | 53 ++++++++++++++--- tests/src/bin/measure_perf.rs | 89 +++++++++++++++++++++++++++-- 4 files changed, 188 insertions(+), 18 deletions(-) diff --git a/cggmp21/src/keygen.rs b/cggmp21/src/keygen.rs index d5af229..a966f50 100644 --- a/cggmp21/src/keygen.rs +++ b/cggmp21/src/keygen.rs @@ -10,6 +10,7 @@ use rand_core::{CryptoRng, RngCore}; use round_based::{Mpc, MsgId, PartyIndex}; use thiserror::Error; +use crate::progress::Tracer; use crate::{ errors::IoError, key_share::{IncompleteKeyShare, InvalidKeyShare}, @@ -64,6 +65,7 @@ pub struct GenericKeygenBuilder<'a, E: Curve, M, L: SecurityLevel, D: Digest> { reliable_broadcast_enforced: bool, optional_t: M, execution_id: ExecutionId<'a>, + tracer: Option<&'a mut dyn Tracer>, _params: std::marker::PhantomData<(E, L, D)>, } @@ -89,6 +91,7 @@ where optional_t: NonThreshold, reliable_broadcast_enforced: true, execution_id: eid, + tracer: None, _params: std::marker::PhantomData, } } @@ -109,6 +112,7 @@ where optional_t: WithThreshold(t), reliable_broadcast_enforced: self.reliable_broadcast_enforced, execution_id: self.execution_id, + tracer: self.tracer, _params: std::marker::PhantomData, } } @@ -123,6 +127,7 @@ where optional_t: self.optional_t, reliable_broadcast_enforced: self.reliable_broadcast_enforced, execution_id: self.execution_id, + tracer: self.tracer, _params: std::marker::PhantomData, } } @@ -138,10 +143,17 @@ where optional_t: self.optional_t, reliable_broadcast_enforced: self.reliable_broadcast_enforced, execution_id: self.execution_id, + tracer: self.tracer, _params: std::marker::PhantomData, } } + /// Sets a tracer that tracks progress of protocol execution + pub fn set_progress_tracer(mut self, tracer: &'a mut dyn Tracer) -> Self { + self.tracer = Some(tracer); + self + } + #[doc = include_str!("../docs/enforce_reliable_broadcast.md")] pub fn enforce_reliable_broadcast(self, enforce: bool) -> Self { Self { @@ -169,6 +181,7 @@ where M: Mpc>, { non_threshold::run_keygen( + self.tracer, self.i, self.n, self.reliable_broadcast_enforced, @@ -198,6 +211,7 @@ where M: Mpc>, { threshold::run_threshold_keygen( + self.tracer, self.i, self.optional_t.0, self.n, diff --git a/cggmp21/src/keygen/non_threshold.rs b/cggmp21/src/keygen/non_threshold.rs index 6d7ef47..4061524 100644 --- a/cggmp21/src/keygen/non_threshold.rs +++ b/cggmp21/src/keygen/non_threshold.rs @@ -13,6 +13,7 @@ use round_based::{ }; use serde::{Deserialize, Serialize}; +use crate::progress::Tracer; use crate::{ errors::IoError, key_share::{DirtyIncompleteKeyShare, IncompleteKeyShare}, @@ -61,6 +62,7 @@ pub struct MsgRound3 { pub struct MsgReliabilityCheck(pub digest::Output); pub async fn run_keygen( + mut tracer: Option<&mut dyn Tracer>, i: u16, n: u16, reliable_broadcast_enforced: bool, @@ -76,10 +78,12 @@ where R: RngCore + CryptoRng, M: Mpc>, { + tracer.protocol_begins(); + + tracer.stage("Setup networking"); let MpcParty { delivery, .. } = party.into_party(); let (incomings, mut outgoings) = delivery.split(); - // Setup networking let mut rounds = RoundsRouter::>::builder(); let round1 = rounds.add_round(RoundInput::>::broadcast(i, n)); let round1_sync = rounds.add_round(RoundInput::>::broadcast(i, n)); @@ -88,17 +92,23 @@ where let mut rounds = rounds.listen(incomings); // Round 1 + tracer.round_begins(); + + tracer.stage("Compute execution id"); let sid = execution_id.as_bytes(); let tag_htc = hash_to_curve::Tag::new(sid).ok_or(Bug::InvalidHashToCurveTag)?; + tracer.stage("Sample x_i, rid_i"); let x_i = SecretScalar::::random(rng); let X_i = Point::generator() * &x_i; let mut rid = L::Rid::default(); rng.fill_bytes(rid.as_mut()); + tracer.stage("Sample schnorr commitment"); let (sch_secret, sch_commit) = schnorr_pok::prover_commits_ephemeral_secret::(rng); + tracer.stage("Commit to public data"); let (hash_commit, decommit) = HashCommit::::builder() .mix_bytes(sid) .mix(n) @@ -111,36 +121,53 @@ where let my_commitment = MsgRound1 { commitment: hash_commit, }; + + tracer.send_msg(); outgoings .send(Outgoing::broadcast(Msg::Round1(my_commitment.clone()))) .await .map_err(IoError::send_message)?; + tracer.msg_sent(); // Round 2 + tracer.round_begins(); + + tracer.receive_msgs(); let commitments = rounds .complete(round1) .await .map_err(IoError::receive_message)? .into_vec_including_me(my_commitment); + tracer.msgs_received(); // Optional reliability check if reliable_broadcast_enforced { + tracer.stage("Hash received msgs (reliability check)"); let h_i = commitments .iter() .try_fold(D::new(), hash_message) .map_err(Bug::HashMessage)? .finalize(); + + tracer.send_msg(); outgoings .send(Outgoing::broadcast(Msg::ReliabilityCheck( MsgReliabilityCheck(h_i.clone()), ))) .await .map_err(IoError::send_message)?; + tracer.msg_sent(); + tracer.round_begins(); + + tracer.receive_msgs(); let round1_hashes = rounds .complete(round1_sync) .await .map_err(IoError::receive_message)?; + tracer.msgs_received(); + + tracer.stage("Assert other parties hashed messages (reliability check)"); let parties_have_different_hashes = round1_hashes .into_iter_indexed() .filter(|(_j, _msg_id, hash_j)| hash_j.0 != h_i) @@ -157,19 +184,25 @@ where sch_commit, decommit, }; + tracer.send_msg(); outgoings .send(Outgoing::broadcast(Msg::Round2(my_decommitment.clone()))) .await .map_err(IoError::send_message)?; + tracer.msg_sent(); // Round 3 + tracer.round_begins(); + + tracer.receive_msgs(); let decommitments = rounds .complete(round2) .await .map_err(IoError::receive_message)? .into_vec_including_me(my_decommitment); + tracer.msgs_received(); - // Validate decommitments + tracer.stage("Validate decommitments"); let blame = (0u16..) .zip(&commitments) .zip(&decommitments) @@ -190,7 +223,7 @@ where return Err(KeygenAborted::InvalidDecommitment { parties: blame }.into()); } - // Calculate challenge + tracer.stage("Calculate challege rid"); let rid = decommitments .iter() .map(|d| &d.rid) @@ -199,22 +232,29 @@ where .map_err(Bug::HashToScalarError)?; let challenge = schnorr_pok::Challenge { nonce: challenge }; - // Prove knowledge of `x_i` + tracer.stage("Prove knowledge of `x_i`"); let sch_proof = schnorr_pok::prove(&sch_secret, &challenge, &x_i); + tracer.send_msg(); let my_sch_proof = MsgRound3 { sch_proof }; outgoings .send(Outgoing::broadcast(Msg::Round3(my_sch_proof.clone()))) .await .map_err(IoError::send_message)?; + tracer.msg_sent(); // Round 4 + tracer.round_begins(); + + tracer.receive_msgs(); let sch_proofs = rounds .complete(round3) .await .map_err(IoError::receive_message)? .into_vec_including_me(my_sch_proof); + tracer.msgs_received(); + tracer.stage("Validate schnorr proofs"); let mut blame = vec![]; for ((j, decommitment), sch_proof) in (0u16..).zip(&decommitments).zip(&sch_proofs) { let challenge = Scalar::::hash_concat(tag_htc, &[&j.to_be_bytes(), rid.as_ref()]) @@ -232,6 +272,8 @@ where return Err(KeygenAborted::InvalidSchnorrProof { parties: blame }.into()); } + tracer.protocol_ends(); + Ok(DirtyIncompleteKeyShare { curve: Default::default(), i, diff --git a/cggmp21/src/keygen/threshold.rs b/cggmp21/src/keygen/threshold.rs index b8d9b36..ef3057d 100644 --- a/cggmp21/src/keygen/threshold.rs +++ b/cggmp21/src/keygen/threshold.rs @@ -14,6 +14,7 @@ use round_based::{ use serde::{Deserialize, Serialize}; use crate::key_share::DirtyIncompleteKeyShare; +use crate::progress::Tracer; use crate::{ errors::IoError, key_share::{IncompleteKeyShare, VssSetup}, @@ -70,6 +71,7 @@ pub struct MsgRound3 { pub struct MsgReliabilityCheck(pub digest::Output); pub async fn run_threshold_keygen( + mut tracer: Option<&mut dyn Tracer>, i: u16, t: u16, n: u16, @@ -86,10 +88,12 @@ where R: RngCore + CryptoRng, M: Mpc>, { + tracer.protocol_begins(); + + tracer.stage("Setup networking"); let MpcParty { delivery, .. } = party.into_party(); let (incomings, mut outgoings) = delivery.split(); - // Setup networking let mut rounds = RoundsRouter::>::builder(); let round1 = rounds.add_round(RoundInput::>::broadcast(i, n)); let round1_sync = rounds.add_round(RoundInput::>::broadcast(i, n)); @@ -99,10 +103,13 @@ where let mut rounds = rounds.listen(incomings); // Round 1 + tracer.round_begins(); + tracer.stage("Compute execution id"); let sid = execution_id.as_bytes(); let tag_htc = hash_to_curve::Tag::new(sid).ok_or(Bug::InvalidHashToCurveTag)?; + tracer.stage("Sample rid_i, schnorr commitment, polynomial"); let mut rid = L::Rid::default(); rng.fill_bytes(rid.as_mut()); @@ -121,6 +128,7 @@ where .collect::>(); debug_assert_eq!(sigmas.len(), usize::from(n)); + tracer.stage("Commit to public data"); let (hash_commit, decommit) = HashCommit::::builder() .mix_bytes(sid) .mix(n) @@ -131,6 +139,7 @@ where .mix(h.0) .commit(rng); + tracer.send_msg(); let my_commitment = MsgRound1 { commitment: hash_commit, }; @@ -138,32 +147,46 @@ where .send(Outgoing::broadcast(Msg::Round1(my_commitment.clone()))) .await .map_err(IoError::send_message)?; + tracer.msg_sent(); // Round 2 + tracer.round_begins(); + tracer.receive_msgs(); let commitments = rounds .complete(round1) .await .map_err(IoError::receive_message)?; + tracer.msgs_received(); // Optional reliability check if reliable_broadcast_enforced { + tracer.stage("Hash received msgs (reliability check)"); let h_i = commitments .iter_including_me(&my_commitment) .try_fold(D::new(), hash_message) .map_err(Bug::HashMessage)? .finalize(); + + tracer.send_msg(); outgoings .send(Outgoing::broadcast(Msg::ReliabilityCheck( MsgReliabilityCheck(h_i.clone()), ))) .await .map_err(IoError::send_message)?; + tracer.msg_sent(); + + tracer.round_begins(); + tracer.receive_msgs(); let hashes = rounds .complete(round1_sync) .await .map_err(IoError::receive_message)?; + tracer.msgs_received(); + + tracer.stage("Assert other parties hashed messages (reliability check)"); let parties_have_different_hashes = hashes .into_iter_indexed() .filter(|(_j, _msg_id, h_j)| h_i != h_j.0) @@ -174,6 +197,7 @@ where } } + tracer.send_msg(); let my_decommitment = MsgRound2Broad { rid, Ss: Ss.clone(), @@ -196,9 +220,12 @@ where .await .map_err(IoError::send_message)?; } + tracer.msg_sent(); // Round 3 + tracer.round_begins(); + tracer.receive_msgs(); let decommitments = rounds .complete(round2_broad) .await @@ -207,8 +234,9 @@ where .complete(round2_uni) .await .map_err(IoError::receive_message)?; + tracer.msgs_received(); - // Validate decommitments + tracer.stage("Validate decommitments"); let blame = commitments .iter_indexed() .zip(decommitments.iter()) @@ -230,7 +258,7 @@ where return Err(KeygenAborted::InvalidDecommitment { parties: blame }.into()); } - // Validate data size + tracer.stage("Validate data size"); let blame = decommitments .iter_indexed() .filter(|(_, _, d)| d.Ss.len() != usize::from(t)) @@ -240,7 +268,7 @@ where return Err(KeygenAborted::InvalidDataSize { parties: blame }.into()); } - // Validate Feldmann VSS + tracer.stage("Validate Feldmann VSS"); let blame = decommitments .iter_indexed() .zip(sigmas_msg.iter()) @@ -254,7 +282,7 @@ where return Err(KeygenAborted::FeldmanVerificationFailed { parties: blame }.into()); } - // Validation done, compute key data + tracer.stage("Compute key data"); let rid = decommitments .iter_including_me(&my_decommitment) .map(|d| &d.rid) @@ -272,7 +300,7 @@ where let sigma = SecretScalar::new(&mut sigma); debug_assert_eq!(Point::generator() * &sigma, ys[usize::from(i)]); - // Calculate challenge + tracer.stage("Calculate challenge"); let challenge = Scalar::::hash_concat( tag_htc, &[ @@ -285,22 +313,28 @@ where .map_err(Bug::HashToScalarError)?; let challenge = schnorr_pok::Challenge { nonce: challenge }; - // Prove knowledge of `sigma_i` + tracer.stage("Prove knowledge of `sigma_i`"); let z = schnorr_pok::prove(&r, &challenge, &sigma); + tracer.send_msg(); let my_sch_proof = MsgRound3 { sch_proof: z }; outgoings .send(Outgoing::broadcast(Msg::Round3(my_sch_proof.clone()))) .await .map_err(IoError::send_message)?; + tracer.msg_sent(); - // Output determination + // Output round + tracer.round_begins(); + tracer.receive_msgs(); let sch_proofs = rounds .complete(round3) .await .map_err(IoError::receive_message)?; + tracer.msgs_received(); + tracer.stage("Validate schnorr proofs"); let mut blame = vec![]; for ((j, decommitment), sch_proof) in utils::iter_peers(i, n) .zip(decommitments.iter()) @@ -329,6 +363,7 @@ where return Err(KeygenAborted::InvalidSchnorrProof { parties: blame }.into()); } + tracer.stage("Derive resulting public key and other data"); let y: Point = decommitments .iter_including_me(&my_decommitment) .map(|d| d.Ss[0]) @@ -338,6 +373,8 @@ where .collect::>>() .ok_or(Bug::NonZeroScalar)?; + tracer.protocol_ends(); + Ok(DirtyIncompleteKeyShare { curve: Default::default(), i, diff --git a/tests/src/bin/measure_perf.rs b/tests/src/bin/measure_perf.rs index 9cc099c..d9cf176 100644 --- a/tests/src/bin/measure_perf.rs +++ b/tests/src/bin/measure_perf.rs @@ -1,17 +1,18 @@ use anyhow::Context; use cggmp21::{progress::PerfProfiler, signing::DataToSign, ExecutionId}; -use rand::{Rng, SeedableRng}; -use rand_chacha::ChaCha20Rng; +use rand::Rng; use rand_dev::DevRng; use round_based::simulation::Simulation; use sha2::Sha256; -type E = generic_ec::curves::Secp256r1; +type E = generic_ec::curves::Secp256k1; type L = cggmp21::security_level::ReasonablySecure; type D = sha2::Sha256; struct Args { n: Vec, + bench_non_threshold_keygen: bool, + bench_threshold_keygen: bool, bench_refresh: bool, bench_signing: bool, } @@ -23,11 +24,17 @@ fn args() -> Args { .argument::("N") .parse(|s| s.split(',').map(std::str::FromStr::from_str).collect()) .fallback(vec![3, 5, 7, 10]); + let bench_non_threshold_keygen = bpaf::long("no-bench-non-threshold-keygen") + .switch() + .map(|b| !b); + let bench_threshold_keygen = bpaf::long("no-bench-threshold-keygen").switch().map(|b| !b); let bench_refresh = bpaf::long("no-bench-refresh").switch().map(|b| !b); let bench_signing = bpaf::long("no-bench-signing").switch().map(|b| !b); bpaf::construct!(Args { n, + bench_non_threshold_keygen, + bench_threshold_keygen, bench_refresh, bench_signing }) @@ -44,6 +51,74 @@ async fn main() { // since performance of t-out-of-n protocol should be roughly the same as t-out-of-t for n in args.n { println!("n = {n}"); + println!(); + + if args.bench_non_threshold_keygen { + let eid: [u8; 32] = rng.gen(); + let eid = ExecutionId::new(&eid); + + let mut simulation = + Simulation::>::new(); + + let outputs = (0..n).map(|i| { + let party = simulation.add_party(); + let mut party_rng = rng.fork(); + + let mut profiler = PerfProfiler::new(); + + async move { + let _key_share = cggmp21::keygen(eid, i, n) + .set_progress_tracer(&mut profiler) + .start(&mut party_rng, party) + .await + .context("keygen failed")?; + profiler.get_report().context("get perf report") + } + }); + + let perf_reports = futures::future::try_join_all(outputs) + .await + .expect("non-threshold keygen failed"); + + println!("Non-threshold DKG"); + println!("{}", perf_reports[0].clone().display_io(false)); + println!(); + } + + if args.bench_threshold_keygen && n > 2 { + let t = n - 1; + + let eid: [u8; 32] = rng.gen(); + let eid = ExecutionId::new(&eid); + + let mut simulation = Simulation::>::new(); + + let outputs = (0..n).map(|i| { + let party = simulation.add_party(); + let mut party_rng = rng.fork(); + + let mut profiler = PerfProfiler::new(); + + async move { + let _key_share = cggmp21::keygen(eid, i, n) + .set_threshold(t) + .set_progress_tracer(&mut profiler) + .start(&mut party_rng, party) + .await + .context("keygen failed")?; + profiler.get_report().context("get perf report") + } + }); + + let perf_reports = futures::future::try_join_all(outputs) + .await + .expect("threshold keygen failed"); + + println!("Threshold DKG"); + println!("{}", perf_reports[0].clone().display_io(false)); + println!(); + } + let shares = cggmp21_tests::CACHED_SHARES .get_shares::(None, n) .expect("retrieve key shares from cache"); @@ -59,7 +134,7 @@ async fn main() { let outputs = shares.iter().map(|share| { let party = simulation.add_party(); - let mut party_rng = ChaCha20Rng::from_seed(rng.gen()); + let mut party_rng = rng.fork(); let pregen = primes.next().expect("Can't get pregenerated prime"); let mut profiler = PerfProfiler::new(); @@ -76,10 +151,11 @@ async fn main() { let perf_reports = futures::future::try_join_all(outputs) .await - .expect("signing failed"); + .expect("key refresh failed"); println!("Key refresh protocol"); println!("{}", perf_reports[0].clone().display_io(false)); + println!(); } if args.bench_signing { @@ -97,7 +173,7 @@ async fn main() { let mut outputs = vec![]; for (i, share) in (0..).zip(&shares) { let party = simulation.add_party(); - let mut party_rng = ChaCha20Rng::from_seed(rng.gen()); + let mut party_rng = rng.fork(); let mut profiler = PerfProfiler::new(); @@ -117,6 +193,7 @@ async fn main() { println!("Signing protocol"); println!("{}", perf_reports[0].clone().display_io(false)); + println!(); } } } From 589049510775a2e1ee02d810d6b3988255563b77 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Wed, 21 Jun 2023 10:02:29 +0000 Subject: [PATCH 2/3] Split 'compute key data' stage into several stages --- cggmp21/src/keygen/threshold.rs | 4 +++- tests/src/bin/measure_perf.rs | 12 ++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/cggmp21/src/keygen/threshold.rs b/cggmp21/src/keygen/threshold.rs index ef3057d..2737273 100644 --- a/cggmp21/src/keygen/threshold.rs +++ b/cggmp21/src/keygen/threshold.rs @@ -282,11 +282,12 @@ where return Err(KeygenAborted::FeldmanVerificationFailed { parties: blame }.into()); } - tracer.stage("Compute key data"); + tracer.stage("Compute rid"); let rid = decommitments .iter_including_me(&my_decommitment) .map(|d| &d.rid) .fold(L::Rid::default(), xor_array); + tracer.stage("Compute Ys"); let ys = (0..n) .map(|l| { decommitments @@ -295,6 +296,7 @@ where .sum() }) .collect::>(); + tracer.stage("Compute sigma"); let sigma: Scalar = sigmas_msg.iter().map(|msg| msg.sigma).sum(); let mut sigma = sigma + sigmas[usize::from(i)]; let sigma = SecretScalar::new(&mut sigma); diff --git a/tests/src/bin/measure_perf.rs b/tests/src/bin/measure_perf.rs index d9cf176..19cf838 100644 --- a/tests/src/bin/measure_perf.rs +++ b/tests/src/bin/measure_perf.rs @@ -119,11 +119,11 @@ async fn main() { println!(); } - let shares = cggmp21_tests::CACHED_SHARES - .get_shares::(None, n) - .expect("retrieve key shares from cache"); - if args.bench_refresh { + let shares = cggmp21_tests::CACHED_SHARES + .get_shares::(None, n) + .expect("retrieve key shares from cache"); + let eid: [u8; 32] = rng.gen(); let eid = ExecutionId::new(&eid); @@ -159,6 +159,10 @@ async fn main() { } if args.bench_signing { + let shares = cggmp21_tests::CACHED_SHARES + .get_shares::(None, n) + .expect("retrieve key shares from cache"); + let eid: [u8; 32] = rng.gen(); let eid = ExecutionId::new(&eid); From 914f8f9acf46e1f2671bbda0c7f5eb0c0c038129 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Thu, 22 Jun 2023 12:19:17 +0000 Subject: [PATCH 3/3] Move comment below --- tests/src/bin/measure_perf.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/bin/measure_perf.rs b/tests/src/bin/measure_perf.rs index 19cf838..9132780 100644 --- a/tests/src/bin/measure_perf.rs +++ b/tests/src/bin/measure_perf.rs @@ -47,8 +47,6 @@ async fn main() { let args = args(); let mut rng = DevRng::new(); - // Note that we don't parametrize performance tests by `t` as it doesn't make much sense - // since performance of t-out-of-n protocol should be roughly the same as t-out-of-t for n in args.n { println!("n = {n}"); println!(); @@ -159,6 +157,8 @@ async fn main() { } if args.bench_signing { + // Note that we don't parametrize signing performance tests by `t` as it doesn't make much sense + // since performance of t-out-of-n protocol should be roughly the same as t-out-of-t let shares = cggmp21_tests::CACHED_SHARES .get_shares::(None, n) .expect("retrieve key shares from cache");