diff --git a/CHANGELOG.md b/CHANGELOG.md index 34bbcee4..e6ac0145 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.0] - in development + +- Switch the protocol framework to `manul`. ([#156]) + + +[#156]: https://github.com/entropyxyz/synedrion/pull/156 + + ## [0.2.0] - 2024-11-17 - Signature and elliptic curve dependencies reset back to stable versions. (#[154]) diff --git a/synedrion/Cargo.toml b/synedrion/Cargo.toml index a75d6aaf..c71899f0 100644 --- a/synedrion/Cargo.toml +++ b/synedrion/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "synedrion" authors = ['Entropy Cryptography '] -version = "0.2.0" +version = "0.3.0-dev" edition = "2021" license = "AGPL-3.0-or-later" description = "Threshold signing library based on Canetti-Gennaro-Goldfeder-Makriyannis-Peled '21 scheme" @@ -10,6 +10,7 @@ readme = "README.md" categories = ["cryptography", "no-std"] [dependencies] +manul = "0.1" signature = { version = "2", default-features = false, features = ["alloc"] } k256 = { version = "0.13", default-features = false, features = ["ecdsa", "arithmetic"] } rand_core = { version = "0.6.4", default-features = false } @@ -32,6 +33,7 @@ bincode = { version = "2.0.0-rc.3", default-features = false, features = ["serde displaydoc = { version = "0.2", default-features = false } [dev-dependencies] +manul = { version = "0.1", features = ["dev"] } rand_chacha = "0.3" serde_assert = "0.8" tokio = { version = "1", features = ["rt", "sync", "time", "macros"] } @@ -41,12 +43,8 @@ k256 = {version = "0.13", default-features = false, features = ["ecdsa", "arithm impls = "1" hex = { version = "0.4", default-features = false, features = ["alloc"] } -[features] -bench-internals = [] # makes some internal functions public to allow external benchmarks - [[bench]] bench = true name = "bench" harness = false -required-features = ["bench-internals"] path = "benches/bench.rs" diff --git a/synedrion/benches/bench.rs b/synedrion/benches/bench.rs index c2d5d023..77961cc2 100644 --- a/synedrion/benches/bench.rs +++ b/synedrion/benches/bench.rs @@ -1,36 +1,83 @@ -use criterion::{criterion_group, criterion_main, Criterion}; -use rand_core::OsRng; +use std::collections::BTreeSet; -use synedrion::{ - bench_internals::{ - key_init, key_refresh, presigning, signing, PresigningInputs, SigningInputs, - }, - TestParams, +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use manul::{ + dev::{run_sync, BinaryFormat, TestSessionParams, TestSigner, TestVerifier}, + session::signature::Keypair, }; +use rand_core::OsRng; +use synedrion::{AuxGen, AuxInfo, InteractiveSigning, KeyInit, KeyShare, TestParams}; fn bench_happy_paths(c: &mut Criterion) { let mut group = c.benchmark_group("happy path"); - type Params = TestParams; + type SessionParams = TestSessionParams; - group.bench_function("KeyGen, 2 parties", |b| { - b.iter(|| key_init::(&mut OsRng, 2)) - }); + let signers = (0..2).map(TestSigner::new).collect::>(); + let all_ids = signers + .iter() + .map(|signer| signer.verifying_key()) + .collect::>(); - let presigning_inputs = PresigningInputs::new(&mut OsRng, 2); - let signing_inputs = SigningInputs::new(&mut OsRng, &presigning_inputs); - - group.bench_function("Signing, 2 parties", |b| { - b.iter(|| signing::(&mut OsRng, &presigning_inputs, &signing_inputs)) + group.bench_function("KeyInit, 2 parties", |b| { + b.iter_batched( + || { + signers + .iter() + .map(|signer| { + let entry_point = + KeyInit::::new(all_ids.clone()).unwrap(); + (*signer, entry_point) + }) + .collect::>() + }, + |entry_points| run_sync::<_, SessionParams>(&mut OsRng, entry_points).unwrap(), + BatchSize::SmallInput, + ) }); + let key_shares = KeyShare::new_centralized(&mut OsRng, &all_ids, None); + let aux_infos = AuxInfo::new_centralized(&mut OsRng, &all_ids); + let message = [1u8; 32]; + group.sample_size(10); - group.bench_function("Presigning, 2 parties", |b| { - b.iter(|| presigning::(&mut OsRng, &presigning_inputs)) + group.bench_function("InteractiveSigning, 2 parties", |b| { + b.iter_batched( + || { + signers + .iter() + .map(|signer| { + let id = signer.verifying_key(); + let entry_point = InteractiveSigning::::new( + message, + key_shares[&id].clone(), + aux_infos[&id].clone(), + ); + (*signer, entry_point) + }) + .collect::>() + }, + |entry_points| run_sync::<_, SessionParams>(&mut OsRng, entry_points).unwrap(), + BatchSize::LargeInput, + ) }); - group.bench_function("KeyRefresh, 2 parties", |b| { - b.iter(|| key_refresh::(&mut OsRng, 2)) + group.sample_size(20); + group.bench_function("AuxGen, 2 parties", |b| { + b.iter_batched( + || { + signers + .iter() + .map(|signer| { + let entry_point = + AuxGen::::new(all_ids.clone()).unwrap(); + (*signer, entry_point) + }) + .collect::>() + }, + |entry_points| run_sync::<_, SessionParams>(&mut OsRng, entry_points).unwrap(), + BatchSize::SmallInput, + ) }); group.finish() diff --git a/synedrion/src/bench_internals.rs b/synedrion/src/bench_internals.rs deleted file mode 100644 index 73c338e6..00000000 --- a/synedrion/src/bench_internals.rs +++ /dev/null @@ -1,185 +0,0 @@ -//! Public exports for use in benchmarks. - -//! Functions containing sequential executions of CGGMP21 protocols, -//! intended for benchmarking. - -use alloc::collections::{BTreeMap, BTreeSet}; - -use rand_core::CryptoRngCore; - -use super::cggmp21::{ - key_init, key_refresh, presigning, signing, AuxInfo, KeyShare, PresigningData, SchemeParams, -}; -use crate::curve::Scalar; -use crate::rounds::{ - test_utils::{step_next_round, step_result, step_round, Id, Without}, - FirstRound, -}; - -/// A sequential execution of the KeyGen protocol for all parties. -pub fn key_init(rng: &mut impl CryptoRngCore, num_parties: usize) { - let mut shared_randomness = [0u8; 32]; - rng.fill_bytes(&mut shared_randomness); - - let ids = BTreeSet::from_iter((0..num_parties as u32).map(Id)); - - let r1 = ids - .iter() - .map(|id| { - let round = key_init::Round1::::new( - rng, - &shared_randomness, - ids.clone().without(id), - *id, - (), - ) - .unwrap(); - (*id, round) - }) - .collect(); - - let r1a = step_round(rng, r1).unwrap(); - let r2 = step_next_round(rng, r1a).unwrap(); - let r2a = step_round(rng, r2).unwrap(); - let r3 = step_next_round(rng, r2a).unwrap(); - let r3a = step_round(rng, r3).unwrap(); - let _shares = step_result(rng, r3a).unwrap(); -} - -/// A sequential execution of the KeyRefresh/Auxiliary protocol for all parties. -pub fn key_refresh(rng: &mut impl CryptoRngCore, num_parties: usize) { - let mut shared_randomness = [0u8; 32]; - rng.fill_bytes(&mut shared_randomness); - - let ids = BTreeSet::from_iter((0..num_parties as u32).map(Id)); - - let r1 = ids - .iter() - .map(|id| { - let round = key_refresh::Round1::::new( - rng, - &shared_randomness, - ids.clone().without(id), - *id, - (), - ) - .unwrap(); - (*id, round) - }) - .collect(); - - let r1a = step_round(rng, r1).unwrap(); - let r2 = step_next_round(rng, r1a).unwrap(); - let r2a = step_round(rng, r2).unwrap(); - let r3 = step_next_round(rng, r2a).unwrap(); - let r3a = step_round(rng, r3).unwrap(); - let _changes = step_result(rng, r3a).unwrap(); -} - -/// A public struct to use for benchmarking of Presigning protocol, -/// to avoid exposing actual crate-private entities. -pub struct PresigningInputs { - ids: BTreeSet, - key_shares: BTreeMap>, - aux_infos: BTreeMap>, -} - -impl PresigningInputs

{ - /// Creates new test data to use in the Presigning and Signing benchmarks. - pub fn new(rng: &mut impl CryptoRngCore, num_parties: usize) -> Self { - let ids = BTreeSet::from_iter((0..num_parties as u32).map(Id)); - let key_shares = KeyShare::new_centralized(rng, &ids, None); - let aux_infos = AuxInfo::new_centralized(rng, &ids); - Self { - ids, - key_shares, - aux_infos, - } - } -} - -/// A sequential execution of the Presigning protocol for all parties. -pub fn presigning(rng: &mut impl CryptoRngCore, inputs: &PresigningInputs

) { - let mut shared_randomness = [0u8; 32]; - rng.fill_bytes(&mut shared_randomness); - - let r1 = inputs - .ids - .iter() - .map(|id| { - let round = presigning::Round1::::new( - rng, - &shared_randomness, - inputs.ids.clone().without(id), - *id, - (inputs.key_shares[id].clone(), inputs.aux_infos[id].clone()), - ) - .unwrap(); - (*id, round) - }) - .collect(); - - let r1a = step_round(rng, r1).unwrap(); - let r2 = step_next_round(rng, r1a).unwrap(); - let r2a = step_round(rng, r2).unwrap(); - let r3 = step_next_round(rng, r2a).unwrap(); - let r3a = step_round(rng, r3).unwrap(); - let _presigning_datas = step_result(rng, r3a).unwrap(); -} - -/// A public struct to use for benchmarking of Signing protocol, -/// to avoid exposing actual crate-private entities. -pub struct SigningInputs { - ids: BTreeSet, - presigning_datas: BTreeMap>, -} - -impl SigningInputs

{ - /// Creates new test data to use in the Signing benchmark. - pub fn new(rng: &mut impl CryptoRngCore, presigning_inputs: &PresigningInputs

) -> Self { - Self { - ids: presigning_inputs.ids.clone(), - presigning_datas: PresigningData::new_centralized( - rng, - &presigning_inputs.key_shares, - &presigning_inputs.aux_infos, - ), - } - } -} - -/// A sequential execution of the Presigning protocol for all parties. -pub fn signing( - rng: &mut impl CryptoRngCore, - presigning_inputs: &PresigningInputs

, - signing_inputs: &SigningInputs

, -) { - let mut shared_randomness = [0u8; 32]; - rng.fill_bytes(&mut shared_randomness); - - let message = Scalar::random(rng); - - let r1 = signing_inputs - .ids - .iter() - .map(|id| { - let round = signing::Round1::new( - rng, - &shared_randomness, - signing_inputs.ids.clone().without(id), - *id, - signing::Inputs { - message, - presigning: signing_inputs.presigning_datas[id].clone(), - key_share: presigning_inputs.key_shares[id].clone(), - aux_info: presigning_inputs.aux_infos[id].clone(), - }, - ) - .unwrap(); - (*id, round) - }) - .collect(); - - let r1a = step_round(rng, r1).unwrap(); - let _signatures = step_result(rng, r1a).unwrap(); -} diff --git a/synedrion/src/cggmp21.rs b/synedrion/src/cggmp21.rs index ccb1cac6..1c422d9b 100644 --- a/synedrion/src/cggmp21.rs +++ b/synedrion/src/cggmp21.rs @@ -12,16 +12,10 @@ mod params; mod protocols; mod sigma; -pub use entities::{AuxInfo, KeyShare, KeyShareChange, PresigningData}; +pub use entities::{AuxInfo, KeyShare, KeyShareChange}; pub(crate) use entities::{PublicAuxInfo, SecretAuxInfo}; pub use params::{ProductionParams, SchemeParams, TestParams}; -pub(crate) use protocols::{aux_gen, interactive_signing, key_gen, key_init, key_refresh}; pub use protocols::{ - AuxGenError, AuxGenResult, InteractiveSigningError, InteractiveSigningProof, - InteractiveSigningResult, KeyGenError, KeyGenProof, KeyGenResult, KeyInitError, KeyInitResult, - KeyRefreshResult, PresigningError, PresigningProof, PresigningResult, SigningProof, - SigningResult, + AuxGen, AuxGenProtocol, InteractiveSigning, InteractiveSigningProtocol, KeyInit, + KeyInitProtocol, KeyRefresh, KeyRefreshProtocol, PrehashedMessage, }; - -#[cfg(feature = "bench-internals")] -pub(crate) use protocols::{presigning, signing}; diff --git a/synedrion/src/cggmp21/entities.rs b/synedrion/src/cggmp21/entities.rs index 285a02df..0d0986d9 100644 --- a/synedrion/src/cggmp21/entities.rs +++ b/synedrion/src/cggmp21/entities.rs @@ -1,24 +1,24 @@ -use alloc::boxed::Box; -use alloc::collections::{BTreeMap, BTreeSet}; -use alloc::vec::Vec; -use core::fmt::Debug; -use core::marker::PhantomData; +use alloc::{ + boxed::Box, + collections::{BTreeMap, BTreeSet}, + vec::Vec, +}; +use core::{fmt::Debug, marker::PhantomData}; use k256::ecdsa::VerifyingKey; use rand_core::CryptoRngCore; use secrecy::{ExposeSecret, SecretBox}; use serde::{Deserialize, Serialize}; -use crate::cggmp21::SchemeParams; -use crate::curve::{Point, Scalar}; -use crate::paillier::{ - CiphertextMod, PaillierParams, PublicKeyPaillier, PublicKeyPaillierPrecomputed, RPParams, - RPParamsMod, Randomizer, SecretKeyPaillier, SecretKeyPaillierPrecomputed, +use crate::{ + cggmp21::SchemeParams, + curve::{Point, Scalar}, + paillier::{ + CiphertextMod, PaillierParams, PublicKeyPaillier, PublicKeyPaillierPrecomputed, RPParams, + RPParamsMod, Randomizer, SecretKeyPaillier, SecretKeyPaillierPrecomputed, + }, + uint::Signed, }; -use crate::uint::Signed; - -#[cfg(any(test, feature = "bench-internals"))] -use crate::paillier::RandomizerMod; /// The result of the KeyInit protocol. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -58,20 +58,20 @@ pub(crate) struct PublicAuxInfo { pub(crate) rp_params: RPParams, // `s_i` and `t_i` } -#[derive(Clone)] +#[derive(Debug, Clone)] pub(crate) struct AuxInfoPrecomputed { pub(crate) secret_aux: SecretAuxInfoPrecomputed

, pub(crate) public_aux: BTreeMap>, } -#[derive(Clone)] +#[derive(Debug, Clone)] pub(crate) struct SecretAuxInfoPrecomputed { pub(crate) paillier_sk: SecretKeyPaillierPrecomputed, #[allow(dead_code)] // TODO (#36): this will be needed for the 6-round presigning protocol. pub(crate) el_gamal_sk: SecretBox, // `y_i` } -#[derive(Clone)] +#[derive(Debug, Clone)] pub(crate) struct PublicAuxInfoPrecomputed { #[allow(dead_code)] // TODO (#36): this will be needed for the 6-round presigning protocol. pub(crate) el_gamal_pk: Point, @@ -93,7 +93,7 @@ pub struct KeyShareChange { /// The result of the Presigning protocol. #[derive(Debug, Clone)] -pub struct PresigningData { +pub(crate) struct PresigningData { pub(crate) nonce: Scalar, // x-coordinate of $R$ /// An additive share of the ephemeral scalar. pub(crate) ephemeral_scalar_share: SecretBox, // $k_i$ @@ -126,7 +126,7 @@ pub(crate) struct PresigningValues { impl KeyShare { /// Updates a key share with a change obtained from KeyRefresh protocol. - pub(crate) fn update(self, change: KeyShareChange) -> Self { + pub fn update(self, change: KeyShareChange) -> Self { // TODO (#68): check that party_idx is the same for both, and the number of parties is the same assert_eq!(self.owner, change.owner); @@ -276,156 +276,6 @@ impl AuxInfo { } } -impl PresigningData -where - P: SchemeParams, - I: Ord + Clone + PartialEq, -{ - /// Creates a consistent set of presigning data for testing purposes. - #[cfg(any(test, feature = "bench-internals"))] - pub(crate) fn new_centralized( - rng: &mut impl CryptoRngCore, - key_shares: &BTreeMap>, - aux_infos: &BTreeMap>, - ) -> BTreeMap { - let ids = key_shares.keys().cloned().collect::>(); - - let ephemeral_scalar = Scalar::random(rng); - let nonce = ephemeral_scalar - .invert() - .unwrap() - .mul_by_generator() - .x_coordinate(); - let ephemeral_scalar_shares = ephemeral_scalar.split(rng, key_shares.len()); - - let ephemeral_scalar_shares = ids - .iter() - .zip(ephemeral_scalar_shares) - .map(|(id, k)| (id.clone(), k)) - .collect::>(); - - let public_keys = aux_infos - .first_key_value() - .unwrap() - .1 - .public_aux - .iter() - .map(|(id, aux)| (id, aux.paillier_pk.to_precomputed())) - .collect::>(); - - let all_cap_k = ephemeral_scalar_shares - .iter() - .map(|(id, k)| { - ( - id.clone(), - CiphertextMod::new(rng, &public_keys[id], &P::uint_from_scalar(k)), - ) - }) - .collect::>(); - - let mut hat_betas = BTreeMap::new(); - let mut hat_ss = BTreeMap::new(); - let mut hat_cap_ds = BTreeMap::new(); - let mut hat_rs = BTreeMap::new(); - let mut hat_cap_fs = BTreeMap::new(); - - for id_i in ids.iter() { - let x_i = key_shares[id_i].secret_share.clone(); - let pk_i = &public_keys[id_i]; - - for id_j in ids.iter().filter(|id| id != &id_i) { - let hat_beta = Signed::random_bounded_bits(rng, P::LP_BOUND); - let hat_s = RandomizerMod::random(rng, &public_keys[&id_j]).retrieve(); - let hat_r = RandomizerMod::random(rng, pk_i).retrieve(); - - let hat_cap_d = &all_cap_k[id_j] - * P::signed_from_scalar(x_i.expose_secret()).unwrap() - + CiphertextMod::new_with_randomizer_signed( - &public_keys[&id_j], - &-hat_beta, - &hat_s, - ); - let hat_cap_f = CiphertextMod::new_with_randomizer_signed(pk_i, &hat_beta, &hat_r); - - let id_ij = (id_i.clone(), id_j.clone()); - let id_ji = (id_j.clone(), id_i.clone()); - - hat_betas.insert(id_ij.clone(), hat_beta); - hat_ss.insert(id_ij.clone(), hat_s.clone()); - hat_rs.insert(id_ij.clone(), hat_r); - - hat_cap_ds.insert(id_ji.clone(), hat_cap_d); - hat_cap_fs.insert(id_ji.clone(), hat_cap_f); - } - } - - let mut presigning = BTreeMap::new(); - - for id_i in ids.iter() { - let id_i = id_i.clone(); - - let mut values = BTreeMap::new(); - - for id_j in ids.iter().filter(|id| id != &&id_i) { - let id_ij = (id_i.clone(), id_j.clone()); - let id_ji = (id_j.clone(), id_i.clone()); - - values.insert( - id_j.clone(), - PresigningValues { - hat_beta: SecretBox::new(Box::new(hat_betas[&id_ij])), - hat_r: hat_rs[&id_ij].clone(), - hat_s: hat_ss[&id_ij].clone(), - hat_cap_d_received: hat_cap_ds[&id_ij].clone(), - hat_cap_d: hat_cap_ds[&id_ji].clone(), - hat_cap_f: hat_cap_fs[&id_ji].clone(), - cap_k: all_cap_k[id_j].clone(), - }, - ); - } - - let x_i = key_shares[&id_i].secret_share.clone(); - let k_i = ephemeral_scalar_shares[&id_i]; - - let alpha_sum: Signed<_> = ids - .iter() - .filter(|id| id != &&id_i) - .map(|id_j| { - P::signed_from_scalar(key_shares[id_j].secret_share.expose_secret()).unwrap() - * P::signed_from_scalar(&k_i).unwrap() - - hat_betas[&(id_j.clone(), id_i.clone())] - }) - .sum(); - - let beta_sum: Signed<_> = ids - .iter() - .filter(|id| id != &&id_i) - .map(|id_j| hat_betas[&(id_i.clone(), id_j.clone())]) - .sum(); - let product_share_nonreduced = P::signed_from_scalar(x_i.expose_secret()).unwrap() - * P::signed_from_scalar(&k_i).unwrap() - + alpha_sum - + beta_sum; - - presigning.insert( - id_i.clone(), - PresigningData { - nonce, - ephemeral_scalar_share: SecretBox::new(Box::new(k_i)), - product_share: SecretBox::new(Box::new(P::scalar_from_signed( - &product_share_nonreduced, - ))), - product_share_nonreduced, - cap_k: all_cap_k[&id_i].clone(), - values, - }, - ); - } - - presigning - } -} - #[cfg(test)] mod tests { use alloc::collections::BTreeSet; diff --git a/synedrion/src/cggmp21/params.rs b/synedrion/src/cggmp21/params.rs index a5d455f2..4a9f3280 100644 --- a/synedrion/src/cggmp21/params.rs +++ b/synedrion/src/cggmp21/params.rs @@ -1,20 +1,22 @@ use core::fmt::Debug; -use crate::curve::{Curve, Scalar, ORDER}; -use crate::paillier::PaillierParams; -use crate::tools::hashing::{Chain, HashableType}; -use crate::uint::{ - subtle::ConditionallySelectable, Bounded, Encoding, NonZero, Signed, U1024Mod, U2048Mod, - U4096Mod, U512Mod, Uint, Zero, U1024, U2048, U4096, U512, U8192, -}; // We're depending on a pre-release `crypto-bigint` version, // and `k256` depends on the released one. // So as long as that is the case, `k256` `Uint` is separate // from the one used throughout the crate. use k256::elliptic_curve::bigint::Uint as K256Uint; - use serde::{Deserialize, Serialize}; +use crate::{ + curve::{Curve, Scalar, ORDER}, + paillier::PaillierParams, + tools::hashing::{Chain, HashableType}, + uint::{ + subtle::ConditionallySelectable, Bounded, Encoding, NonZero, Signed, U1024Mod, U2048Mod, + U4096Mod, U512Mod, Uint, Zero, U1024, U2048, U4096, U512, U8192, + }, +}; + #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PaillierTest; diff --git a/synedrion/src/cggmp21/protocols.rs b/synedrion/src/cggmp21/protocols.rs index 6c358af0..dba241ae 100644 --- a/synedrion/src/cggmp21/protocols.rs +++ b/synedrion/src/cggmp21/protocols.rs @@ -1,17 +1,12 @@ pub(crate) mod aux_gen; pub(crate) mod interactive_signing; -pub(crate) mod key_gen; pub(crate) mod key_init; pub(crate) mod key_refresh; -pub(crate) mod presigning; -pub(crate) mod signing; -pub use aux_gen::{AuxGenError, AuxGenResult}; -pub use interactive_signing::{ - InteractiveSigningError, InteractiveSigningProof, InteractiveSigningResult, -}; -pub use key_gen::{KeyGenError, KeyGenProof, KeyGenResult}; -pub use key_init::{KeyInitError, KeyInitResult}; -pub use key_refresh::KeyRefreshResult; -pub use presigning::{PresigningError, PresigningProof, PresigningResult}; -pub use signing::{SigningProof, SigningResult}; +#[cfg(test)] +pub(crate) mod signing_malicious; + +pub use aux_gen::{AuxGen, AuxGenProtocol}; +pub use interactive_signing::{InteractiveSigning, InteractiveSigningProtocol, PrehashedMessage}; +pub use key_init::{KeyInit, KeyInitProtocol}; +pub use key_refresh::{KeyRefresh, KeyRefreshProtocol}; diff --git a/synedrion/src/cggmp21/protocols/aux_gen.rs b/synedrion/src/cggmp21/protocols/aux_gen.rs index 803dd54f..26d1d627 100644 --- a/synedrion/src/cggmp21/protocols/aux_gen.rs +++ b/synedrion/src/cggmp21/protocols/aux_gen.rs @@ -1,12 +1,20 @@ //! AuxGen protocol, a part of the paper's Auxiliary Info. & Key Refresh in Three Rounds (Fig. 6) //! that only generates the auxiliary data. -use alloc::boxed::Box; -use alloc::collections::{BTreeMap, BTreeSet}; -use alloc::string::String; -use core::fmt::Debug; -use core::marker::PhantomData; +use alloc::{ + boxed::Box, + collections::{BTreeMap, BTreeSet}, + string::String, + vec::Vec, +}; +use core::{fmt::Debug, marker::PhantomData}; +use crypto_bigint::BitOps; +use manul::protocol::{ + Artifact, BoxedRound, Deserializer, DirectMessage, EchoBroadcast, EntryPoint, FinalizeOutcome, + LocalError, NormalBroadcast, PartyId, Payload, Protocol, ProtocolError, ProtocolMessagePart, + ProtocolValidationError, ReceiveError, Round, RoundId, Serializer, +}; use rand_core::CryptoRngCore; use secrecy::SecretBox; use serde::{Deserialize, Serialize}; @@ -15,34 +23,33 @@ use super::super::{ sigma::{FacProof, ModProof, PrmProof, SchCommitment, SchProof, SchSecret}, AuxInfo, PublicAuxInfo, SchemeParams, SecretAuxInfo, }; -use crate::curve::{Point, Scalar}; -use crate::paillier::{ - PublicKeyPaillier, PublicKeyPaillierPrecomputed, RPParams, RPParamsMod, RPSecret, - SecretKeyPaillier, SecretKeyPaillierPrecomputed, +use crate::{ + curve::{Point, Scalar}, + paillier::{ + PublicKeyPaillier, PublicKeyPaillierPrecomputed, RPParams, RPParamsMod, RPSecret, + SecretKeyPaillier, SecretKeyPaillierPrecomputed, + }, + tools::{ + bitvec::BitVec, + hashing::{Chain, FofHasher, HashOutput}, + DowncastMap, Without, + }, }; -use crate::rounds::{ - no_broadcast_messages, no_direct_messages, FinalizableToNextRound, FinalizableToResult, - FinalizeError, FirstRound, InitError, ProtocolResult, Round, ToNextRound, ToResult, -}; -use crate::tools::bitvec::BitVec; -use crate::tools::hashing::{Chain, FofHasher, HashOutput}; -use crypto_bigint::BitOps; -/// Possible results of the AuxGen protocol. +/// A protocol that generates auxiliary info for signing. #[derive(Debug, Clone, Copy)] -pub struct AuxGenResult(PhantomData

, PhantomData); +pub struct AuxGenProtocol(PhantomData<(P, I)>); -impl ProtocolResult for AuxGenResult { - type Success = AuxInfo; - type ProvableError = AuxGenError; - type CorrectnessProof = (); +impl Protocol for AuxGenProtocol { + type Result = AuxInfo; + type ProtocolError = AuxGenError; } /// Possible errors for AuxGen protocol. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct AuxGenError(#[allow(dead_code)] AuxGenErrorEnum); -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] enum AuxGenErrorEnum { // TODO (#43): this can be removed when error verification is added #[allow(dead_code)] @@ -52,66 +59,77 @@ enum AuxGenErrorEnum { Round3(String), } -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(bound(serialize = "PrmProof

: Serialize"))] -#[serde(bound(deserialize = "PrmProof

: for<'x> Deserialize<'x>"))] -pub struct PublicData1 { - cap_y: Point, - cap_b: SchCommitment, - paillier_pk: PublicKeyPaillier, // $N_i$ - rp_params: RPParams, // $s_i$ and $t_i$ - hat_psi: PrmProof

, - rho: BitVec, - u: BitVec, -} +impl ProtocolError for AuxGenError { + fn description(&self) -> String { + unimplemented!() + } -#[derive(Debug, Clone)] -pub struct PublicData1Precomp { - data: PublicData1

, - paillier_pk: PublicKeyPaillierPrecomputed, - rp_params: RPParamsMod, + fn required_direct_messages(&self) -> BTreeSet { + unimplemented!() + } + + fn required_echo_broadcasts(&self) -> BTreeSet { + unimplemented!() + } + + fn required_combined_echos(&self) -> BTreeSet { + unimplemented!() + } + + fn verify_messages_constitute_error( + &self, + _deserializer: &Deserializer, + _echo_broadcast: &EchoBroadcast, + _normal_broadcat: &NormalBroadcast, + _direct_message: &DirectMessage, + _echo_broadcasts: &BTreeMap, + _normal_broadcasts: &BTreeMap, + _direct_messages: &BTreeMap, + _combined_echos: &BTreeMap>, + ) -> Result<(), ProtocolValidationError> { + unimplemented!() + } } -struct Context { - paillier_sk: SecretKeyPaillierPrecomputed, - y: Scalar, - tau_y: SchSecret, - data_precomp: PublicData1Precomp

, - my_id: I, - other_ids: BTreeSet, - sid_hash: HashOutput, +/// An entry point for the [`AuxGenProtocol`]. +#[derive(Debug, Clone)] +pub struct AuxGen { + all_ids: BTreeSet, + phantom: PhantomData

, } -impl PublicData1

{ - fn hash(&self, sid_hash: &HashOutput, my_id: &I) -> HashOutput { - FofHasher::new_with_dst(b"Auxiliary") - .chain(sid_hash) - .chain(my_id) - .chain(self) - .finalize() +impl AuxGen { + /// Creates a new entry point given the set of the participants' IDs + /// (including this node's). + pub fn new(all_ids: BTreeSet) -> Result { + Ok(Self { + all_ids, + phantom: PhantomData, + }) } } -pub struct Round1 { - context: Context, -} +impl EntryPoint for AuxGen { + type Protocol = AuxGenProtocol; -impl FirstRound for Round1 { - type Inputs = (); - fn new( + fn make_round( + self, rng: &mut impl CryptoRngCore, shared_randomness: &[u8], - other_ids: BTreeSet, - my_id: I, - _inputs: Self::Inputs, - ) -> Result { - let mut all_ids = other_ids.clone(); - all_ids.insert(my_id.clone()); + id: &I, + ) -> Result, LocalError> { + if !self.all_ids.contains(id) { + return Err(LocalError::new( + "The given node IDs must contain this node's ID", + )); + } + + let other_ids = self.all_ids.clone().without(id); let sid_hash = FofHasher::new_with_dst(b"SID") .chain_type::

() .chain(&shared_randomness) - .chain(&all_ids) + .chain(&self.all_ids) .finalize(); // $p_i$, $q_i$ @@ -131,7 +149,7 @@ impl FirstRound for Roun // Ring-Pedersen parameters ($s$, $t$) bundled in a single object. let rp_params = RPParamsMod::random_with_secret(rng, &lambda, paillier_pk); - let aux = (&sid_hash, &my_id); + let aux = (&sid_hash, id); let hat_psi = PrmProof::

::new(rng, &paillier_sk, &lambda, &rp_params, &aux); let rho = BitVec::random(rng, P::SECURITY_PARAMETER); @@ -158,93 +176,145 @@ impl FirstRound for Roun y, tau_y, data_precomp, - my_id, + my_id: id.clone(), other_ids, sid_hash, }; - Ok(Self { context }) + Ok(BoxedRound::new_dynamic(Round1 { context })) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(bound(serialize = "PrmProof

: Serialize"))] +#[serde(bound(deserialize = "PrmProof

: for<'x> Deserialize<'x>"))] +struct PublicData1 { + cap_y: Point, + cap_b: SchCommitment, + paillier_pk: PublicKeyPaillier, // $N_i$ + rp_params: RPParams, // $s_i$ and $t_i$ + hat_psi: PrmProof

, + rho: BitVec, + u: BitVec, +} + +#[derive(Debug, Clone)] +struct PublicData1Precomp { + data: PublicData1

, + paillier_pk: PublicKeyPaillierPrecomputed, + rp_params: RPParamsMod, +} + +#[derive(Debug)] +struct Context { + paillier_sk: SecretKeyPaillierPrecomputed, + y: Scalar, + tau_y: SchSecret, + data_precomp: PublicData1Precomp

, + my_id: I, + other_ids: BTreeSet, + sid_hash: HashOutput, +} + +impl PublicData1

{ + fn hash(&self, sid_hash: &HashOutput, my_id: &I) -> HashOutput { + FofHasher::new_with_dst(b"Auxiliary") + .chain(sid_hash) + .chain(my_id) + .chain(self) + .finalize() } } +#[derive(Debug)] +struct Round1 { + context: Context, +} + #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Round1Message { +struct Round1Message { cap_v: HashOutput, } -pub struct Round1Payload { +struct Round1Payload { cap_v: HashOutput, } -impl Round for Round1 { - type Type = ToNextRound; - type Result = AuxGenResult; - const ROUND_NUM: u8 = 1; - const NEXT_ROUND_NUM: Option = Some(2); +impl Round for Round1 { + type Protocol = AuxGenProtocol; - fn other_ids(&self) -> &BTreeSet { - &self.context.other_ids + fn id(&self) -> RoundId { + RoundId::new(1) + } + + fn possible_next_rounds(&self) -> BTreeSet { + BTreeSet::from([RoundId::new(2)]) } - fn my_id(&self) -> &I { - &self.context.my_id + fn message_destinations(&self) -> &BTreeSet { + &self.context.other_ids } - const REQUIRES_ECHO: bool = true; - type BroadcastMessage = Round1Message; - type DirectMessage = (); - type Payload = Round1Payload; - type Artifact = (); + fn expecting_messages_from(&self) -> &BTreeSet { + &self.context.other_ids + } - fn make_broadcast_message( + fn make_echo_broadcast( &self, _rng: &mut impl CryptoRngCore, - ) -> Option { - Some(Round1Message { - cap_v: self - .context - .data_precomp - .data - .hash(&self.context.sid_hash, self.my_id()), - }) + serializer: &Serializer, + ) -> Result { + EchoBroadcast::new( + serializer, + Round1Message { + cap_v: self + .context + .data_precomp + .data + .hash(&self.context.sid_hash, &self.context.my_id), + }, + ) } - no_direct_messages!(I); - - fn verify_message( + fn receive_message( &self, _rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, _from: &I, - broadcast_msg: Self::BroadcastMessage, - _direct_msg: Self::DirectMessage, - ) -> Result::ProvableError> { - Ok(Round1Payload { - cap_v: broadcast_msg.cap_v, - }) + echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, + direct_message: DirectMessage, + ) -> Result> { + normal_broadcast.assert_is_none()?; + direct_message.assert_is_none()?; + let echo_broadcast = echo_broadcast.deserialize::(deserializer)?; + Ok(Payload::new(Round1Payload { + cap_v: echo_broadcast.cap_v, + })) } -} -impl FinalizableToNextRound - for Round1 -{ - type NextRound = Round2; - fn finalize_to_next_round( + fn finalize( self, _rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - _artifacts: BTreeMap>::Artifact>, - ) -> Result> { - Ok(Round2 { - context: self.context, - others_cap_v: payloads - .into_iter() - .map(|(id, payload)| (id, payload.cap_v)) - .collect(), - }) + payloads: BTreeMap, + _artifacts: BTreeMap, + ) -> Result, LocalError> { + let payloads = payloads.downcast_all::()?; + let others_cap_v = payloads + .into_iter() + .map(|(id, payload)| (id, payload.cap_v)) + .collect(); + Ok(FinalizeOutcome::AnotherRound(BoxedRound::new_dynamic( + Round2 { + context: self.context, + others_cap_v, + }, + ))) } } -pub struct Round2 { +#[derive(Debug)] +struct Round2 { context: Context, others_cap_v: BTreeMap, } @@ -252,94 +322,100 @@ pub struct Round2 { #[derive(Clone, Serialize, Deserialize)] #[serde(bound(serialize = "PublicData1

: Serialize"))] #[serde(bound(deserialize = "PublicData1

: for<'x> Deserialize<'x>"))] -pub struct Round2Message { +struct Round2Message { data: PublicData1

, } -pub struct Round2Payload { +struct Round2Payload { data: PublicData1Precomp

, } -impl Round for Round2 { - type Type = ToNextRound; - type Result = AuxGenResult; - const ROUND_NUM: u8 = 2; - const NEXT_ROUND_NUM: Option = Some(3); +impl Round for Round2 { + type Protocol = AuxGenProtocol; - fn other_ids(&self) -> &BTreeSet { - &self.context.other_ids + fn id(&self) -> RoundId { + RoundId::new(2) + } + + fn possible_next_rounds(&self) -> BTreeSet { + BTreeSet::from([RoundId::new(3)]) } - fn my_id(&self) -> &I { - &self.context.my_id + fn message_destinations(&self) -> &BTreeSet { + &self.context.other_ids } - type BroadcastMessage = Round2Message

; - type DirectMessage = (); - type Payload = Round2Payload

; - type Artifact = (); + fn expecting_messages_from(&self) -> &BTreeSet { + &self.context.other_ids + } - fn make_broadcast_message( + fn make_normal_broadcast( &self, _rng: &mut impl CryptoRngCore, - ) -> Option { - Some(Round2Message { - data: self.context.data_precomp.data.clone(), - }) + serializer: &Serializer, + ) -> Result { + NormalBroadcast::new( + serializer, + Round2Message { + data: self.context.data_precomp.data.clone(), + }, + ) } - no_direct_messages!(I); - - fn verify_message( + fn receive_message( &self, _rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, from: &I, - broadcast_msg: Self::BroadcastMessage, - _direct_msg: Self::DirectMessage, - ) -> Result::ProvableError> { - if &broadcast_msg.data.hash(&self.context.sid_hash, from) + echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, + direct_message: DirectMessage, + ) -> Result> { + echo_broadcast.assert_is_none()?; + direct_message.assert_is_none()?; + let normal_broadcast = normal_broadcast.deserialize::>(deserializer)?; + + if &normal_broadcast.data.hash(&self.context.sid_hash, from) != self.others_cap_v.get(from).unwrap() { - return Err(AuxGenError(AuxGenErrorEnum::Round2("Hash mismatch".into()))); + return Err(ReceiveError::protocol(AuxGenError( + AuxGenErrorEnum::Round2("Hash mismatch".into()), + ))); } - let paillier_pk = broadcast_msg.data.paillier_pk.to_precomputed(); + let paillier_pk = normal_broadcast.data.paillier_pk.to_precomputed(); if (paillier_pk.modulus().bits_vartime() as usize) < 8 * P::SECURITY_PARAMETER { - return Err(AuxGenError(AuxGenErrorEnum::Round2( - "Paillier modulus is too small".into(), + return Err(ReceiveError::protocol(AuxGenError( + AuxGenErrorEnum::Round2("Paillier modulus is too small".into()), ))); } let aux = (&self.context.sid_hash, &from); - let rp_params = broadcast_msg.data.rp_params.to_mod(&paillier_pk); - if !broadcast_msg.data.hat_psi.verify(&rp_params, &aux) { - return Err(AuxGenError(AuxGenErrorEnum::Round2( - "PRM verification failed".into(), + let rp_params = normal_broadcast.data.rp_params.to_mod(&paillier_pk); + if !normal_broadcast.data.hat_psi.verify(&rp_params, &aux) { + return Err(ReceiveError::protocol(AuxGenError( + AuxGenErrorEnum::Round2("PRM verification failed".into()), ))); } - Ok(Round2Payload { + Ok(Payload::new(Round2Payload { data: PublicData1Precomp { - data: broadcast_msg.data, + data: normal_broadcast.data, paillier_pk, rp_params, }, - }) + })) } -} -impl FinalizableToNextRound - for Round2 -{ - type NextRound = Round3; - fn finalize_to_next_round( + fn finalize( self, rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - _artifacts: BTreeMap>::Artifact>, - ) -> Result> { + payloads: BTreeMap, + _artifacts: BTreeMap, + ) -> Result, LocalError> { + let payloads = payloads.downcast_all::>()?; let others_data = payloads .into_iter() .map(|(id, payload)| (id, payload.data)) @@ -349,11 +425,14 @@ impl FinalizableToNextRound rho ^= &data.data.rho; } - Ok(Round3::new(rng, self.context, others_data, rho)) + Ok(FinalizeOutcome::AnotherRound(BoxedRound::new_dynamic( + Round3::new(rng, self.context, others_data, rho), + ))) } } -pub struct Round3 { +#[derive(Debug)] +struct Round3 { context: Context, rho: BitVec, others_data: BTreeMap>, @@ -370,13 +449,13 @@ pub struct Round3 { ModProof

: for<'x> Deserialize<'x>, FacProof

: for<'x> Deserialize<'x>, "))] -pub struct PublicData2 { +struct PublicData2 { psi_mod: ModProof

, // $\psi_i$, a P^{mod} for the Paillier modulus phi: FacProof

, pi: SchProof, } -impl Round3 { +impl Round3 { fn new( rng: &mut impl CryptoRngCore, context: Context, @@ -407,36 +486,39 @@ impl Round3 { #[derive(Clone, Serialize, Deserialize)] #[serde(bound(serialize = "PublicData2

: Serialize"))] #[serde(bound(deserialize = "PublicData2

: for<'x> Deserialize<'x>"))] -pub struct Round3Message { +struct Round3Message { data2: PublicData2

, } -impl Round for Round3 { - type Type = ToResult; - type Result = AuxGenResult; - const ROUND_NUM: u8 = 3; - const NEXT_ROUND_NUM: Option = None; +impl Round for Round3 { + type Protocol = AuxGenProtocol; - fn other_ids(&self) -> &BTreeSet { - &self.context.other_ids + fn id(&self) -> RoundId { + RoundId::new(3) } - fn my_id(&self) -> &I { - &self.context.my_id + fn possible_next_rounds(&self) -> BTreeSet { + BTreeSet::new() } - type BroadcastMessage = (); - type DirectMessage = Round3Message

; - type Payload = (); - type Artifact = (); + fn may_produce_result(&self) -> bool { + true + } - no_broadcast_messages!(); + fn message_destinations(&self) -> &BTreeSet { + &self.context.other_ids + } + + fn expecting_messages_from(&self) -> &BTreeSet { + &self.context.other_ids + } fn make_direct_message( &self, rng: &mut impl CryptoRngCore, + serializer: &Serializer, destination: &I, - ) -> (Self::DirectMessage, Self::Artifact) { + ) -> Result<(DirectMessage, Option), LocalError> { let aux = (&self.context.sid_hash, &self.context.my_id, &self.rho); let phi = FacProof::new( @@ -452,62 +534,68 @@ impl Round for Round3 Result::ProvableError> { + echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, + direct_message: DirectMessage, + ) -> Result> { + echo_broadcast.assert_is_none()?; + normal_broadcast.assert_is_none()?; + let direct_message = direct_message.deserialize::>(deserializer)?; + let sender_data = &self.others_data.get(from).unwrap(); let aux = (&self.context.sid_hash, &from, &self.rho); - if !direct_msg + if !direct_message .data2 .psi_mod .verify(rng, &sender_data.paillier_pk, &aux) { - return Err(AuxGenError(AuxGenErrorEnum::Round3( - "Mod proof verification failed".into(), + return Err(ReceiveError::protocol(AuxGenError( + AuxGenErrorEnum::Round3("Mod proof verification failed".into()), ))); } - if !direct_msg.data2.phi.verify( + if !direct_message.data2.phi.verify( &sender_data.paillier_pk, &self.context.data_precomp.rp_params, &aux, ) { - return Err(AuxGenError(AuxGenErrorEnum::Round3( - "Fac proof verification failed".into(), + return Err(ReceiveError::protocol(AuxGenError( + AuxGenErrorEnum::Round3("Fac proof verification failed".into()), ))); } - if !direct_msg + if !direct_message .data2 .pi .verify(&sender_data.data.cap_b, &sender_data.data.cap_y, &aux) { - return Err(AuxGenError(AuxGenErrorEnum::Round3( - "Sch proof verification (Y) failed".into(), + return Err(ReceiveError::protocol(AuxGenError( + AuxGenErrorEnum::Round3("Sch proof verification (Y) failed".into()), ))); } - Ok(()) + Ok(Payload::empty()) } -} -impl FinalizableToResult for Round3 { - fn finalize_to_result( + fn finalize( self, _rng: &mut impl CryptoRngCore, - _payloads: BTreeMap>::Payload>, - _artifacts: BTreeMap>::Artifact>, - ) -> Result<::Success, FinalizeError> { - let my_id = self.my_id().clone(); + _payloads: BTreeMap, + _artifacts: BTreeMap, + ) -> Result, LocalError> { + let my_id = self.context.my_id.clone(); let mut all_data = self.others_data; all_data.insert(my_id.clone(), self.context.data_precomp); @@ -536,7 +624,7 @@ impl FinalizableToResult public_aux, }; - Ok(aux_info) + Ok(FinalizeOutcome::Result(aux_info)) } } @@ -544,44 +632,36 @@ impl FinalizableToResult mod tests { use alloc::collections::BTreeSet; - use rand_core::{OsRng, RngCore}; + use manul::{ + dev::{run_sync, BinaryFormat, TestSessionParams, TestSigner, TestVerifier}, + session::signature::Keypair, + }; + use rand_core::OsRng; use secrecy::ExposeSecret; - use super::Round1; + use super::AuxGen; use crate::cggmp21::TestParams; - use crate::rounds::{ - test_utils::{step_next_round, step_result, step_round, Id, Without}, - FirstRound, - }; #[test] fn execute_aux_gen() { - let mut shared_randomness = [0u8; 32]; - OsRng.fill_bytes(&mut shared_randomness); + let signers = (0..3).map(TestSigner::new).collect::>(); - let ids = BTreeSet::from([Id(0), Id(1), Id(2)]); - - let r1 = ids + let all_ids = signers .iter() - .map(|id| { - let round = Round1::::new( - &mut OsRng, - &shared_randomness, - ids.clone().without(id), - *id, - (), - ) - .unwrap(); - (*id, round) + .map(|signer| signer.verifying_key()) + .collect::>(); + let entry_points = signers + .into_iter() + .map(|signer| { + let entry_point = AuxGen::::new(all_ids.clone()).unwrap(); + (signer, entry_point) }) - .collect(); + .collect::>(); - let r1a = step_round(&mut OsRng, r1).unwrap(); - let r2 = step_next_round(&mut OsRng, r1a).unwrap(); - let r2a = step_round(&mut OsRng, r2).unwrap(); - let r3 = step_next_round(&mut OsRng, r2a).unwrap(); - let r3a = step_round(&mut OsRng, r3).unwrap(); - let aux_infos = step_result(&mut OsRng, r3a).unwrap(); + let aux_infos = run_sync::<_, TestSessionParams>(&mut OsRng, entry_points) + .unwrap() + .results() + .unwrap(); for (id, aux_info) in aux_infos.iter() { for other_aux_info in aux_infos.values() { diff --git a/synedrion/src/cggmp21/protocols/interactive_signing.rs b/synedrion/src/cggmp21/protocols/interactive_signing.rs index cd8540af..cfe5fb47 100644 --- a/synedrion/src/cggmp21/protocols/interactive_signing.rs +++ b/synedrion/src/cggmp21/protocols/interactive_signing.rs @@ -1,290 +1,1311 @@ -use alloc::boxed::Box; -use alloc::collections::{BTreeMap, BTreeSet}; -use core::fmt::Debug; -use core::marker::PhantomData; +//! Merged Presigning and Signing protocols, +//! in the paper ECDSA Pre-Signing (Fig. 7) and Signing (Fig. 8). +use alloc::{ + boxed::Box, + collections::{BTreeMap, BTreeSet}, + string::String, + vec::Vec, +}; +use core::{fmt::Debug, marker::PhantomData}; + +use manul::protocol::{ + Artifact, BoxedRound, Deserializer, DirectMessage, EchoBroadcast, EntryPoint, FinalizeOutcome, + LocalError, NormalBroadcast, PartyId, Payload, Protocol, ProtocolError, ProtocolMessagePart, + ProtocolValidationError, ReceiveError, Round, RoundId, Serializer, +}; use rand_core::CryptoRngCore; -use serde::Serialize; - -use super::super::params::SchemeParams; -use super::super::{AuxInfo, KeyShare}; -use super::presigning::{self, PresigningResult}; -use super::signing::{self, SigningResult}; -use crate::curve::{RecoverableSignature, Scalar}; -use crate::rounds::{ - wrap_finalize_error, CorrectnessProofWrapper, FinalizableToNextRound, FinalizableToResult, - FinalizeError, FirstRound, InitError, ProtocolResult, ProvableErrorWrapper, Round, - RoundWrapper, ToNextRound, ToResult, WrappedRound, +use secrecy::{ExposeSecret, SecretBox}; +use serde::{Deserialize, Serialize}; + +use super::super::{ + entities::{AuxInfoPrecomputed, PresigningData, PresigningValues}, + sigma::{AffGProof, DecProof, EncProof, LogStarProof, MulProof, MulStarProof}, + AuxInfo, KeyShare, SchemeParams, +}; +use crate::{ + curve::{Point, RecoverableSignature, Scalar}, + paillier::{Ciphertext, CiphertextMod, PaillierParams, Randomizer, RandomizerMod}, + tools::{ + hashing::{Chain, FofHasher, HashOutput}, + DowncastMap, Without, + }, + uint::Signed, }; -/// Possible results of the merged Presigning and Signing protocols. -#[derive(Debug)] -pub struct InteractiveSigningResult(PhantomData

, PhantomData); +/// A protocol for creating all the data necessary for signing +/// that doesn't require knowing the actual message being signed. +#[derive(Debug, Clone, Copy)] +pub struct InteractiveSigningProtocol(PhantomData<(P, I)>); -impl ProtocolResult for InteractiveSigningResult { - type Success = RecoverableSignature; - type ProvableError = InteractiveSigningError; - type CorrectnessProof = InteractiveSigningProof; +impl Protocol for InteractiveSigningProtocol { + type Result = RecoverableSignature; + type ProtocolError = InteractiveSigningError; } -/// Possible verifiable errors of the merged Presigning and Signing protocols. -#[derive(Debug)] -pub enum InteractiveSigningError { - /// An error in the Presigning part of the protocol. - Presigning( as ProtocolResult>::ProvableError), - /// An error in the Signing part of the protocol. - Signing( as ProtocolResult>::ProvableError), +/// Possible verifiable errors of the Presigning protocol. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum InteractiveSigningError { + /// An error in Round 1. + Round1(String), + /// An error in Round 2. + Round2(String), + /// An error in Round 3. + Round3(String), + /// An error in the signing error round. + SigningError(String), } -/// A proof of a node's correct behavior for the merged Presigning and Signing protocols. -#[derive(Debug)] -pub enum InteractiveSigningProof { - /// A proof for the Presigning part of the protocol. - Presigning( as ProtocolResult>::CorrectnessProof), - /// A proof for the Signing part of the protocol. - Signing( as ProtocolResult>::CorrectnessProof), -} +impl ProtocolError for InteractiveSigningError { + fn description(&self) -> String { + "".into() + } -impl ProvableErrorWrapper> - for InteractiveSigningResult -{ - fn wrap_error( - error: as ProtocolResult>::ProvableError, - ) -> Self::ProvableError { - InteractiveSigningError::Presigning(error) + fn required_direct_messages(&self) -> BTreeSet { + BTreeSet::new() } -} -impl CorrectnessProofWrapper> - for InteractiveSigningResult -{ - fn wrap_proof( - proof: as ProtocolResult>::CorrectnessProof, - ) -> Self::CorrectnessProof { - InteractiveSigningProof::Presigning(proof) + fn required_echo_broadcasts(&self) -> BTreeSet { + BTreeSet::new() + } + + fn required_combined_echos(&self) -> BTreeSet { + BTreeSet::new() + } + + fn verify_messages_constitute_error( + &self, + _deserializer: &Deserializer, + _echo_broadcast: &EchoBroadcast, + _normal_broadcat: &NormalBroadcast, + _direct_message: &DirectMessage, + _echo_broadcasts: &BTreeMap, + _normal_broadcasts: &BTreeMap, + _direct_messages: &BTreeMap, + _combined_echos: &BTreeMap>, + ) -> Result<(), ProtocolValidationError> { + Ok(()) } } -impl ProvableErrorWrapper> - for InteractiveSigningResult -{ - fn wrap_error( - error: as ProtocolResult>::ProvableError, - ) -> Self::ProvableError { - InteractiveSigningError::Signing(error) +/// Prehashed message to sign. +pub type PrehashedMessage = [u8; 32]; + +/// An entry point for the [`InteractiveSigningProtocol`]. +#[derive(Debug)] +pub struct InteractiveSigning { + key_share: KeyShare, + aux_info: AuxInfo, + prehashed_message: PrehashedMessage, +} + +impl InteractiveSigning { + /// Creates a new entry point given a share of the secret key. + pub fn new( + prehashed_message: PrehashedMessage, + key_share: KeyShare, + aux_info: AuxInfo, + ) -> Self { + // TODO: check that both are consistent + Self { + prehashed_message, + key_share, + aux_info, + } } } -impl CorrectnessProofWrapper> - for InteractiveSigningResult -{ - fn wrap_proof( - proof: as ProtocolResult>::CorrectnessProof, - ) -> Self::CorrectnessProof { - InteractiveSigningProof::Signing(proof) +impl EntryPoint for InteractiveSigning { + type Protocol = InteractiveSigningProtocol; + + fn make_round( + self, + rng: &mut impl CryptoRngCore, + shared_randomness: &[u8], + id: &I, + ) -> Result, LocalError> { + let key_share = self.key_share; + let aux_info = self.aux_info; + + if id != key_share.owner() || id != aux_info.owner() { + return Err(LocalError::new( + "ID mismatch between the signer, the key share and the aux info", + )); + } + + let other_ids = key_share + .public_shares + .keys() + .cloned() + .collect::>() + .without(id); + + // This includes the info of $ssid$ in the paper + // (scheme parameters + public data from all shares - hashed in `share_set_id`), + // with the session randomness added. + let ssid_hash = FofHasher::new_with_dst(b"ShareSetID") + .chain_type::

() + .chain(&shared_randomness) + .chain(&key_share.public_shares) + .chain(&aux_info.public_aux) + .finalize(); + + let aux_info = aux_info.to_precomputed(); + + // TODO (#68): check that KeyShare is consistent with AuxInfo + + // The share of an ephemeral scalar + let k = Scalar::random(rng); + // The share of the mask used to generate the inverse of the ephemeral scalar + let gamma = Scalar::random(rng); + + let pk = aux_info.secret_aux.paillier_sk.public_key(); + + let nu = RandomizerMod::::random(rng, pk); + let cap_g = + CiphertextMod::new_with_randomizer(pk, &P::uint_from_scalar(&gamma), &nu.retrieve()); + + let rho = RandomizerMod::::random(rng, pk); + let cap_k = + CiphertextMod::new_with_randomizer(pk, &P::uint_from_scalar(&k), &rho.retrieve()); + + Ok(BoxedRound::new_dynamic(Round1 { + context: Context { + ssid_hash, + my_id: id.clone(), + message: Scalar::from_reduced_bytes(&self.prehashed_message), + other_ids, + key_share, + aux_info, + k, + gamma, + rho, + nu, + }, + cap_k, + cap_g, + })) } } +#[derive(Debug)] struct Context { - shared_randomness: Box<[u8]>, - key_share: KeyShare, - aux_info: AuxInfo, + ssid_hash: HashOutput, + my_id: I, message: Scalar, + other_ids: BTreeSet, + key_share: KeyShare, + aux_info: AuxInfoPrecomputed, + k: Scalar, + gamma: Scalar, + rho: RandomizerMod, + nu: RandomizerMod, } -#[derive(Clone)] -pub(crate) struct Inputs { - pub(crate) key_share: KeyShare, - pub(crate) aux_info: AuxInfo, - pub(crate) message: Scalar, +#[derive(Debug)] +struct Round1 { + context: Context, + cap_k: CiphertextMod, + cap_g: CiphertextMod, } -pub(crate) struct Round1 { - round: presigning::Round1, - context: Context, +#[derive(Clone, Serialize, Deserialize)] +#[serde(bound(serialize = "Ciphertext: Serialize"))] +#[serde(bound(deserialize = "Ciphertext: for<'x> Deserialize<'x>"))] +struct Round1BroadcastMessage { + cap_k: Ciphertext, + cap_g: Ciphertext, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(bound(serialize = "EncProof

: Serialize"))] +#[serde(bound(deserialize = "EncProof

: for<'x> Deserialize<'x>"))] +struct Round1DirectMessage { + psi0: EncProof

, } -impl FirstRound for Round1 { - type Inputs = Inputs; - fn new( +struct Round1Payload { + cap_k: Ciphertext, + cap_g: Ciphertext, +} + +impl Round for Round1 { + type Protocol = InteractiveSigningProtocol; + + fn id(&self) -> RoundId { + RoundId::new(1) + } + + fn possible_next_rounds(&self) -> BTreeSet { + BTreeSet::from([RoundId::new(2)]) + } + + fn message_destinations(&self) -> &BTreeSet { + &self.context.other_ids + } + + fn expecting_messages_from(&self) -> &BTreeSet { + &self.context.other_ids + } + + fn make_echo_broadcast( + &self, + _rng: &mut impl CryptoRngCore, + serializer: &Serializer, + ) -> Result { + EchoBroadcast::new( + serializer, + Round1BroadcastMessage::

{ + cap_k: self.cap_k.retrieve(), + cap_g: self.cap_g.retrieve(), + }, + ) + } + + fn make_direct_message( + &self, rng: &mut impl CryptoRngCore, - shared_randomness: &[u8], - other_ids: BTreeSet, - my_id: I, - inputs: Self::Inputs, - ) -> Result { - let round = presigning::Round1::new( + serializer: &Serializer, + destination: &I, + ) -> Result<(DirectMessage, Option), LocalError> { + let aux = (&self.context.ssid_hash, &destination); + let psi0 = EncProof::new( rng, - shared_randomness, - other_ids, - my_id, - (inputs.key_share.clone(), inputs.aux_info.clone()), - )?; - let context = Context { - shared_randomness: shared_randomness.into(), - key_share: inputs.key_share, - aux_info: inputs.aux_info, - message: inputs.message, - }; - Ok(Self { context, round }) + &P::signed_from_scalar(&self.context.k).unwrap(), + &self.context.rho, + self.context.aux_info.secret_aux.paillier_sk.public_key(), + &self.cap_k, + &self.context.aux_info.public_aux[destination].rp_params, + &aux, + ); + + Ok(( + DirectMessage::new(serializer, Round1DirectMessage::

{ psi0 })?, + None, + )) } -} -impl RoundWrapper for Round1 { - type Type = ToNextRound; - type Result = InteractiveSigningResult; - type InnerRound = presigning::Round1; - const ROUND_NUM: u8 = 1; - const NEXT_ROUND_NUM: Option = Some(2); - fn inner_round(&self) -> &Self::InnerRound { - &self.round + fn receive_message( + &self, + _rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, + from: &I, + echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, + direct_message: DirectMessage, + ) -> Result> { + normal_broadcast.assert_is_none()?; + + let direct_message = direct_message.deserialize::>(deserializer)?; + let echo_broadcast = + echo_broadcast.deserialize::>(deserializer)?; + + let aux = (&self.context.ssid_hash, &self.context.my_id); + + let public_aux = &self.context.aux_info.public_aux[&self.context.my_id]; + + let from_pk = &self.context.aux_info.public_aux[from].paillier_pk; + + if !direct_message.psi0.verify( + from_pk, + &echo_broadcast.cap_k.to_mod(from_pk), + &public_aux.rp_params, + &aux, + ) { + return Err(ReceiveError::protocol(InteractiveSigningError::Round1( + "Failed to verify EncProof".into(), + ))); + } + + Ok(Payload::new(Round1Payload::

{ + cap_k: echo_broadcast.cap_k, + cap_g: echo_broadcast.cap_g, + })) + } + + fn finalize( + self, + _rng: &mut impl CryptoRngCore, + payloads: BTreeMap, + _artifacts: BTreeMap, + ) -> Result, LocalError> { + let payloads = payloads.downcast_all::>()?; + + let (others_cap_k, others_cap_g): (BTreeMap<_, _>, BTreeMap<_, _>) = payloads + .into_iter() + .map(|(id, payload)| ((id.clone(), payload.cap_k), (id, payload.cap_g))) + .unzip(); + + let my_id = self.context.my_id.clone(); + + let mut all_cap_k = others_cap_k + .into_iter() + .map(|(id, ciphertext)| { + let ciphertext_mod = + ciphertext.to_mod(&self.context.aux_info.public_aux[&id].paillier_pk); + (id, ciphertext_mod) + }) + .collect::>(); + all_cap_k.insert(my_id.clone(), self.cap_k); + + let mut all_cap_g = others_cap_g + .into_iter() + .map(|(id, ciphertext)| { + let ciphertext_mod = + ciphertext.to_mod(&self.context.aux_info.public_aux[&id].paillier_pk); + (id, ciphertext_mod) + }) + .collect::>(); + all_cap_g.insert(my_id, self.cap_g); + + Ok(FinalizeOutcome::AnotherRound(BoxedRound::new_dynamic( + Round2 { + context: self.context, + all_cap_k, + all_cap_g, + }, + ))) } } -impl WrappedRound for Round1 {} +#[derive(Debug)] +struct Round2 { + context: Context, + all_cap_k: BTreeMap>, + all_cap_g: BTreeMap>, +} -impl FinalizableToNextRound - for Round1 -{ - type NextRound = Round2; - fn finalize_to_next_round( - self, +#[derive(Clone, Serialize, Deserialize)] +#[serde(bound(serialize = " + Ciphertext: Serialize, + AffGProof

: Serialize, + LogStarProof

: Serialize, +"))] +#[serde(bound(deserialize = " + Ciphertext: for<'x> Deserialize<'x>, + AffGProof

: for<'x> Deserialize<'x>, + LogStarProof

: for<'x> Deserialize<'x>, +"))] +struct Round2Message { + cap_gamma: Point, + cap_d: Ciphertext, + hat_cap_d: Ciphertext, + cap_f: Ciphertext, + hat_cap_f: Ciphertext, + psi: AffGProof

, + hat_psi: AffGProof

, + hat_psi_prime: LogStarProof

, +} + +#[derive(Debug, Clone)] +struct Round2Artifact { + beta: SecretBox::Uint>>, + hat_beta: SecretBox::Uint>>, + r: Randomizer, + s: Randomizer, + hat_r: Randomizer, + hat_s: Randomizer, + cap_d: CiphertextMod, + cap_f: CiphertextMod, + hat_cap_d: CiphertextMod, + hat_cap_f: CiphertextMod, +} + +struct Round2Payload { + cap_gamma: Point, + alpha: Signed<::Uint>, + hat_alpha: Signed<::Uint>, + cap_d: CiphertextMod, + hat_cap_d: CiphertextMod, +} + +impl Round for Round2 { + type Protocol = InteractiveSigningProtocol; + + fn id(&self) -> RoundId { + RoundId::new(2) + } + + fn possible_next_rounds(&self) -> BTreeSet { + BTreeSet::from([RoundId::new(3)]) + } + + fn message_destinations(&self) -> &BTreeSet { + &self.context.other_ids + } + + fn expecting_messages_from(&self) -> &BTreeSet { + &self.context.other_ids + } + + fn make_direct_message( + &self, rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - artifacts: BTreeMap>::Artifact>, - ) -> Result> { - let round = self - .round - .finalize_to_next_round(rng, payloads, artifacts) - .map_err(wrap_finalize_error)?; - Ok(Round2 { - round, - context: self.context, - }) + serializer: &Serializer, + destination: &I, + ) -> Result<(DirectMessage, Option), LocalError> { + let aux = (&self.context.ssid_hash, &self.context.my_id); + + let cap_gamma = self.context.gamma.mul_by_generator(); + let pk = self.context.aux_info.secret_aux.paillier_sk.public_key(); + + let target_pk = &self.context.aux_info.public_aux[destination].paillier_pk; + + let beta = SecretBox::new(Box::new(Signed::random_bounded_bits(rng, P::LP_BOUND))); + let hat_beta = SecretBox::new(Box::new(Signed::random_bounded_bits(rng, P::LP_BOUND))); + let r = RandomizerMod::random(rng, pk); + let s = RandomizerMod::random(rng, target_pk); + let hat_r = RandomizerMod::random(rng, pk); + let hat_s = RandomizerMod::random(rng, target_pk); + + let cap_f = + CiphertextMod::new_with_randomizer_signed(pk, beta.expose_secret(), &r.retrieve()); + let cap_d = &self.all_cap_k[destination] + * P::signed_from_scalar(&self.context.gamma).unwrap() + + CiphertextMod::new_with_randomizer_signed( + target_pk, + &-beta.expose_secret(), + &s.retrieve(), + ); + + let hat_cap_f = CiphertextMod::new_with_randomizer_signed( + pk, + hat_beta.expose_secret(), + &hat_r.retrieve(), + ); + let hat_cap_d = &self.all_cap_k[destination] + * P::signed_from_scalar(self.context.key_share.secret_share.expose_secret()).unwrap() + + CiphertextMod::new_with_randomizer_signed( + target_pk, + &-hat_beta.expose_secret(), + &hat_s.retrieve(), + ); + + let public_aux = &self.context.aux_info.public_aux[destination]; + let rp = &public_aux.rp_params; + + let psi = AffGProof::new( + rng, + &P::signed_from_scalar(&self.context.gamma).unwrap(), + &beta, + s.clone(), + r.clone(), + target_pk, + pk, + &self.all_cap_k[destination], + &cap_d, + &cap_f, + &cap_gamma, + rp, + &aux, + ); + + let hat_psi = AffGProof::new( + rng, + &P::signed_from_scalar(self.context.key_share.secret_share.expose_secret()).unwrap(), + &hat_beta, + hat_s.clone(), + hat_r.clone(), + target_pk, + pk, + &self.all_cap_k[destination], + &hat_cap_d, + &hat_cap_f, + &self.context.key_share.public_shares[&self.context.my_id], + rp, + &aux, + ); + + let hat_psi_prime = LogStarProof::new( + rng, + &P::signed_from_scalar(&self.context.gamma).unwrap(), + &self.context.nu, + pk, + &self.all_cap_g[&self.context.my_id], + &Point::GENERATOR, + &cap_gamma, + rp, + &aux, + ); + + let msg = DirectMessage::new( + serializer, + Round2Message::

{ + cap_gamma, + cap_d: cap_d.retrieve(), + cap_f: cap_f.retrieve(), + hat_cap_d: hat_cap_d.retrieve(), + hat_cap_f: hat_cap_f.retrieve(), + psi, + hat_psi, + hat_psi_prime, + }, + )?; + + let artifact = Artifact::new(Round2Artifact::

{ + beta, + hat_beta, + r: r.retrieve(), + s: s.retrieve(), + hat_r: hat_r.retrieve(), + hat_s: hat_s.retrieve(), + cap_d, + cap_f, + hat_cap_d, + hat_cap_f, + }); + + Ok((msg, Some(artifact))) + } + + fn receive_message( + &self, + _rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, + from: &I, + echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, + direct_message: DirectMessage, + ) -> Result> { + echo_broadcast.assert_is_none()?; + normal_broadcast.assert_is_none()?; + let direct_message = direct_message.deserialize::>(deserializer)?; + + let aux = (&self.context.ssid_hash, &from); + let pk = &self.context.aux_info.secret_aux.paillier_sk.public_key(); + let from_pk = &self.context.aux_info.public_aux[from].paillier_pk; + + let cap_x = self.context.key_share.public_shares[from]; + + let public_aux = &self.context.aux_info.public_aux[&self.context.my_id]; + let rp = &public_aux.rp_params; + + let cap_d = direct_message.cap_d.to_mod(pk); + let hat_cap_d = direct_message.hat_cap_d.to_mod(pk); + + if !direct_message.psi.verify( + pk, + from_pk, + &self.all_cap_k[&self.context.my_id], + &cap_d, + &direct_message.cap_f.to_mod(from_pk), + &direct_message.cap_gamma, + rp, + &aux, + ) { + return Err(ReceiveError::protocol(InteractiveSigningError::Round2( + "Failed to verify AffGProof (psi)".into(), + ))); + } + + if !direct_message.hat_psi.verify( + pk, + from_pk, + &self.all_cap_k[&self.context.my_id], + &hat_cap_d, + &direct_message.hat_cap_f.to_mod(from_pk), + &cap_x, + rp, + &aux, + ) { + return Err(ReceiveError::protocol(InteractiveSigningError::Round2( + "Failed to verify AffGProof (hat_psi)".into(), + ))); + } + + if !direct_message.hat_psi_prime.verify( + from_pk, + &self.all_cap_g[from], + &Point::GENERATOR, + &direct_message.cap_gamma, + rp, + &aux, + ) { + return Err(ReceiveError::protocol(InteractiveSigningError::Round2( + "Failed to verify LogStarProof".into(), + ))); + } + + let alpha = cap_d.decrypt_signed(&self.context.aux_info.secret_aux.paillier_sk); + let hat_alpha = hat_cap_d.decrypt_signed(&self.context.aux_info.secret_aux.paillier_sk); + + // `alpha == x * y + z` where `0 <= x, y < q`, and `-2^l' <= z <= 2^l'`, + // where `q` is the curve order. + // We will need this bound later, so we're asserting it. + let alpha = alpha + .assert_bit_bound_usize(core::cmp::max(2 * P::L_BOUND, P::LP_BOUND) + 1) + .unwrap(); + let hat_alpha = hat_alpha + .assert_bit_bound_usize(core::cmp::max(2 * P::L_BOUND, P::LP_BOUND) + 1) + .unwrap(); + + Ok(Payload::new(Round2Payload::

{ + cap_gamma: direct_message.cap_gamma, + alpha, + hat_alpha, + cap_d, + hat_cap_d, + })) + } + + fn finalize( + self, + _rng: &mut impl CryptoRngCore, + payloads: BTreeMap, + artifacts: BTreeMap, + ) -> Result, LocalError> { + let payloads = payloads.downcast_all::>()?; + let artifacts = artifacts.downcast_all::>()?; + + let cap_gamma = payloads + .values() + .map(|payload| payload.cap_gamma) + .sum::() + + self.context.gamma.mul_by_generator(); + + let cap_delta = cap_gamma * self.context.k; + + let alpha_sum: Signed<_> = payloads.values().map(|p| p.alpha).sum(); + let beta_sum: Signed<_> = artifacts.values().map(|p| p.beta.expose_secret()).sum(); + let delta = P::signed_from_scalar(&self.context.gamma).unwrap() + * P::signed_from_scalar(&self.context.k).unwrap() + + alpha_sum + + beta_sum; + + let hat_alpha_sum: Signed<_> = payloads.values().map(|payload| payload.hat_alpha).sum(); + let hat_beta_sum: Signed<_> = artifacts + .values() + .map(|artifact| artifact.hat_beta.expose_secret()) + .sum(); + let chi = P::signed_from_scalar(self.context.key_share.secret_share.expose_secret()) + .unwrap() + * P::signed_from_scalar(&self.context.k).unwrap() + + hat_alpha_sum + + hat_beta_sum; + + let (cap_ds, hat_cap_ds) = payloads + .into_iter() + .map(|(id, payload)| ((id.clone(), payload.cap_d), (id, payload.hat_cap_d))) + .unzip(); + + Ok(FinalizeOutcome::AnotherRound(BoxedRound::new_dynamic( + Round3 { + context: self.context, + delta, + chi, + cap_delta, + cap_gamma, + all_cap_k: self.all_cap_k, + all_cap_g: self.all_cap_g, + cap_ds, + hat_cap_ds, + round2_artifacts: artifacts, + }, + ))) } } -pub(crate) struct Round2 { - round: presigning::Round2, +#[derive(Debug)] +struct Round3 { context: Context, + delta: Signed<::Uint>, + chi: Signed<::Uint>, + cap_delta: Point, + cap_gamma: Point, + all_cap_k: BTreeMap>, + all_cap_g: BTreeMap>, + cap_ds: BTreeMap>, + hat_cap_ds: BTreeMap>, + round2_artifacts: BTreeMap>, } -impl RoundWrapper for Round2 { - type Type = ToNextRound; - type Result = InteractiveSigningResult; - type InnerRound = presigning::Round2; - const ROUND_NUM: u8 = 2; - const NEXT_ROUND_NUM: Option = Some(3); - fn inner_round(&self) -> &Self::InnerRound { - &self.round - } +#[derive(Clone, Serialize, Deserialize)] +#[serde(bound(serialize = "LogStarProof

: Serialize"))] +#[serde(bound(deserialize = "LogStarProof

: for<'x> Deserialize<'x>"))] +struct Round3Message { + delta: Scalar, + cap_delta: Point, + psi_pprime: LogStarProof

, +} + +struct Round3Payload { + delta: Scalar, + cap_delta: Point, } -impl WrappedRound for Round2 {} +impl Round for Round3 { + type Protocol = InteractiveSigningProtocol; -impl FinalizableToNextRound - for Round2 -{ - type NextRound = Round3; - fn finalize_to_next_round( + fn id(&self) -> RoundId { + RoundId::new(3) + } + + fn possible_next_rounds(&self) -> BTreeSet { + BTreeSet::from([RoundId::new(4)]) + } + + fn message_destinations(&self) -> &BTreeSet { + &self.context.other_ids + } + + fn expecting_messages_from(&self) -> &BTreeSet { + &self.context.other_ids + } + + fn make_direct_message( + &self, + rng: &mut impl CryptoRngCore, + serializer: &Serializer, + destination: &I, + ) -> Result<(DirectMessage, Option), LocalError> { + let aux = (&self.context.ssid_hash, &self.context.my_id); + let pk = &self.context.aux_info.secret_aux.paillier_sk.public_key(); + + let public_aux = &self.context.aux_info.public_aux[destination]; + let rp = &public_aux.rp_params; + + let psi_pprime = LogStarProof::new( + rng, + &P::signed_from_scalar(&self.context.k).unwrap(), + &self.context.rho, + pk, + &self.all_cap_k[&self.context.my_id], + &self.cap_gamma, + &self.cap_delta, + rp, + &aux, + ); + + let dm = DirectMessage::new( + serializer, + Round3Message::

{ + delta: P::scalar_from_signed(&self.delta), + cap_delta: self.cap_delta, + psi_pprime, + }, + )?; + + Ok((dm, None)) + } + + fn receive_message( + &self, + _rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, + from: &I, + echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, + direct_message: DirectMessage, + ) -> Result> { + echo_broadcast.assert_is_none()?; + normal_broadcast.assert_is_none()?; + let direct_message = direct_message.deserialize::>(deserializer)?; + + let aux = (&self.context.ssid_hash, &from); + let from_pk = &self.context.aux_info.public_aux[from].paillier_pk; + + let public_aux = &self.context.aux_info.public_aux[&self.context.my_id]; + let rp = &public_aux.rp_params; + + if !direct_message.psi_pprime.verify( + from_pk, + &self.all_cap_k[from], + &self.cap_gamma, + &direct_message.cap_delta, + rp, + &aux, + ) { + return Err(ReceiveError::protocol(InteractiveSigningError::Round3( + "Failed to verify Log-Star proof".into(), + ))); + } + Ok(Payload::new(Round3Payload { + delta: direct_message.delta, + cap_delta: direct_message.cap_delta, + })) + } + + fn finalize( self, rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - artifacts: BTreeMap>::Artifact>, - ) -> Result> { - let round = self - .round - .finalize_to_next_round(rng, payloads, artifacts) - .map_err(wrap_finalize_error)?; - Ok(Round3 { - round, - context: self.context, - }) + payloads: BTreeMap, + _artifacts: BTreeMap, + ) -> Result, LocalError> { + let payloads = payloads.downcast_all::()?; + + let (deltas, cap_deltas): (BTreeMap<_, _>, BTreeMap<_, _>) = payloads + .into_iter() + .map(|(id, payload)| ((id.clone(), payload.delta), (id, payload.cap_delta))) + .unzip(); + + let scalar_delta = P::scalar_from_signed(&self.delta); + let assembled_delta: Scalar = scalar_delta + deltas.values().sum::(); + let assembled_cap_delta: Point = self.cap_delta + cap_deltas.values().sum::(); + + if assembled_delta.mul_by_generator() == assembled_cap_delta { + let nonce = (self.cap_gamma * assembled_delta.invert().unwrap()).x_coordinate(); + let my_id = self.context.my_id.clone(); + + let values = self + .round2_artifacts + .into_iter() + .map(|(id, artifact)| { + let values = PresigningValues { + hat_beta: artifact.hat_beta, + hat_r: artifact.hat_r, + hat_s: artifact.hat_s, + cap_k: self.all_cap_k[&id].clone(), + hat_cap_d_received: self.hat_cap_ds[&id].clone(), + hat_cap_d: artifact.hat_cap_d, + hat_cap_f: artifact.hat_cap_f, + }; + (id, values) + }) + .collect(); + + let presigning_data = PresigningData { + nonce, + ephemeral_scalar_share: SecretBox::new(Box::new(self.context.k)), + product_share: SecretBox::new(Box::new(P::scalar_from_signed(&self.chi))), + product_share_nonreduced: self.chi, + cap_k: self.all_cap_k[&my_id].clone(), + values, + }; + + return Ok(FinalizeOutcome::AnotherRound(BoxedRound::new_dynamic( + Round4::new(self.context, presigning_data), + ))); + } + + // Construct the correctness proofs + + let sk = &self.context.aux_info.secret_aux.paillier_sk; + let pk = sk.public_key(); + + let aux = (&self.context.ssid_hash, &self.context.my_id); + + // Aff-g proofs + + let mut aff_g_proofs = Vec::new(); + + let cap_gamma = self.context.gamma.mul_by_generator(); + + for id_j in self.context.other_ids.iter() { + let r2_artefacts = &self.round2_artifacts[id_j]; + + for id_l in self.context.other_ids.iter().filter(|id| id != &id_j) { + let target_pk = &self.context.aux_info.public_aux[id_j].paillier_pk; + let rp = &self.context.aux_info.public_aux[id_l].rp_params; + + let beta = &self.round2_artifacts[id_j].beta; + let r = &self.round2_artifacts[id_j].r; + let s = &self.round2_artifacts[id_j].s; + + let p_aff_g = AffGProof::

::new( + rng, + &P::signed_from_scalar(&self.context.gamma).unwrap(), + beta, + s.to_mod(target_pk), + r.to_mod(pk), + target_pk, + pk, + &self.all_cap_k[id_j], + &r2_artefacts.cap_d, + &r2_artefacts.cap_f, + &cap_gamma, + rp, + &aux, + ); + + assert!(p_aff_g.verify( + target_pk, + pk, + &self.all_cap_k[id_j], + &r2_artefacts.cap_d, + &r2_artefacts.cap_f, + &cap_gamma, + rp, + &aux, + )); + + aff_g_proofs.push((id_j.clone(), id_l.clone(), p_aff_g)); + } + } + + // Mul proof + + let rho = RandomizerMod::random(rng, pk); + let cap_h = (&self.all_cap_g[&self.context.my_id] + * P::bounded_from_scalar(&self.context.k).unwrap()) + .mul_randomizer(&rho.retrieve()); + + let p_mul = MulProof::

::new( + rng, + &P::signed_from_scalar(&self.context.k).unwrap(), + &self.context.rho, + &rho, + pk, + &self.all_cap_k[&self.context.my_id], + &self.all_cap_g[&self.context.my_id], + &cap_h, + &aux, + ); + assert!(p_mul.verify( + pk, + &self.all_cap_k[&self.context.my_id], + &self.all_cap_g[&self.context.my_id], + &cap_h, + &aux + )); + + // Dec proof + + let mut ciphertext = cap_h.clone(); + + for id_j in self.context.other_ids.iter() { + ciphertext = ciphertext + + self.cap_ds.get(id_j).unwrap() + + &self.round2_artifacts.get(id_j).unwrap().cap_f; + } + + let rho = ciphertext.derive_randomizer(sk); + + let mut dec_proofs = Vec::new(); + for id_j in self.context.other_ids.iter() { + let p_dec = DecProof::

::new( + rng, + &self.delta, + &rho, + pk, + &scalar_delta, + &ciphertext, + &self.context.aux_info.public_aux[id_j].rp_params, + &aux, + ); + assert!(p_dec.verify( + pk, + &scalar_delta, + &ciphertext, + &self.context.aux_info.public_aux[id_j].rp_params, + &aux + )); + dec_proofs.push((id_j.clone(), p_dec)); + } + + unimplemented!() } } -pub(crate) struct Round3 { - round: presigning::Round3, +#[derive(Debug)] +struct Round4 { context: Context, + presigning: PresigningData, + r: Scalar, + sigma: Scalar, } -impl RoundWrapper for Round3 { - type Type = ToNextRound; - type Result = InteractiveSigningResult; - type InnerRound = presigning::Round3; - const ROUND_NUM: u8 = 3; - const NEXT_ROUND_NUM: Option = Some(4); - fn inner_round(&self) -> &Self::InnerRound { - &self.round +impl Round4 +where + P: SchemeParams, + I: PartyId, +{ + fn new(context: Context, presigning: PresigningData) -> Self { + let r = presigning.nonce; + let sigma = presigning.ephemeral_scalar_share.expose_secret() * &context.message + + r * presigning.product_share.expose_secret(); + Self { + context, + presigning, + r, + sigma, + } } } -impl WrappedRound for Round3 {} +#[derive(Clone, Serialize, Deserialize)] +pub(super) struct Round4Message { + pub(crate) sigma: Scalar, +} -impl FinalizableToNextRound - for Round3 -{ - type NextRound = Round4; - fn finalize_to_next_round( +struct Round4Payload { + sigma: Scalar, +} + +impl Round for Round4 { + type Protocol = InteractiveSigningProtocol; + + fn id(&self) -> RoundId { + RoundId::new(4) + } + + fn possible_next_rounds(&self) -> BTreeSet { + BTreeSet::from([RoundId::new(5)]) + } + + fn may_produce_result(&self) -> bool { + true + } + + fn message_destinations(&self) -> &BTreeSet { + &self.context.other_ids + } + + fn expecting_messages_from(&self) -> &BTreeSet { + &self.context.other_ids + } + + fn make_normal_broadcast( + &self, + _rng: &mut impl CryptoRngCore, + serializer: &Serializer, + ) -> Result { + NormalBroadcast::new(serializer, Round4Message { sigma: self.sigma }) + } + + fn receive_message( + &self, + _rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, + _from: &I, + echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, + direct_message: DirectMessage, + ) -> Result> { + echo_broadcast.assert_is_none()?; + direct_message.assert_is_none()?; + let normal_broadcast = normal_broadcast.deserialize::(deserializer)?; + + Ok(Payload::new(Round4Payload { + sigma: normal_broadcast.sigma, + })) + } + + fn finalize( self, rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - artifacts: BTreeMap>::Artifact>, - ) -> Result> { - let other_ids = self.other_ids().clone(); - let my_id = self.my_id().clone(); - let presigning_data = self - .round - .finalize_to_result(rng, payloads, artifacts) - .map_err(wrap_finalize_error)?; - let signing_context = signing::Inputs { - message: self.context.message, - presigning: presigning_data, - key_share: self.context.key_share, - aux_info: self.context.aux_info, - }; - let signing_round = signing::Round1::new( - rng, - &self.context.shared_randomness, - other_ids, - my_id, - signing_context, - ) - .map_err(FinalizeError::Init)?; + payloads: BTreeMap, + _artifacts: BTreeMap, + ) -> Result, LocalError> { + let payloads = payloads.downcast_all::()?; + + let assembled_sigma = payloads + .values() + .map(|payload| payload.sigma) + .sum::() + + self.sigma; + + let signature = RecoverableSignature::from_scalars( + &self.r, + &assembled_sigma, + &self.context.key_share.verifying_key_as_point(), + &self.context.message, + ); + + if let Some(signature) = signature { + return Ok(FinalizeOutcome::Result(signature)); + } + + let my_id = self.context.my_id.clone(); + let aux = (&self.context.ssid_hash, &my_id); + + let sk = &self.context.aux_info.secret_aux.paillier_sk; + let pk = sk.public_key(); + + // Aff-g proofs + + let mut aff_g_proofs = Vec::new(); + + for id_j in self.context.other_ids.iter() { + for id_l in self.context.other_ids.iter().filter(|id| id != &id_j) { + let target_pk = &self.context.aux_info.public_aux[id_j].paillier_pk; + let rp = &self.context.aux_info.public_aux[id_l].rp_params; + + let values = self.presigning.values.get(id_j).unwrap(); + + let p_aff_g = AffGProof::

::new( + rng, + &P::signed_from_scalar(self.context.key_share.secret_share.expose_secret()) + .unwrap(), + &values.hat_beta, + values.hat_s.to_mod(target_pk), + values.hat_r.to_mod(pk), + target_pk, + pk, + &values.cap_k, + &values.hat_cap_d, + &values.hat_cap_f, + &self.context.key_share.public_shares[&my_id], + rp, + &aux, + ); + + assert!(p_aff_g.verify( + target_pk, + pk, + &values.cap_k, + &values.hat_cap_d, + &values.hat_cap_f, + &self.context.key_share.public_shares[&my_id], + rp, + &aux, + )); + + aff_g_proofs.push((id_j.clone(), id_l.clone(), p_aff_g)); + } + } + + // mul* proofs + + let x = &self.context.key_share.secret_share; + let cap_x = self.context.key_share.public_shares[&my_id]; - Ok(Round4 { - round: signing_round, - }) + let rho = RandomizerMod::random(rng, pk); + let hat_cap_h = (&self.presigning.cap_k + * P::bounded_from_scalar(x.expose_secret()).unwrap()) + .mul_randomizer(&rho.retrieve()); + + let aux = (&self.context.ssid_hash, &my_id); + + let mut mul_star_proofs = Vec::new(); + + for id_l in self.context.other_ids.iter() { + let p_mul = MulStarProof::

::new( + rng, + &P::signed_from_scalar(x.expose_secret()).unwrap(), + &rho, + pk, + &self.presigning.cap_k, + &hat_cap_h, + &cap_x, + &self.context.aux_info.public_aux[id_l].rp_params, + &aux, + ); + + assert!(p_mul.verify( + pk, + &self.presigning.cap_k, + &hat_cap_h, + &cap_x, + &self.context.aux_info.public_aux[id_l].rp_params, + &aux, + )); + + mul_star_proofs.push((id_l.clone(), p_mul)); + } + + // dec proofs + + let mut ciphertext = hat_cap_h.clone(); + for id_j in self.context.other_ids.iter() { + let values = &self.presigning.values.get(id_j).unwrap(); + ciphertext = ciphertext + &values.hat_cap_d_received + &values.hat_cap_f; + } + + let r = self.presigning.nonce; + + let ciphertext = ciphertext * P::bounded_from_scalar(&r).unwrap() + + &self.presigning.cap_k * P::bounded_from_scalar(&self.context.message).unwrap(); + + let rho = ciphertext.derive_randomizer(sk); + // This is the same as `s_part` but if all the calculations were performed + // without reducing modulo curve order. + let s_part_nonreduced = + P::signed_from_scalar(self.presigning.ephemeral_scalar_share.expose_secret()).unwrap() + * P::signed_from_scalar(&self.context.message).unwrap() + + self.presigning.product_share_nonreduced * P::signed_from_scalar(&r).unwrap(); + + let mut dec_proofs = Vec::new(); + for id_l in self.context.other_ids.iter() { + let p_dec = DecProof::

::new( + rng, + &s_part_nonreduced, + &rho, + pk, + &self.sigma, + &ciphertext, + &self.context.aux_info.public_aux[id_l].rp_params, + &aux, + ); + assert!(p_dec.verify( + pk, + &self.sigma, + &ciphertext, + &self.context.aux_info.public_aux[id_l].rp_params, + &aux, + )); + dec_proofs.push((id_l.clone(), p_dec)); + } + + Ok(FinalizeOutcome::AnotherRound(BoxedRound::new_dynamic( + SigningErrorRound { + context: self.context, + }, + ))) } } -pub(crate) struct Round4 { - round: signing::Round1, +#[derive(Debug)] +struct SigningErrorRound { + context: Context, } -impl RoundWrapper for Round4 { - type Type = ToResult; - type Result = InteractiveSigningResult; - type InnerRound = signing::Round1; - const ROUND_NUM: u8 = 4; - const NEXT_ROUND_NUM: Option = None; - fn inner_round(&self) -> &Self::InnerRound { - &self.round +#[derive(Debug, Clone, Serialize, Deserialize)] +struct SigningErrorMessage(PhantomData<(P, I)>); + +impl Round for SigningErrorRound { + type Protocol = InteractiveSigningProtocol; + + fn id(&self) -> RoundId { + RoundId::new(5) } -} -impl WrappedRound for Round4 {} + fn possible_next_rounds(&self) -> BTreeSet { + BTreeSet::new() + } + + fn message_destinations(&self) -> &BTreeSet { + &self.context.other_ids + } + + fn expecting_messages_from(&self) -> &BTreeSet { + &self.context.other_ids + } + + fn make_echo_broadcast( + &self, + _rng: &mut impl CryptoRngCore, + serializer: &Serializer, + ) -> Result { + EchoBroadcast::new(serializer, SigningErrorMessage::(PhantomData)) + } -impl FinalizableToResult for Round4 { - fn finalize_to_result( + fn receive_message( + &self, + _rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, + _from: &I, + echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, + direct_message: DirectMessage, + ) -> Result> { + normal_broadcast.assert_is_none()?; + direct_message.assert_is_none()?; + let _echo_broadcast = + echo_broadcast.deserialize::>(deserializer)?; + Err(ReceiveError::protocol( + InteractiveSigningError::SigningError("Signing error stub".into()), + )) + } + + fn finalize( self, - rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - artifacts: BTreeMap>::Artifact>, - ) -> Result<::Success, FinalizeError> { - self.round - .finalize_to_result(rng, payloads, artifacts) - .map_err(wrap_finalize_error) + _rng: &mut impl CryptoRngCore, + _payloads: BTreeMap, + _artifacts: BTreeMap, + ) -> Result, LocalError> { + Err(LocalError::new( + "One of the messages should have been missing or invalid", + )) } } @@ -293,67 +1314,59 @@ mod tests { use alloc::collections::BTreeSet; use k256::ecdsa::{signature::hazmat::PrehashVerifier, VerifyingKey}; + use manul::{ + dev::{run_sync, BinaryFormat, TestSessionParams, TestSigner, TestVerifier}, + session::signature::Keypair, + }; use rand_core::{OsRng, RngCore}; - use super::{Inputs, Round1}; + use super::InteractiveSigning; use crate::cggmp21::{AuxInfo, KeyShare, TestParams}; - use crate::curve::Scalar; - use crate::rounds::{ - test_utils::{step_next_round, step_result, step_round, Id, Without}, - FirstRound, - }; #[test] fn execute_interactive_signing() { - let mut shared_randomness = [0u8; 32]; - OsRng.fill_bytes(&mut shared_randomness); - - let message = Scalar::random(&mut OsRng); + let signers = (0..3).map(TestSigner::new).collect::>(); + let ids = signers + .iter() + .map(|signer| signer.verifying_key()) + .collect::>(); + let ids_set = BTreeSet::from_iter(ids.clone()); - let ids = BTreeSet::from([Id(0), Id(1), Id(2)]); + let key_shares = + KeyShare::::new_centralized(&mut OsRng, &ids_set, None); + let aux_infos = AuxInfo::new_centralized(&mut OsRng, &ids_set); - let key_shares = KeyShare::new_centralized(&mut OsRng, &ids, None); - let aux_infos = AuxInfo::new_centralized(&mut OsRng, &ids); + let mut message = [0u8; 32]; + OsRng.fill_bytes(&mut message); - let r1 = ids - .iter() - .map(|id| { - let round = Round1::::new( - &mut OsRng, - &shared_randomness, - ids.clone().without(id), - *id, - Inputs { - message, - key_share: key_shares[id].clone(), - aux_info: aux_infos[id].clone(), - }, - ) - .unwrap(); - (*id, round) + let entry_points = signers + .into_iter() + .map(|signer| { + let id = signer.verifying_key(); + let entry_point = InteractiveSigning::new( + message, + key_shares[&id].clone(), + aux_infos[&id].clone(), + ); + (signer, entry_point) }) .collect(); - let r1a = step_round(&mut OsRng, r1).unwrap(); - let r2 = step_next_round(&mut OsRng, r1a).unwrap(); - let r2a = step_round(&mut OsRng, r2).unwrap(); - let r3 = step_next_round(&mut OsRng, r2a).unwrap(); - let r3a = step_round(&mut OsRng, r3).unwrap(); - let r4 = step_next_round(&mut OsRng, r3a).unwrap(); - let r4a = step_round(&mut OsRng, r4).unwrap(); - let signatures = step_result(&mut OsRng, r4a).unwrap(); + let signatures = run_sync::<_, TestSessionParams>(&mut OsRng, entry_points) + .unwrap() + .results() + .unwrap(); for signature in signatures.values() { let (sig, rec_id) = signature.to_backend(); - let vkey = key_shares[&Id(0)].verifying_key().unwrap(); + let vkey = key_shares[&ids[0]].verifying_key().unwrap(); // Check that the signature can be verified - vkey.verify_prehash(&message.to_bytes(), &sig).unwrap(); + vkey.verify_prehash(&message, &sig).unwrap(); // Check that the key can be recovered - let recovered_key = - VerifyingKey::recover_from_prehash(&message.to_bytes(), &sig, rec_id).unwrap(); + let recovered_key = VerifyingKey::recover_from_prehash(&message, &sig, rec_id).unwrap(); assert_eq!(recovered_key, vkey); } } diff --git a/synedrion/src/cggmp21/protocols/key_gen.rs b/synedrion/src/cggmp21/protocols/key_gen.rs deleted file mode 100644 index a3fad3b0..00000000 --- a/synedrion/src/cggmp21/protocols/key_gen.rs +++ /dev/null @@ -1,370 +0,0 @@ -//! Merged KeyInit and KeyRefresh protocols, to generate a full key share in one go. -//! Since both take three rounds and are independent, we can execute them in parallel. - -use alloc::collections::{BTreeMap, BTreeSet}; -use core::fmt::Debug; -use core::marker::PhantomData; - -use rand_core::CryptoRngCore; -use serde::Serialize; - -use super::super::{AuxInfo, KeyShare, SchemeParams}; -use super::key_init::{self, KeyInitResult}; -use super::key_refresh::{self, KeyRefreshResult}; -use crate::rounds::{ - no_direct_messages, wrap_finalize_error, CorrectnessProofWrapper, FinalizableToNextRound, - FinalizableToResult, FinalizeError, FirstRound, InitError, ProtocolResult, Round, ToNextRound, - ToResult, -}; - -/// Possible results of the merged KeyGen and KeyRefresh protocols. -#[derive(Debug)] -pub struct KeyGenResult(PhantomData

, PhantomData); - -impl ProtocolResult for KeyGenResult { - type Success = (KeyShare, AuxInfo); - type ProvableError = KeyGenError; - type CorrectnessProof = KeyGenProof; -} - -/// Possible verifiable errors of the merged KeyGen and KeyRefresh protocols. -#[derive(Debug)] -pub enum KeyGenError { - /// An error in the KeyGen part of the protocol. - KeyInit( as ProtocolResult>::ProvableError), - /// An error in the KeyRefresh part of the protocol. - KeyRefresh( as ProtocolResult>::ProvableError), -} - -/// A proof of a node's correct behavior for the merged KeyGen and KeyRefresh protocols. -#[derive(Debug)] -pub enum KeyGenProof { - /// A proof for the KeyGen part of the protocol. - KeyInit( as ProtocolResult>::CorrectnessProof), - /// A proof for the KeyRefresh part of the protocol. - KeyRefresh( as ProtocolResult>::CorrectnessProof), -} - -impl CorrectnessProofWrapper> - for KeyGenResult -{ - fn wrap_proof( - proof: as ProtocolResult>::CorrectnessProof, - ) -> Self::CorrectnessProof { - KeyGenProof::KeyInit(proof) - } -} - -impl CorrectnessProofWrapper> - for KeyGenResult -{ - fn wrap_proof( - proof: as ProtocolResult>::CorrectnessProof, - ) -> Self::CorrectnessProof { - KeyGenProof::KeyRefresh(proof) - } -} - -pub(crate) struct Round1 { - key_init_round: key_init::Round1, - key_refresh_round: key_refresh::Round1, -} - -impl FirstRound for Round1 { - type Inputs = (); - fn new( - rng: &mut impl CryptoRngCore, - shared_randomness: &[u8], - other_ids: BTreeSet, - my_id: I, - _inputs: Self::Inputs, - ) -> Result { - let key_init_round = - key_init::Round1::new(rng, shared_randomness, other_ids.clone(), my_id.clone(), ())?; - let key_refresh_round = - key_refresh::Round1::new(rng, shared_randomness, other_ids, my_id, ())?; - Ok(Self { - key_init_round, - key_refresh_round, - }) - } -} - -impl Round for Round1 { - type Type = ToNextRound; - type Result = KeyGenResult; - const ROUND_NUM: u8 = 1; - const NEXT_ROUND_NUM: Option = Some(2); - - fn other_ids(&self) -> &BTreeSet { - self.key_init_round.other_ids() - } - - fn my_id(&self) -> &I { - self.key_init_round.my_id() - } - - const REQUIRES_ECHO: bool = as Round>::REQUIRES_ECHO - || as Round>::REQUIRES_ECHO; - type BroadcastMessage = ( - as Round>::BroadcastMessage, - as Round>::BroadcastMessage, - ); - type DirectMessage = (); - type Payload = ( - as Round>::Payload, - as Round>::Payload, - ); - type Artifact = (); - - fn make_broadcast_message( - &self, - rng: &mut impl CryptoRngCore, - ) -> Option { - // Can unwrap here since both protocols always send out broadcasts. - let key_init_message = self.key_init_round.make_broadcast_message(rng).unwrap(); - let key_refresh_message = self.key_refresh_round.make_broadcast_message(rng).unwrap(); - - Some((key_init_message, key_refresh_message)) - } - - no_direct_messages!(I); - - fn verify_message( - &self, - rng: &mut impl CryptoRngCore, - from: &I, - broadcast_msg: Self::BroadcastMessage, - _direct_msg: Self::DirectMessage, - ) -> Result::ProvableError> { - let (key_init_message, key_refresh_message) = broadcast_msg; - let key_init_payload = self - .key_init_round - .verify_message(rng, from, key_init_message, ()) - .map_err(KeyGenError::KeyInit)?; - let key_refresh_payload = self - .key_refresh_round - .verify_message(rng, from, key_refresh_message, ()) - .map_err(KeyGenError::KeyRefresh)?; - Ok((key_init_payload, key_refresh_payload)) - } -} - -impl FinalizableToNextRound - for Round1 -{ - type NextRound = Round2; - fn finalize_to_next_round( - self, - rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - _artifacts: BTreeMap>::Artifact>, - ) -> Result> { - let (key_init_payloads, key_refresh_payloads) = payloads - .into_iter() - .map(|(id, (init_payload, refresh_payload))| { - ((id.clone(), init_payload), (id, refresh_payload)) - }) - .unzip(); - - let key_init_round = self - .key_init_round - .finalize_to_next_round(rng, key_init_payloads, BTreeMap::new()) - .map_err(wrap_finalize_error)?; - let key_refresh_round = self - .key_refresh_round - .finalize_to_next_round(rng, key_refresh_payloads, BTreeMap::new()) - .map_err(wrap_finalize_error)?; - Ok(Round2 { - key_init_round, - key_refresh_round, - }) - } -} - -pub(crate) struct Round2 { - key_init_round: key_init::Round2, - key_refresh_round: key_refresh::Round2, -} - -impl Round for Round2 { - type Type = ToNextRound; - type Result = KeyGenResult; - const ROUND_NUM: u8 = 2; - const NEXT_ROUND_NUM: Option = Some(3); - - fn other_ids(&self) -> &BTreeSet { - self.key_init_round.other_ids() - } - - fn my_id(&self) -> &I { - self.key_init_round.my_id() - } - - const REQUIRES_ECHO: bool = as Round>::REQUIRES_ECHO - || as Round>::REQUIRES_ECHO; - type BroadcastMessage = ( - as Round>::BroadcastMessage, - as Round>::BroadcastMessage, - ); - type DirectMessage = (); - type Payload = ( - as Round>::Payload, - as Round>::Payload, - ); - type Artifact = (); - - fn make_broadcast_message( - &self, - rng: &mut impl CryptoRngCore, - ) -> Option { - // Can unwrap here since both protocols always send out broadcasts. - let key_init_message = self.key_init_round.make_broadcast_message(rng).unwrap(); - let key_refresh_message = self.key_refresh_round.make_broadcast_message(rng).unwrap(); - - Some((key_init_message, key_refresh_message)) - } - - no_direct_messages!(I); - - fn verify_message( - &self, - rng: &mut impl CryptoRngCore, - from: &I, - broadcast_msg: Self::BroadcastMessage, - _direct_msg: Self::DirectMessage, - ) -> Result::ProvableError> { - let (key_init_message, key_refresh_message) = broadcast_msg; - let key_init_payload = self - .key_init_round - .verify_message(rng, from, key_init_message, ()) - .map_err(KeyGenError::KeyInit)?; - let key_refresh_payload = self - .key_refresh_round - .verify_message(rng, from, key_refresh_message, ()) - .map_err(KeyGenError::KeyRefresh)?; - Ok((key_init_payload, key_refresh_payload)) - } -} - -impl FinalizableToNextRound - for Round2 -{ - type NextRound = Round3; - fn finalize_to_next_round( - self, - rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - _artifacts: BTreeMap>::Artifact>, - ) -> Result> { - let (key_init_payloads, key_refresh_payloads) = payloads - .into_iter() - .map(|(id, (init_payload, refresh_payload))| { - ((id.clone(), init_payload), (id, refresh_payload)) - }) - .unzip(); - - let key_init_round = self - .key_init_round - .finalize_to_next_round(rng, key_init_payloads, BTreeMap::new()) - .map_err(wrap_finalize_error)?; - let key_refresh_round = self - .key_refresh_round - .finalize_to_next_round(rng, key_refresh_payloads, BTreeMap::new()) - .map_err(wrap_finalize_error)?; - Ok(Round3 { - key_init_round, - key_refresh_round, - }) - } -} - -pub(crate) struct Round3 { - key_init_round: key_init::Round3, - key_refresh_round: key_refresh::Round3, -} - -impl Round for Round3 { - type Type = ToResult; - type Result = KeyGenResult; - const ROUND_NUM: u8 = 3; - const NEXT_ROUND_NUM: Option = None; - - fn other_ids(&self) -> &BTreeSet { - self.key_init_round.other_ids() - } - - fn my_id(&self) -> &I { - self.key_init_round.my_id() - } - - const REQUIRES_ECHO: bool = as Round>::REQUIRES_ECHO - || as Round>::REQUIRES_ECHO; - type BroadcastMessage = as Round>::BroadcastMessage; - type DirectMessage = as Round>::DirectMessage; - type Payload = ( - as Round>::Payload, - as Round>::Payload, - ); - type Artifact = as Round>::Artifact; - - fn make_broadcast_message( - &self, - rng: &mut impl CryptoRngCore, - ) -> Option { - self.key_init_round.make_broadcast_message(rng) - } - - fn make_direct_message( - &self, - rng: &mut impl CryptoRngCore, - destination: &I, - ) -> (Self::DirectMessage, Self::Artifact) { - self.key_refresh_round.make_direct_message(rng, destination) - } - - fn verify_message( - &self, - rng: &mut impl CryptoRngCore, - from: &I, - broadcast_msg: Self::BroadcastMessage, - direct_msg: Self::DirectMessage, - ) -> Result::ProvableError> { - #[allow(clippy::let_unit_value)] - let key_init_payload = self - .key_init_round - .verify_message(rng, from, broadcast_msg, ()) - .map_err(KeyGenError::KeyInit)?; - let key_refresh_payload = self - .key_refresh_round - .verify_message(rng, from, (), direct_msg) - .map_err(KeyGenError::KeyRefresh)?; - Ok((key_init_payload, key_refresh_payload)) - } -} - -impl FinalizableToResult for Round3 { - fn finalize_to_result( - self, - rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - artifacts: BTreeMap>::Artifact>, - ) -> Result<::Success, FinalizeError> { - let (key_init_payloads, key_refresh_payloads) = payloads - .into_iter() - .map(|(id, (init_payload, refresh_payload))| { - ((id.clone(), init_payload), (id, refresh_payload)) - }) - .unzip(); - - let key_share = self - .key_init_round - .finalize_to_result(rng, key_init_payloads, BTreeMap::new()) - .map_err(wrap_finalize_error)?; - let (key_share_change, aux_info) = self - .key_refresh_round - .finalize_to_result(rng, key_refresh_payloads, artifacts) - .map_err(wrap_finalize_error)?; - Ok((key_share.update(key_share_change), aux_info)) - } -} diff --git a/synedrion/src/cggmp21/protocols/key_init.rs b/synedrion/src/cggmp21/protocols/key_init.rs index 5813918d..daf88b76 100644 --- a/synedrion/src/cggmp21/protocols/key_init.rs +++ b/synedrion/src/cggmp21/protocols/key_init.rs @@ -2,11 +2,19 @@ //! Note that this protocol only generates the key itself which is not enough to perform signing; //! auxiliary parameters need to be generated as well (during the KeyRefresh protocol). -use alloc::boxed::Box; -use alloc::collections::{BTreeMap, BTreeSet}; -use core::fmt::Debug; -use core::marker::PhantomData; +use alloc::{ + boxed::Box, + collections::{BTreeMap, BTreeSet}, + string::String, + vec::Vec, +}; +use core::{fmt::Debug, marker::PhantomData}; +use manul::protocol::{ + Artifact, BoxedRound, Deserializer, DirectMessage, EchoBroadcast, EntryPoint, FinalizeOutcome, + LocalError, NormalBroadcast, PartyId, Payload, Protocol, ProtocolError, ProtocolMessagePart, + ProtocolValidationError, ReceiveError, Round, RoundId, Serializer, +}; use rand_core::CryptoRngCore; use secrecy::SecretBox; use serde::{Deserialize, Serialize}; @@ -15,26 +23,26 @@ use super::super::{ sigma::{SchCommitment, SchProof, SchSecret}, KeyShare, SchemeParams, }; -use crate::curve::{Point, Scalar}; -use crate::rounds::{ - no_direct_messages, FinalizableToNextRound, FinalizableToResult, FinalizeError, FirstRound, - InitError, ProtocolResult, Round, ToNextRound, ToResult, +use crate::{ + curve::{Point, Scalar}, + tools::{ + bitvec::BitVec, + hashing::{Chain, FofHasher, HashOutput}, + DowncastMap, Without, + }, }; -use crate::tools::bitvec::BitVec; -use crate::tools::hashing::{Chain, FofHasher, HashOutput}; -/// Possible results of the KeyGen protocol. -#[derive(Debug, Clone, Copy)] -pub struct KeyInitResult(PhantomData

, PhantomData); +/// A protocol that generates shares of a new secret key on each node. +#[derive(Debug)] +pub struct KeyInitProtocol(PhantomData<(P, I)>); -impl ProtocolResult for KeyInitResult { - type Success = KeyShare; - type ProvableError = KeyInitError; - type CorrectnessProof = (); +impl Protocol for KeyInitProtocol { + type Result = KeyShare; + type ProtocolError = KeyInitError; } /// Possible verifiable errors of the KeyGen protocol. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum KeyInitError { /// A hash mismatch in Round 2. R2HashMismatch, @@ -42,6 +50,38 @@ pub enum KeyInitError { R3InvalidSchProof, } +impl ProtocolError for KeyInitError { + fn description(&self) -> String { + unimplemented!() + } + + fn required_direct_messages(&self) -> BTreeSet { + unimplemented!() + } + + fn required_echo_broadcasts(&self) -> BTreeSet { + unimplemented!() + } + + fn required_combined_echos(&self) -> BTreeSet { + unimplemented!() + } + + fn verify_messages_constitute_error( + &self, + _deserializer: &Deserializer, + _echo_broadcast: &EchoBroadcast, + _normal_broadcat: &NormalBroadcast, + _direct_message: &DirectMessage, + _echo_broadcasts: &BTreeMap, + _normal_broadcasts: &BTreeMap, + _direct_messages: &BTreeMap, + _combined_echos: &BTreeMap>, + ) -> Result<(), ProtocolValidationError> { + unimplemented!() + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] struct PublicData { cap_x: Point, @@ -61,36 +101,45 @@ impl PublicData

{ } } -struct Context { - other_ids: BTreeSet, - my_id: I, - x: Scalar, - tau: SchSecret, - public_data: PublicData

, - sid_hash: HashOutput, +/// An entry point for the [`KeyInitProtocol`]. +#[derive(Debug, Clone)] +pub struct KeyInit { + all_ids: BTreeSet, + phantom: PhantomData

, } -pub struct Round1 { - context: Context, +impl KeyInit { + /// Creates a new entry point given the set of the participants' IDs + /// (including this node's). + pub fn new(all_ids: BTreeSet) -> Result { + Ok(Self { + all_ids, + phantom: PhantomData, + }) + } } -impl FirstRound for Round1 { - type Inputs = (); +impl EntryPoint for KeyInit { + type Protocol = KeyInitProtocol; - fn new( + fn make_round( + self, rng: &mut impl CryptoRngCore, shared_randomness: &[u8], - other_ids: BTreeSet, - my_id: I, - _inputs: Self::Inputs, - ) -> Result { - let mut all_ids = other_ids.clone(); - all_ids.insert(my_id.clone()); + id: &I, + ) -> Result, LocalError> { + if !self.all_ids.contains(id) { + return Err(LocalError::new( + "The given node IDs must contain this node's ID", + )); + } + + let other_ids = self.all_ids.clone().without(id); let sid_hash = FofHasher::new_with_dst(b"SID") .chain_type::

() .chain(&shared_randomness) - .chain(&all_ids) + .chain(&self.all_ids) .finalize(); // The secret share @@ -113,91 +162,107 @@ impl FirstRound for Roun let context = Context { other_ids, - my_id, + my_id: id.clone(), x, tau, public_data, sid_hash, }; - Ok(Self { context }) + Ok(BoxedRound::new_dynamic(Round1 { context })) } } +#[derive(Debug)] +struct Context { + other_ids: BTreeSet, + my_id: I, + x: Scalar, + tau: SchSecret, + public_data: PublicData

, + sid_hash: HashOutput, +} + +#[derive(Debug)] +struct Round1 { + context: Context, +} + #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Round1Message { +struct Round1Message { cap_v: HashOutput, } -pub struct Round1Payload { +struct Round1Payload { cap_v: HashOutput, } -impl Round for Round1 { - type Type = ToNextRound; - type Result = KeyInitResult; - const ROUND_NUM: u8 = 1; - const NEXT_ROUND_NUM: Option = Some(2); +impl Round for Round1 { + type Protocol = KeyInitProtocol; - fn other_ids(&self) -> &BTreeSet { - &self.context.other_ids + fn id(&self) -> RoundId { + RoundId::new(1) + } + + fn possible_next_rounds(&self) -> BTreeSet { + BTreeSet::from([RoundId::new(2)]) } - fn my_id(&self) -> &I { - &self.context.my_id + fn message_destinations(&self) -> &BTreeSet { + &self.context.other_ids } - const REQUIRES_ECHO: bool = true; - type BroadcastMessage = Round1Message; - type DirectMessage = (); - type Payload = Round1Payload; - type Artifact = (); + fn expecting_messages_from(&self) -> &BTreeSet { + &self.context.other_ids + } - fn make_broadcast_message( + fn make_echo_broadcast( &self, _rng: &mut impl CryptoRngCore, - ) -> Option { + serializer: &Serializer, + ) -> Result { let cap_v = self .context .public_data - .hash(&self.context.sid_hash, self.my_id()); - Some(Round1Message { cap_v }) + .hash(&self.context.sid_hash, &self.context.my_id); + EchoBroadcast::new(serializer, Round1Message { cap_v }) } - no_direct_messages!(I); - - fn verify_message( + fn receive_message( &self, _rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, _from: &I, - broadcast_msg: Self::BroadcastMessage, - _direct_msg: Self::DirectMessage, - ) -> Result::ProvableError> { - Ok(Round1Payload { - cap_v: broadcast_msg.cap_v, - }) + echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, + direct_message: DirectMessage, + ) -> Result> { + normal_broadcast.assert_is_none()?; + direct_message.assert_is_none()?; + let echo = echo_broadcast.deserialize::(deserializer)?; + Ok(Payload::new(Round1Payload { cap_v: echo.cap_v })) } -} -impl FinalizableToNextRound - for Round1 -{ - type NextRound = Round2; - fn finalize_to_next_round( + fn finalize( self, _rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - _artifacts: BTreeMap>::Artifact>, - ) -> Result> { - Ok(Round2 { - others_cap_v: payloads.into_iter().map(|(k, v)| (k, v.cap_v)).collect(), - context: self.context, - phantom: PhantomData, - }) + payloads: BTreeMap, + _artifacts: BTreeMap, + ) -> Result, LocalError> { + let payloads = payloads.downcast_all::()?; + let others_cap_v = payloads.into_iter().map(|(k, v)| (k, v.cap_v)).collect(); + Ok(FinalizeOutcome::AnotherRound(BoxedRound::new_dynamic( + Round2 { + others_cap_v, + context: self.context, + phantom: PhantomData, + }, + ))) } } -pub struct Round2 { +#[derive(Debug)] +struct Round2 { context: Context, others_cap_v: BTreeMap, phantom: PhantomData

, @@ -206,88 +271,95 @@ pub struct Round2 { #[derive(Clone, Serialize, Deserialize)] #[serde(bound(serialize = "PublicData

: Serialize"))] #[serde(bound(deserialize = "PublicData

: for<'x> Deserialize<'x>"))] -pub struct Round2Message { +struct Round2Message { data: PublicData

, } -pub struct Round2Payload { +struct Round2Payload { data: PublicData

, } -impl Round for Round2 { - type Type = ToNextRound; - type Result = KeyInitResult; - const ROUND_NUM: u8 = 2; - const NEXT_ROUND_NUM: Option = Some(3); +impl Round for Round2 { + type Protocol = KeyInitProtocol; - fn other_ids(&self) -> &BTreeSet { - &self.context.other_ids + fn id(&self) -> RoundId { + RoundId::new(2) + } + + fn possible_next_rounds(&self) -> BTreeSet { + BTreeSet::from([RoundId::new(3)]) } - fn my_id(&self) -> &I { - &self.context.my_id + fn message_destinations(&self) -> &BTreeSet { + &self.context.other_ids } - type BroadcastMessage = Round2Message

; - type DirectMessage = (); - type Payload = Round2Payload

; - type Artifact = (); + fn expecting_messages_from(&self) -> &BTreeSet { + &self.context.other_ids + } - fn make_broadcast_message( + fn make_echo_broadcast( &self, _rng: &mut impl CryptoRngCore, - ) -> Option { - Some(Round2Message { - data: self.context.public_data.clone(), - }) + serializer: &Serializer, + ) -> Result { + EchoBroadcast::new( + serializer, + Round2Message { + data: self.context.public_data.clone(), + }, + ) } - no_direct_messages!(I); - - fn verify_message( + fn receive_message( &self, _rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, from: &I, - broadcast_msg: Self::BroadcastMessage, - _direct_msg: Self::DirectMessage, - ) -> Result::ProvableError> { - if &broadcast_msg.data.hash(&self.context.sid_hash, from) - != self.others_cap_v.get(from).unwrap() - { - return Err(KeyInitError::R2HashMismatch); + echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, + direct_message: DirectMessage, + ) -> Result> { + normal_broadcast.assert_is_none()?; + direct_message.assert_is_none()?; + let echo = echo_broadcast.deserialize::>(deserializer)?; + + if &echo.data.hash(&self.context.sid_hash, from) != self.others_cap_v.get(from).unwrap() { + return Err(ReceiveError::protocol(KeyInitError::R2HashMismatch)); } - Ok(Round2Payload { - data: broadcast_msg.data, - }) + Ok(Payload::new(Round2Payload { data: echo.data })) } -} -impl FinalizableToNextRound - for Round2 -{ - type NextRound = Round3; - fn finalize_to_next_round( + fn finalize( self, _rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - _artifacts: BTreeMap>::Artifact>, - ) -> Result> { + payloads: BTreeMap, + _artifacts: BTreeMap, + ) -> Result, LocalError> { let mut rid = self.context.public_data.rid.clone(); + + let payloads = payloads.downcast_all::>()?; + for payload in payloads.values() { rid ^= &payload.data.rid; } - Ok(Round3 { - context: self.context, - others_data: payloads.into_iter().map(|(k, v)| (k, v.data)).collect(), - rid, - phantom: PhantomData, - }) + let others_data = payloads.into_iter().map(|(k, v)| (k, v.data)).collect(); + + Ok(FinalizeOutcome::AnotherRound(BoxedRound::new_dynamic( + Round3 { + context: self.context, + others_data, + rid, + phantom: PhantomData, + }, + ))) } } -pub struct Round3 { +#[derive(Debug)] +struct Round3 { context: Context, others_data: BTreeMap>, rid: BitVec, @@ -295,34 +367,39 @@ pub struct Round3 { } #[derive(Clone, Serialize, Deserialize)] -pub struct Round3Message { +struct Round3Message { psi: SchProof, } -impl Round for Round3 { - type Type = ToResult; - type Result = KeyInitResult; - const ROUND_NUM: u8 = 3; - const NEXT_ROUND_NUM: Option = None; +impl Round for Round3 { + type Protocol = KeyInitProtocol; - fn other_ids(&self) -> &BTreeSet { - &self.context.other_ids + fn id(&self) -> RoundId { + RoundId::new(3) + } + + fn possible_next_rounds(&self) -> BTreeSet { + BTreeSet::new() } - fn my_id(&self) -> &I { - &self.context.my_id + fn may_produce_result(&self) -> bool { + true } - type BroadcastMessage = Round3Message; - type DirectMessage = (); - type Payload = (); - type Artifact = (); + fn message_destinations(&self) -> &BTreeSet { + &self.context.other_ids + } + + fn expecting_messages_from(&self) -> &BTreeSet { + &self.context.other_ids + } - fn make_broadcast_message( + fn make_normal_broadcast( &self, _rng: &mut impl CryptoRngCore, - ) -> Option { - let aux = (&self.context.sid_hash, self.my_id(), &self.rid); + serializer: &Serializer, + ) -> Result { + let aux = (&self.context.sid_hash, &self.context.my_id, &self.rid); let psi = SchProof::new( &self.context.tau, &self.context.x, @@ -330,48 +407,51 @@ impl Round for Round3 Result::ProvableError> { + echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, + direct_message: DirectMessage, + ) -> Result> { + echo_broadcast.assert_is_none()?; + direct_message.assert_is_none()?; + + let bc = normal_broadcast.deserialize::(deserializer)?; + let data = self.others_data.get(from).unwrap(); let aux = (&self.context.sid_hash, from, &self.rid); - if !broadcast_msg.psi.verify(&data.cap_a, &data.cap_x, &aux) { - return Err(KeyInitError::R3InvalidSchProof); + if !bc.psi.verify(&data.cap_a, &data.cap_x, &aux) { + return Err(ReceiveError::protocol(KeyInitError::R3InvalidSchProof)); } - Ok(()) + Ok(Payload::empty()) } -} -impl FinalizableToResult for Round3 { - fn finalize_to_result( + fn finalize( self, _rng: &mut impl CryptoRngCore, - _payloads: BTreeMap>::Payload>, - _artifacts: BTreeMap>::Artifact>, - ) -> Result<::Success, FinalizeError> { - let my_id = self.my_id().clone(); + _payloads: BTreeMap, + _artifacts: BTreeMap, + ) -> Result, LocalError> { + let my_id = self.context.my_id.clone(); let mut public_shares = self .others_data .into_iter() .map(|(k, v)| (k, v.cap_x)) .collect::>(); public_shares.insert(my_id.clone(), self.context.public_data.cap_x); - Ok(KeyShare { + Ok(FinalizeOutcome::Result(KeyShare { owner: my_id, secret_share: SecretBox::new(Box::new(self.context.x)), public_shares, phantom: PhantomData, - }) + })) } } @@ -379,44 +459,38 @@ impl FinalizableToResult mod tests { use alloc::collections::{BTreeMap, BTreeSet}; - use rand_core::{OsRng, RngCore}; + use manul::{ + dev::{run_sync, BinaryFormat, TestSessionParams, TestSigner, TestVerifier}, + session::signature::Keypair, + }; + use rand_core::OsRng; use secrecy::ExposeSecret; - use super::Round1; + use super::KeyInit; use crate::cggmp21::TestParams; - use crate::rounds::{ - test_utils::{step_next_round, step_result, step_round, Id, Without}, - FirstRound, - }; #[test] fn execute_keygen() { - let mut shared_randomness = [0u8; 32]; - OsRng.fill_bytes(&mut shared_randomness); - - let ids = BTreeSet::from([Id(0), Id(1), Id(2)]); + let signers = (0..3).map(TestSigner::new).collect::>(); + let id0 = signers[0].verifying_key(); - let r1 = ids + let all_ids = signers .iter() - .map(|id| { - let round = Round1::::new( - &mut OsRng, - &shared_randomness, - ids.clone().without(id), - *id, - (), - ) - .unwrap(); - (*id, round) + .map(|signer| signer.verifying_key()) + .collect::>(); + let entry_points = signers + .into_iter() + .map(|signer| { + let entry_point = + KeyInit::::new(all_ids.clone()).unwrap(); + (signer, entry_point) }) - .collect(); + .collect::>(); - let r1a = step_round(&mut OsRng, r1).unwrap(); - let r2 = step_next_round(&mut OsRng, r1a).unwrap(); - let r2a = step_round(&mut OsRng, r2).unwrap(); - let r3 = step_next_round(&mut OsRng, r2a).unwrap(); - let r3a = step_round(&mut OsRng, r3).unwrap(); - let shares = step_result(&mut OsRng, r3a).unwrap(); + let shares = run_sync::<_, TestSessionParams>(&mut OsRng, entry_points) + .unwrap() + .results() + .unwrap(); // Check that the sets of public keys are the same at each node @@ -425,10 +499,10 @@ mod tests { .map(|(id, share)| (*id, share.public_shares.clone())) .collect::>(); - assert!(public_sets.values().all(|pk| pk == &public_sets[&Id(0)])); + assert!(public_sets.values().all(|pk| pk == &public_sets[&id0])); // Check that the public keys correspond to the secret key shares - let public_set = &public_sets[&Id(0)]; + let public_set = &public_sets[&id0]; let public_from_secret = shares .into_iter() diff --git a/synedrion/src/cggmp21/protocols/key_refresh.rs b/synedrion/src/cggmp21/protocols/key_refresh.rs index 59fd7468..fa9362e3 100644 --- a/synedrion/src/cggmp21/protocols/key_refresh.rs +++ b/synedrion/src/cggmp21/protocols/key_refresh.rs @@ -2,13 +2,20 @@ //! This protocol generates an update to the secret key shares and new auxiliary parameters //! for ZK proofs (e.g. Paillier keys). -use alloc::boxed::Box; -use alloc::collections::{BTreeMap, BTreeSet}; -use alloc::string::String; -use alloc::vec::Vec; -use core::fmt::Debug; -use core::marker::PhantomData; +use alloc::{ + boxed::Box, + collections::{BTreeMap, BTreeSet}, + string::String, + vec::Vec, +}; +use core::{fmt::Debug, marker::PhantomData}; +use crypto_bigint::BitOps; +use manul::protocol::{ + Artifact, BoxedRound, Deserializer, DirectMessage, EchoBroadcast, EntryPoint, FinalizeOutcome, + LocalError, NormalBroadcast, PartyId, Payload, Protocol, ProtocolError, ProtocolMessagePart, + ProtocolValidationError, ReceiveError, Round, RoundId, Serializer, +}; use rand_core::CryptoRngCore; use secrecy::SecretBox; use serde::{Deserialize, Serialize}; @@ -17,33 +24,39 @@ use super::super::{ sigma::{FacProof, ModProof, PrmProof, SchCommitment, SchProof, SchSecret}, AuxInfo, KeyShareChange, PublicAuxInfo, SchemeParams, SecretAuxInfo, }; -use crate::curve::{Point, Scalar}; -use crate::paillier::{ - Ciphertext, CiphertextMod, PublicKeyPaillier, PublicKeyPaillierPrecomputed, RPParams, - RPParamsMod, RPSecret, Randomizer, SecretKeyPaillier, SecretKeyPaillierPrecomputed, -}; -use crate::rounds::{ - no_broadcast_messages, no_direct_messages, FinalizableToNextRound, FinalizableToResult, - FinalizeError, FirstRound, InitError, ProtocolResult, Round, ToNextRound, ToResult, +use crate::{ + curve::{Point, Scalar}, + paillier::{ + Ciphertext, CiphertextMod, PublicKeyPaillier, PublicKeyPaillierPrecomputed, RPParams, + RPParamsMod, RPSecret, Randomizer, SecretKeyPaillier, SecretKeyPaillierPrecomputed, + }, + tools::{ + bitvec::BitVec, + hashing::{Chain, FofHasher, HashOutput}, + DowncastMap, Without, + }, }; -use crate::tools::bitvec::BitVec; -use crate::tools::hashing::{Chain, FofHasher, HashOutput}; -use crypto_bigint::BitOps; -/// Possible results of the KeyRefresh protocol. +/// A protocol for generating auxiliary information for signing, +/// and a simultaneous generation of updates for the secret key shares. #[derive(Debug)] -pub struct KeyRefreshResult(PhantomData

, PhantomData); +pub struct KeyRefreshProtocol(PhantomData<(P, I)>); -impl ProtocolResult for KeyRefreshResult { - type Success = (KeyShareChange, AuxInfo); - type ProvableError = KeyRefreshError

; - type CorrectnessProof = (); +impl Protocol for KeyRefreshProtocol { + type Result = (KeyShareChange, AuxInfo); + type ProtocolError = KeyRefreshError

; } -#[derive(Debug)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(bound(serialize = " + KeyRefreshErrorEnum

: Serialize, +"))] +#[serde(bound(deserialize = " + KeyRefreshErrorEnum

: for<'x> Deserialize<'x>, +"))] pub struct KeyRefreshError(KeyRefreshErrorEnum

); -#[derive(Debug)] +#[derive(Debug, Clone, Serialize, Deserialize)] enum KeyRefreshErrorEnum { // TODO (#43): this can be removed when error verification is added #[allow(dead_code)] @@ -59,72 +72,76 @@ enum KeyRefreshErrorEnum { mu: Randomizer, }, } -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(bound(serialize = " - PrmProof

: Serialize, - "))] -#[serde(bound(deserialize = " - PrmProof

: for<'x> Deserialize<'x>, - "))] -pub struct PublicData1 { - cap_x_to_send: Vec, // $X_i^j$ where $i$ is this party's index - cap_a_to_send: Vec, // $A_i^j$ where $i$ is this party's index - cap_y: Point, - cap_b: SchCommitment, - paillier_pk: PublicKeyPaillier, // $N_i$ - rp_params: RPParams, // $s_i$ and $t_i$ - hat_psi: PrmProof

, - rho: BitVec, - u: BitVec, -} -#[derive(Debug, Clone)] -pub struct PublicData1Precomp { - data: PublicData1

, - paillier_pk: PublicKeyPaillierPrecomputed, - rp_params: RPParamsMod, +impl ProtocolError for KeyRefreshError

{ + fn description(&self) -> String { + unimplemented!() + } + + fn required_direct_messages(&self) -> BTreeSet { + unimplemented!() + } + + fn required_echo_broadcasts(&self) -> BTreeSet { + unimplemented!() + } + + fn required_combined_echos(&self) -> BTreeSet { + unimplemented!() + } + + fn verify_messages_constitute_error( + &self, + _deserializer: &Deserializer, + _echo_broadcast: &EchoBroadcast, + _normal_broadcat: &NormalBroadcast, + _direct_message: &DirectMessage, + _echo_broadcasts: &BTreeMap, + _normal_broadcasts: &BTreeMap, + _direct_messages: &BTreeMap, + _combined_echos: &BTreeMap>, + ) -> Result<(), ProtocolValidationError> { + unimplemented!() + } } -struct Context { - paillier_sk: SecretKeyPaillierPrecomputed, - y: Scalar, - x_to_send: BTreeMap, // $x_i^j$ where $i$ is this party's index - tau_y: SchSecret, - tau_x: BTreeMap, - data_precomp: PublicData1Precomp

, - my_id: I, - other_ids: BTreeSet, - sid_hash: HashOutput, - ids_ordering: BTreeMap, +/// An entry point for the [`KeyRefreshProtocol`]. +#[derive(Debug, Clone)] +pub struct KeyRefresh { + all_ids: BTreeSet, + phantom: PhantomData

, } -impl PublicData1

{ - fn hash(&self, sid_hash: &HashOutput, id: &I) -> HashOutput { - FofHasher::new_with_dst(b"Auxiliary") - .chain(sid_hash) - .chain(id) - .chain(self) - .finalize() +impl KeyRefresh { + /// Creates a new entry point given the set of the participants' IDs + /// (including this node's). + pub fn new(all_ids: BTreeSet) -> Result { + Ok(Self { + all_ids, + phantom: PhantomData, + }) } } -pub struct Round1 { - context: Context, -} +impl EntryPoint for KeyRefresh { + type Protocol = KeyRefreshProtocol; -impl FirstRound for Round1 { - type Inputs = (); - fn new( + fn make_round( + self, rng: &mut impl CryptoRngCore, shared_randomness: &[u8], - other_ids: BTreeSet, - my_id: I, - _inputs: Self::Inputs, - ) -> Result { - let mut all_ids = other_ids.clone(); - all_ids.insert(my_id.clone()); - - let ids_ordering = all_ids + id: &I, + ) -> Result, LocalError> { + if !self.all_ids.contains(id) { + return Err(LocalError::new( + "The given node IDs must contain this node's ID", + )); + } + + let other_ids = self.all_ids.clone().without(id); + + let ids_ordering = self + .all_ids .iter() .cloned() .enumerate() @@ -134,7 +151,7 @@ impl FirstRound for Roun let sid_hash = FofHasher::new_with_dst(b"SID") .chain_type::

() .chain(&shared_randomness) - .chain(&all_ids) + .chain(&self.all_ids) .finalize(); // $p_i$, $q_i$ @@ -151,10 +168,11 @@ impl FirstRound for Roun let cap_b = SchCommitment::new(&tau_y); // Secret share updates for each node ($x_i^j$ where $i$ is this party's index). - let x_to_send = all_ids + let x_to_send = self + .all_ids .iter() .cloned() - .zip(Scalar::ZERO.split(rng, all_ids.len())) + .zip(Scalar::ZERO.split(rng, self.all_ids.len())) .collect::>(); // Public counterparts of secret share updates ($X_i^j$ where $i$ is this party's index). @@ -164,11 +182,12 @@ impl FirstRound for Roun // Ring-Pedersen parameters ($s$, $t$) bundled in a single object. let rp_params = RPParamsMod::random_with_secret(rng, &lambda, paillier_pk); - let aux = (&sid_hash, &my_id); + let aux = (&sid_hash, id); let hat_psi = PrmProof::

::new(rng, &paillier_sk, &lambda, &rp_params, &aux); // The secrets share changes ($\tau_j$, not to be confused with $\tau$) - let tau_x = all_ids + let tau_x = self + .all_ids .iter() .map(|id| (id.clone(), SchSecret::random(rng))) .collect::>(); @@ -204,95 +223,155 @@ impl FirstRound for Roun tau_x, tau_y, data_precomp, - my_id, + my_id: id.clone(), other_ids, sid_hash, ids_ordering, }; - Ok(Self { context }) + Ok(BoxedRound::new_dynamic(Round1 { context })) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(bound(serialize = " + PrmProof

: Serialize, + "))] +#[serde(bound(deserialize = " + PrmProof

: for<'x> Deserialize<'x>, + "))] +struct PublicData1 { + cap_x_to_send: Vec, // $X_i^j$ where $i$ is this party's index + cap_a_to_send: Vec, // $A_i^j$ where $i$ is this party's index + cap_y: Point, + cap_b: SchCommitment, + paillier_pk: PublicKeyPaillier, // $N_i$ + rp_params: RPParams, // $s_i$ and $t_i$ + hat_psi: PrmProof

, + rho: BitVec, + u: BitVec, +} + +#[derive(Debug, Clone)] +struct PublicData1Precomp { + data: PublicData1

, + paillier_pk: PublicKeyPaillierPrecomputed, + rp_params: RPParamsMod, +} + +#[derive(Debug)] +struct Context { + paillier_sk: SecretKeyPaillierPrecomputed, + y: Scalar, + x_to_send: BTreeMap, // $x_i^j$ where $i$ is this party's index + tau_y: SchSecret, + tau_x: BTreeMap, + data_precomp: PublicData1Precomp

, + my_id: I, + other_ids: BTreeSet, + sid_hash: HashOutput, + ids_ordering: BTreeMap, +} + +impl PublicData1

{ + fn hash(&self, sid_hash: &HashOutput, id: &I) -> HashOutput { + FofHasher::new_with_dst(b"Auxiliary") + .chain(sid_hash) + .chain(id) + .chain(self) + .finalize() } } +#[derive(Debug)] +struct Round1 { + context: Context, +} + #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Round1Message { +struct Round1Message { cap_v: HashOutput, } -pub struct Round1Payload { +struct Round1Payload { cap_v: HashOutput, } -impl Round for Round1 { - type Type = ToNextRound; - type Result = KeyRefreshResult; - const ROUND_NUM: u8 = 1; - const NEXT_ROUND_NUM: Option = Some(2); +impl Round for Round1 { + type Protocol = KeyRefreshProtocol; - fn other_ids(&self) -> &BTreeSet { - &self.context.other_ids + fn id(&self) -> RoundId { + RoundId::new(1) } - fn my_id(&self) -> &I { - &self.context.my_id + fn possible_next_rounds(&self) -> BTreeSet { + BTreeSet::from([RoundId::new(2)]) } - const REQUIRES_ECHO: bool = true; - type BroadcastMessage = Round1Message; - type DirectMessage = (); - type Payload = Round1Payload; - type Artifact = (); + fn message_destinations(&self) -> &BTreeSet { + &self.context.other_ids + } + + fn expecting_messages_from(&self) -> &BTreeSet { + &self.context.other_ids + } - fn make_broadcast_message( + fn make_echo_broadcast( &self, _rng: &mut impl CryptoRngCore, - ) -> Option { - Some(Round1Message { - cap_v: self - .context - .data_precomp - .data - .hash(&self.context.sid_hash, self.my_id()), - }) + serializer: &Serializer, + ) -> Result { + EchoBroadcast::new( + serializer, + Round1Message { + cap_v: self + .context + .data_precomp + .data + .hash(&self.context.sid_hash, &self.context.my_id), + }, + ) } - no_direct_messages!(I); - - fn verify_message( + fn receive_message( &self, _rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, _from: &I, - broadcast_msg: Self::BroadcastMessage, - _direct_msg: Self::DirectMessage, - ) -> Result::ProvableError> { - Ok(Round1Payload { - cap_v: broadcast_msg.cap_v, - }) + echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, + direct_message: DirectMessage, + ) -> Result> { + normal_broadcast.assert_is_none()?; + direct_message.assert_is_none()?; + let echo_broadcast = echo_broadcast.deserialize::(deserializer)?; + Ok(Payload::new(Round1Payload { + cap_v: echo_broadcast.cap_v, + })) } -} -impl FinalizableToNextRound - for Round1 -{ - type NextRound = Round2; - fn finalize_to_next_round( + fn finalize( self, _rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - _artifacts: BTreeMap>::Artifact>, - ) -> Result> { + payloads: BTreeMap, + _artifacts: BTreeMap, + ) -> Result, LocalError> { + let payloads = payloads.downcast_all::()?; let others_cap_v = payloads .into_iter() .map(|(id, payload)| (id, payload.cap_v)) .collect(); - Ok(Round2 { - context: self.context, - others_cap_v, - }) + Ok(FinalizeOutcome::AnotherRound(BoxedRound::new_dynamic( + Round2 { + context: self.context, + others_cap_v, + }, + ))) } } -pub struct Round2 { +#[derive(Debug)] +struct Round2 { context: Context, others_cap_v: BTreeMap, } @@ -300,102 +379,106 @@ pub struct Round2 { #[derive(Clone, Serialize, Deserialize)] #[serde(bound(serialize = "PublicData1

: Serialize"))] #[serde(bound(deserialize = "PublicData1

: for<'x> Deserialize<'x>"))] -pub struct Round2Message { +struct Round2Message { data: PublicData1

, } -pub struct Round2Payload { +struct Round2Payload { data: PublicData1Precomp

, } -impl Round for Round2 { - type Type = ToNextRound; - type Result = KeyRefreshResult; - const ROUND_NUM: u8 = 2; - const NEXT_ROUND_NUM: Option = Some(3); +impl Round for Round2 { + type Protocol = KeyRefreshProtocol; - fn other_ids(&self) -> &BTreeSet { - &self.context.other_ids + fn id(&self) -> RoundId { + RoundId::new(2) + } + + fn possible_next_rounds(&self) -> BTreeSet { + BTreeSet::from([RoundId::new(3)]) } - fn my_id(&self) -> &I { - &self.context.my_id + fn message_destinations(&self) -> &BTreeSet { + &self.context.other_ids } - type BroadcastMessage = Round2Message

; - type DirectMessage = (); - type Payload = Round2Payload

; - type Artifact = (); + fn expecting_messages_from(&self) -> &BTreeSet { + &self.context.other_ids + } - fn make_broadcast_message( + fn make_normal_broadcast( &self, _rng: &mut impl CryptoRngCore, - ) -> Option { - Some(Round2Message { - data: self.context.data_precomp.data.clone(), - }) + serializer: &Serializer, + ) -> Result { + NormalBroadcast::new( + serializer, + Round2Message { + data: self.context.data_precomp.data.clone(), + }, + ) } - no_direct_messages!(I); - - fn verify_message( + fn receive_message( &self, _rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, from: &I, - broadcast_msg: Self::BroadcastMessage, - _direct_msg: Self::DirectMessage, - ) -> Result::ProvableError> { - if &broadcast_msg.data.hash(&self.context.sid_hash, from) + echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, + direct_message: DirectMessage, + ) -> Result> { + echo_broadcast.assert_is_none()?; + direct_message.assert_is_none()?; + let normal_broadcast = normal_broadcast.deserialize::>(deserializer)?; + + if &normal_broadcast.data.hash(&self.context.sid_hash, from) != self.others_cap_v.get(from).unwrap() { - return Err(KeyRefreshError(KeyRefreshErrorEnum::Round2( - "Hash mismatch".into(), + return Err(ReceiveError::protocol(KeyRefreshError( + KeyRefreshErrorEnum::Round2("Hash mismatch".into()), ))); } - let paillier_pk = broadcast_msg.data.paillier_pk.to_precomputed(); + let paillier_pk = normal_broadcast.data.paillier_pk.to_precomputed(); if (paillier_pk.modulus().bits_vartime() as usize) < 8 * P::SECURITY_PARAMETER { - return Err(KeyRefreshError(KeyRefreshErrorEnum::Round2( - "Paillier modulus is too small".into(), + return Err(ReceiveError::protocol(KeyRefreshError( + KeyRefreshErrorEnum::Round2("Paillier modulus is too small".into()), ))); } - if broadcast_msg.data.cap_x_to_send.iter().sum::() != Point::IDENTITY { - return Err(KeyRefreshError(KeyRefreshErrorEnum::Round2( - "Sum of X points is not identity".into(), + if normal_broadcast.data.cap_x_to_send.iter().sum::() != Point::IDENTITY { + return Err(ReceiveError::protocol(KeyRefreshError( + KeyRefreshErrorEnum::Round2("Sum of X points is not identity".into()), ))); } let aux = (&self.context.sid_hash, &from); - let rp_params = broadcast_msg.data.rp_params.to_mod(&paillier_pk); - if !broadcast_msg.data.hat_psi.verify(&rp_params, &aux) { - return Err(KeyRefreshError(KeyRefreshErrorEnum::Round2( - "PRM verification failed".into(), + let rp_params = normal_broadcast.data.rp_params.to_mod(&paillier_pk); + if !normal_broadcast.data.hat_psi.verify(&rp_params, &aux) { + return Err(ReceiveError::protocol(KeyRefreshError( + KeyRefreshErrorEnum::Round2("PRM verification failed".into()), ))); } - Ok(Round2Payload { + Ok(Payload::new(Round2Payload { data: PublicData1Precomp { - data: broadcast_msg.data, + data: normal_broadcast.data, paillier_pk, rp_params, }, - }) + })) } -} -impl FinalizableToNextRound - for Round2 -{ - type NextRound = Round3; - fn finalize_to_next_round( + fn finalize( self, rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - _artifacts: BTreeMap>::Artifact>, - ) -> Result> { + payloads: BTreeMap, + _artifacts: BTreeMap, + ) -> Result, LocalError> { + let payloads = payloads.downcast_all::>()?; let others_data = payloads .into_iter() .map(|(id, payload)| (id, payload.data)) @@ -405,11 +488,14 @@ impl FinalizableToNextRound rho ^= &data.data.rho; } - Ok(Round3::new(rng, self.context, others_data, rho)) + Ok(FinalizeOutcome::AnotherRound(BoxedRound::new_dynamic( + Round3::new(rng, self.context, others_data, rho), + ))) } } -pub struct Round3 { +#[derive(Debug)] +struct Round3 { context: Context, rho: BitVec, others_data: BTreeMap>, @@ -428,7 +514,7 @@ pub struct Round3 { FacProof

: for<'x> Deserialize<'x>, Ciphertext: for<'x> Deserialize<'x>, "))] -pub struct PublicData2 { +struct PublicData2 { psi_mod: ModProof

, // $\psi_i$, a P^{mod} for the Paillier modulus phi: FacProof

, pi: SchProof, @@ -436,7 +522,7 @@ pub struct PublicData2 { psi_sch: SchProof, // $psi_i^j$, a P^{sch} for the secret share change } -impl Round3 { +impl Round3 { fn new( rng: &mut impl CryptoRngCore, context: Context, @@ -467,41 +553,44 @@ impl Round3 { #[derive(Clone, Serialize, Deserialize)] #[serde(bound(serialize = "PublicData2

: Serialize"))] #[serde(bound(deserialize = "PublicData2

: for<'x> Deserialize<'x>"))] -pub struct Round3Message { +struct Round3Message { data2: PublicData2

, } -pub struct Round3Payload { +struct Round3Payload { x: Scalar, // $x_j^i$, a secret share change received from the party $j$ } -impl Round for Round3 { - type Type = ToResult; - type Result = KeyRefreshResult; - const ROUND_NUM: u8 = 3; - const NEXT_ROUND_NUM: Option = None; +impl Round for Round3 { + type Protocol = KeyRefreshProtocol; - fn other_ids(&self) -> &BTreeSet { - &self.context.other_ids + fn id(&self) -> RoundId { + RoundId::new(3) } - fn my_id(&self) -> &I { - &self.context.my_id + fn possible_next_rounds(&self) -> BTreeSet { + BTreeSet::new() } - type BroadcastMessage = (); - type DirectMessage = Round3Message

; - type Payload = Round3Payload; - type Artifact = (); + fn may_produce_result(&self) -> bool { + true + } - no_broadcast_messages!(); + fn message_destinations(&self) -> &BTreeSet { + &self.context.other_ids + } + + fn expecting_messages_from(&self) -> &BTreeSet { + &self.context.other_ids + } fn make_direct_message( &self, rng: &mut impl CryptoRngCore, + serializer: &Serializer, destination: &I, - ) -> (Self::DirectMessage, Self::Artifact) { - let aux = (&self.context.sid_hash, self.my_id(), &self.rho); + ) -> Result<(DirectMessage, Option), LocalError> { + let aux = (&self.context.sid_hash, &self.context.my_id, &self.rho); let data = self.others_data.get(destination).unwrap(); @@ -535,98 +624,105 @@ impl Round for Round3 Result::ProvableError> { + echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, + direct_message: DirectMessage, + ) -> Result> { + echo_broadcast.assert_is_none()?; + normal_broadcast.assert_is_none()?; + let direct_message = direct_message.deserialize::>(deserializer)?; + let sender_data = &self.others_data.get(from).unwrap(); - let enc_x = direct_msg + let enc_x = direct_message .data2 .paillier_enc_x .to_mod(self.context.paillier_sk.public_key()); let x = P::scalar_from_uint(&enc_x.decrypt(&self.context.paillier_sk)); - let my_idx = self.context.ids_ordering[self.my_id()]; + let my_idx = self.context.ids_ordering[&self.context.my_id]; if x.mul_by_generator() != sender_data.data.cap_x_to_send[my_idx] { let mu = enc_x.derive_randomizer(&self.context.paillier_sk); - return Err(KeyRefreshError( + return Err(ReceiveError::protocol(KeyRefreshError( KeyRefreshErrorEnum::Round3MismatchedSecret { - cap_c: direct_msg.data2.paillier_enc_x, + cap_c: direct_message.data2.paillier_enc_x, x, mu: mu.retrieve(), }, - )); + ))); } let aux = (&self.context.sid_hash, &from, &self.rho); - if !direct_msg + if !direct_message .data2 .psi_mod .verify(rng, &sender_data.paillier_pk, &aux) { - return Err(KeyRefreshError(KeyRefreshErrorEnum::Round3( - "Mod proof verification failed".into(), + return Err(ReceiveError::protocol(KeyRefreshError( + KeyRefreshErrorEnum::Round3("Mod proof verification failed".into()), ))); } - if !direct_msg.data2.phi.verify( + if !direct_message.data2.phi.verify( &sender_data.paillier_pk, &self.context.data_precomp.rp_params, &aux, ) { - return Err(KeyRefreshError(KeyRefreshErrorEnum::Round3( - "Fac proof verification failed".into(), + return Err(ReceiveError::protocol(KeyRefreshError( + KeyRefreshErrorEnum::Round3("Fac proof verification failed".into()), ))); } - if !direct_msg + if !direct_message .data2 .pi .verify(&sender_data.data.cap_b, &sender_data.data.cap_y, &aux) { - return Err(KeyRefreshError(KeyRefreshErrorEnum::Round3( - "Sch proof verification (Y) failed".into(), + return Err(ReceiveError::protocol(KeyRefreshError( + KeyRefreshErrorEnum::Round3("Sch proof verification (Y) failed".into()), ))); } - if !direct_msg.data2.psi_sch.verify( + if !direct_message.data2.psi_sch.verify( &sender_data.data.cap_a_to_send[my_idx], &sender_data.data.cap_x_to_send[my_idx], &aux, ) { - return Err(KeyRefreshError(KeyRefreshErrorEnum::Round3( - "Sch proof verification (X) failed".into(), + return Err(ReceiveError::protocol(KeyRefreshError( + KeyRefreshErrorEnum::Round3("Sch proof verification (X) failed".into()), ))); } - Ok(Round3Payload { x }) + Ok(Payload::new(Round3Payload { x })) } -} -impl FinalizableToResult for Round3 { - fn finalize_to_result( + fn finalize( self, _rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - _artifacts: BTreeMap>::Artifact>, - ) -> Result<::Success, FinalizeError> { + payloads: BTreeMap, + _artifacts: BTreeMap, + ) -> Result, LocalError> { + let payloads = payloads.downcast_all::()?; let others_x = payloads .into_iter() .map(|(id, payload)| (id, payload.x)) .collect::>(); // The combined secret share change - let x_star = others_x.values().sum::() + self.context.x_to_send[self.my_id()]; + let x_star = + others_x.values().sum::() + self.context.x_to_send[&self.context.my_id]; let my_id = self.context.my_id.clone(); let mut all_ids = self.context.other_ids; @@ -682,7 +778,7 @@ impl FinalizableToResult public_aux, }; - Ok((key_share_change, aux_info)) + Ok(FinalizeOutcome::Result((key_share_change, aux_info))) } } @@ -691,45 +787,37 @@ mod tests { use alloc::collections::{BTreeMap, BTreeSet}; - use rand_core::{OsRng, RngCore}; + use manul::{ + dev::{run_sync, BinaryFormat, TestSessionParams, TestSigner, TestVerifier}, + session::signature::Keypair, + }; + use rand_core::OsRng; use secrecy::ExposeSecret; - use super::Round1; - use crate::cggmp21::TestParams; - use crate::curve::Scalar; - use crate::rounds::{ - test_utils::{step_next_round, step_result, step_round, Id, Without}, - FirstRound, - }; + use super::KeyRefresh; + use crate::{cggmp21::TestParams, curve::Scalar}; #[test] fn execute_key_refresh() { - let mut shared_randomness = [0u8; 32]; - OsRng.fill_bytes(&mut shared_randomness); - - let ids = BTreeSet::from([Id(0), Id(1), Id(2)]); + let signers = (0..3).map(TestSigner::new).collect::>(); - let r1 = ids + let all_ids = signers .iter() - .map(|id| { - let round = Round1::::new( - &mut OsRng, - &shared_randomness, - ids.clone().without(id), - *id, - (), - ) - .unwrap(); - (*id, round) + .map(|signer| signer.verifying_key()) + .collect::>(); + let entry_points = signers + .into_iter() + .map(|signer| { + let entry_point = + KeyRefresh::::new(all_ids.clone()).unwrap(); + (signer, entry_point) }) - .collect(); + .collect::>(); - let r1a = step_round(&mut OsRng, r1).unwrap(); - let r2 = step_next_round(&mut OsRng, r1a).unwrap(); - let r2a = step_round(&mut OsRng, r2).unwrap(); - let r3 = step_next_round(&mut OsRng, r2a).unwrap(); - let r3a = step_round(&mut OsRng, r3).unwrap(); - let results = step_result(&mut OsRng, r3a).unwrap(); + let results = run_sync::<_, TestSessionParams>(&mut OsRng, entry_points) + .unwrap() + .results() + .unwrap(); let (changes, aux_infos): (BTreeMap<_, _>, BTreeMap<_, _>) = results .into_iter() diff --git a/synedrion/src/cggmp21/protocols/presigning.rs b/synedrion/src/cggmp21/protocols/presigning.rs deleted file mode 100644 index 18783065..00000000 --- a/synedrion/src/cggmp21/protocols/presigning.rs +++ /dev/null @@ -1,962 +0,0 @@ -//! Presigning protocol, in the paper ECDSA Pre-Signing (Fig. 7). - -use alloc::boxed::Box; -use alloc::collections::{BTreeMap, BTreeSet}; -use alloc::string::String; -use alloc::vec::Vec; -use core::fmt::Debug; -use core::marker::PhantomData; - -use rand_core::CryptoRngCore; -use secrecy::{ExposeSecret, SecretBox}; -use serde::{Deserialize, Serialize}; - -use super::super::{ - entities::{AuxInfoPrecomputed, PresigningValues}, - sigma::{AffGProof, DecProof, EncProof, LogStarProof, MulProof}, - AuxInfo, KeyShare, PresigningData, SchemeParams, -}; -use crate::curve::{Point, Scalar}; -use crate::paillier::{Ciphertext, CiphertextMod, PaillierParams, Randomizer, RandomizerMod}; -use crate::rounds::{ - no_broadcast_messages, FinalizableToNextRound, FinalizableToResult, FinalizeError, FirstRound, - InitError, ProtocolResult, Round, ToNextRound, ToResult, -}; -use crate::tools::hashing::{Chain, FofHasher, HashOutput}; -use crate::uint::Signed; - -/// Possible results of the Presigning protocol. -#[derive(Debug)] -pub struct PresigningResult(PhantomData

, PhantomData); - -impl ProtocolResult for PresigningResult { - type Success = PresigningData; - type ProvableError = PresigningError; - type CorrectnessProof = PresigningProof; -} - -/// Possible verifiable errors of the Presigning protocol. -#[derive(Debug, Clone)] -pub enum PresigningError { - /// An error in Round 1. - Round1(String), - /// An error in Round 2. - Round2(String), - /// An error in Round 3. - Round3(String), -} - -struct Context { - ssid_hash: HashOutput, - my_id: I, - other_ids: BTreeSet, - key_share: KeyShare, - aux_info: AuxInfoPrecomputed, - k: Scalar, - gamma: Scalar, - rho: RandomizerMod, - nu: RandomizerMod, -} - -pub struct Round1 { - context: Context, - cap_k: CiphertextMod, - cap_g: CiphertextMod, -} - -impl FirstRound for Round1 { - type Inputs = (KeyShare, AuxInfo); - fn new( - rng: &mut impl CryptoRngCore, - shared_randomness: &[u8], - other_ids: BTreeSet, - my_id: I, - inputs: Self::Inputs, - ) -> Result { - let (key_share, aux_info) = inputs; - - // This includes the info of $ssid$ in the paper - // (scheme parameters + public data from all shares - hashed in `share_set_id`), - // with the session randomness added. - let ssid_hash = FofHasher::new_with_dst(b"ShareSetID") - .chain_type::

() - .chain(&shared_randomness) - .chain(&key_share.public_shares) - .chain(&aux_info.public_aux) - .finalize(); - - let aux_info = aux_info.to_precomputed(); - - // TODO (#68): check that KeyShare is consistent with num_parties/party_idx - - // The share of an ephemeral scalar - let k = Scalar::random(rng); - // The share of the mask used to generate the inverse of the ephemeral scalar - let gamma = Scalar::random(rng); - - let pk = aux_info.secret_aux.paillier_sk.public_key(); - - let nu = RandomizerMod::::random(rng, pk); - let cap_g = - CiphertextMod::new_with_randomizer(pk, &P::uint_from_scalar(&gamma), &nu.retrieve()); - - let rho = RandomizerMod::::random(rng, pk); - let cap_k = - CiphertextMod::new_with_randomizer(pk, &P::uint_from_scalar(&k), &rho.retrieve()); - - Ok(Self { - context: Context { - ssid_hash, - my_id, - other_ids, - key_share, - aux_info, - k, - gamma, - rho, - nu, - }, - cap_k, - cap_g, - }) - } -} - -#[derive(Clone, Serialize, Deserialize)] -#[serde(bound(serialize = "Ciphertext: Serialize"))] -#[serde(bound(deserialize = "Ciphertext: for<'x> Deserialize<'x>"))] -pub struct Round1BroadcastMessage { - cap_k: Ciphertext, - cap_g: Ciphertext, -} - -#[derive(Clone, Serialize, Deserialize)] -#[serde(bound(serialize = "EncProof

: Serialize"))] -#[serde(bound(deserialize = "EncProof

: for<'x> Deserialize<'x>"))] -pub struct Round1DirectMessage { - psi0: EncProof

, -} - -pub struct Round1Payload { - cap_k: Ciphertext, - cap_g: Ciphertext, -} - -impl Round for Round1 { - type Type = ToNextRound; - type Result = PresigningResult; - const ROUND_NUM: u8 = 1; - const NEXT_ROUND_NUM: Option = Some(2); - - fn other_ids(&self) -> &BTreeSet { - &self.context.other_ids - } - - fn my_id(&self) -> &I { - &self.context.my_id - } - - const REQUIRES_ECHO: bool = true; - type BroadcastMessage = Round1BroadcastMessage

; - type DirectMessage = Round1DirectMessage

; - type Payload = Round1Payload

; - type Artifact = (); - - fn make_broadcast_message( - &self, - _rng: &mut impl CryptoRngCore, - ) -> Option { - Some(Round1BroadcastMessage { - cap_k: self.cap_k.retrieve(), - cap_g: self.cap_g.retrieve(), - }) - } - - fn make_direct_message( - &self, - rng: &mut impl CryptoRngCore, - destination: &I, - ) -> (Self::DirectMessage, Self::Artifact) { - let aux = (&self.context.ssid_hash, &destination); - let psi0 = EncProof::new( - rng, - &P::signed_from_scalar(&self.context.k).unwrap(), - &self.context.rho, - self.context.aux_info.secret_aux.paillier_sk.public_key(), - &self.cap_k, - &self.context.aux_info.public_aux[destination].rp_params, - &aux, - ); - - (Round1DirectMessage { psi0 }, ()) - } - - fn verify_message( - &self, - _rng: &mut impl CryptoRngCore, - from: &I, - broadcast_msg: Self::BroadcastMessage, - direct_msg: Self::DirectMessage, - ) -> Result::ProvableError> { - let aux = (&self.context.ssid_hash, self.my_id()); - - let public_aux = &self.context.aux_info.public_aux[self.my_id()]; - - let from_pk = &self.context.aux_info.public_aux[from].paillier_pk; - - if !direct_msg.psi0.verify( - from_pk, - &broadcast_msg.cap_k.to_mod(from_pk), - &public_aux.rp_params, - &aux, - ) { - return Err(PresigningError::Round1("Failed to verify EncProof".into())); - } - - Ok(Round1Payload { - cap_k: broadcast_msg.cap_k, - cap_g: broadcast_msg.cap_g, - }) - } -} - -impl FinalizableToNextRound - for Round1 -{ - type NextRound = Round2; - fn finalize_to_next_round( - self, - _rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - _artifacts: BTreeMap>::Artifact>, - ) -> Result> { - let (others_cap_k, others_cap_g): (BTreeMap<_, _>, BTreeMap<_, _>) = payloads - .into_iter() - .map(|(id, payload)| ((id.clone(), payload.cap_k), (id, payload.cap_g))) - .unzip(); - - let my_id = self.my_id().clone(); - - let mut all_cap_k = others_cap_k - .into_iter() - .map(|(id, ciphertext)| { - let ciphertext_mod = - ciphertext.to_mod(&self.context.aux_info.public_aux[&id].paillier_pk); - (id, ciphertext_mod) - }) - .collect::>(); - all_cap_k.insert(my_id.clone(), self.cap_k); - - let mut all_cap_g = others_cap_g - .into_iter() - .map(|(id, ciphertext)| { - let ciphertext_mod = - ciphertext.to_mod(&self.context.aux_info.public_aux[&id].paillier_pk); - (id, ciphertext_mod) - }) - .collect::>(); - all_cap_g.insert(my_id, self.cap_g); - - Ok(Round2 { - context: self.context, - all_cap_k, - all_cap_g, - }) - } -} - -pub struct Round2 { - context: Context, - all_cap_k: BTreeMap>, - all_cap_g: BTreeMap>, -} - -#[derive(Clone, Serialize, Deserialize)] -#[serde(bound(serialize = " - Ciphertext: Serialize, - AffGProof

: Serialize, - LogStarProof

: Serialize, -"))] -#[serde(bound(deserialize = " - Ciphertext: for<'x> Deserialize<'x>, - AffGProof

: for<'x> Deserialize<'x>, - LogStarProof

: for<'x> Deserialize<'x>, -"))] -pub struct Round2Message { - cap_gamma: Point, - cap_d: Ciphertext, - hat_cap_d: Ciphertext, - cap_f: Ciphertext, - hat_cap_f: Ciphertext, - psi: AffGProof

, - hat_psi: AffGProof

, - hat_psi_prime: LogStarProof

, -} - -#[derive(Debug, Clone)] -pub struct Round2Artifact { - beta: SecretBox::Uint>>, - hat_beta: SecretBox::Uint>>, - r: Randomizer, - s: Randomizer, - hat_r: Randomizer, - hat_s: Randomizer, - cap_d: CiphertextMod, - cap_f: CiphertextMod, - hat_cap_d: CiphertextMod, - hat_cap_f: CiphertextMod, -} - -pub struct Round2Payload { - cap_gamma: Point, - alpha: Signed<::Uint>, - hat_alpha: Signed<::Uint>, - cap_d: CiphertextMod, - hat_cap_d: CiphertextMod, -} - -impl Round for Round2 { - type Type = ToNextRound; - type Result = PresigningResult; - const ROUND_NUM: u8 = 2; - const NEXT_ROUND_NUM: Option = Some(3); - - fn other_ids(&self) -> &BTreeSet { - &self.context.other_ids - } - - fn my_id(&self) -> &I { - &self.context.my_id - } - - type BroadcastMessage = (); - type DirectMessage = Round2Message

; - type Payload = Round2Payload

; - type Artifact = Round2Artifact

; - - no_broadcast_messages!(); - - fn make_direct_message( - &self, - rng: &mut impl CryptoRngCore, - destination: &I, - ) -> (Self::DirectMessage, Self::Artifact) { - let aux = (&self.context.ssid_hash, &self.my_id()); - - let cap_gamma = self.context.gamma.mul_by_generator(); - let pk = self.context.aux_info.secret_aux.paillier_sk.public_key(); - - let target_pk = &self.context.aux_info.public_aux[destination].paillier_pk; - - let beta = SecretBox::new(Box::new(Signed::random_bounded_bits(rng, P::LP_BOUND))); - let hat_beta = SecretBox::new(Box::new(Signed::random_bounded_bits(rng, P::LP_BOUND))); - let r = RandomizerMod::random(rng, pk); - let s = RandomizerMod::random(rng, target_pk); - let hat_r = RandomizerMod::random(rng, pk); - let hat_s = RandomizerMod::random(rng, target_pk); - - let cap_f = - CiphertextMod::new_with_randomizer_signed(pk, beta.expose_secret(), &r.retrieve()); - let cap_d = &self.all_cap_k[destination] - * P::signed_from_scalar(&self.context.gamma).unwrap() - + CiphertextMod::new_with_randomizer_signed( - target_pk, - &-beta.expose_secret(), - &s.retrieve(), - ); - - let hat_cap_f = CiphertextMod::new_with_randomizer_signed( - pk, - hat_beta.expose_secret(), - &hat_r.retrieve(), - ); - let hat_cap_d = &self.all_cap_k[destination] - * P::signed_from_scalar(self.context.key_share.secret_share.expose_secret()).unwrap() - + CiphertextMod::new_with_randomizer_signed( - target_pk, - &-hat_beta.expose_secret(), - &hat_s.retrieve(), - ); - - let public_aux = &self.context.aux_info.public_aux[destination]; - let rp = &public_aux.rp_params; - - let psi = AffGProof::new( - rng, - &P::signed_from_scalar(&self.context.gamma).unwrap(), - &beta, - s.clone(), - r.clone(), - target_pk, - pk, - &self.all_cap_k[destination], - &cap_d, - &cap_f, - &cap_gamma, - rp, - &aux, - ); - - let hat_psi = AffGProof::new( - rng, - &P::signed_from_scalar(self.context.key_share.secret_share.expose_secret()).unwrap(), - &hat_beta, - hat_s.clone(), - hat_r.clone(), - target_pk, - pk, - &self.all_cap_k[destination], - &hat_cap_d, - &hat_cap_f, - &self.context.key_share.public_shares[self.my_id()], - rp, - &aux, - ); - - let hat_psi_prime = LogStarProof::new( - rng, - &P::signed_from_scalar(&self.context.gamma).unwrap(), - &self.context.nu, - pk, - &self.all_cap_g[self.my_id()], - &Point::GENERATOR, - &cap_gamma, - rp, - &aux, - ); - - let msg = Round2Message { - cap_gamma, - cap_d: cap_d.retrieve(), - cap_f: cap_f.retrieve(), - hat_cap_d: hat_cap_d.retrieve(), - hat_cap_f: hat_cap_f.retrieve(), - psi, - hat_psi, - hat_psi_prime, - }; - - let artifact = Round2Artifact { - beta, - hat_beta, - r: r.retrieve(), - s: s.retrieve(), - hat_r: hat_r.retrieve(), - hat_s: hat_s.retrieve(), - cap_d, - cap_f, - hat_cap_d, - hat_cap_f, - }; - - (msg, artifact) - } - - fn verify_message( - &self, - _rng: &mut impl CryptoRngCore, - from: &I, - _broadcast_msg: Self::BroadcastMessage, - direct_msg: Self::DirectMessage, - ) -> Result::ProvableError> { - let aux = (&self.context.ssid_hash, &from); - let pk = &self.context.aux_info.secret_aux.paillier_sk.public_key(); - let from_pk = &self.context.aux_info.public_aux[from].paillier_pk; - - let cap_x = self.context.key_share.public_shares[from]; - - let public_aux = &self.context.aux_info.public_aux[self.my_id()]; - let rp = &public_aux.rp_params; - - let cap_d = direct_msg.cap_d.to_mod(pk); - let hat_cap_d = direct_msg.hat_cap_d.to_mod(pk); - - if !direct_msg.psi.verify( - pk, - from_pk, - &self.all_cap_k[self.my_id()], - &cap_d, - &direct_msg.cap_f.to_mod(from_pk), - &direct_msg.cap_gamma, - rp, - &aux, - ) { - return Err(PresigningError::Round2( - "Failed to verify AffGProof (psi)".into(), - )); - } - - if !direct_msg.hat_psi.verify( - pk, - from_pk, - &self.all_cap_k[self.my_id()], - &hat_cap_d, - &direct_msg.hat_cap_f.to_mod(from_pk), - &cap_x, - rp, - &aux, - ) { - return Err(PresigningError::Round2( - "Failed to verify AffGProof (hat_psi)".into(), - )); - } - - if !direct_msg.hat_psi_prime.verify( - from_pk, - &self.all_cap_g[from], - &Point::GENERATOR, - &direct_msg.cap_gamma, - rp, - &aux, - ) { - return Err(PresigningError::Round2( - "Failed to verify LogStarProof".into(), - )); - } - - let alpha = cap_d.decrypt_signed(&self.context.aux_info.secret_aux.paillier_sk); - let hat_alpha = hat_cap_d.decrypt_signed(&self.context.aux_info.secret_aux.paillier_sk); - - // `alpha == x * y + z` where `0 <= x, y < q`, and `-2^l' <= z <= 2^l'`, - // where `q` is the curve order. - // We will need this bound later, so we're asserting it. - let alpha = alpha - .assert_bit_bound_usize(core::cmp::max(2 * P::L_BOUND, P::LP_BOUND) + 1) - .unwrap(); - let hat_alpha = hat_alpha - .assert_bit_bound_usize(core::cmp::max(2 * P::L_BOUND, P::LP_BOUND) + 1) - .unwrap(); - - Ok(Round2Payload { - cap_gamma: direct_msg.cap_gamma, - alpha, - hat_alpha, - cap_d, - hat_cap_d, - }) - } -} - -impl FinalizableToNextRound - for Round2 -{ - type NextRound = Round3; - fn finalize_to_next_round( - self, - _rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - artifacts: BTreeMap>::Artifact>, - ) -> Result> { - let cap_gamma = payloads - .values() - .map(|payload| payload.cap_gamma) - .sum::() - + self.context.gamma.mul_by_generator(); - - let cap_delta = cap_gamma * self.context.k; - - let alpha_sum: Signed<_> = payloads.values().map(|p| p.alpha).sum(); - let beta_sum: Signed<_> = artifacts.values().map(|p| p.beta.expose_secret()).sum(); - let delta = P::signed_from_scalar(&self.context.gamma).unwrap() - * P::signed_from_scalar(&self.context.k).unwrap() - + alpha_sum - + beta_sum; - - let hat_alpha_sum: Signed<_> = payloads.values().map(|payload| payload.hat_alpha).sum(); - let hat_beta_sum: Signed<_> = artifacts - .values() - .map(|artifact| artifact.hat_beta.expose_secret()) - .sum(); - let chi = P::signed_from_scalar(self.context.key_share.secret_share.expose_secret()) - .unwrap() - * P::signed_from_scalar(&self.context.k).unwrap() - + hat_alpha_sum - + hat_beta_sum; - - let (cap_ds, hat_cap_ds) = payloads - .into_iter() - .map(|(id, payload)| ((id.clone(), payload.cap_d), (id, payload.hat_cap_d))) - .unzip(); - - Ok(Round3 { - context: self.context, - delta, - chi, - cap_delta, - cap_gamma, - all_cap_k: self.all_cap_k, - all_cap_g: self.all_cap_g, - cap_ds, - hat_cap_ds, - round2_artifacts: artifacts, - }) - } -} - -pub struct Round3 { - context: Context, - delta: Signed<::Uint>, - chi: Signed<::Uint>, - cap_delta: Point, - cap_gamma: Point, - all_cap_k: BTreeMap>, - all_cap_g: BTreeMap>, - cap_ds: BTreeMap>, - hat_cap_ds: BTreeMap>, - round2_artifacts: BTreeMap>, -} - -#[derive(Clone, Serialize, Deserialize)] -#[serde(bound(serialize = "LogStarProof

: Serialize"))] -#[serde(bound(deserialize = "LogStarProof

: for<'x> Deserialize<'x>"))] -pub struct Round3Message { - delta: Scalar, - cap_delta: Point, - psi_pprime: LogStarProof

, -} - -pub struct Round3Payload { - delta: Scalar, - cap_delta: Point, -} - -impl Round for Round3 { - type Type = ToResult; - type Result = PresigningResult; - const ROUND_NUM: u8 = 3; - const NEXT_ROUND_NUM: Option = None; - - fn other_ids(&self) -> &BTreeSet { - &self.context.other_ids - } - - fn my_id(&self) -> &I { - &self.context.my_id - } - - type BroadcastMessage = (); - type DirectMessage = Round3Message

; - type Payload = Round3Payload; - type Artifact = (); - - no_broadcast_messages!(); - - fn make_direct_message( - &self, - rng: &mut impl CryptoRngCore, - destination: &I, - ) -> (Self::DirectMessage, Self::Artifact) { - let aux = (&self.context.ssid_hash, &self.my_id()); - let pk = &self.context.aux_info.secret_aux.paillier_sk.public_key(); - - let public_aux = &self.context.aux_info.public_aux[destination]; - let rp = &public_aux.rp_params; - - let psi_pprime = LogStarProof::new( - rng, - &P::signed_from_scalar(&self.context.k).unwrap(), - &self.context.rho, - pk, - &self.all_cap_k[self.my_id()], - &self.cap_gamma, - &self.cap_delta, - rp, - &aux, - ); - let message = Round3Message { - delta: P::scalar_from_signed(&self.delta), - cap_delta: self.cap_delta, - psi_pprime, - }; - - (message, ()) - } - - fn verify_message( - &self, - _rng: &mut impl CryptoRngCore, - from: &I, - _broadcast_msg: Self::BroadcastMessage, - direct_msg: Self::DirectMessage, - ) -> Result::ProvableError> { - let aux = (&self.context.ssid_hash, &from); - let from_pk = &self.context.aux_info.public_aux[from].paillier_pk; - - let public_aux = &self.context.aux_info.public_aux[self.my_id()]; - let rp = &public_aux.rp_params; - - if !direct_msg.psi_pprime.verify( - from_pk, - &self.all_cap_k[from], - &self.cap_gamma, - &direct_msg.cap_delta, - rp, - &aux, - ) { - return Err(PresigningError::Round3( - "Failed to verify Log-Star proof".into(), - )); - } - Ok(Round3Payload { - delta: direct_msg.delta, - cap_delta: direct_msg.cap_delta, - }) - } -} - -/// A proof of a node's correct behavior for the Presigning protocol. -#[allow(dead_code)] // TODO (#43): this can be removed when error verification is added -#[derive(Debug, Clone)] -pub struct PresigningProof { - aff_g_proofs: Vec<(I, I, AffGProof

)>, - mul_proof: MulProof

, - dec_proofs: Vec<(I, DecProof

)>, -} - -impl FinalizableToResult for Round3 { - fn finalize_to_result( - self, - rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - _artifacts: BTreeMap>::Artifact>, - ) -> Result<::Success, FinalizeError> { - let (deltas, cap_deltas): (BTreeMap<_, _>, BTreeMap<_, _>) = payloads - .into_iter() - .map(|(id, payload)| ((id.clone(), payload.delta), (id, payload.cap_delta))) - .unzip(); - - let scalar_delta = P::scalar_from_signed(&self.delta); - let assembled_delta: Scalar = scalar_delta + deltas.values().sum::(); - let assembled_cap_delta: Point = self.cap_delta + cap_deltas.values().sum::(); - - if assembled_delta.mul_by_generator() == assembled_cap_delta { - let nonce = (self.cap_gamma * assembled_delta.invert().unwrap()).x_coordinate(); - let my_id = self.my_id().clone(); - - let values = self - .round2_artifacts - .into_iter() - .map(|(id, artifact)| { - let values = PresigningValues { - hat_beta: artifact.hat_beta, - hat_r: artifact.hat_r, - hat_s: artifact.hat_s, - cap_k: self.all_cap_k[&id].clone(), - hat_cap_d_received: self.hat_cap_ds[&id].clone(), - hat_cap_d: artifact.hat_cap_d, - hat_cap_f: artifact.hat_cap_f, - }; - (id, values) - }) - .collect(); - - return Ok(PresigningData { - nonce, - ephemeral_scalar_share: SecretBox::new(Box::new(self.context.k)), - product_share: SecretBox::new(Box::new(P::scalar_from_signed(&self.chi))), - product_share_nonreduced: self.chi, - cap_k: self.all_cap_k[&my_id].clone(), - values, - }); - } - - // Construct the correctness proofs - - let sk = &self.context.aux_info.secret_aux.paillier_sk; - let pk = sk.public_key(); - - let aux = (&self.context.ssid_hash, &self.my_id()); - - // Aff-g proofs - - let mut aff_g_proofs = Vec::new(); - - let cap_gamma = self.context.gamma.mul_by_generator(); - - for id_j in self.other_ids() { - let r2_artefacts = &self.round2_artifacts[id_j]; - - for id_l in self.other_ids().iter().filter(|id| id != &id_j) { - let target_pk = &self.context.aux_info.public_aux[id_j].paillier_pk; - let rp = &self.context.aux_info.public_aux[id_l].rp_params; - - let beta = &self.round2_artifacts[id_j].beta; - let r = &self.round2_artifacts[id_j].r; - let s = &self.round2_artifacts[id_j].s; - - let p_aff_g = AffGProof::

::new( - rng, - &P::signed_from_scalar(&self.context.gamma).unwrap(), - beta, - s.to_mod(target_pk), - r.to_mod(pk), - target_pk, - pk, - &self.all_cap_k[id_j], - &r2_artefacts.cap_d, - &r2_artefacts.cap_f, - &cap_gamma, - rp, - &aux, - ); - - assert!(p_aff_g.verify( - target_pk, - pk, - &self.all_cap_k[id_j], - &r2_artefacts.cap_d, - &r2_artefacts.cap_f, - &cap_gamma, - rp, - &aux, - )); - - aff_g_proofs.push((id_j.clone(), id_l.clone(), p_aff_g)); - } - } - - // Mul proof - - let rho = RandomizerMod::random(rng, pk); - let cap_h = (&self.all_cap_g[self.my_id()] - * P::bounded_from_scalar(&self.context.k).unwrap()) - .mul_randomizer(&rho.retrieve()); - - let p_mul = MulProof::

::new( - rng, - &P::signed_from_scalar(&self.context.k).unwrap(), - &self.context.rho, - &rho, - pk, - &self.all_cap_k[self.my_id()], - &self.all_cap_g[self.my_id()], - &cap_h, - &aux, - ); - assert!(p_mul.verify( - pk, - &self.all_cap_k[self.my_id()], - &self.all_cap_g[self.my_id()], - &cap_h, - &aux - )); - - // Dec proof - - let mut ciphertext = cap_h.clone(); - - for id_j in self.other_ids() { - ciphertext = ciphertext - + self.cap_ds.get(id_j).unwrap() - + &self.round2_artifacts.get(id_j).unwrap().cap_f; - } - - let rho = ciphertext.derive_randomizer(sk); - - let mut dec_proofs = Vec::new(); - for id_j in self.other_ids() { - let p_dec = DecProof::

::new( - rng, - &self.delta, - &rho, - pk, - &scalar_delta, - &ciphertext, - &self.context.aux_info.public_aux[id_j].rp_params, - &aux, - ); - assert!(p_dec.verify( - pk, - &scalar_delta, - &ciphertext, - &self.context.aux_info.public_aux[id_j].rp_params, - &aux - )); - dec_proofs.push((id_j.clone(), p_dec)); - } - - Err(FinalizeError::Proof(PresigningProof { - aff_g_proofs, - dec_proofs, - mul_proof: p_mul, - })) - } -} - -#[cfg(test)] -mod tests { - use alloc::collections::BTreeSet; - - use rand_core::{OsRng, RngCore}; - use secrecy::ExposeSecret; - - use super::Round1; - use crate::cggmp21::{AuxInfo, KeyShare, TestParams}; - use crate::curve::Scalar; - use crate::rounds::{ - test_utils::{step_next_round, step_result, step_round, Id, Without}, - FirstRound, - }; - - #[test] - fn execute_presigning() { - let mut shared_randomness = [0u8; 32]; - OsRng.fill_bytes(&mut shared_randomness); - - let ids = BTreeSet::from([Id(0), Id(1), Id(2)]); - - let key_shares = KeyShare::new_centralized(&mut OsRng, &ids, None); - let aux_infos = AuxInfo::new_centralized(&mut OsRng, &ids); - - let r1 = ids - .iter() - .map(|id| { - let round = Round1::::new( - &mut OsRng, - &shared_randomness, - ids.clone().without(id), - *id, - (key_shares[id].clone(), aux_infos[id].clone()), - ) - .unwrap(); - (*id, round) - }) - .collect(); - - let r1a = step_round(&mut OsRng, r1).unwrap(); - let r2 = step_next_round(&mut OsRng, r1a).unwrap(); - let r2a = step_round(&mut OsRng, r2).unwrap(); - let r3 = step_next_round(&mut OsRng, r2a).unwrap(); - let r3a = step_round(&mut OsRng, r3).unwrap(); - let presigning_datas = step_result(&mut OsRng, r3a).unwrap(); - - // Check that each node ends up with the same nonce. - assert_eq!( - presigning_datas[&Id(0)].nonce, - presigning_datas[&Id(1)].nonce - ); - assert_eq!( - presigning_datas[&Id(0)].nonce, - presigning_datas[&Id(2)].nonce - ); - - // Check that the additive shares were constructed in a consistent way. - let k: Scalar = presigning_datas - .values() - .map(|data| data.ephemeral_scalar_share.expose_secret()) - .sum(); - let k_times_x: Scalar = presigning_datas - .values() - .map(|data| data.product_share.expose_secret()) - .sum(); - let x: Scalar = key_shares - .values() - .map(|share| share.secret_share.expose_secret()) - .sum(); - assert_eq!(x * k, k_times_x); - assert_eq!( - k.invert().unwrap().mul_by_generator().x_coordinate(), - presigning_datas[&Id(0)].nonce - ); - } -} diff --git a/synedrion/src/cggmp21/protocols/signing.rs b/synedrion/src/cggmp21/protocols/signing.rs deleted file mode 100644 index 35e704b4..00000000 --- a/synedrion/src/cggmp21/protocols/signing.rs +++ /dev/null @@ -1,474 +0,0 @@ -//! Signing using previously calculated presigning data, in the paper ECDSA Signing (Fig. 8). - -use alloc::collections::{BTreeMap, BTreeSet}; -use alloc::vec::Vec; -use core::fmt::Debug; -use core::marker::PhantomData; - -use rand_core::CryptoRngCore; -use secrecy::ExposeSecret; -use serde::{Deserialize, Serialize}; - -use super::super::{ - entities::AuxInfoPrecomputed, - sigma::{AffGProof, DecProof, MulStarProof}, - AuxInfo, KeyShare, PresigningData, SchemeParams, -}; -use crate::curve::{RecoverableSignature, Scalar}; -use crate::paillier::RandomizerMod; -use crate::rounds::{ - no_direct_messages, FinalizableToResult, FinalizeError, FirstRound, InitError, ProtocolResult, - Round, ToResult, -}; -use crate::tools::hashing::{Chain, FofHasher, HashOutput}; - -/// Possible results of the Signing protocol. -#[derive(Debug)] -pub struct SigningResult(PhantomData

, PhantomData); - -impl ProtocolResult for SigningResult { - type Success = RecoverableSignature; - type ProvableError = (); - type CorrectnessProof = SigningProof; -} - -/// A proof of a node's correct behavior for the Signing protocol. -#[allow(dead_code)] // TODO (#43): this can be removed when error verification is added -#[derive(Debug, Clone)] -pub struct SigningProof { - aff_g_proofs: Vec<(I, I, AffGProof

)>, - mul_star_proofs: Vec<(I, MulStarProof

)>, - dec_proofs: Vec<(I, DecProof

)>, -} - -pub struct Round1 { - ssid_hash: HashOutput, - r: Scalar, - sigma: Scalar, - inputs: Inputs, - aux_info: AuxInfoPrecomputed, - other_ids: BTreeSet, - my_id: I, -} - -#[derive(Clone)] -pub struct Inputs { - pub message: Scalar, - pub presigning: PresigningData, - pub key_share: KeyShare, - pub aux_info: AuxInfo, -} - -impl FirstRound for Round1 { - type Inputs = Inputs; - fn new( - _rng: &mut impl CryptoRngCore, - shared_randomness: &[u8], - other_ids: BTreeSet, - my_id: I, - inputs: Self::Inputs, - ) -> Result { - // This includes the info of $ssid$ in the paper - // (scheme parameters + public data from all shares - hashed in `share_set_id`), - // with the session randomness added. - let ssid_hash = FofHasher::new_with_dst(b"ShareSetID") - .chain_type::

() - .chain(&shared_randomness) - .chain(&inputs.key_share.public_shares) - .chain(&inputs.aux_info.public_aux) - .finalize(); - - let r = inputs.presigning.nonce; - let sigma = inputs.presigning.ephemeral_scalar_share.expose_secret() * &inputs.message - + r * inputs.presigning.product_share.expose_secret(); - Ok(Self { - ssid_hash, - r, - sigma, - aux_info: inputs.aux_info.clone().to_precomputed(), - inputs, - other_ids, - my_id, - }) - } -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct Round1Message { - sigma: Scalar, -} - -pub struct Round1Payload { - sigma: Scalar, -} - -impl Round for Round1 { - type Type = ToResult; - type Result = SigningResult; - const ROUND_NUM: u8 = 1; - const NEXT_ROUND_NUM: Option = None; - - fn other_ids(&self) -> &BTreeSet { - &self.other_ids - } - - fn my_id(&self) -> &I { - &self.my_id - } - - type BroadcastMessage = Round1Message; - type DirectMessage = (); - type Payload = Round1Payload; - type Artifact = (); - - fn make_broadcast_message( - &self, - _rng: &mut impl CryptoRngCore, - ) -> Option { - Some(Round1Message { sigma: self.sigma }) - } - - no_direct_messages!(I); - - fn verify_message( - &self, - _rng: &mut impl CryptoRngCore, - _from: &I, - broadcast_msg: Self::BroadcastMessage, - _direct_msg: Self::DirectMessage, - ) -> Result::ProvableError> { - Ok(Round1Payload { - sigma: broadcast_msg.sigma, - }) - } -} - -impl FinalizableToResult for Round1 { - fn finalize_to_result( - self, - rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - _artifacts: BTreeMap>::Artifact>, - ) -> Result<::Success, FinalizeError> { - let assembled_sigma = payloads - .values() - .map(|payload| payload.sigma) - .sum::() - + self.sigma; - - let signature = RecoverableSignature::from_scalars( - &self.r, - &assembled_sigma, - &self.inputs.key_share.verifying_key_as_point(), - &self.inputs.message, - ); - - if let Some(signature) = signature { - return Ok(signature); - } - - let my_id = self.my_id().clone(); - let aux = (&self.ssid_hash, &my_id); - - let sk = &self.aux_info.secret_aux.paillier_sk; - let pk = sk.public_key(); - - // Aff-g proofs - - let mut aff_g_proofs = Vec::new(); - - for id_j in self.other_ids() { - for id_l in self.other_ids().iter().filter(|id| id != &id_j) { - let target_pk = &self.aux_info.public_aux[id_j].paillier_pk; - let rp = &self.aux_info.public_aux[id_l].rp_params; - - let values = self.inputs.presigning.values.get(id_j).unwrap(); - - let p_aff_g = AffGProof::

::new( - rng, - &P::signed_from_scalar(self.inputs.key_share.secret_share.expose_secret()) - .unwrap(), - &values.hat_beta, - values.hat_s.to_mod(target_pk), - values.hat_r.to_mod(pk), - target_pk, - pk, - &values.cap_k, - &values.hat_cap_d, - &values.hat_cap_f, - &self.inputs.key_share.public_shares[&my_id], - rp, - &aux, - ); - - assert!(p_aff_g.verify( - target_pk, - pk, - &values.cap_k, - &values.hat_cap_d, - &values.hat_cap_f, - &self.inputs.key_share.public_shares[&my_id], - rp, - &aux, - )); - - aff_g_proofs.push((id_j.clone(), id_l.clone(), p_aff_g)); - } - } - - // mul* proofs - - let x = &self.inputs.key_share.secret_share; - let cap_x = self.inputs.key_share.public_shares[&my_id]; - - let rho = RandomizerMod::random(rng, pk); - let hat_cap_h = (&self.inputs.presigning.cap_k - * P::bounded_from_scalar(x.expose_secret()).unwrap()) - .mul_randomizer(&rho.retrieve()); - - let aux = (&self.ssid_hash, &my_id); - - let mut mul_star_proofs = Vec::new(); - - for id_l in self.other_ids() { - let p_mul = MulStarProof::

::new( - rng, - &P::signed_from_scalar(x.expose_secret()).unwrap(), - &rho, - pk, - &self.inputs.presigning.cap_k, - &hat_cap_h, - &cap_x, - &self.aux_info.public_aux[id_l].rp_params, - &aux, - ); - - assert!(p_mul.verify( - pk, - &self.inputs.presigning.cap_k, - &hat_cap_h, - &cap_x, - &self.aux_info.public_aux[id_l].rp_params, - &aux, - )); - - mul_star_proofs.push((id_l.clone(), p_mul)); - } - - // dec proofs - - let mut ciphertext = hat_cap_h.clone(); - for id_j in self.other_ids() { - let values = &self.inputs.presigning.values.get(id_j).unwrap(); - ciphertext = ciphertext + &values.hat_cap_d_received + &values.hat_cap_f; - } - - let r = self.inputs.presigning.nonce; - - let ciphertext = ciphertext * P::bounded_from_scalar(&r).unwrap() - + &self.inputs.presigning.cap_k * P::bounded_from_scalar(&self.inputs.message).unwrap(); - - let rho = ciphertext.derive_randomizer(sk); - // This is the same as `s_part` but if all the calculations were performed - // without reducing modulo curve order. - let s_part_nonreduced = P::signed_from_scalar( - self.inputs - .presigning - .ephemeral_scalar_share - .expose_secret(), - ) - .unwrap() - * P::signed_from_scalar(&self.inputs.message).unwrap() - + self.inputs.presigning.product_share_nonreduced * P::signed_from_scalar(&r).unwrap(); - - let mut dec_proofs = Vec::new(); - for id_l in self.other_ids() { - let p_dec = DecProof::

::new( - rng, - &s_part_nonreduced, - &rho, - pk, - &self.sigma, - &ciphertext, - &self.aux_info.public_aux[id_l].rp_params, - &aux, - ); - assert!(p_dec.verify( - pk, - &self.sigma, - &ciphertext, - &self.aux_info.public_aux[id_l].rp_params, - &aux, - )); - dec_proofs.push((id_l.clone(), p_dec)); - } - - let proof = SigningProof { - aff_g_proofs, - mul_star_proofs, - dec_proofs, - }; - - Err(FinalizeError::Proof(proof)) - } -} - -#[cfg(test)] -mod tests { - use alloc::collections::{BTreeMap, BTreeSet}; - - use k256::ecdsa::{signature::hazmat::PrehashVerifier, VerifyingKey}; - use rand_core::{OsRng, RngCore}; - - use super::{Inputs, Round1}; - use crate::cggmp21::{AuxInfo, KeyShare, PresigningData, TestParams}; - use crate::curve::Scalar; - use crate::rounds::FinalizeError; - use crate::rounds::{ - test_utils::{step_result, step_round, Id, Without}, - FinalizableToResult, FirstRound, - }; - use crate::{RecoverableSignature, SigningProof}; - - struct TestContext { - message: Scalar, - randomness: [u8; 32], - presigning_data: BTreeMap>, - key_shares: BTreeMap>, - aux_infos: BTreeMap>, - } - impl TestContext { - fn new(ids: &BTreeSet) -> Self { - let mut randomness = [0u8; 32]; - OsRng.fill_bytes(&mut randomness); - let key_shares = KeyShare::new_centralized(&mut OsRng, ids, None); - let aux_infos = AuxInfo::new_centralized(&mut OsRng, ids); - - let presigning_data = - PresigningData::new_centralized(&mut OsRng, &key_shares, &aux_infos); - - Self { - message: Scalar::random(&mut OsRng), - randomness, - presigning_data, - key_shares, - aux_infos, - } - } - } - - fn make_test_round1( - shared_randomness: &[u8], - ids: &BTreeSet, - id: &Id, - presigning_data: &BTreeMap>, - message: Scalar, - key_shares: &BTreeMap>, - aux_infos: &BTreeMap>, - ) -> Round1 { - Round1::::new( - &mut OsRng, - shared_randomness, - ids.clone().without(id), - *id, - Inputs { - presigning: presigning_data[id].clone(), - message, - key_share: key_shares[id].clone(), - aux_info: aux_infos[id].clone(), - }, - ) - .unwrap() - } - fn make_rounds(ctx: &TestContext, ids: &BTreeSet) -> BTreeMap> { - ids.iter() - .map(|id| { - let round = make_test_round1( - &ctx.randomness, - ids, - id, - &ctx.presigning_data, - ctx.message, - &ctx.key_shares, - &ctx.aux_infos, - ); - (*id, round) - }) - .collect() - } - - fn check_sig( - signature: &RecoverableSignature, - key_shares: &BTreeMap>, - message: &Scalar, - ) { - let (sig, rec_id) = signature.to_backend(); - let vkey = key_shares[&Id(0)].verifying_key().unwrap(); - - // Check that the signature can be verified - vkey.verify_prehash(&message.to_bytes(), &sig).unwrap(); - - // Check that the key can be recovered - let recovered_key = - VerifyingKey::recover_from_prehash(&message.to_bytes(), &sig, rec_id).unwrap(); - - assert_eq!(recovered_key, vkey); - } - - #[test] - fn execute_signing() { - let ids = BTreeSet::from([Id(0), Id(1), Id(2)]); - let ctx = TestContext::new(&ids); - - let r1 = make_rounds(&ctx, &ids); - - let r1a = step_round(&mut OsRng, r1).unwrap(); - - let signatures = step_result(&mut OsRng, r1a).unwrap(); - - for signature in signatures.values() { - check_sig(signature, &ctx.key_shares, &ctx.message); - } - } - - #[test] - fn cheating_signer() { - let ids = BTreeSet::from([Id(0), Id(1), Id(2)]); - let ctx = TestContext::new(&ids); - - let r1 = make_rounds(&ctx, &ids); - - let mut r1a = step_round(&mut OsRng, r1).unwrap(); - - // Manipulate second party's signature, causing finalize_to_result to fail - let assr = r1a.get_mut(&Id(1)).unwrap(); - assr.round.r = Scalar::random_nonzero(&mut OsRng); - - // First party is fine - match r1a.pop_first() { - Some((id, assr)) => { - assert!(id == Id(0)); - let finalized = - assr.round - .finalize_to_result(&mut OsRng, assr.payloads, assr.artifacts); - assert!(finalized.is_ok()); - check_sig(&finalized.unwrap(), &ctx.key_shares, &ctx.message); - } - None => unreachable!(), - } - // Second is bad - match r1a.pop_first() { - Some((id, assr)) => { - assert!(id == Id(1)); - let finalized = - assr.round - .finalize_to_result(&mut OsRng, assr.payloads, assr.artifacts); - assert!(finalized.is_err()); - assert!( - matches!(finalized, Err(err) if matches!(&err, FinalizeError::Proof(SigningProof{..}))) - ); - } - None => unreachable!(), - } - } -} diff --git a/synedrion/src/cggmp21/protocols/signing_malicious.rs b/synedrion/src/cggmp21/protocols/signing_malicious.rs new file mode 100644 index 00000000..e8b86816 --- /dev/null +++ b/synedrion/src/cggmp21/protocols/signing_malicious.rs @@ -0,0 +1,102 @@ +use alloc::collections::BTreeSet; +use core::marker::PhantomData; + +use manul::{ + combinators::misbehave::{Misbehaving, MisbehavingEntryPoint}, + dev::{run_sync, BinaryFormat, TestSessionParams, TestSigner, TestVerifier}, + protocol::{ + BoxedRound, Deserializer, EntryPoint, LocalError, NormalBroadcast, PartyId, + ProtocolMessagePart, RoundId, Serializer, + }, + session::signature::Keypair, +}; +use rand_core::{CryptoRngCore, OsRng, RngCore}; + +use super::{ + super::SchemeParams, + interactive_signing::{InteractiveSigning, Round4Message}, +}; +use crate::{ + cggmp21::{AuxInfo, KeyShare, TestParams}, + curve::Scalar, +}; + +#[derive(Debug, Clone, Copy)] +enum Behavior { + InvalidSigma, +} + +struct MaliciousSigningOverride

(PhantomData

); + +impl Misbehaving for MaliciousSigningOverride

{ + type EntryPoint = InteractiveSigning; + + fn modify_normal_broadcast( + rng: &mut impl CryptoRngCore, + round: &BoxedRound>::Protocol>, + behavior: &Behavior, + serializer: &Serializer, + _deserializer: &Deserializer, + normal_broadcast: NormalBroadcast, + ) -> Result { + let bc = if round.id() == RoundId::new(4) { + match behavior { + Behavior::InvalidSigma => { + let message = Round4Message { + sigma: Scalar::random(rng), + }; + NormalBroadcast::new(serializer, message)? + } + } + } else { + normal_broadcast + }; + Ok(bc) + } +} + +type MaliciousSigning = MisbehavingEntryPoint>; + +#[test] +fn execute_signing() { + let signers = (0..3).map(TestSigner::new).collect::>(); + let ids = signers + .iter() + .map(|signer| signer.verifying_key()) + .collect::>(); + let ids_set = BTreeSet::from_iter(ids.clone()); + + let key_shares = + KeyShare::::new_centralized(&mut OsRng, &ids_set, None); + let aux_infos = AuxInfo::new_centralized(&mut OsRng, &ids_set); + + let mut message = [0u8; 32]; + OsRng.fill_bytes(&mut message); + + let entry_points = signers + .into_iter() + .map(|signer| { + let id = signer.verifying_key(); + let signing = + InteractiveSigning::new(message, key_shares[&id].clone(), aux_infos[&id].clone()); + let behavior = if id == ids[0] { + Some(Behavior::InvalidSigma) + } else { + None + }; + let entry_points = MaliciousSigning::new(signing, behavior); + (signer, entry_points) + }) + .collect(); + + let mut reports = run_sync::<_, TestSessionParams>(&mut OsRng, entry_points) + .unwrap() + .reports; + + let _report0 = reports.remove(&ids[0]).unwrap(); + let report1 = reports.remove(&ids[1]).unwrap(); + let report2 = reports.remove(&ids[2]).unwrap(); + + assert!(!report1.provable_errors.is_empty()); + assert!(!report2.provable_errors.is_empty()); +} diff --git a/synedrion/src/cggmp21/sigma/aff_g.rs b/synedrion/src/cggmp21/sigma/aff_g.rs index 8ecb843e..8af90715 100644 --- a/synedrion/src/cggmp21/sigma/aff_g.rs +++ b/synedrion/src/cggmp21/sigma/aff_g.rs @@ -5,13 +5,15 @@ use secrecy::{ExposeSecret, SecretBox}; use serde::{Deserialize, Serialize}; use super::super::SchemeParams; -use crate::curve::Point; -use crate::paillier::{ - Ciphertext, CiphertextMod, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, - RPParamsMod, Randomizer, RandomizerMod, +use crate::{ + curve::Point, + paillier::{ + Ciphertext, CiphertextMod, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, + RPParamsMod, Randomizer, RandomizerMod, + }, + tools::hashing::{Chain, Hashable, XofHasher}, + uint::Signed, }; -use crate::tools::hashing::{Chain, Hashable, XofHasher}; -use crate::uint::Signed; const HASH_TAG: &[u8] = b"P_aff_g"; @@ -271,9 +273,11 @@ mod tests { use secrecy::{ExposeSecret, SecretBox}; use super::AffGProof; - use crate::cggmp21::{SchemeParams, TestParams}; - use crate::paillier::{CiphertextMod, RPParamsMod, RandomizerMod, SecretKeyPaillier}; - use crate::uint::Signed; + use crate::{ + cggmp21::{SchemeParams, TestParams}, + paillier::{CiphertextMod, RPParamsMod, RandomizerMod, SecretKeyPaillier}, + uint::Signed, + }; #[test] fn prove_and_verify() { diff --git a/synedrion/src/cggmp21/sigma/dec.rs b/synedrion/src/cggmp21/sigma/dec.rs index 9687aa43..bf182d89 100644 --- a/synedrion/src/cggmp21/sigma/dec.rs +++ b/synedrion/src/cggmp21/sigma/dec.rs @@ -4,13 +4,15 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::super::SchemeParams; -use crate::curve::Scalar; -use crate::paillier::{ - Ciphertext, CiphertextMod, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, - RPParamsMod, Randomizer, RandomizerMod, +use crate::{ + curve::Scalar, + paillier::{ + Ciphertext, CiphertextMod, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, + RPParamsMod, Randomizer, RandomizerMod, + }, + tools::hashing::{Chain, Hashable, XofHasher}, + uint::Signed, }; -use crate::tools::hashing::{Chain, Hashable, XofHasher}; -use crate::uint::Signed; const HASH_TAG: &[u8] = b"P_dec"; @@ -163,11 +165,11 @@ mod tests { use rand_core::OsRng; use super::DecProof; - use crate::cggmp21::{SchemeParams, TestParams}; - use crate::paillier::{ - CiphertextMod, PaillierParams, RPParamsMod, RandomizerMod, SecretKeyPaillier, + use crate::{ + cggmp21::{SchemeParams, TestParams}, + paillier::{CiphertextMod, PaillierParams, RPParamsMod, RandomizerMod, SecretKeyPaillier}, + uint::Signed, }; - use crate::uint::Signed; #[test] fn prove_and_verify() { diff --git a/synedrion/src/cggmp21/sigma/enc.rs b/synedrion/src/cggmp21/sigma/enc.rs index 93770ec8..bc9a841f 100644 --- a/synedrion/src/cggmp21/sigma/enc.rs +++ b/synedrion/src/cggmp21/sigma/enc.rs @@ -4,12 +4,14 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::super::SchemeParams; -use crate::paillier::{ - Ciphertext, CiphertextMod, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, - RPParamsMod, Randomizer, RandomizerMod, +use crate::{ + paillier::{ + Ciphertext, CiphertextMod, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, + RPParamsMod, Randomizer, RandomizerMod, + }, + tools::hashing::{Chain, Hashable, XofHasher}, + uint::Signed, }; -use crate::tools::hashing::{Chain, Hashable, XofHasher}; -use crate::uint::Signed; const HASH_TAG: &[u8] = b"P_enc"; @@ -148,9 +150,11 @@ mod tests { use rand_core::OsRng; use super::EncProof; - use crate::cggmp21::{SchemeParams, TestParams}; - use crate::paillier::{CiphertextMod, RPParamsMod, RandomizerMod, SecretKeyPaillier}; - use crate::uint::Signed; + use crate::{ + cggmp21::{SchemeParams, TestParams}, + paillier::{CiphertextMod, RPParamsMod, RandomizerMod, SecretKeyPaillier}, + uint::Signed, + }; #[test] fn prove_and_verify() { diff --git a/synedrion/src/cggmp21/sigma/fac.rs b/synedrion/src/cggmp21/sigma/fac.rs index 0cfb48c2..8f60cbdf 100644 --- a/synedrion/src/cggmp21/sigma/fac.rs +++ b/synedrion/src/cggmp21/sigma/fac.rs @@ -5,12 +5,14 @@ use secrecy::ExposeSecret; use serde::{Deserialize, Serialize}; use super::super::SchemeParams; -use crate::paillier::{ - PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, RPParamsMod, - SecretKeyPaillierPrecomputed, +use crate::{ + paillier::{ + PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, RPParamsMod, + SecretKeyPaillierPrecomputed, + }, + tools::hashing::{Chain, Hashable, XofHasher}, + uint::{Bounded, Integer, Signed}, }; -use crate::tools::hashing::{Chain, Hashable, XofHasher}; -use crate::uint::{Bounded, Integer, Signed}; const HASH_TAG: &[u8] = b"P_fac"; @@ -221,8 +223,10 @@ mod tests { use rand_core::OsRng; use super::FacProof; - use crate::cggmp21::{SchemeParams, TestParams}; - use crate::paillier::{RPParamsMod, SecretKeyPaillier}; + use crate::{ + cggmp21::{SchemeParams, TestParams}, + paillier::{RPParamsMod, SecretKeyPaillier}, + }; #[test] fn prove_and_verify() { diff --git a/synedrion/src/cggmp21/sigma/log_star.rs b/synedrion/src/cggmp21/sigma/log_star.rs index be8852ac..6b81c967 100644 --- a/synedrion/src/cggmp21/sigma/log_star.rs +++ b/synedrion/src/cggmp21/sigma/log_star.rs @@ -4,13 +4,15 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::super::SchemeParams; -use crate::curve::Point; -use crate::paillier::{ - Ciphertext, CiphertextMod, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, - RPParamsMod, Randomizer, RandomizerMod, +use crate::{ + curve::Point, + paillier::{ + Ciphertext, CiphertextMod, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, + RPParamsMod, Randomizer, RandomizerMod, + }, + tools::hashing::{Chain, Hashable, XofHasher}, + uint::Signed, }; -use crate::tools::hashing::{Chain, Hashable, XofHasher}; -use crate::uint::Signed; const HASH_TAG: &[u8] = b"P_log*"; @@ -169,10 +171,12 @@ mod tests { use rand_core::OsRng; use super::LogStarProof; - use crate::cggmp21::{SchemeParams, TestParams}; - use crate::curve::{Point, Scalar}; - use crate::paillier::{CiphertextMod, RPParamsMod, RandomizerMod, SecretKeyPaillier}; - use crate::uint::Signed; + use crate::{ + cggmp21::{SchemeParams, TestParams}, + curve::{Point, Scalar}, + paillier::{CiphertextMod, RPParamsMod, RandomizerMod, SecretKeyPaillier}, + uint::Signed, + }; #[test] fn prove_and_verify() { diff --git a/synedrion/src/cggmp21/sigma/mod_.rs b/synedrion/src/cggmp21/sigma/mod_.rs index 30da90d3..3e1c1f0c 100644 --- a/synedrion/src/cggmp21/sigma/mod_.rs +++ b/synedrion/src/cggmp21/sigma/mod_.rs @@ -2,14 +2,16 @@ use alloc::vec::Vec; +use crypto_bigint::{PowBoundedExp, Square}; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::super::SchemeParams; -use crate::paillier::{PaillierParams, PublicKeyPaillierPrecomputed, SecretKeyPaillierPrecomputed}; -use crate::tools::hashing::{uint_from_xof, Chain, Hashable, XofHasher}; -use crate::uint::{RandomPrimeWithRng, Retrieve, ToMontgomery}; -use crypto_bigint::{PowBoundedExp, Square}; +use crate::{ + paillier::{PaillierParams, PublicKeyPaillierPrecomputed, SecretKeyPaillierPrecomputed}, + tools::hashing::{uint_from_xof, Chain, Hashable, XofHasher}, + uint::{RandomPrimeWithRng, Retrieve, ToMontgomery}, +}; const HASH_TAG: &[u8] = b"P_mod"; @@ -191,8 +193,10 @@ mod tests { use rand_core::OsRng; use super::ModProof; - use crate::cggmp21::{SchemeParams, TestParams}; - use crate::paillier::SecretKeyPaillier; + use crate::{ + cggmp21::{SchemeParams, TestParams}, + paillier::SecretKeyPaillier, + }; #[test] fn prove_and_verify() { diff --git a/synedrion/src/cggmp21/sigma/mul.rs b/synedrion/src/cggmp21/sigma/mul.rs index 1f277bf1..7d73ee27 100644 --- a/synedrion/src/cggmp21/sigma/mul.rs +++ b/synedrion/src/cggmp21/sigma/mul.rs @@ -4,12 +4,14 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::super::SchemeParams; -use crate::paillier::{ - Ciphertext, CiphertextMod, PaillierParams, PublicKeyPaillierPrecomputed, Randomizer, - RandomizerMod, +use crate::{ + paillier::{ + Ciphertext, CiphertextMod, PaillierParams, PublicKeyPaillierPrecomputed, Randomizer, + RandomizerMod, + }, + tools::hashing::{Chain, Hashable, XofHasher}, + uint::{Bounded, Retrieve, Signed}, }; -use crate::tools::hashing::{Chain, Hashable, XofHasher}; -use crate::uint::{Bounded, Retrieve, Signed}; const HASH_TAG: &[u8] = b"P_mul"; @@ -155,9 +157,11 @@ mod tests { use rand_core::OsRng; use super::MulProof; - use crate::cggmp21::{SchemeParams, TestParams}; - use crate::paillier::{CiphertextMod, RandomizerMod, SecretKeyPaillier}; - use crate::uint::Signed; + use crate::{ + cggmp21::{SchemeParams, TestParams}, + paillier::{CiphertextMod, RandomizerMod, SecretKeyPaillier}, + uint::Signed, + }; #[test] fn prove_and_verify() { diff --git a/synedrion/src/cggmp21/sigma/mul_star.rs b/synedrion/src/cggmp21/sigma/mul_star.rs index 0c8ffb2a..493419fe 100644 --- a/synedrion/src/cggmp21/sigma/mul_star.rs +++ b/synedrion/src/cggmp21/sigma/mul_star.rs @@ -4,13 +4,15 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::super::SchemeParams; -use crate::curve::Point; -use crate::paillier::{ - Ciphertext, CiphertextMod, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, - RPParamsMod, Randomizer, RandomizerMod, +use crate::{ + curve::Point, + paillier::{ + Ciphertext, CiphertextMod, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, + RPParamsMod, Randomizer, RandomizerMod, + }, + tools::hashing::{Chain, Hashable, XofHasher}, + uint::Signed, }; -use crate::tools::hashing::{Chain, Hashable, XofHasher}; -use crate::uint::Signed; const HASH_TAG: &[u8] = b"P_mul*"; @@ -181,9 +183,11 @@ mod tests { use rand_core::OsRng; use super::MulStarProof; - use crate::cggmp21::{SchemeParams, TestParams}; - use crate::paillier::{CiphertextMod, RPParamsMod, RandomizerMod, SecretKeyPaillier}; - use crate::uint::Signed; + use crate::{ + cggmp21::{SchemeParams, TestParams}, + paillier::{CiphertextMod, RPParamsMod, RandomizerMod, SecretKeyPaillier}, + uint::Signed, + }; #[test] fn prove_and_verify() { diff --git a/synedrion/src/cggmp21/sigma/prm.rs b/synedrion/src/cggmp21/sigma/prm.rs index e086c1fd..7ad98fff 100644 --- a/synedrion/src/cggmp21/sigma/prm.rs +++ b/synedrion/src/cggmp21/sigma/prm.rs @@ -5,19 +5,21 @@ use alloc::{vec, vec::Vec}; +use crypto_bigint::PowBoundedExp; use digest::XofReader; use rand_core::CryptoRngCore; use secrecy::ExposeSecret; use serde::{Deserialize, Serialize}; use super::super::SchemeParams; -use crate::paillier::{PaillierParams, RPParamsMod, RPSecret, SecretKeyPaillierPrecomputed}; -use crate::tools::hashing::{Chain, Hashable, XofHasher}; -use crate::uint::{ - subtle::{Choice, ConditionallySelectable}, - Bounded, Retrieve, ToMontgomery, +use crate::{ + paillier::{PaillierParams, RPParamsMod, RPSecret, SecretKeyPaillierPrecomputed}, + tools::hashing::{Chain, Hashable, XofHasher}, + uint::{ + subtle::{Choice, ConditionallySelectable}, + Bounded, Retrieve, ToMontgomery, + }, }; -use crypto_bigint::PowBoundedExp; const HASH_TAG: &[u8] = b"P_prm"; @@ -151,8 +153,10 @@ mod tests { use rand_core::OsRng; use super::PrmProof; - use crate::cggmp21::{SchemeParams, TestParams}; - use crate::paillier::{RPParamsMod, RPSecret, SecretKeyPaillier}; + use crate::{ + cggmp21::{SchemeParams, TestParams}, + paillier::{RPParamsMod, RPSecret, SecretKeyPaillier}, + }; #[test] fn prove_and_verify() { diff --git a/synedrion/src/cggmp21/sigma/sch.rs b/synedrion/src/cggmp21/sigma/sch.rs index af6e88b9..505745f5 100644 --- a/synedrion/src/cggmp21/sigma/sch.rs +++ b/synedrion/src/cggmp21/sigma/sch.rs @@ -5,22 +5,28 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; +use zeroize::ZeroizeOnDrop; -use crate::curve::{Point, Scalar}; -use crate::tools::hashing::{Chain, FofHasher, Hashable}; +use crate::{ + curve::{Point, Scalar}, + tools::{ + hashing::{Chain, FofHasher, Hashable}, + HideDebug, + }, +}; const HASH_TAG: &[u8] = b"P_sch"; /// Secret data the proof is based on (~ signing key) -#[derive(Clone)] +#[derive(Debug, Clone, ZeroizeOnDrop)] pub(crate) struct SchSecret( /// `\alpha` - Scalar, + HideDebug, ); impl SchSecret { pub fn random(rng: &mut impl CryptoRngCore) -> Self { - Self(Scalar::random(rng)) + Self(Scalar::random(rng).into()) } } @@ -73,7 +79,7 @@ impl SchProof { aux: &impl Hashable, ) -> Self { let challenge = SchChallenge::new(cap_x, commitment, aux); - let proof = proof_secret.0 + challenge.0 * x; + let proof = *proof_secret.0 + challenge.0 * x; Self { challenge, proof } } diff --git a/synedrion/src/constructors.rs b/synedrion/src/constructors.rs deleted file mode 100644 index 7aa7a015..00000000 --- a/synedrion/src/constructors.rs +++ /dev/null @@ -1,202 +0,0 @@ -use alloc::collections::BTreeSet; -use core::fmt::Debug; - -use rand_core::CryptoRngCore; -use serde::{Deserialize, Serialize}; -use signature::{ - hazmat::{PrehashVerifier, RandomizedPrehashSigner}, - Keypair, -}; - -use crate::cggmp21::{ - aux_gen, interactive_signing, key_gen, key_init, key_refresh, AuxGenResult, AuxInfo, - InteractiveSigningResult, KeyGenResult, KeyInitResult, KeyRefreshResult, KeyShare, - SchemeParams, -}; -use crate::curve::Scalar; -use crate::sessions::{LocalError, Session, SessionId}; -use crate::www02::{key_resharing, KeyResharingInputs, KeyResharingResult}; - -/// Prehashed message to sign. -pub type PrehashedMessage = [u8; 32]; - -/// Creates the initial state for the joined KeyGen and KeyRefresh+Auxiliary protocols. -pub fn make_key_init_session( - rng: &mut impl CryptoRngCore, - session_id: SessionId, - signer: Signer, - verifiers: &BTreeSet, -) -> Result, Sig, Signer, Verifier>, LocalError> -where - Sig: Clone + Serialize + for<'de> Deserialize<'de> + PartialEq + Eq, - P: SchemeParams + 'static, - Signer: RandomizedPrehashSigner + Keypair, - Verifier: PrehashVerifier - + Debug - + Clone - + Ord - + Serialize - + for<'de> Deserialize<'de> - + Send - + Sync - + 'static, -{ - Session::new::>(rng, session_id, signer, verifiers, ()) -} - -/// Creates the initial state for the joined KeyGen and KeyRefresh+Auxiliary protocols. -pub fn make_key_gen_session( - rng: &mut impl CryptoRngCore, - session_id: SessionId, - signer: Signer, - verifiers: &BTreeSet, -) -> Result, Sig, Signer, Verifier>, LocalError> -where - Sig: Clone + Serialize + for<'de> Deserialize<'de> + PartialEq + Eq, - P: SchemeParams + 'static, - Signer: RandomizedPrehashSigner + Keypair, - Verifier: PrehashVerifier - + Debug - + Clone - + Ord - + Serialize - + for<'de> Deserialize<'de> - + Send - + Sync - + 'static, -{ - Session::new::>(rng, session_id, signer, verifiers, ()) -} - -/// Creates the initial state for the joined KeyGen and KeyRefresh+Auxiliary protocols. -pub fn make_aux_gen_session( - rng: &mut impl CryptoRngCore, - session_id: SessionId, - signer: Signer, - verifiers: &BTreeSet, -) -> Result, Sig, Signer, Verifier>, LocalError> -where - Sig: Clone + Serialize + for<'de> Deserialize<'de> + PartialEq + Eq, - P: SchemeParams + 'static, - Signer: RandomizedPrehashSigner + Keypair, - Verifier: PrehashVerifier - + Debug - + Clone - + Ord - + Serialize - + for<'de> Deserialize<'de> - + Send - + Sync - + 'static, -{ - Session::new::>(rng, session_id, signer, verifiers, ()) -} - -/// Creates the initial state for the KeyRefresh+Auxiliary protocol. -pub fn make_key_refresh_session( - rng: &mut impl CryptoRngCore, - session_id: SessionId, - signer: Signer, - verifiers: &BTreeSet, -) -> Result, Sig, Signer, Verifier>, LocalError> -where - Sig: Clone + Serialize + for<'de> Deserialize<'de> + PartialEq + Eq, - P: SchemeParams + 'static, - Signer: RandomizedPrehashSigner + Keypair, - Verifier: PrehashVerifier - + Debug - + Clone - + Ord - + Serialize - + for<'de> Deserialize<'de> - + Send - + Sync - + 'static, -{ - Session::new::>(rng, session_id, signer, verifiers, ()) -} - -/// Creates the initial state for the joined Presigning and Signing protocols. -pub fn make_interactive_signing_session( - rng: &mut impl CryptoRngCore, - session_id: SessionId, - signer: Signer, - verifiers: &BTreeSet, - key_share: &KeyShare, - aux_info: &AuxInfo, - prehashed_message: &PrehashedMessage, -) -> Result, Sig, Signer, Verifier>, LocalError> -where - Sig: Clone + Serialize + for<'de> Deserialize<'de> + PartialEq + Eq, - P: SchemeParams + 'static, - Signer: RandomizedPrehashSigner + Keypair, - Verifier: PrehashVerifier - + Debug - + Clone - + Ord - + Serialize - + for<'de> Deserialize<'de> - + Send - + Sync - + 'static, -{ - // TODO (#68): check that key share and aux data owner corresponds to the signer - if !verifiers.is_subset(&key_share.all_parties()) { - return Err(LocalError( - "The given verifiers are not a subset of the ones in the key share".into(), - )); - } - - let scalar_message = Scalar::from_reduced_bytes(prehashed_message); - - let inputs = interactive_signing::Inputs { - key_share: key_share.clone(), - aux_info: aux_info.clone(), - message: scalar_message, - }; - - Session::new::>( - rng, session_id, signer, verifiers, inputs, - ) -} - -/// Creates the initial state for the Key Resharing protocol. -pub fn make_key_resharing_session( - rng: &mut impl CryptoRngCore, - session_id: SessionId, - signer: Signer, - verifiers: &BTreeSet, - inputs: KeyResharingInputs, -) -> Result, Sig, Signer, Verifier>, LocalError> -where - Sig: Clone + Serialize + for<'de> Deserialize<'de> + PartialEq + Eq, - P: SchemeParams + 'static, - Signer: RandomizedPrehashSigner + Keypair, - Verifier: PrehashVerifier - + Debug - + Clone - + Ord - + Serialize - + for<'de> Deserialize<'de> - + Send - + Sync - + 'static, -{ - let verifiers_set = BTreeSet::from_iter(verifiers.iter().cloned()); - - if !inputs.new_holders.is_subset(&verifiers_set) { - return Err(LocalError( - "The new holders must be a subset of all parties".into(), - )); - } - - if let Some(new_holder) = inputs.new_holder.as_ref() { - if !new_holder.old_holders.is_subset(&verifiers_set) { - return Err(LocalError( - "The old holders must be a subset of all parties".into(), - )); - } - } - - Session::new::>(rng, session_id, signer, verifiers, inputs) -} diff --git a/synedrion/src/curve.rs b/synedrion/src/curve.rs index f9bae338..787389c3 100644 --- a/synedrion/src/curve.rs +++ b/synedrion/src/curve.rs @@ -6,7 +6,6 @@ mod arithmetic; mod ecdsa; -pub(crate) use arithmetic::ORDER; +pub(crate) use arithmetic::{Curve, Point, Scalar, ORDER}; pub use self::ecdsa::RecoverableSignature; -pub(crate) use arithmetic::{Curve, Point, Scalar}; diff --git a/synedrion/src/curve/arithmetic.rs b/synedrion/src/curve/arithmetic.rs index 9afa3bf5..1cb46689 100644 --- a/synedrion/src/curve/arithmetic.rs +++ b/synedrion/src/curve/arithmetic.rs @@ -1,11 +1,10 @@ -use alloc::format; -use alloc::string::String; -use alloc::{vec, vec::Vec}; -use core::default::Default; -use core::ops::{Add, Mul, Neg, Sub}; +use alloc::{format, string::String, vec, vec::Vec}; +use core::{ + default::Default, + ops::{Add, Mul, Neg, Sub}, +}; use digest::Digest; -use k256::elliptic_curve::group::ff::PrimeField; use k256::elliptic_curve::{ bigint::U256, // Note that this type is different from typenum::U256 generic_array::{typenum::marker_traits::Unsigned, GenericArray}, @@ -20,6 +19,7 @@ use k256::elliptic_curve::{ }; use k256::{ ecdsa::{SigningKey, VerifyingKey}, + elliptic_curve::group::ff::PrimeField, Secp256k1, }; use rand_core::CryptoRngCore; diff --git a/synedrion/src/lib.rs b/synedrion/src/lib.rs index 0175af59..e48caa64 100644 --- a/synedrion/src/lib.rs +++ b/synedrion/src/lib.rs @@ -16,16 +16,9 @@ extern crate alloc; -// Expose interal entities for benchmarks -#[cfg(feature = "bench-internals")] -pub mod bench_internals; - mod cggmp21; -mod constructors; mod curve; mod paillier; -mod rounds; -pub mod sessions; mod tools; mod uint; mod www02; @@ -37,18 +30,11 @@ pub use k256::ecdsa; pub use signature; pub use cggmp21::{ - AuxGenError, AuxGenResult, AuxInfo, InteractiveSigningError, InteractiveSigningProof, - InteractiveSigningResult, KeyGenError, KeyGenProof, KeyGenResult, KeyInitError, KeyInitResult, - KeyRefreshResult, KeyShare, KeyShareChange, PresigningError, PresigningProof, PresigningResult, - ProductionParams, SchemeParams, SigningProof, SigningResult, TestParams, -}; -pub use constructors::{ - make_aux_gen_session, make_interactive_signing_session, make_key_gen_session, - make_key_init_session, make_key_refresh_session, make_key_resharing_session, PrehashedMessage, + AuxGen, AuxGenProtocol, AuxInfo, InteractiveSigning, InteractiveSigningProtocol, KeyInit, + KeyInitProtocol, KeyRefresh, KeyRefreshProtocol, KeyShare, PrehashedMessage, ProductionParams, + SchemeParams, TestParams, }; pub use curve::RecoverableSignature; -pub use rounds::ProtocolResult; -pub use sessions::{FinalizeOutcome, MessageBundle, Session, SessionId}; pub use www02::{ - DeriveChildKey, KeyResharingInputs, KeyResharingResult, NewHolder, OldHolder, ThresholdKeyShare, + DeriveChildKey, KeyResharing, KeyResharingProtocol, NewHolder, OldHolder, ThresholdKeyShare, }; diff --git a/synedrion/src/paillier/encryption.rs b/synedrion/src/paillier/encryption.rs index 4fd58af3..7ca77226 100644 --- a/synedrion/src/paillier/encryption.rs +++ b/synedrion/src/paillier/encryption.rs @@ -1,5 +1,7 @@ -use core::marker::PhantomData; -use core::ops::{Add, Mul}; +use core::{ + marker::PhantomData, + ops::{Add, Mul}, +}; use crypto_bigint::{Invert, Monty, PowBoundedExp, ShrVartime, WrappingSub}; use rand_core::CryptoRngCore; @@ -7,8 +9,10 @@ use secrecy::ExposeSecret; use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; -use super::keys::{PublicKeyPaillierPrecomputed, SecretKeyPaillierPrecomputed}; -use super::params::PaillierParams; +use super::{ + keys::{PublicKeyPaillierPrecomputed, SecretKeyPaillierPrecomputed}, + params::PaillierParams, +}; use crate::uint::{ subtle::{Choice, ConditionallyNegatable, ConditionallySelectable}, Bounded, Exponentiable, HasWide, NonZero, Retrieve, Signed, ToMontgomery, @@ -419,17 +423,17 @@ impl Mul> for &CiphertextMod

{ #[cfg(test)] mod tests { + use crypto_bigint::{Encoding, Integer, ShrVartime, WrappingSub}; use rand_core::OsRng; - use super::super::params::PaillierTest; - use super::super::{PaillierParams, SecretKeyPaillier}; - use super::{CiphertextMod, RandomizerMod}; - + use super::{ + super::{params::PaillierTest, PaillierParams, SecretKeyPaillier}, + CiphertextMod, RandomizerMod, + }; use crate::uint::{ subtle::{ConditionallyNegatable, ConditionallySelectable}, HasWide, NonZero, RandomMod, Signed, }; - use crypto_bigint::{Encoding, Integer, ShrVartime, WrappingSub}; fn mul_mod(lhs: &T, rhs: &Signed, modulus: &NonZero) -> T where diff --git a/synedrion/src/paillier/keys.rs b/synedrion/src/paillier/keys.rs index a3860269..a1575a55 100644 --- a/synedrion/src/paillier/keys.rs +++ b/synedrion/src/paillier/keys.rs @@ -1,7 +1,9 @@ use alloc::boxed::Box; use core::fmt::Debug; +use crypto_bigint::{InvMod, Monty, Odd, ShrVartime, Square, WrappingAdd, WrappingSub}; use rand_core::CryptoRngCore; +use secrecy::{ExposeSecret, SecretBox}; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; @@ -11,8 +13,6 @@ use crate::uint::{ Bounded, CheckedAdd, CheckedSub, HasWide, Integer, Invert, NonZero, PowBoundedExp, RandomMod, RandomPrimeWithRng, Retrieve, Signed, ToMontgomery, }; -use crypto_bigint::{InvMod, Monty, Odd, ShrVartime, Square, WrappingAdd, WrappingSub}; -use secrecy::{ExposeSecret, SecretBox}; #[derive(Debug, Deserialize)] pub(crate) struct SecretKeyPaillier { @@ -150,7 +150,7 @@ impl SecretKeyPaillier

{ } } -#[derive(Clone)] +#[derive(Debug, Clone)] pub(crate) struct SecretKeyPaillierPrecomputed { sk: SecretKeyPaillier

, totient: SecretBox>, @@ -427,8 +427,7 @@ mod tests { use serde::Serialize; use serde_assert::Token; - use super::super::params::PaillierTest; - use super::SecretKeyPaillier; + use super::{super::params::PaillierTest, SecretKeyPaillier}; #[test] fn basics() { diff --git a/synedrion/src/paillier/params.rs b/synedrion/src/paillier/params.rs index f2d290bf..2e083348 100644 --- a/synedrion/src/paillier/params.rs +++ b/synedrion/src/paillier/params.rs @@ -7,14 +7,13 @@ use crypto_primes::RandomPrimeWithRng; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; +#[cfg(test)] +use crate::uint::{U1024Mod, U2048Mod, U512Mod, U1024, U2048, U4096, U512}; use crate::{ tools::hashing::Hashable, uint::{Exponentiable, HasWide, ToMontgomery}, }; -#[cfg(test)] -use crate::uint::{U1024Mod, U2048Mod, U512Mod, U1024, U2048, U4096, U512}; - pub trait PaillierParams: core::fmt::Debug + PartialEq + Eq + Clone + Send + Sync { /// The size of one of the pair of RSA primes. const PRIME_BITS: usize; diff --git a/synedrion/src/paillier/ring_pedersen.rs b/synedrion/src/paillier/ring_pedersen.rs index 708d34f1..4796f318 100644 --- a/synedrion/src/paillier/ring_pedersen.rs +++ b/synedrion/src/paillier/ring_pedersen.rs @@ -1,12 +1,12 @@ use core::ops::Mul; +use crypto_bigint::{PowBoundedExp, Square}; use rand_core::CryptoRngCore; use secrecy::{ExposeSecret, SecretBox}; use serde::{Deserialize, Serialize}; use super::{PaillierParams, PublicKeyPaillierPrecomputed, SecretKeyPaillierPrecomputed}; use crate::uint::{Bounded, Exponentiable, Retrieve, Signed, ToMontgomery}; -use crypto_bigint::{PowBoundedExp, Square}; pub(crate) struct RPSecret(Bounded); diff --git a/synedrion/src/rounds.rs b/synedrion/src/rounds.rs deleted file mode 100644 index 0b729471..00000000 --- a/synedrion/src/rounds.rs +++ /dev/null @@ -1,14 +0,0 @@ -mod generic; -mod wrappers; - -#[cfg(any(test, feature = "bench-internals"))] -pub(crate) mod test_utils; - -pub use generic::ProtocolResult; -pub(crate) use generic::{ - no_broadcast_messages, no_direct_messages, FinalizableToNextRound, FinalizableToResult, - FinalizationRequirement, FinalizeError, FirstRound, InitError, Round, ToNextRound, ToResult, -}; -pub(crate) use wrappers::{ - wrap_finalize_error, CorrectnessProofWrapper, ProvableErrorWrapper, RoundWrapper, WrappedRound, -}; diff --git a/synedrion/src/rounds/generic.rs b/synedrion/src/rounds/generic.rs deleted file mode 100644 index 59a6575f..00000000 --- a/synedrion/src/rounds/generic.rs +++ /dev/null @@ -1,203 +0,0 @@ -use alloc::collections::{BTreeMap, BTreeSet}; -use alloc::string::String; -use core::fmt::Debug; - -use displaydoc::Display; -use rand_core::CryptoRngCore; -use serde::{Deserialize, Serialize}; - -/// A round that sends out direct messages. -pub(crate) trait Round { - type Type: FinalizableType; - type Result: ProtocolResult; - const ROUND_NUM: u8; - // TODO (#78): find a way to derive it from `ROUND_NUM` - const NEXT_ROUND_NUM: Option; - - fn other_ids(&self) -> &BTreeSet; - fn my_id(&self) -> &I; - - /// The part of the message sent directly to nodes, and can be different for each node. - type DirectMessage: Serialize + for<'de> Deserialize<'de>; - - /// The part of the message that is the same for each destination node. - type BroadcastMessage: Serialize + for<'de> Deserialize<'de>; - - /// Whether all the nodes receiving the broadcast should make sure they got the same message. - const REQUIRES_ECHO: bool = false; - - /// The processed message from another node, to be collected to finalize the round. - type Payload; - - /// Data created when creating a message, to be preserved until the finalization stage. - type Artifact; - - /// The indices of the parties that should receive the messages. - // Assuming these destinations are for both broadcast and direct messages; - // the broadcasts are only separated to allow optimizations (create once, sign once) - // and support echo-broadcasting. - fn message_destinations(&self) -> &BTreeSet { - self.other_ids() - } - - fn expecting_messages_from(&self) -> &BTreeSet { - self.other_ids() - } - - /// Creates the direct message for the given party. - fn make_direct_message( - &self, - rng: &mut impl CryptoRngCore, - destination: &I, - ) -> (Self::DirectMessage, Self::Artifact); - - /// Creates the broadcast message. - /// - /// Returns ``None`` if the node does not send messages this round - /// (that is, [`message_destinations`] returns an empty list). - fn make_broadcast_message( - &self, - rng: &mut impl CryptoRngCore, - ) -> Option; - - /// Processes a direct messsage received from the party `from`. - // Note that since we assume broadcast and direct messages have the same list of destinations, - // if `BroadcastMessage` is not `()` there will be a serialized broadcast - // in the received message, from which we can construct `broadcast_msg`. - fn verify_message( - &self, - rng: &mut impl CryptoRngCore, - from: &I, - broadcast_msg: Self::BroadcastMessage, - direct_msg: Self::DirectMessage, - ) -> Result::ProvableError>; - - fn finalization_requirement() -> FinalizationRequirement { - FinalizationRequirement::All - } - - fn can_finalize(&self, received: &BTreeSet) -> bool { - match Self::finalization_requirement() { - FinalizationRequirement::All => self.other_ids().is_subset(received), - FinalizationRequirement::Custom => panic!("`can_finalize` must be implemented"), - } - } - - fn missing_messages(&self, received: &BTreeSet) -> BTreeSet { - match Self::finalization_requirement() { - FinalizationRequirement::All => { - self.other_ids().difference(received).cloned().collect() - } - FinalizationRequirement::Custom => panic!("`missing_messages` must be implemented"), - } - } -} - -/// Typed outcomes of a protocol, specific for each protocol -/// (in addition to non-specific errors common for all protocols). -pub trait ProtocolResult: Debug { - /// The result obtained on successful termination of the protocol. - type Success; - /// A collection of data which, in combination with the messages received, - /// can be used to prove malicious behavior of a remote node. - type ProvableError: Debug; - /// A collection of data which, in combination with the messages received, - /// can be used to prove correct behavior of this node. - /// - /// That is, on errors where the culprit cannot be immediately identified, - /// each node will have to provide the correctness proof for itself. - type CorrectnessProof: Debug; -} - -// This trait is used to fix the possible options for `Round::Type`. -// Techincally it is not used besides that, so clippy complains. -#[allow(dead_code)] -pub trait FinalizableType {} - -pub struct ToResult; - -impl FinalizableType for ToResult {} - -pub struct ToNextRound; - -impl FinalizableType for ToNextRound {} - -#[allow(clippy::enum_variant_names)] -pub(crate) enum FinalizationRequirement { - All, - Custom, -} - -pub(crate) trait FinalizableToResult: Round { - fn finalize_to_result( - self, - rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - artifacts: BTreeMap>::Artifact>, - ) -> Result<::Success, FinalizeError>; -} - -pub(crate) trait FinalizableToNextRound: - Round -{ - type NextRound: Round; - fn finalize_to_next_round( - self, - rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - artifacts: BTreeMap>::Artifact>, - ) -> Result>; -} - -#[derive(Debug)] -pub enum FinalizeError { - Proof(Res::CorrectnessProof), - /// Returned when there is an error chaining the start of another protocol - /// on the finalization of the previous one. - Init(InitError), -} - -/// An error that can occur when initializing a protocol. -#[derive(Debug, Clone, Display)] -#[displaydoc("Error when initializing a protocol ({0})")] -pub struct InitError(pub(crate) String); - -pub(crate) trait FirstRound: Round + Sized { - type Inputs; - fn new( - rng: &mut impl CryptoRngCore, - shared_randomness: &[u8], - other_ids: BTreeSet, - my_id: I, - inputs: Self::Inputs, - ) -> Result; -} - -// These will be possible to do via trait specialization when it becomes stable. - -macro_rules! no_broadcast_messages { - () => { - fn make_broadcast_message( - &self, - _rng: &mut impl CryptoRngCore, - ) -> Option { - Some(()) - } - }; -} - -pub(crate) use no_broadcast_messages; - -macro_rules! no_direct_messages { - ($id_type: ty) => { - fn make_direct_message( - &self, - _rng: &mut impl CryptoRngCore, - _destination: &$id_type, - ) -> (Self::DirectMessage, Self::Artifact) { - ((), ()) - } - }; -} - -pub(crate) use no_direct_messages; diff --git a/synedrion/src/rounds/test_utils.rs b/synedrion/src/rounds/test_utils.rs deleted file mode 100644 index fe5c405b..00000000 --- a/synedrion/src/rounds/test_utils.rs +++ /dev/null @@ -1,169 +0,0 @@ -use alloc::collections::{BTreeMap, BTreeSet}; -use alloc::format; -use alloc::string::String; -use alloc::vec::Vec; -use core::fmt::Debug; - -use displaydoc::Display; -use rand_core::CryptoRngCore; -use serde::Serialize; - -use super::generic::{FinalizableToNextRound, FinalizableToResult, ProtocolResult, Round}; -use super::FinalizeError; - -/// A simple identity type for tests. -#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize)] -pub(crate) struct Id(pub(crate) u32); - -#[derive(Debug, Display)] -pub(crate) enum StepError { - /// Error when finalizing the round (missing messages). - AccumFinalize, - /// Error when verifying a received message. - #[displaydoc("Error when verifying a received message ({0})")] - Receive(String), - /// A party attempted to send a message to itself. - #[displaydoc("A party {0:?} attempted to send a message to itself")] - MessageToItself(I), -} - -pub(crate) struct AssembledRound> { - pub round: R, - pub payloads: BTreeMap>::Payload>, - pub artifacts: BTreeMap>::Artifact>, -} - -pub(crate) fn step_round( - rng: &mut impl CryptoRngCore, - rounds: BTreeMap, -) -> Result>, StepError> -where - R: Round, - >::BroadcastMessage: Clone, - I: Debug + Clone + Ord + PartialEq, -{ - // Collect outgoing messages - - let mut artifact_accums = rounds - .keys() - .cloned() - .map(|id| (id, BTreeMap::new())) - .collect::>(); - - // `to, from, message` - let mut messages = Vec::<( - I, - I, - ( - >::BroadcastMessage, - >::DirectMessage, - ), - )>::new(); - - for (from, round) in rounds.iter() { - let destinations = round.message_destinations(); - let broadcast = round.make_broadcast_message(rng); - - for to in destinations { - if to == from { - return Err(StepError::MessageToItself(from.clone())); - } - - let (direct, artifact) = round.make_direct_message(rng, to); - // Can unwrap here since the destinations list is not empty - messages.push(( - to.clone(), - from.clone(), - (broadcast.clone().unwrap(), direct), - )); - artifact_accums - .get_mut(from) - .unwrap() - .insert(to.clone(), artifact); - } - } - - // Deliver messages - - let mut payload_accums = rounds - .keys() - .cloned() - .map(|id| (id, BTreeMap::new())) - .collect::>(); - for (to, from, (broadcast, direct)) in messages.into_iter() { - let round = &rounds[&to]; - let payload = round - .verify_message(rng, &from, broadcast, direct) - .map_err(|err| StepError::Receive(format!("{:?}", err)))?; - payload_accums.get_mut(&to).unwrap().insert(from, payload); - } - - // Assemble - - let mut assembled = BTreeMap::new(); - for (id, round) in rounds.into_iter() { - let (_, payloads) = payload_accums.remove_entry(&id).unwrap(); - let (_, artifacts) = artifact_accums.remove_entry(&id).unwrap(); - let received = payloads.keys().cloned().collect::>(); - if !round.can_finalize(&received) { - return Err(StepError::AccumFinalize); - }; - - assembled.insert( - id, - AssembledRound { - round, - payloads, - artifacts, - }, - ); - } - Ok(assembled) -} - -pub(crate) fn step_next_round>( - rng: &mut impl CryptoRngCore, - assembled_rounds: BTreeMap>, -) -> Result, FinalizeError> { - let mut results = BTreeMap::new(); - for (id, assembled_round) in assembled_rounds.into_iter() { - let next_round = assembled_round.round.finalize_to_next_round( - rng, - assembled_round.payloads, - assembled_round.artifacts, - )?; - results.insert(id, next_round); - } - Ok(results) -} - -#[allow(clippy::type_complexity)] -pub(crate) fn step_result>( - rng: &mut impl CryptoRngCore, - assembled_rounds: BTreeMap>, -) -> Result::Success>, FinalizeError> { - let mut results = BTreeMap::new(); - for (id, assembled_round) in assembled_rounds.into_iter() { - let next_round = assembled_round.round.finalize_to_result( - rng, - assembled_round.payloads, - assembled_round.artifacts, - )?; - results.insert(id, next_round); - } - Ok(results) -} - -pub(crate) trait Without { - type Item; - fn without(self, item: &Self::Item) -> Self; -} - -impl Without for BTreeSet { - type Item = T; - fn without(self, item: &Self::Item) -> Self { - let mut set = self; - set.remove(item); - set - } -} diff --git a/synedrion/src/rounds/wrappers.rs b/synedrion/src/rounds/wrappers.rs deleted file mode 100644 index d5ff5ee1..00000000 --- a/synedrion/src/rounds/wrappers.rs +++ /dev/null @@ -1,90 +0,0 @@ -use alloc::collections::BTreeSet; - -use rand_core::CryptoRngCore; - -use super::generic::{ - FinalizableType, FinalizationRequirement, FinalizeError, ProtocolResult, Round, -}; - -pub(crate) trait ProvableErrorWrapper: ProtocolResult { - fn wrap_error(error: Res::ProvableError) -> Self::ProvableError; -} - -pub(crate) trait CorrectnessProofWrapper: ProtocolResult { - fn wrap_proof(proof: Res::CorrectnessProof) -> Self::CorrectnessProof; -} - -pub(crate) fn wrap_finalize_error>( - error: FinalizeError, -) -> FinalizeError { - match error { - FinalizeError::Init(msg) => FinalizeError::Init(msg), - FinalizeError::Proof(proof) => FinalizeError::Proof(Res::wrap_proof(proof)), - } -} - -pub(crate) trait RoundWrapper { - type Result: ProtocolResult + ProvableErrorWrapper<>::Result>; - type Type: FinalizableType; - type InnerRound: Round; - const ROUND_NUM: u8; - const NEXT_ROUND_NUM: Option; - fn inner_round(&self) -> &Self::InnerRound; -} - -pub(crate) trait WrappedRound {} - -impl + WrappedRound> Round for T { - type Type = T::Type; - type Result = T::Result; - const ROUND_NUM: u8 = T::ROUND_NUM; - const NEXT_ROUND_NUM: Option = T::NEXT_ROUND_NUM; - - fn other_ids(&self) -> &BTreeSet { - self.inner_round().other_ids() - } - - fn my_id(&self) -> &I { - self.inner_round().my_id() - } - - const REQUIRES_ECHO: bool = T::InnerRound::REQUIRES_ECHO; - type BroadcastMessage = >::BroadcastMessage; - type DirectMessage = >::DirectMessage; - type Payload = >::Payload; - type Artifact = >::Artifact; - - fn message_destinations(&self) -> &BTreeSet { - self.inner_round().message_destinations() - } - - fn make_broadcast_message( - &self, - rng: &mut impl CryptoRngCore, - ) -> Option { - self.inner_round().make_broadcast_message(rng) - } - - fn make_direct_message( - &self, - rng: &mut impl CryptoRngCore, - destination: &I, - ) -> (Self::DirectMessage, Self::Artifact) { - self.inner_round().make_direct_message(rng, destination) - } - - fn verify_message( - &self, - rng: &mut impl CryptoRngCore, - from: &I, - broadcast_msg: Self::BroadcastMessage, - direct_msg: Self::DirectMessage, - ) -> Result::ProvableError> { - self.inner_round() - .verify_message(rng, from, broadcast_msg, direct_msg) - .map_err(Self::Result::wrap_error) - } - fn finalization_requirement() -> FinalizationRequirement { - T::InnerRound::finalization_requirement() - } -} diff --git a/synedrion/src/sessions.rs b/synedrion/src/sessions.rs deleted file mode 100644 index 44f0eeb3..00000000 --- a/synedrion/src/sessions.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Mutable wrappers around the protocols for easier handling. - -mod echo; -mod error; -mod message_bundle; -mod session; -mod signed_message; -mod type_erased; - -pub use echo::EchoError; -pub use error::{Error, LocalError, ProvableError, RemoteError, RemoteErrorEnum}; -pub use message_bundle::MessageBundle; -pub use session::{ - Artifact, FinalizeOutcome, PreprocessedMessage, ProcessedMessage, RoundAccumulator, Session, -}; -pub use signed_message::SessionId; diff --git a/synedrion/src/sessions/echo.rs b/synedrion/src/sessions/echo.rs deleted file mode 100644 index 8190c701..00000000 --- a/synedrion/src/sessions/echo.rs +++ /dev/null @@ -1,143 +0,0 @@ -use alloc::boxed::Box; -use alloc::collections::{BTreeMap, BTreeSet}; -use alloc::string::{String, ToString}; -use alloc::vec::Vec; - -use serde::{Deserialize, Serialize}; - -use super::error::LocalError; -use super::signed_message::{SignedMessage, VerifiedMessage}; -use super::type_erased::{deserialize_message, serialize_message}; - -#[derive(Clone)] -pub(crate) struct EchoRound { - destinations: BTreeSet, - broadcasts: BTreeMap>, -} - -#[derive(Serialize, Deserialize)] -struct Message { - broadcasts: Vec<(I, SignedMessage)>, -} - -/// Errors that can occur during an echo round. -#[derive(Debug, Clone)] -pub enum EchoError { - /// Cannot deserialize the message. - CannotDeserialize(String), - /// Unexpected number of broadcasts in the message. - UnexpectedNumberOfBroadcasts, - /// A broadcast from one of the parties is missing. - MissingBroadcast, - /// The broadcasts received during the echo round - /// do not match the ones received previously. - ConflictingBroadcasts, -} - -impl EchoRound -where - I: Clone + Ord + PartialEq + Serialize + for<'de> Deserialize<'de>, - Sig: Clone + Serialize + for<'de> Deserialize<'de> + PartialEq + Eq, -{ - pub fn new(broadcasts: BTreeMap>) -> Self { - let destinations = broadcasts.keys().cloned().collect(); - Self { - broadcasts, - destinations, - } - } - - pub fn message_destinations(&self) -> &BTreeSet { - &self.destinations - } - - pub fn expecting_messages_from(&self) -> &BTreeSet { - &self.destinations - } - - pub fn make_broadcast(&self) -> Box<[u8]> { - let message = Message { - broadcasts: self - .broadcasts - .clone() - .into_iter() - .map(|(idx, msg)| (idx, msg.into_unverified())) - .collect(), - }; - serialize_message(&message).unwrap() - } - - pub fn verify_broadcast(&self, from: &I, payload: &[u8]) -> Result<(), EchoError> { - // TODO (#68): check that the direct payload is empty? - let message: Message = deserialize_message(payload) - .map_err(|err| EchoError::CannotDeserialize(err.to_string()))?; - - // TODO (#68): check that there are no repeating indices, and the indices are in range. - let bc_map = message.broadcasts.into_iter().collect::>(); - - if bc_map.len() != self.broadcasts.len() { - return Err(EchoError::UnexpectedNumberOfBroadcasts); - } - - for (id, broadcast) in self.broadcasts.iter() { - // The party `from` won't send us its own broadcast the second time. - // It gives no additional assurance. - if id == from { - continue; - } - - let echoed_bc = bc_map.get(id).ok_or(EchoError::MissingBroadcast)?; - - if !broadcast.as_unverified().is_same_as(echoed_bc) { - return Err(EchoError::ConflictingBroadcasts); - } - } - - Ok(()) - } - - pub fn missing_messages(&self, accum: &EchoAccum) -> BTreeSet { - self.expecting_messages_from() - .difference(&accum.received_messages) - .cloned() - .collect() - } - - pub fn can_finalize(&self, accum: &EchoAccum) -> bool { - &accum.received_messages == self.expecting_messages_from() - } - - pub fn finalize(self, accum: EchoAccum) -> Result<(), LocalError> { - if &accum.received_messages == self.expecting_messages_from() { - Ok(()) - } else { - Err(LocalError( - "Not enough messages to finalize the echo round".into(), - )) - } - } -} - -pub(crate) struct EchoAccum { - received_messages: BTreeSet, -} - -impl EchoAccum { - pub fn new() -> Self { - Self { - received_messages: BTreeSet::new(), - } - } - - pub fn contains(&self, from: &I) -> bool { - self.received_messages.contains(from) - } - - pub fn add_echo_received(&mut self, from: &I) -> Option<()> { - if self.received_messages.insert(from.clone()) { - Some(()) - } else { - None - } - } -} diff --git a/synedrion/src/sessions/error.rs b/synedrion/src/sessions/error.rs deleted file mode 100644 index 0de2f6a0..00000000 --- a/synedrion/src/sessions/error.rs +++ /dev/null @@ -1,77 +0,0 @@ -use alloc::string::String; - -use displaydoc::Display; - -use super::echo::EchoError; -use crate::rounds::ProtocolResult; - -/// Possible errors returned by session methods. -#[derive(Debug)] -pub enum Error { - /// Indicates an error on this party's side. - Local(LocalError), - /// A provable fault of another party. - // TODO (#43): attach the party's messages up to this round - // for this to be verifiable by a third party - Provable { - /// The index of the failed party. - party: Verifier, - /// The error that occurred. - error: ProvableError, - }, - /// An error occurred, but the fault of a specific party cannot be immediately proven. - /// This structure instead proves that this party performed its calculations correctly. - Proof { - // TODO (#43): attach all received messages from other parties. - // What else do we need to verify it? - /// The proof of correctness. - proof: Res::CorrectnessProof, - }, - /// An error caused by remote party, unprovable at this level. - /// - /// This error may be eventually provable if there are some external guarantees - /// provided by the communication channel. - Remote(RemoteError), -} - -/// An error on this party's side. -/// Can be caused by an incorrect usage, a bug in the implementation, or some environment error. -#[derive(Clone, Debug, Display)] -#[displaydoc("Local error: {0}")] -pub struct LocalError(pub(crate) String); - -/// An unprovable fault of another party. -#[derive(Clone, Debug, Display)] -pub struct RemoteError { - /// The offending party. - pub party: Verifier, - /// The error type - pub error: RemoteErrorEnum, -} - -/// Types of unprovable faults of another party. -#[derive(Clone, Debug, Display)] -pub enum RemoteErrorEnum { - /// Session ID does not match the one provided to the local session constructor. - UnexpectedSessionId, - /// A message is intended for an unexpected round (not the current one or the next one). - OutOfOrderMessage, - /// A message from this party has already been received. - DuplicateMessage, - /// The message signature does not match its contents: {0}. - InvalidSignature(String), - /// The message has invalid contents, but the fault is unprovable: {0}. - // (e.g. correctly signed messages belonging to a different session, possibly a replay attack) - InvalidContents(String), -} - -/// A provable fault of another party. -#[derive(Debug)] -pub enum ProvableError { - /// A protocol error. - Protocol(Res::ProvableError), - /// Failed to deserialize the message. - CannotDeserialize(String), - /// Echo round failed. - Echo(EchoError), -} diff --git a/synedrion/src/sessions/message_bundle.rs b/synedrion/src/sessions/message_bundle.rs deleted file mode 100644 index 9844ec4c..00000000 --- a/synedrion/src/sessions/message_bundle.rs +++ /dev/null @@ -1,173 +0,0 @@ -use alloc::string::String; - -use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; -use signature::hazmat::PrehashVerifier; - -use super::error::LocalError; -use super::signed_message::{MessageType, SessionId, SignedMessage, VerifiedMessage}; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub(crate) enum MessageBundleEnum { - Broadcast(M), - Direct(M), - Both { broadcast: M, direct: M }, - Echo(M), -} - -/// Combined message from a single round -#[derive(Clone, Debug)] -pub struct MessageBundle { - session_id: SessionId, - round: u8, - is_echo: bool, - bundle: MessageBundleEnum>, -} - -impl Serialize for MessageBundle { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.bundle.serialize(serializer) - } -} - -impl<'de, Sig: Deserialize<'de>> Deserialize<'de> for MessageBundle { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let unchecked = MessageBundleEnum::deserialize(deserializer)?; - MessageBundle::try_from(unchecked).map_err(D::Error::custom) - } -} - -impl TryFrom>> for MessageBundle { - type Error = LocalError; - fn try_from(unchecked: MessageBundleEnum>) -> Result { - let (session_id, round, is_echo) = match &unchecked { - MessageBundleEnum::Broadcast(msg) => { - if msg.message_type() != MessageType::Broadcast { - return Err(LocalError( - "Invalid message type of the broadcast field".into(), - )); - } - (msg.session_id(), msg.round(), false) - } - MessageBundleEnum::Direct(msg) => { - if msg.message_type() != MessageType::Direct { - return Err(LocalError( - "Invalid message type of the direct field".into(), - )); - } - (msg.session_id(), msg.round(), false) - } - MessageBundleEnum::Echo(msg) => { - if msg.message_type() != MessageType::Echo { - return Err(LocalError("Invalid message type of the echo field".into())); - } - (msg.session_id(), msg.round(), true) - } - MessageBundleEnum::Both { broadcast, direct } => { - if broadcast.session_id() != direct.session_id() { - return Err(LocalError("Mismatched session IDs".into())); - } - if broadcast.round() != direct.round() { - return Err(LocalError("Mismatched round numbers".into())); - } - if broadcast.message_type() != MessageType::Broadcast { - return Err(LocalError( - "Invalid message type of the broadcast field".into(), - )); - } - if direct.message_type() != MessageType::Direct { - return Err(LocalError( - "Invalid message type of the direct field".into(), - )); - } - (broadcast.session_id(), broadcast.round(), false) - } - }; - Ok(Self { - session_id: *session_id, - round, - is_echo, - bundle: unchecked, - }) - } -} - -impl MessageBundle { - /// The session ID of the messages. - pub fn session_id(&self) -> &SessionId { - &self.session_id - } - - /// The round of the messages. - pub fn round(&self) -> u8 { - self.round - } - - /// Whether the bundle corresponds to an echo round. - pub fn is_echo(&self) -> bool { - self.is_echo - } - - pub(crate) fn verify( - self, - verifier: &impl PrehashVerifier, - ) -> Result, String> { - let verified_messages = match self.bundle { - MessageBundleEnum::Broadcast(msg) => { - MessageBundleEnum::Broadcast(msg.verify(verifier)?) - } - MessageBundleEnum::Direct(msg) => MessageBundleEnum::Direct(msg.verify(verifier)?), - MessageBundleEnum::Echo(msg) => MessageBundleEnum::Echo(msg.verify(verifier)?), - MessageBundleEnum::Both { broadcast, direct } => MessageBundleEnum::Both { - broadcast: broadcast.verify(verifier)?, - direct: direct.verify(verifier)?, - }, - }; - Ok(VerifiedMessageBundle(verified_messages)) - } -} - -#[derive(Clone, Debug)] -pub(crate) struct VerifiedMessageBundle(MessageBundleEnum>); - -impl VerifiedMessageBundle { - pub fn broadcast_payload(&self) -> Option<&[u8]> { - match &self.0 { - MessageBundleEnum::Broadcast(msg) => Some(msg.payload()), - MessageBundleEnum::Both { broadcast, .. } => Some(broadcast.payload()), - _ => None, - } - } - - pub fn broadcast_message(&self) -> Option<&VerifiedMessage> { - match &self.0 { - MessageBundleEnum::Broadcast(msg) => Some(msg), - MessageBundleEnum::Both { broadcast, .. } => Some(broadcast), - _ => None, - } - } - - pub fn direct_payload(&self) -> Option<&[u8]> { - match &self.0 { - MessageBundleEnum::Direct(msg) => Some(msg.payload()), - MessageBundleEnum::Both { direct, .. } => Some(direct.payload()), - _ => None, - } - } - - pub fn echo_payload(&self) -> Option<&[u8]> { - match &self.0 { - MessageBundleEnum::Echo(msg) => Some(msg.payload()), - _ => None, - } - } - - pub fn is_echo(&self) -> bool { - matches!(&self.0, MessageBundleEnum::Echo(_)) - } -} diff --git a/synedrion/src/sessions/session.rs b/synedrion/src/sessions/session.rs deleted file mode 100644 index 7758f273..00000000 --- a/synedrion/src/sessions/session.rs +++ /dev/null @@ -1,726 +0,0 @@ -use alloc::boxed::Box; -use alloc::collections::{BTreeMap, BTreeSet}; -use alloc::format; -use alloc::vec::Vec; -use core::fmt::Debug; - -use rand_core::CryptoRngCore; -use serde::{Deserialize, Serialize}; -use signature::{ - hazmat::{PrehashVerifier, RandomizedPrehashSigner}, - Keypair, -}; - -use super::echo::{EchoAccum, EchoRound}; -use super::error::{Error, LocalError, ProvableError, RemoteError, RemoteErrorEnum}; -use super::message_bundle::{MessageBundle, MessageBundleEnum, VerifiedMessageBundle}; -use super::signed_message::{MessageType, SessionId, SignedMessage, VerifiedMessage}; -use super::type_erased::{ - self, AccumAddError, DynArtifact, DynFinalizable, DynPayload, DynRoundAccum, ReceiveError, -}; -use crate::rounds::{self, FirstRound, ProtocolResult, Round}; - -struct Context { - signer: Signer, - my_id: Verifier, - session_id: SessionId, -} - -enum SessionType { - Normal { - this_round: Box>, - broadcast: Option>, - }, - Echo { - next_round: Box>, - echo_round: EchoRound, - }, -} - -/// The session state where it is ready to send messages. -pub struct Session { - tp: SessionType, - context: Context, -} - -enum MessageFor { - ThisRound, - NextRound, -} - -fn route_message_normal( - round: &dyn DynFinalizable, - message: &MessageBundle, -) -> Result { - let this_round = round.round_num(); - let next_round = round.next_round_num(); - let requires_echo = round.requires_echo(); - - let message_round = message.round(); - let message_is_echo = message.is_echo(); - - if message_round == this_round && !message_is_echo { - return Ok(MessageFor::ThisRound); - } - - let for_next_round = - // This is a normal round, and the next round exists, and the message is for it - (!requires_echo && next_round.is_some() && message_round == next_round.unwrap() && !message_is_echo) || - // This is an echo round, and the message is from the echo round - (requires_echo && message_round == this_round && message_is_echo); - - if for_next_round { - return Ok(MessageFor::NextRound); - } - - Err(RemoteErrorEnum::OutOfOrderMessage) -} - -fn route_message_echo( - next_round: &dyn DynFinalizable, - message: &MessageBundle, -) -> Result { - let next_round = next_round.round_num(); - let message_round = message.round(); - let message_is_echo = message.is_echo(); - - if message_round == next_round - 1 && message_is_echo { - return Ok(MessageFor::ThisRound); - } - - if message_round == next_round && !message_is_echo { - return Ok(MessageFor::NextRound); - } - - Err(RemoteErrorEnum::OutOfOrderMessage) -} - -fn wrap_receive_result( - from: &Verifier, - result: Result>, -) -> Result> { - // TODO (#43): we need to attach all the necessary messages here, - // to make sure that every provable error can be independently verified - // given the party's verifying key. - result.map_err(|err| match err { - ReceiveError::InvalidContents(msg) => Error::Remote(RemoteError { - party: from.clone(), - error: RemoteErrorEnum::InvalidContents(msg), - }), - ReceiveError::CannotDeserialize(msg) => Error::Provable { - party: from.clone(), - error: ProvableError::CannotDeserialize(msg), - }, - ReceiveError::Protocol(err) => Error::Provable { - party: from.clone(), - error: ProvableError::Protocol(err), - }, - }) -} - -/// Possible outcomes of successfully finalizing a round. -pub enum FinalizeOutcome { - /// The protocol result is available. - Success(Res::Success), - /// Starting the next round. - AnotherRound { - /// The new session object. - session: Session, - /// The messages for the new round received during the previous round. - cached_messages: Vec>, - }, -} - -impl Session -where - Res: ProtocolResult, - Signer: RandomizedPrehashSigner + Keypair, - Verifier: Debug + Clone + PrehashVerifier + Ord + Serialize + for<'de> Deserialize<'de>, - Sig: Clone + Serialize + for<'de> Deserialize<'de> + PartialEq + Eq, -{ - pub(crate) fn new< - R: FirstRound - + DynFinalizable - + Round - + 'static, - >( - rng: &mut impl CryptoRngCore, - session_id: SessionId, - signer: Signer, - verifiers: &BTreeSet, - inputs: R::Inputs, - ) -> Result { - let my_id = signer.verifying_key(); - let mut other_parties = verifiers.clone(); - other_parties.remove(&my_id); - let typed_round = R::new( - rng, - session_id.as_ref(), - other_parties, - my_id.clone(), - inputs, - ) - .map_err(|err| LocalError(format!("Failed to initialize the protocol: {err:?}")))?; - let round: Box> = Box::new(typed_round); - let context = Context { - my_id, - signer, - session_id, - }; - Self::new_internal(rng, context, round) - } - - fn new_internal( - rng: &mut impl CryptoRngCore, - context: Context, - round: Box>, - ) -> Result { - let broadcast = round.make_broadcast_message(rng)?; - - let signed_broadcast = if let Some(payload) = broadcast { - Some( - VerifiedMessage::new( - rng, - &context.signer, - &context.session_id, - round.round_num(), - MessageType::Broadcast, - &payload, - )? - .into_unverified(), - ) - } else { - None - }; - - Ok(Self { - tp: SessionType::Normal { - this_round: round, - broadcast: signed_broadcast, - }, - context, - }) - } - - /// This session's verifier object. - pub fn verifier(&self) -> Verifier { - self.context.signer.verifying_key() - } - - /// This session's ID. - pub fn session_id(&self) -> SessionId { - self.context.session_id - } - - /// Returns a pair of the current round index and whether it is an echo round. - pub fn current_round(&self) -> (u8, bool) { - match &self.tp { - SessionType::Normal { this_round, .. } => (this_round.round_num(), false), - SessionType::Echo { next_round, .. } => (next_round.round_num() - 1, true), - } - } - - /// Create an accumulator to store message creation and processing results of this round. - pub fn make_accumulator(&self) -> RoundAccumulator { - RoundAccumulator::new(self.is_echo_round()) - } - - /// Returns `true` if the round can be finalized. - pub fn can_finalize( - &self, - accum: &RoundAccumulator, - ) -> Result { - match &self.tp { - SessionType::Normal { this_round, .. } => Ok(this_round.can_finalize(&accum.processed)), - SessionType::Echo { echo_round, .. } => { - let echo_accum = accum.echo_accum.as_ref().ok_or(LocalError( - "This is an echo round, but the accumulator is in an invalid state".into(), - ))?; - Ok(echo_round.can_finalize(echo_accum)) - } - } - } - - /// Returns a list of parties whose messages for this round have not been received yet. - pub fn missing_messages( - &self, - accum: &RoundAccumulator, - ) -> Result, LocalError> { - match &self.tp { - SessionType::Normal { this_round, .. } => { - Ok(this_round.missing_messages(&accum.processed)) - } - SessionType::Echo { echo_round, .. } => { - let echo_accum = accum.echo_accum.as_ref().ok_or(LocalError( - "This is an echo round, but the accumulator is in an invalid state".into(), - ))?; - Ok(echo_round.missing_messages(echo_accum)) - } - } - } - - fn is_echo_round(&self) -> bool { - match &self.tp { - SessionType::Normal { .. } => false, - SessionType::Echo { .. } => true, - } - } - - /// Returns the party indices to which the messages of this round should be sent. - pub fn message_destinations(&self) -> &BTreeSet { - match &self.tp { - SessionType::Normal { this_round, .. } => this_round.message_destinations(), - SessionType::Echo { echo_round, .. } => echo_round.message_destinations(), - } - } - - fn expecting_messages_from(&self) -> &BTreeSet { - match &self.tp { - SessionType::Normal { this_round, .. } => this_round.expecting_messages_from(), - SessionType::Echo { echo_round, .. } => echo_round.expecting_messages_from(), - } - } - - /// Returns the message for the given destination - /// (must be one of those returned by [`Self::message_destinations`]. - pub fn make_message( - &self, - rng: &mut impl CryptoRngCore, - destination: &Verifier, - ) -> Result<(MessageBundle, Artifact), LocalError> { - match &self.tp { - SessionType::Normal { - this_round, - broadcast, - } => { - let round_num = this_round.round_num(); - let (payload, artifact) = this_round.make_direct_message(rng, destination)?; - - let direct_message = if let Some(payload) = payload { - Some( - VerifiedMessage::new( - rng, - &self.context.signer, - &self.context.session_id, - round_num, - MessageType::Direct, - &payload, - )? - .into_unverified(), - ) - } else { - None - }; - - let message = MessageBundle::try_from(match (broadcast, direct_message) { - (Some(broadcast), Some(direct)) => MessageBundleEnum::Both { - broadcast: broadcast.clone(), - direct, - }, - (None, Some(direct)) => MessageBundleEnum::Direct(direct), - (Some(broadcast), None) => MessageBundleEnum::Broadcast(broadcast.clone()), - (None, None) => return Err(LocalError("The round must send messages".into())), - })?; - - Ok(( - message, - Artifact { - destination: destination.clone(), - artifact, - }, - )) - } - SessionType::Echo { - next_round, - echo_round, - } => { - let round_num = next_round.round_num() - 1; - let payload = echo_round.make_broadcast(); - let artifact = DynArtifact::null(); - let message = VerifiedMessage::new( - rng, - &self.context.signer, - &self.context.session_id, - round_num, - MessageType::Echo, - &payload, - )? - .into_unverified(); - Ok(( - MessageBundle::try_from(MessageBundleEnum::Echo(message))?, - Artifact { - destination: destination.clone(), - artifact, - }, - )) - } - } - } - - fn route_message( - &self, - from: &Verifier, - message: &MessageBundle, - ) -> Result> { - let message_for = match &self.tp { - SessionType::Normal { this_round, .. } => { - route_message_normal(this_round.as_ref(), message) - } - SessionType::Echo { next_round, .. } => { - route_message_echo(next_round.as_ref(), message) - } - }; - - message_for.map_err(|err| { - Error::Remote(RemoteError { - party: from.clone(), - error: err, - }) - }) - } - - /// Perform quick checks on a received message. - pub fn preprocess_message( - &self, - accum: &mut RoundAccumulator, - from: &Verifier, - message: MessageBundle, - ) -> Result>, Error> { - // This is an unprovable fault (may be a replay attack) - if message.session_id() != &self.context.session_id { - return Err(Error::Remote(RemoteError { - party: from.clone(), - error: RemoteErrorEnum::UnexpectedSessionId, - })); - } - - let message_for = self.route_message(from, &message)?; - - let verified_message = message.verify(from).map_err(|err| { - Error::Remote(RemoteError { - party: from.clone(), - error: RemoteErrorEnum::InvalidSignature(err), - }) - })?; - - if from == &self.context.my_id { - return Err(Error::Local(LocalError( - "Cannot take a message from myself".into(), - ))); - } - - let preprocessed = PreprocessedMessage { - from: from.clone(), - message: verified_message, - }; - - Ok(match message_for { - MessageFor::ThisRound => { - if !self.expecting_messages_from().contains(from) { - return Err(Error::Local(LocalError( - "The sender is not in the list of expected senders.".into(), - ))); - } - - if accum.is_already_processed(&preprocessed) { - return Err(Error::Remote(RemoteError { - party: from.clone(), - error: RemoteErrorEnum::DuplicateMessage, - })); - } - Some(preprocessed) - } - MessageFor::NextRound => { - if accum.is_already_cached(&preprocessed) { - return Err(Error::Remote(RemoteError { - party: from.clone(), - error: RemoteErrorEnum::DuplicateMessage, - })); - } - accum.add_cached_message(preprocessed); - None - } - }) - } - - /// Process a received message from another party. - pub fn process_message( - &self, - rng: &mut impl CryptoRngCore, - preprocessed: PreprocessedMessage, - ) -> Result, Error> { - let from = preprocessed.from; - let message = preprocessed.message; - match &self.tp { - SessionType::Normal { this_round, .. } => { - let result = this_round.verify_message( - rng, - &from, - message.broadcast_payload(), - message.direct_payload(), - ); - let payload = wrap_receive_result(&from, result)?; - Ok(ProcessedMessage { - from: from.clone(), - message: ProcessedMessageEnum::Payload { payload, message }, - }) - } - SessionType::Echo { echo_round, .. } => { - echo_round - .verify_broadcast(&from, message.echo_payload().unwrap()) - .map_err(|err| Error::Provable { - party: from.clone(), - error: ProvableError::Echo(err), - })?; - Ok(ProcessedMessage { - from: from.clone(), - message: ProcessedMessageEnum::Echo, - }) - } - } - } - - /// Try to finalize the round. - pub fn finalize_round( - self, - rng: &mut impl CryptoRngCore, - accum: RoundAccumulator, - ) -> Result, Error> { - match self.tp { - SessionType::Normal { this_round, .. } => { - Self::finalize_regular_round(self.context, this_round, rng, accum) - } - SessionType::Echo { - echo_round, - next_round, - } => Self::finalize_echo_round(self.context, echo_round, next_round, rng, accum), - } - } - - fn finalize_regular_round( - context: Context, - round: Box>, - rng: &mut impl CryptoRngCore, - accum: RoundAccumulator, - ) -> Result, Error> { - let requires_echo = round.requires_echo(); - - let outcome = round - .finalize(rng, accum.processed) - .map_err(|err| match err { - type_erased::FinalizeError::Protocol(err) => match err { - rounds::FinalizeError::Init(err) => Error::Local(LocalError(format!( - "Failed to initialize the protocol: {err:?}" - ))), - rounds::FinalizeError::Proof(proof) => Error::Proof { proof }, - }, - type_erased::FinalizeError::Accumulator(err) => { - Error::Local(LocalError(format!("Failed to finalize: {err:?}"))) - } - })?; - - match outcome { - type_erased::FinalizeOutcome::Success(res) => Ok(FinalizeOutcome::Success(res)), - type_erased::FinalizeOutcome::AnotherRound(next_round) => { - if requires_echo { - let broadcasts = accum - .received_messages - .iter() - .map(|(id, combined)| { - (id.clone(), combined.broadcast_message().unwrap().clone()) - }) - .collect(); - - let echo_round = EchoRound::new(broadcasts); - let session = Session { - tp: SessionType::Echo { - next_round, - echo_round, - }, - context, - }; - Ok(FinalizeOutcome::AnotherRound { - session, - cached_messages: accum.cached_messages.into_values().collect(), - }) - } else { - let session = - Session::new_internal(rng, context, next_round).map_err(Error::Local)?; - Ok(FinalizeOutcome::AnotherRound { - session, - cached_messages: accum.cached_messages.into_values().collect(), - }) - } - } - } - } - - fn finalize_echo_round( - context: Context, - echo_round: EchoRound, - next_round: Box>, - rng: &mut impl CryptoRngCore, - accum: RoundAccumulator, - ) -> Result, Error> { - let echo_accum = accum.echo_accum.ok_or(Error::Local(LocalError( - "The accumulator is in the invalid state for the echo round".into(), - )))?; - - echo_round.finalize(echo_accum).map_err(Error::Local)?; - - let session = Session::new_internal(rng, context, next_round).map_err(Error::Local)?; - - Ok(FinalizeOutcome::AnotherRound { - session, - cached_messages: accum.cached_messages.into_values().collect(), - }) - } -} - -/// A mutable accumulator created for each round to assemble processed messages from other parties. -pub struct RoundAccumulator { - received_messages: BTreeMap>, - processed: DynRoundAccum, - cached_messages: BTreeMap>, - echo_accum: Option>, -} - -impl RoundAccumulator { - fn new(is_echo_round: bool) -> Self { - Self { - received_messages: BTreeMap::new(), - processed: DynRoundAccum::new(), - cached_messages: BTreeMap::new(), - echo_accum: if is_echo_round { - Some(EchoAccum::new()) - } else { - None - }, - } - } - - /// Save an artifact produced by [`Session::make_message`]. - pub fn add_artifact(&mut self, artifact: Artifact) -> Result<(), LocalError> { - self.processed - .add_artifact(&artifact.destination, artifact.artifact) - .map_err(|err| match err { - AccumAddError::SlotTaken => LocalError(format!( - "Artifact for the destination {:?} was already added", - artifact.destination - )), - }) - } - - /// Save a processed message produced by [`Session::process_message`]. - pub fn add_processed_message( - &mut self, - pm: ProcessedMessage, - ) -> Result>, LocalError> { - match pm.message { - ProcessedMessageEnum::Payload { payload, message } => { - if let Err(AccumAddError::SlotTaken) = self.processed.add_payload(&pm.from, payload) - { - return Ok(Err(RemoteError { - party: pm.from, - error: RemoteErrorEnum::DuplicateMessage, - })); - } - self.received_messages.insert(pm.from, message); - } - ProcessedMessageEnum::Echo => match &mut self.echo_accum { - Some(accum) => { - if accum.add_echo_received(&pm.from).is_none() { - return Ok(Err(RemoteError { - party: pm.from, - error: RemoteErrorEnum::DuplicateMessage, - })); - } - } - None => return Err(LocalError("This is not an echo round".into())), - }, - } - Ok(Ok(())) - } - - fn is_already_processed(&self, preprocessed: &PreprocessedMessage) -> bool { - if preprocessed.message.is_echo() { - self.echo_accum - .as_ref() - .unwrap() - .contains(&preprocessed.from) - } else { - self.processed.contains(&preprocessed.from) - } - } - - fn is_already_cached(&self, preprocessed: &PreprocessedMessage) -> bool { - self.cached_messages.contains_key(&preprocessed.from) - } - - fn add_cached_message(&mut self, preprocessed: PreprocessedMessage) { - self.cached_messages - .insert(preprocessed.from.clone(), preprocessed); - } -} - -/// Data produced when creating a direct message to another party -/// that has to be preserved for further processing. -pub struct Artifact { - destination: Verifier, - artifact: DynArtifact, -} - -/// A message that passed initial validity checks. -pub struct PreprocessedMessage { - from: Verifier, - message: VerifiedMessageBundle, -} - -/// A processed message from another party. -pub struct ProcessedMessage { - from: Verifier, - message: ProcessedMessageEnum, -} - -enum ProcessedMessageEnum { - Payload { - payload: DynPayload, - message: VerifiedMessageBundle, - }, - Echo, -} - -#[cfg(test)] -mod tests { - use impls::impls; - use k256::ecdsa::{Signature, SigningKey, VerifyingKey}; - - use super::{Artifact, MessageBundle, PreprocessedMessage, ProcessedMessage, Session}; - use crate::ProtocolResult; - - #[test] - fn test_concurrency_bounds() { - // In order to support parallel message creation and processing we need that - // certain generic types could be Send and/or Sync. - // - // Since they are generic, this depends on the exact type parameters supplied by the user, - // so if the user does not want parallelism, they may not use Send/Sync generic parameters. - // But we want to make sure that if the generic parameters are Send/Sync, - // our types are too. - - #[derive(Debug)] - struct DummyResult; - - impl ProtocolResult for DummyResult { - type Success = (); - type ProvableError = (); - type CorrectnessProof = (); - } - - assert!(impls!(Session: Sync)); - assert!(impls!(MessageBundle: Send)); - assert!(impls!(Artifact: Send)); - assert!(impls!(PreprocessedMessage: Send)); - assert!(impls!(ProcessedMessage: Send)); - } -} diff --git a/synedrion/src/sessions/signed_message.rs b/synedrion/src/sessions/signed_message.rs deleted file mode 100644 index f7921540..00000000 --- a/synedrion/src/sessions/signed_message.rs +++ /dev/null @@ -1,157 +0,0 @@ -use alloc::boxed::Box; -use alloc::format; -use alloc::string::{String, ToString}; - -use rand_core::CryptoRngCore; -use serde::{Deserialize, Serialize}; -use serde_encoded_bytes::{Base64, SliceLike}; -use signature::hazmat::{PrehashVerifier, RandomizedPrehashSigner}; - -use super::error::LocalError; -use crate::tools::hashing::{Chain, FofHasher, HashOutput}; - -/// A session identifier shared between the parties. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub struct SessionId(HashOutput); - -impl SessionId { - /// Deterministically creates a session ID from the given bytestring. - pub fn from_seed(seed: &[u8]) -> Self { - Self( - FofHasher::new_with_dst(b"SessionId") - .chain(&seed) - .finalize(), - ) - } -} - -impl AsRef<[u8]> for SessionId { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -fn message_hash( - session_id: &SessionId, - round: u8, - message_type: MessageType, - payload: &[u8], -) -> HashOutput { - FofHasher::new_with_dst(b"SignedMessage") - .chain(session_id) - .chain(&round) - .chain(&message_type) - .chain(&payload) - .finalize() -} - -/// Protocol message type. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)] -pub enum MessageType { - Broadcast, - /// Regular messaging part of the round. - Direct, - /// A service message for echo-broadcast. - Echo, -} - -/// A (yet) unverified message from a round that includes the payload signature. -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] -pub struct SignedMessage { - session_id: SessionId, - round: u8, - message_type: MessageType, - #[serde(with = "SliceLike::")] - payload: Box<[u8]>, - signature: Sig, -} - -impl SignedMessage { - pub(crate) fn verify( - self, - verifier: &impl PrehashVerifier, - ) -> Result, String> { - verifier - .verify_prehash( - message_hash( - &self.session_id, - self.round, - self.message_type, - &self.payload, - ) - .as_ref(), - &self.signature, - ) - .map_err(|err| format!("{:?}", err))?; - Ok(VerifiedMessage(self)) - } - - /// The session ID of this message. - pub fn session_id(&self) -> &SessionId { - &self.session_id - } - - /// The round of this message. - pub fn round(&self) -> u8 { - self.round - } - - /// The message type. - pub fn message_type(&self) -> MessageType { - self.message_type - } - - /// Compares the "significant" part of the messages (that is, everything but signatures) - pub fn is_same_as(&self, other: &Self) -> bool { - self.session_id == other.session_id - && self.round == other.round - && self.message_type == other.message_type - && self.payload == other.payload - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub(crate) struct VerifiedMessage(SignedMessage); - -impl VerifiedMessage { - pub(crate) fn new( - rng: &mut impl CryptoRngCore, - signer: &impl RandomizedPrehashSigner, - session_id: &SessionId, - round: u8, - message_type: MessageType, - message_bytes: &[u8], - ) -> Result { - // In order for the messages be impossible to reuse by a malicious third party, - // we need to sign, besides the message itself, the session and the round in this session - // it belongs to. - // We also need the exact way we sign this to be a part of the public ABI, - // so that these signatures could be verified by a third party. - - let signature = signer - .sign_prehash_with_rng( - rng, - message_hash(session_id, round, message_type, message_bytes).as_ref(), - ) - .map_err(|err| LocalError(err.to_string()))?; - Ok(Self(SignedMessage { - session_id: *session_id, - round, - message_type, - payload: message_bytes.into(), - signature, - })) - } - - pub fn as_unverified(&self) -> &SignedMessage { - &self.0 - } - - pub fn into_unverified(self) -> SignedMessage { - self.0 - } - - pub fn payload(&self) -> &[u8] { - &self.0.payload - } -} diff --git a/synedrion/src/sessions/type_erased.rs b/synedrion/src/sessions/type_erased.rs deleted file mode 100644 index ef964d15..00000000 --- a/synedrion/src/sessions/type_erased.rs +++ /dev/null @@ -1,421 +0,0 @@ -/*! -This module maps the static typed interface of the rounds into boxable traits. -This way they can be used in a state machine loop without code repetition. -*/ - -use alloc::boxed::Box; -use alloc::collections::{BTreeMap, BTreeSet}; -use alloc::format; -use alloc::string::{String, ToString}; -use core::any::{Any, TypeId}; - -use rand_core::CryptoRngCore; -use serde::{Deserialize, Serialize}; - -use super::error::LocalError; -use crate::rounds::{ - self, FinalizableToNextRound, FinalizableToResult, ProtocolResult, Round, ToNextRound, ToResult, -}; - -pub(crate) fn serialize_message(message: &impl Serialize) -> Result, LocalError> { - bincode::serde::encode_to_vec(message, bincode::config::standard()) - .map(|serialized| serialized.into_boxed_slice()) - .map_err(|err| LocalError(format!("Failed to serialize: {err:?}"))) -} - -pub(crate) fn deserialize_message Deserialize<'de>>( - message_bytes: &[u8], -) -> Result { - bincode::serde::decode_borrowed_from_slice(message_bytes, bincode::config::standard()) - .map_err(|err| err.to_string()) -} - -pub(crate) enum FinalizeOutcome { - Success(Res::Success), - AnotherRound(Box>), -} - -#[derive(Debug)] -pub enum AccumAddError { - /// An item with the given origin has already been added to the accumulator. - SlotTaken, -} - -#[derive(Debug)] -pub(crate) enum ReceiveError { - InvalidContents(String), - /// Error while deserializing the given message. - CannotDeserialize(String), - /// An error from the protocol level - Protocol(Res::ProvableError), -} - -#[derive(Debug)] -pub(crate) enum FinalizeError { - /// An error from the protocol level - Protocol(rounds::FinalizeError), - /// Cannot finalize. - Accumulator(String), -} - -/// Since object-safe trait methods cannot take `impl CryptoRngCore` arguments, -/// this structure wraps the dynamic object and exposes a `CryptoRngCore` interface, -/// to be passed to statically typed round methods. -struct BoxedRng<'a>(&'a mut dyn CryptoRngCore); - -impl rand_core::CryptoRng for BoxedRng<'_> {} - -impl rand_core::RngCore for BoxedRng<'_> { - fn next_u32(&mut self) -> u32 { - self.0.next_u32() - } - fn next_u64(&mut self) -> u64 { - self.0.next_u64() - } - fn fill_bytes(&mut self, dest: &mut [u8]) { - self.0.fill_bytes(dest) - } - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { - self.0.try_fill_bytes(dest) - } -} - -pub(crate) struct DynPayload(Box); - -pub(crate) struct DynArtifact(Box); - -impl DynArtifact { - pub fn null() -> Self { - Self(Box::new(())) - } -} - -/// An object-safe trait wrapping `Round`. -pub(crate) trait DynRound: Send + Sync { - fn round_num(&self) -> u8; - fn next_round_num(&self) -> Option; - - fn requires_echo(&self) -> bool; - fn message_destinations(&self) -> &BTreeSet; - fn expecting_messages_from(&self) -> &BTreeSet; - fn make_broadcast_message( - &self, - rng: &mut dyn CryptoRngCore, - ) -> Result>, LocalError>; - #[allow(clippy::type_complexity)] - fn make_direct_message( - &self, - rng: &mut dyn CryptoRngCore, - destination: &I, - ) -> Result<(Option>, DynArtifact), LocalError>; - fn verify_message( - &self, - rng: &mut dyn CryptoRngCore, - from: &I, - broadcast_data: Option<&[u8]>, - direct_data: Option<&[u8]>, - ) -> Result>; - fn can_finalize(&self, accum: &DynRoundAccum) -> bool; - fn missing_messages(&self, accum: &DynRoundAccum) -> BTreeSet; -} - -fn is_null_type() -> bool { - TypeId::of::() == TypeId::of::<()>() -} - -impl DynRound for R -where - I: Ord + Clone, - R: Round + Send + Sync, - >::BroadcastMessage: 'static, - >::DirectMessage: 'static, - >::Payload: 'static + Send, - >::Artifact: 'static + Send, -{ - fn round_num(&self) -> u8 { - R::ROUND_NUM - } - - fn next_round_num(&self) -> Option { - R::NEXT_ROUND_NUM - } - - fn message_destinations(&self) -> &BTreeSet { - self.message_destinations() - } - - fn expecting_messages_from(&self) -> &BTreeSet { - self.expecting_messages_from() - } - - fn make_broadcast_message( - &self, - rng: &mut dyn CryptoRngCore, - ) -> Result>, LocalError> { - if is_null_type::() { - return Ok(None); - } - - let mut boxed_rng = BoxedRng(rng); - let typed_message = self.make_broadcast_message(&mut boxed_rng); - let serialized = typed_message - .map(|message| serialize_message(&message)) - .transpose()?; - Ok(serialized) - } - - fn make_direct_message( - &self, - rng: &mut dyn CryptoRngCore, - destination: &I, - ) -> Result<(Option>, DynArtifact), LocalError> { - let null_message = is_null_type::(); - let null_artifact = is_null_type::(); - - if null_message && null_artifact { - return Ok((None, DynArtifact::null())); - } - - let mut boxed_rng = BoxedRng(rng); - let (typed_message, typed_artifact) = self.make_direct_message(&mut boxed_rng, destination); - - let message = if null_message { - None - } else { - Some(serialize_message(&typed_message)?) - }; - - Ok((message, DynArtifact(Box::new(typed_artifact)))) - } - - fn verify_message( - &self, - rng: &mut dyn CryptoRngCore, - from: &I, - broadcast_data: Option<&[u8]>, - direct_data: Option<&[u8]>, - ) -> Result> { - let null_broadcast = is_null_type::(); - let null_direct = is_null_type::(); - - let broadcast_data = if let Some(data) = broadcast_data { - data - } else { - if !null_broadcast { - return Err(ReceiveError::InvalidContents( - "Expected a non-null broadcast message".into(), - )); - } - b"" - }; - - let broadcast_message: >::BroadcastMessage = - match deserialize_message(broadcast_data) { - Ok(message) => message, - Err(err) => return Err(ReceiveError::CannotDeserialize(err)), - }; - - let direct_data = if let Some(data) = direct_data { - data - } else { - if !null_direct { - return Err(ReceiveError::InvalidContents( - "Expected a non-null direct message".into(), - )); - } - b"" - }; - - let direct_message: >::DirectMessage = match deserialize_message(direct_data) - { - Ok(message) => message, - Err(err) => return Err(ReceiveError::CannotDeserialize(err)), - }; - - let mut boxed_rng = BoxedRng(rng); - - let payload = self - .verify_message(&mut boxed_rng, from, broadcast_message, direct_message) - .map_err(ReceiveError::Protocol)?; - - Ok(DynPayload(Box::new(payload))) - } - - fn requires_echo(&self) -> bool { - >::REQUIRES_ECHO - } - - fn can_finalize(&self, accum: &DynRoundAccum) -> bool { - self.can_finalize(&accum.received) - } - - fn missing_messages(&self, accum: &DynRoundAccum) -> BTreeSet { - self.missing_messages(&accum.received) - } -} - -pub(crate) struct DynRoundAccum { - received: BTreeSet, - payloads: BTreeMap, - artifacts: BTreeMap, -} - -struct RoundAccum> { - payloads: BTreeMap>::Payload>, - artifacts: BTreeMap>::Artifact>, -} - -impl DynRoundAccum { - pub fn new() -> Self { - Self { - received: BTreeSet::new(), - payloads: BTreeMap::new(), - artifacts: BTreeMap::new(), - } - } - - pub fn contains(&self, from: &I) -> bool { - self.received.contains(from) - } - - pub fn add_payload(&mut self, from: &I, payload: DynPayload) -> Result<(), AccumAddError> { - if self.received.contains(from) { - return Err(AccumAddError::SlotTaken); - } - self.received.insert(from.clone()); - self.payloads.insert(from.clone(), payload); - Ok(()) - } - - pub fn add_artifact( - &mut self, - destination: &I, - artifact: DynArtifact, - ) -> Result<(), AccumAddError> { - if self.artifacts.contains_key(destination) { - return Err(AccumAddError::SlotTaken); - } - self.artifacts.insert(destination.clone(), artifact); - Ok(()) - } - - fn finalize>(self) -> Result, String> - where - >::Payload: 'static, - >::Artifact: 'static, - { - let payloads = self - .payloads - .into_iter() - .map(|(id, elem)| downcast::<>::Payload>(elem.0).map(|elem| (id, elem))) - .collect::, _>>()?; - let artifacts = self - .artifacts - .into_iter() - .map(|(id, elem)| downcast::<>::Artifact>(elem.0).map(|elem| (id, elem))) - .collect::, _>>()?; - Ok(RoundAccum { - payloads, - artifacts, - }) - } -} - -fn downcast(boxed: Box) -> Result { - Ok(*(boxed - .downcast::() - .map_err(|_| format!("Failed to downcast into {}", core::any::type_name::()))?)) -} - -pub(crate) trait DynFinalizable: DynRound { - fn finalize( - self: Box, - rng: &mut dyn CryptoRngCore, - accum: DynRoundAccum, - ) -> Result, FinalizeError>; -} - -// This is needed because Rust does not currently support exclusive trait implementations. -// We want to implement `DynFinalizable` depending on whether the type is -// `FinalizableToResult` or `FinalizableToNextRound`. -// A way to do it is to exploit the fact that a trait with an associated type (in our case, `Round`) -// can only be implemented for one value of the associated type (in our case, `Round::Type`), -// and the compiler knows that. -const _: () = { - // This is the boilerplate: - // 1) A helper trait parametrized by a type `T` that will take the values - // of the target associated type, with the same methods as the target trait; - // 2) A blanket implementation for the target trait. - - trait _DynFinalizable { - fn finalize( - self: Box, - rng: &mut dyn CryptoRngCore, - accum: DynRoundAccum, - ) -> Result, FinalizeError>; - } - - impl DynFinalizable for R - where - I: Ord + Clone, - R: Round + Send + Sync + 'static, - >::BroadcastMessage: 'static, - >::DirectMessage: 'static, - >::Payload: Send + 'static, - >::Artifact: Send + 'static, - Self: _DynFinalizable, - { - fn finalize( - self: Box, - rng: &mut dyn CryptoRngCore, - accum: DynRoundAccum, - ) -> Result, FinalizeError> { - Self::finalize(self, rng, accum) - } - } - - // Actual diverging implementations. - - impl _DynFinalizable for R - where - I: Ord + Clone, - >::Payload: Send + 'static, - >::Artifact: Send + 'static, - R: 'static + FinalizableToResult, - { - fn finalize( - self: Box, - rng: &mut dyn CryptoRngCore, - accum: DynRoundAccum, - ) -> Result, FinalizeError> { - let mut boxed_rng = BoxedRng(rng); - let typed_accum = accum.finalize::().map_err(FinalizeError::Accumulator)?; - let result = (*self) - .finalize_to_result(&mut boxed_rng, typed_accum.payloads, typed_accum.artifacts) - .map_err(FinalizeError::Protocol)?; - Ok(FinalizeOutcome::Success(result)) - } - } - - impl _DynFinalizable for R - where - I: Ord + Clone, - >::Payload: Send + 'static, - >::Artifact: Send + 'static, - R: 'static + FinalizableToNextRound, - >::NextRound: DynFinalizable + 'static, - { - fn finalize( - self: Box, - rng: &mut dyn CryptoRngCore, - accum: DynRoundAccum, - ) -> Result, FinalizeError> { - let mut boxed_rng = BoxedRng(rng); - let typed_accum = accum.finalize::().map_err(FinalizeError::Accumulator)?; - let next_round = (*self) - .finalize_to_next_round(&mut boxed_rng, typed_accum.payloads, typed_accum.artifacts) - .map_err(FinalizeError::Protocol)?; - Ok(FinalizeOutcome::AnotherRound(Box::new(next_round))) - } - } -}; diff --git a/synedrion/src/tools.rs b/synedrion/src/tools.rs index 32819c78..3f740302 100644 --- a/synedrion/src/tools.rs +++ b/synedrion/src/tools.rs @@ -1,3 +1,47 @@ +use alloc::collections::{BTreeMap, BTreeSet}; + pub(crate) mod bitvec; pub(crate) mod hashing; +mod hide_debug; pub(crate) mod sss; + +pub(crate) use hide_debug::HideDebug; + +use manul::protocol::{Artifact, LocalError, Payload}; + +pub(crate) trait Without { + type Item; + fn without(self, item: &Self::Item) -> Self; +} + +impl Without for BTreeSet { + type Item = T; + fn without(self, item: &Self::Item) -> Self { + let mut set = self; + set.remove(item); + set + } +} + +pub(crate) trait DowncastMap { + type Key; + fn downcast_all(self) -> Result, LocalError>; +} + +impl DowncastMap for BTreeMap { + type Key = K; + fn downcast_all(self) -> Result, LocalError> { + self.into_iter() + .map(|(k, payload)| payload.try_to_typed::().map(|v| (k, v))) + .collect::>() + } +} + +impl DowncastMap for BTreeMap { + type Key = K; + fn downcast_all(self) -> Result, LocalError> { + self.into_iter() + .map(|(k, artifact)| artifact.try_to_typed::().map(|v| (k, v))) + .collect::>() + } +} diff --git a/synedrion/src/tools/bitvec.rs b/synedrion/src/tools/bitvec.rs index e5679963..cb5b02df 100644 --- a/synedrion/src/tools/bitvec.rs +++ b/synedrion/src/tools/bitvec.rs @@ -1,5 +1,4 @@ -use alloc::boxed::Box; -use alloc::vec; +use alloc::{boxed::Box, vec}; use core::ops::BitXorAssign; use rand_core::CryptoRngCore; diff --git a/synedrion/src/tools/hashing.rs b/synedrion/src/tools/hashing.rs index 08059c23..c786b7eb 100644 --- a/synedrion/src/tools/hashing.rs +++ b/synedrion/src/tools/hashing.rs @@ -1,13 +1,12 @@ +use crypto_bigint::{Encoding, Integer, NonZero}; use digest::{Digest, ExtendableOutput, Update, XofReader}; +use hashing_serializer::HashingSerializer; use serde::{Deserialize, Serialize}; +use serde_encoded_bytes::{ArrayLike, Hex}; use sha2::Sha256; use sha3::{Shake256, Shake256Reader}; -use hashing_serializer::HashingSerializer; -use serde_encoded_bytes::{ArrayLike, Hex}; - use crate::curve::Scalar; -use crypto_bigint::{Encoding, Integer, NonZero}; /// A digest object that takes byte slices or decomposable ([`Hashable`]) objects. pub trait Chain: Sized { diff --git a/synedrion/src/tools/hide_debug.rs b/synedrion/src/tools/hide_debug.rs new file mode 100644 index 00000000..ddda150c --- /dev/null +++ b/synedrion/src/tools/hide_debug.rs @@ -0,0 +1,35 @@ +use core::{ + fmt::Debug, + ops::{Deref, DerefMut}, +}; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Copy, Serialize, Deserialize)] +pub(crate) struct HideDebug(T); + +impl Debug for HideDebug { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("").finish() + } +} + +impl From for HideDebug { + fn from(value: T) -> Self { + Self(value) + } +} + +impl Deref for HideDebug { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for HideDebug { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/synedrion/src/tools/sss.rs b/synedrion/src/tools/sss.rs index 3a3955e4..19f3569d 100644 --- a/synedrion/src/tools/sss.rs +++ b/synedrion/src/tools/sss.rs @@ -1,11 +1,14 @@ -use alloc::collections::BTreeMap; -use alloc::vec::Vec; +use alloc::{collections::BTreeMap, vec::Vec}; use core::ops::{Add, Mul}; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; +use zeroize::ZeroizeOnDrop; -use crate::curve::{Point, Scalar}; +use crate::{ + curve::{Point, Scalar}, + tools::HideDebug, +}; #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct ShareId(Scalar); @@ -37,7 +40,8 @@ where res } -pub(crate) struct Polynomial(Vec); +#[derive(Debug, ZeroizeOnDrop)] +pub(crate) struct Polynomial(HideDebug>); impl Polynomial { pub fn random(rng: &mut impl CryptoRngCore, coeff0: &Scalar, degree: usize) -> Self { @@ -46,7 +50,7 @@ impl Polynomial { for _ in 1..degree { coeffs.push(Scalar::random_nonzero(rng)); } - Self(coeffs) + Self(coeffs.into()) } pub fn evaluate(&self, x: &ShareId) -> Scalar { diff --git a/synedrion/src/uint/bounded.rs b/synedrion/src/uint/bounded.rs index 69c7e555..1bc9cd0e 100644 --- a/synedrion/src/uint/bounded.rs +++ b/synedrion/src/uint/bounded.rs @@ -1,6 +1,4 @@ -use alloc::boxed::Box; -use alloc::format; -use alloc::string::String; +use alloc::{boxed::Box, format, string::String}; use secrecy::{CloneableSecret, SecretBox}; use serde::{Deserialize, Serialize}; diff --git a/synedrion/src/uint/signed.rs b/synedrion/src/uint/signed.rs index 9ecbcefe..85dabe96 100644 --- a/synedrion/src/uint/signed.rs +++ b/synedrion/src/uint/signed.rs @@ -1,5 +1,6 @@ use alloc::{boxed::Box, string::String}; use core::ops::{Add, Mul, Neg, Sub}; + #[cfg(test)] use crypto_bigint::Random; use digest::XofReader; @@ -622,12 +623,14 @@ where #[cfg(test)] mod tests { - use super::Signed; - use crate::uint::U1024; + use std::ops::Neg; + use crypto_bigint::{CheckedSub, U128}; use rand::SeedableRng; use rand_chacha::{self, ChaCha8Rng}; - use std::ops::Neg; + + use super::Signed; + use crate::uint::U1024; const SEED: u64 = 123; #[test] diff --git a/synedrion/src/www02.rs b/synedrion/src/www02.rs index 0ec176df..cd3eab3c 100644 --- a/synedrion/src/www02.rs +++ b/synedrion/src/www02.rs @@ -2,4 +2,4 @@ mod entities; pub(crate) mod key_resharing; pub use entities::{DeriveChildKey, ThresholdKeyShare}; -pub use key_resharing::{KeyResharingInputs, KeyResharingResult, NewHolder, OldHolder}; +pub use key_resharing::{KeyResharing, KeyResharingProtocol, NewHolder, OldHolder}; diff --git a/synedrion/src/www02/entities.rs b/synedrion/src/www02/entities.rs index 1df98b62..b62215fa 100644 --- a/synedrion/src/www02/entities.rs +++ b/synedrion/src/www02/entities.rs @@ -1,8 +1,9 @@ -use alloc::boxed::Box; -use alloc::collections::{BTreeMap, BTreeSet}; -use alloc::vec::Vec; -use core::fmt::Debug; -use core::marker::PhantomData; +use alloc::{ + boxed::Box, + collections::{BTreeMap, BTreeSet}, + vec::Vec, +}; +use core::{fmt::Debug, marker::PhantomData}; use bip32::{DerivationPath, PrivateKey, PrivateKeyBytes, PublicKey}; use k256::ecdsa::{SigningKey, VerifyingKey}; @@ -10,11 +11,16 @@ use rand_core::CryptoRngCore; use secrecy::{ExposeSecret, SecretBox}; use serde::{Deserialize, Serialize}; -use crate::cggmp21::{KeyShare, SchemeParams}; -use crate::curve::{Point, Scalar}; -use crate::tools::hashing::{Chain, FofHasher}; -use crate::tools::sss::{ - interpolation_coeff, shamir_evaluation_points, shamir_join_points, shamir_split, ShareId, +use crate::{ + cggmp21::{KeyShare, SchemeParams}, + curve::{Point, Scalar}, + tools::{ + hashing::{Chain, FofHasher}, + sss::{ + interpolation_coeff, shamir_evaluation_points, shamir_join_points, shamir_split, + ShareId, + }, + }, }; /// A threshold variant of the key share, where any `threshold` shares our of the total number @@ -291,32 +297,43 @@ mod tests { use alloc::collections::BTreeSet; use k256::ecdsa::SigningKey; + use manul::{ + dev::{TestSigner, TestVerifier}, + session::signature::Keypair, + }; use rand_core::OsRng; use secrecy::ExposeSecret; use super::ThresholdKeyShare; - use crate::cggmp21::TestParams; - use crate::curve::Scalar; - use crate::rounds::test_utils::Id; + use crate::{cggmp21::TestParams, curve::Scalar}; #[test] fn threshold_key_share_centralized() { let sk = SigningKey::random(&mut OsRng); - let ids = BTreeSet::from([Id(0), Id(1), Id(2)]); - - let shares = - ThresholdKeyShare::::new_centralized(&mut OsRng, &ids, 2, Some(&sk)); + let signers = (0..3).map(TestSigner::new).collect::>(); + let ids = signers + .iter() + .map(|signer| signer.verifying_key()) + .collect::>(); + let ids_set = ids.iter().cloned().collect::>(); + + let shares = ThresholdKeyShare::::new_centralized( + &mut OsRng, + &ids_set, + 2, + Some(&sk), + ); - assert_eq!(&shares[&Id(0)].verifying_key(), sk.verifying_key()); - assert_eq!(&shares[&Id(1)].verifying_key(), sk.verifying_key()); - assert_eq!(&shares[&Id(2)].verifying_key(), sk.verifying_key()); + assert_eq!(&shares[&ids[0]].verifying_key(), sk.verifying_key()); + assert_eq!(&shares[&ids[1]].verifying_key(), sk.verifying_key()); + assert_eq!(&shares[&ids[2]].verifying_key(), sk.verifying_key()); - assert_eq!(&shares[&Id(0)].verifying_key(), sk.verifying_key()); + assert_eq!(&shares[&ids[0]].verifying_key(), sk.verifying_key()); - let ids_subset = BTreeSet::from([Id(2), Id(0)]); - let nt_share0 = shares[&Id(0)].to_key_share(&ids_subset); - let nt_share1 = shares[&Id(2)].to_key_share(&ids_subset); + let ids_subset = BTreeSet::from([ids[2], ids[0]]); + let nt_share0 = shares[&ids[0]].to_key_share(&ids_subset); + let nt_share1 = shares[&ids[2]].to_key_share(&ids_subset); assert_eq!( nt_share0.secret_share.expose_secret() + nt_share1.secret_share.expose_secret(), diff --git a/synedrion/src/www02/key_resharing.rs b/synedrion/src/www02/key_resharing.rs index 67d00c0a..6129895b 100644 --- a/synedrion/src/www02/key_resharing.rs +++ b/synedrion/src/www02/key_resharing.rs @@ -5,54 +5,94 @@ //! //! (Specifically, REDIST protocol). -use alloc::boxed::Box; -use alloc::collections::{BTreeMap, BTreeSet}; -use alloc::vec::Vec; -use core::fmt::Debug; -use core::marker::PhantomData; +use alloc::{ + boxed::Box, + collections::{BTreeMap, BTreeSet}, + string::String, + vec::Vec, +}; +use core::{fmt::Debug, marker::PhantomData}; use k256::ecdsa::VerifyingKey; +use manul::protocol::{ + Artifact, BoxedRound, Deserializer, DirectMessage, EchoBroadcast, EchoRoundParticipation, + EntryPoint, FinalizeOutcome, LocalError, NormalBroadcast, PartyId, Payload, Protocol, + ProtocolError, ProtocolMessagePart, ProtocolValidationError, ReceiveError, Round, RoundId, + Serializer, +}; use rand_core::CryptoRngCore; use secrecy::{ExposeSecret, SecretBox}; use serde::{Deserialize, Serialize}; use super::ThresholdKeyShare; -use crate::curve::{Point, Scalar}; -use crate::rounds::{ - FinalizableToResult, FinalizationRequirement, FinalizeError, FirstRound, InitError, - ProtocolResult, Round, ToResult, +use crate::{ + curve::{Point, Scalar}, + tools::{ + sss::{ + interpolation_coeff, shamir_join_points, shamir_join_scalars, Polynomial, + PublicPolynomial, ShareId, + }, + DowncastMap, Without, + }, + SchemeParams, }; -use crate::tools::sss::{ - interpolation_coeff, shamir_join_points, shamir_join_scalars, Polynomial, PublicPolynomial, - ShareId, -}; -use crate::SchemeParams; -/// The outcomes of KeyResharing protocol. +/// A protocol for modifying the set of owners of a shared secret key. #[derive(Debug)] -pub struct KeyResharingResult(PhantomData

, PhantomData); +pub struct KeyResharingProtocol(PhantomData<(P, I)>); -impl ProtocolResult for KeyResharingResult { - type Success = Option>; - type ProvableError = KeyResharingError; - type CorrectnessProof = (); +impl Protocol for KeyResharingProtocol { + type Result = Option>; + type ProtocolError = KeyResharingError; } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum KeyResharingError { UnexpectedSender, SubshareMismatch, } +impl ProtocolError for KeyResharingError { + fn description(&self) -> String { + unimplemented!() + } + + fn required_direct_messages(&self) -> BTreeSet { + unimplemented!() + } + + fn required_echo_broadcasts(&self) -> BTreeSet { + unimplemented!() + } + + fn required_combined_echos(&self) -> BTreeSet { + unimplemented!() + } + + fn verify_messages_constitute_error( + &self, + _deserializer: &Deserializer, + _echo_broadcast: &EchoBroadcast, + _normal_broadcat: &NormalBroadcast, + _direct_message: &DirectMessage, + _echo_broadcasts: &BTreeMap, + _normal_broadcasts: &BTreeMap, + _direct_messages: &BTreeMap, + _combined_echos: &BTreeMap>, + ) -> Result<(), ProtocolValidationError> { + unimplemented!() + } +} + /// Old share data. -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct OldHolder { /// The threshold key share. pub key_share: ThresholdKeyShare, } /// New share data. -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct NewHolder { /// The verifying key the old shares add up to. pub verifying_key: VerifyingKey, @@ -62,78 +102,89 @@ pub struct NewHolder { pub old_holders: BTreeSet, } -/// Inputs for the Key Resharing protocol. -#[derive(Clone)] -pub struct KeyResharingInputs { +/// An entry point for the [`KeyResharingProtocol`]. +#[derive(Debug, Clone)] +pub struct KeyResharing { /// Old share data if the node holds it, or `None`. - pub old_holder: Option>, + old_holder: Option>, /// New share data if the node is one of the new holders, or `None`. - pub new_holder: Option>, + new_holder: Option>, /// The new holders of the shares. - pub new_holders: BTreeSet, + new_holders: BTreeSet, /// The new threshold. - pub new_threshold: usize, -} - -struct OldHolderData { - share_id: ShareId, - polynomial: Polynomial, - public_polynomial: PublicPolynomial, + new_threshold: usize, } -struct NewHolderData { - inputs: NewHolder, +impl KeyResharing { + /// Creates a new entry point for the node with the given ID. + pub fn new( + old_holder: Option>, + new_holder: Option>, + new_holders: BTreeSet, + new_threshold: usize, + ) -> Self { + Self { + old_holder, + new_holder, + new_holders, + new_threshold, + } + } } -pub struct Round1 { - old_holder: Option, - new_holder: Option>, - new_share_ids: BTreeMap, - new_threshold: usize, - other_ids: BTreeSet, - my_id: I, - message_destinations: BTreeSet, - phantom: PhantomData

, -} +impl EntryPoint for KeyResharing { + type Protocol = KeyResharingProtocol; -impl FirstRound for Round1 { - type Inputs = KeyResharingInputs; - fn new( + fn make_round( + self, rng: &mut impl CryptoRngCore, _shared_randomness: &[u8], - other_ids: BTreeSet, - my_id: I, - inputs: Self::Inputs, - ) -> Result { + id: &I, + ) -> Result, LocalError> { // Start new share indices from 1. - let new_share_ids = inputs + let new_share_ids = self .new_holders .iter() .enumerate() .map(|(idx, id)| (id.clone(), ShareId::new(idx + 1))) .collect(); - if inputs.old_holder.is_none() && inputs.new_holder.is_none() { - return Err(InitError( - "Either old holder or new holder data must be provided".into(), + if self.old_holder.is_none() && self.new_holder.is_none() { + return Err(LocalError::new( + "Either old holder or new holder data must be provided", )); }; - let message_destinations = if inputs.old_holder.is_some() { + let message_destinations = if self.old_holder.is_some() { // It is possible that a party is both an old holder and a new holder. // This will be processed separately. - let mut new_holders_except_me = inputs.new_holders; - new_holders_except_me.remove(&my_id); - new_holders_except_me + self.new_holders.clone().without(id) + } else { + BTreeSet::new() + }; + + let expecting_messages_from = if let Some(new_holder) = self.new_holder.as_ref() { + // TODO: we only need `old_threshold` of them, but it is not supported yet in `manul`. + new_holder.old_holders.clone().without(id) } else { BTreeSet::new() }; - let old_holder = inputs.old_holder.map(|old_holder| { + let echo_round_participation = if self.old_holder.is_some() && self.new_holder.is_none() { + EchoRoundParticipation::Send + } else if self.new_holder.is_some() && self.old_holder.is_none() { + EchoRoundParticipation::Receive { + echo_targets: self.new_holders.without(id), + } + } else { + EchoRoundParticipation::Default + }; + + let old_holder = self.old_holder.map(|old_holder| { let polynomial = Polynomial::random( rng, old_holder.key_share.secret_share.expose_secret(), - inputs.new_threshold, + self.new_threshold, ); let public_polynomial = polynomial.public(); @@ -144,170 +195,175 @@ impl FirstRound for Round1 { } }); - let new_holder = inputs + let new_holder = self .new_holder .map(|new_holder| NewHolderData { inputs: new_holder }); - Ok(Round1 { + Ok(BoxedRound::new_dynamic(Round1 { old_holder, new_holder, new_share_ids, - new_threshold: inputs.new_threshold, - other_ids, - my_id, + new_threshold: self.new_threshold, + my_id: id.clone(), message_destinations, + expecting_messages_from, + echo_round_participation, phantom: PhantomData, - }) + })) } } +#[derive(Debug)] +struct OldHolderData { + share_id: ShareId, + polynomial: Polynomial, + public_polynomial: PublicPolynomial, +} + +#[derive(Debug)] +struct NewHolderData { + inputs: NewHolder, +} + +#[derive(Debug)] +struct Round1 { + old_holder: Option, + new_holder: Option>, + new_share_ids: BTreeMap, + new_threshold: usize, + my_id: I, + message_destinations: BTreeSet, + expecting_messages_from: BTreeSet, + echo_round_participation: EchoRoundParticipation, + phantom: PhantomData

, +} + #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Round1BroadcastMessage { +struct Round1BroadcastMessage { public_polynomial: PublicPolynomial, old_share_id: ShareId, } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Round1DirectMessage { +struct Round1DirectMessage { subshare: Scalar, } -pub struct Round1Payload { +struct Round1Payload { subshare: Scalar, public_polynomial: PublicPolynomial, old_share_id: ShareId, } -impl Round for Round1 { - type Type = ToResult; - type Result = KeyResharingResult; - const ROUND_NUM: u8 = 1; - const NEXT_ROUND_NUM: Option = None; +impl Round for Round1 { + type Protocol = KeyResharingProtocol; - fn other_ids(&self) -> &BTreeSet { - &self.other_ids + fn id(&self) -> RoundId { + RoundId::new(1) } - fn my_id(&self) -> &I { - &self.my_id + fn possible_next_rounds(&self) -> BTreeSet { + BTreeSet::new() } - const REQUIRES_ECHO: bool = true; - type BroadcastMessage = Round1BroadcastMessage; - type DirectMessage = Round1DirectMessage; - type Payload = Round1Payload; - type Artifact = (); - fn message_destinations(&self) -> &BTreeSet { &self.message_destinations } - fn make_broadcast_message( + fn expecting_messages_from(&self) -> &BTreeSet { + &self.expecting_messages_from + } + + fn echo_round_participation(&self) -> EchoRoundParticipation { + self.echo_round_participation.clone() + } + + fn make_echo_broadcast( &self, _rng: &mut impl CryptoRngCore, - ) -> Option { - self.old_holder - .as_ref() - .map(|old_holder| Round1BroadcastMessage { - public_polynomial: old_holder.public_polynomial.clone(), - old_share_id: old_holder.share_id, - }) + serializer: &Serializer, + ) -> Result { + if let Some(old_holder) = self.old_holder.as_ref() { + EchoBroadcast::new( + serializer, + Round1BroadcastMessage { + public_polynomial: old_holder.public_polynomial.clone(), + old_share_id: old_holder.share_id, + }, + ) + } else { + Ok(EchoBroadcast::none()) + } } fn make_direct_message( &self, _rng: &mut impl CryptoRngCore, + serializer: &Serializer, destination: &I, - ) -> (Self::DirectMessage, Self::Artifact) { + ) -> Result<(DirectMessage, Option), LocalError> { if let Some(old_holder) = self.old_holder.as_ref() { let subshare = old_holder .polynomial .evaluate(&self.new_share_ids[destination]); - (Round1DirectMessage { subshare }, ()) + let dm = DirectMessage::new(serializer, Round1DirectMessage { subshare })?; + Ok((dm, None)) } else { - // TODO (#54): this should be prevented by type system - panic!("This node does not send messages in this round"); + Ok((DirectMessage::none(), None)) } } - fn verify_message( + fn receive_message( &self, _rng: &mut impl CryptoRngCore, + deserializer: &Deserializer, from: &I, - broadcast_msg: Self::BroadcastMessage, - direct_msg: Self::DirectMessage, - ) -> Result::ProvableError> { + echo_broadcast: EchoBroadcast, + normal_broadcast: NormalBroadcast, + direct_message: DirectMessage, + ) -> Result> { + normal_broadcast.assert_is_none()?; + let echo_broadcast = echo_broadcast.deserialize::(deserializer)?; + let direct_message = direct_message.deserialize::(deserializer)?; + if let Some(new_holder) = self.new_holder.as_ref() { if new_holder.inputs.old_holders.contains(from) { - let public_subshare_from_poly = broadcast_msg + let public_subshare_from_poly = echo_broadcast .public_polynomial - .evaluate(&self.new_share_ids[self.my_id()]); - let public_subshare_from_private = direct_msg.subshare.mul_by_generator(); + .evaluate(&self.new_share_ids[&self.my_id]); + let public_subshare_from_private = direct_message.subshare.mul_by_generator(); // Check that the public polynomial sent in the broadcast corresponds to the secret share // sent in the direct message. if public_subshare_from_poly != public_subshare_from_private { - return Err(KeyResharingError::SubshareMismatch); + return Err(ReceiveError::protocol(KeyResharingError::SubshareMismatch)); } - return Ok(Round1Payload { - subshare: direct_msg.subshare, - public_polynomial: broadcast_msg.public_polynomial, - old_share_id: broadcast_msg.old_share_id, - }); + return Ok(Payload::new(Round1Payload { + subshare: direct_message.subshare, + public_polynomial: echo_broadcast.public_polynomial, + old_share_id: echo_broadcast.old_share_id, + })); } } - Err(KeyResharingError::UnexpectedSender) + Err(ReceiveError::protocol(KeyResharingError::UnexpectedSender)) } - fn finalization_requirement() -> FinalizationRequirement { - FinalizationRequirement::Custom - } - - fn can_finalize(&self, received: &BTreeSet) -> bool { - if let Some(new_holder) = self.new_holder.as_ref() { - let threshold = if self.old_holder.is_some() && self.new_holder.is_some() { - new_holder.inputs.old_threshold - 1 - } else { - new_holder.inputs.old_threshold - }; - received.len() >= threshold - } else { - true - } - } - - fn missing_messages(&self, received: &BTreeSet) -> BTreeSet { - if let Some(new_holder) = self.new_holder.as_ref() { - new_holder - .inputs - .old_holders - .iter() - .filter(|id| !received.contains(id) && id != &self.my_id()) - .cloned() - .collect() - } else { - BTreeSet::new() - } - } -} - -impl FinalizableToResult for Round1 { - fn finalize_to_result( + fn finalize( self, _rng: &mut impl CryptoRngCore, - payloads: BTreeMap>::Payload>, - _artifacts: BTreeMap>::Artifact>, - ) -> Result<::Success, FinalizeError> { + payloads: BTreeMap, + _artifacts: BTreeMap, + ) -> Result, LocalError> { // If this party is not a new holder, exit. let new_holder = match self.new_holder.as_ref() { Some(new_holder) => new_holder, - None => return Ok(None), + None => return Ok(FinalizeOutcome::Result(None)), }; - let share_id = self.new_share_ids[self.my_id()]; + let mut payloads = payloads.downcast_all::()?; - let mut payloads = payloads; + let share_id = self.new_share_ids[&self.my_id]; // If this node is both an old and a new holder, // add a simulated payload to the mapping, as if it sent a message to itself. @@ -319,7 +375,7 @@ impl FinalizableToResult for Round1< public_polynomial: old_holder.public_polynomial.clone(), old_share_id: old_holder.share_id, }; - payloads.insert(self.my_id().clone(), my_payload); + payloads.insert(self.my_id.clone(), my_payload); } } @@ -367,14 +423,14 @@ impl FinalizableToResult for Round1< }) .collect(); - Ok(Some(ThresholdKeyShare { - owner: self.my_id().clone(), + Ok(FinalizeOutcome::Result(Some(ThresholdKeyShare { + owner: self.my_id.clone(), threshold: self.new_threshold as u32, secret_share, share_ids: self.new_share_ids, public_shares, phantom: PhantomData, - })) + }))) } } @@ -382,114 +438,91 @@ impl FinalizableToResult for Round1< mod tests { use alloc::collections::{BTreeMap, BTreeSet}; - use rand_core::{OsRng, RngCore}; + use manul::{ + dev::{run_sync, BinaryFormat, TestSessionParams, TestSigner, TestVerifier}, + session::signature::Keypair, + }; + use rand_core::OsRng; use secrecy::ExposeSecret; - use super::ThresholdKeyShare; - use super::{KeyResharingInputs, NewHolder, OldHolder, Round1}; - use crate::rounds::{ - test_utils::{step_result, step_round, Id}, - FirstRound, - }; + use super::{KeyResharing, NewHolder, OldHolder, ThresholdKeyShare}; use crate::TestParams; #[test] fn execute_key_reshare() { - let mut shared_randomness = [0u8; 32]; - OsRng.fill_bytes(&mut shared_randomness); - - let ids = [Id(0), Id(1), Id(2), Id(3)]; + let signers = (0..4).map(TestSigner::new).collect::>(); + let ids = signers + .iter() + .map(|signer| signer.verifying_key()) + .collect::>(); let old_holders = BTreeSet::from([ids[0], ids[1], ids[2]]); let new_holders = BTreeSet::from([ids[1], ids[2], ids[3]]); - let old_key_shares = - ThresholdKeyShare::::new_centralized(&mut OsRng, &old_holders, 2, None); + let old_key_shares = ThresholdKeyShare::::new_centralized( + &mut OsRng, + &old_holders, + 2, + None, + ); let old_vkey = old_key_shares[&ids[0]].verifying_key(); + let new_threshold = 2; + + let party0 = KeyResharing::new( + Some(OldHolder { + key_share: old_key_shares[&ids[0]].clone(), + }), + None, + new_holders.clone(), + new_threshold, + ); + + let party1 = KeyResharing::new( + Some(OldHolder { + key_share: old_key_shares[&ids[1]].clone(), + }), + Some(NewHolder { + verifying_key: old_vkey, + old_threshold: 2, + old_holders: old_holders.clone(), + }), + new_holders.clone(), + new_threshold, + ); + + let party2 = KeyResharing::new( + Some(OldHolder { + key_share: old_key_shares[&ids[2]].clone(), + }), + Some(NewHolder { + verifying_key: old_vkey, + old_threshold: 2, + old_holders: old_holders.clone(), + }), + new_holders.clone(), + new_threshold, + ); + + let party3 = KeyResharing::new( + None, + Some(NewHolder { + verifying_key: old_vkey, + old_threshold: 2, + old_holders: old_holders.clone(), + }), + new_holders.clone(), + new_threshold, + ); + + let entry_points = signers + .into_iter() + .zip([party0, party1, party2, party3]) + .collect::>(); - let party0 = Round1::new( - &mut OsRng, - &shared_randomness, - BTreeSet::from([ids[1], ids[2], ids[3]]), - ids[0], - KeyResharingInputs { - old_holder: Some(OldHolder { - key_share: old_key_shares[&ids[0]].clone(), - }), - new_holder: None, - new_holders: new_holders.clone(), - new_threshold: 2, - }, - ) - .unwrap(); - - let party1 = Round1::new( - &mut OsRng, - &shared_randomness, - BTreeSet::from([ids[0], ids[2], ids[3]]), - ids[1], - KeyResharingInputs { - old_holder: Some(OldHolder { - key_share: old_key_shares[&ids[1]].clone(), - }), - new_holder: Some(NewHolder { - verifying_key: old_vkey, - old_threshold: 2, - old_holders: old_holders.clone(), - }), - new_holders: new_holders.clone(), - new_threshold: 2, - }, - ) - .unwrap(); - - let party2 = Round1::new( - &mut OsRng, - &shared_randomness, - BTreeSet::from([ids[0], ids[1], ids[3]]), - ids[2], - KeyResharingInputs { - old_holder: Some(OldHolder { - key_share: old_key_shares[&ids[2]].clone(), - }), - new_holder: Some(NewHolder { - verifying_key: old_vkey, - old_threshold: 2, - old_holders: old_holders.clone(), - }), - new_holders: new_holders.clone(), - new_threshold: 2, - }, - ) - .unwrap(); - - let party3 = Round1::new( - &mut OsRng, - &shared_randomness, - BTreeSet::from([ids[0], ids[1], ids[2]]), - ids[3], - KeyResharingInputs { - old_holder: None, - new_holder: Some(NewHolder { - verifying_key: old_vkey, - old_threshold: 2, - old_holders: old_holders.clone(), - }), - new_holders: new_holders.clone(), - new_threshold: 2, - }, - ) - .unwrap(); - - let r1 = BTreeMap::from([ - (ids[0], party0), - (ids[1], party1), - (ids[2], party2), - (ids[3], party3), - ]); - - let r1a = step_round(&mut OsRng, r1).unwrap(); - let shares = step_result(&mut OsRng, r1a).unwrap(); + let shares = run_sync::<_, TestSessionParams>(&mut OsRng, entry_points) + .unwrap() + .results() + .unwrap(); // Check that the party that is not among the new holders gets None as a result assert!(shares[&ids[0]].is_none()); diff --git a/synedrion/tests/sessions.rs b/synedrion/tests/sessions.rs deleted file mode 100644 index 3d9a81c2..00000000 --- a/synedrion/tests/sessions.rs +++ /dev/null @@ -1,275 +0,0 @@ -use std::collections::{BTreeMap, BTreeSet}; - -use k256::ecdsa::{signature::hazmat::PrehashVerifier, Signature, SigningKey, VerifyingKey}; -use rand::Rng; -use rand_core::OsRng; -use tokio::sync::mpsc; -use tokio::time::{sleep, Duration}; - -use synedrion::{ - make_interactive_signing_session, make_key_gen_session, AuxInfo, FinalizeOutcome, KeyShare, - MessageBundle, ProtocolResult, Session, SessionId, TestParams, -}; - -type MessageOut = (VerifyingKey, VerifyingKey, MessageBundle); -type MessageIn = (VerifyingKey, MessageBundle); - -fn key_to_str(key: &VerifyingKey) -> String { - hex::encode(&key.to_encoded_point(true).as_bytes()[1..5]) -} - -async fn run_session( - tx: mpsc::Sender, - rx: mpsc::Receiver, - session: Session, -) -> Res::Success { - let mut rx = rx; - - let mut session = session; - let mut cached_messages = Vec::new(); - - let key = session.verifier(); - let key_str = key_to_str(&key); - - loop { - println!( - "{key_str}: *** starting round {:?} ***", - session.current_round() - ); - - // This is kept in the main task since it's mutable, - // and we don't want to bother with synchronization. - let mut accum = session.make_accumulator(); - - // Note: generating/sending messages and verifying newly received messages - // can be done in parallel, with the results being assembled into `accum` - // sequentially in the host task. - - let destinations = session.message_destinations(); - for destination in destinations.iter() { - // In production usage, this will happen in a spawned task - // (since it can take some time to create a message), - // and the artifact will be sent back to the host task - // to be added to the accumulator. - let (message, artifact) = session.make_message(&mut OsRng, destination).unwrap(); - println!( - "{key_str}: sending a message to {}", - key_to_str(destination) - ); - tx.send((key, *destination, message)).await.unwrap(); - - // This will happen in a host task - accum.add_artifact(artifact).unwrap(); - } - - for preprocessed in cached_messages { - // In production usage, this will happen in a spawned task. - println!("{key_str}: applying a cached message"); - let result = session.process_message(&mut OsRng, preprocessed).unwrap(); - - // This will happen in a host task. - accum.add_processed_message(result).unwrap().unwrap(); - } - - while !session.can_finalize(&accum).unwrap() { - // This can be checked if a timeout expired, to see which nodes have not responded yet. - let unresponsive_parties = session.missing_messages(&accum).unwrap(); - assert!(!unresponsive_parties.is_empty()); - - println!("{key_str}: waiting for a message"); - let (from, message) = rx.recv().await.unwrap(); - - // Perform quick checks before proceeding with the verification. - let preprocessed = session - .preprocess_message(&mut accum, &from, message) - .unwrap(); - - if let Some(preprocessed) = preprocessed { - // In production usage, this will happen in a spawned task. - println!("{key_str}: applying a message from {}", key_to_str(&from)); - let result = session.process_message(&mut OsRng, preprocessed).unwrap(); - - // This will happen in a host task. - accum.add_processed_message(result).unwrap().unwrap(); - } - } - - println!("{key_str}: finalizing the round"); - - match session.finalize_round(&mut OsRng, accum).unwrap() { - FinalizeOutcome::Success(res) => break res, - FinalizeOutcome::AnotherRound { - session: new_session, - cached_messages: new_cached_messages, - } => { - session = new_session; - cached_messages = new_cached_messages; - } - } - } -} - -async fn message_dispatcher( - txs: BTreeMap>, - rx: mpsc::Receiver, -) { - let mut rx = rx; - let mut messages = Vec::::new(); - loop { - let msg = match rx.recv().await { - Some(msg) => msg, - None => break, - }; - messages.push(msg); - - while let Ok(msg) = rx.try_recv() { - messages.push(msg) - } - - while !messages.is_empty() { - // Pull a random message from the list, - // to increase the chances that they are delivered out of order. - let message_idx = rand::thread_rng().gen_range(0..messages.len()); - let (id_from, id_to, message) = messages.swap_remove(message_idx); - - txs[&id_to].send((id_from, message)).await.unwrap(); - - // Give up execution so that the tasks could process messages. - sleep(Duration::from_millis(0)).await; - - if let Ok(msg) = rx.try_recv() { - messages.push(msg); - }; - } - } -} - -fn make_signers(num_parties: usize) -> (Vec, Vec) { - let signers = (0..num_parties) - .map(|_| SigningKey::random(&mut OsRng)) - .collect::>(); - let verifiers = signers - .iter() - .map(|signer| *signer.verifying_key()) - .collect::>(); - (signers, verifiers) -} - -async fn run_nodes( - sessions: Vec>, -) -> Vec -where - Res: ProtocolResult + Send + 'static, - Res::Success: Send, -{ - let num_parties = sessions.len(); - - let (dispatcher_tx, dispatcher_rx) = mpsc::channel::(100); - - let channels = (0..num_parties).map(|_| mpsc::channel::(100)); - let (txs, rxs): (Vec>, Vec>) = - channels.unzip(); - let tx_map = sessions - .iter() - .map(|session| session.verifier()) - .zip(txs.into_iter()) - .collect(); - - let dispatcher_task = message_dispatcher(tx_map, dispatcher_rx); - let dispatcher = tokio::spawn(dispatcher_task); - - let handles: Vec> = rxs - .into_iter() - .zip(sessions.into_iter()) - .map(|(rx, session)| { - let node_task = run_session(dispatcher_tx.clone(), rx, session); - tokio::spawn(node_task) - }) - .collect(); - - // Drop the last copy of the dispatcher's incoming channel so that it could finish. - drop(dispatcher_tx); - - let mut results = Vec::with_capacity(num_parties); - for handle in handles { - results.push(handle.await.unwrap()); - } - - dispatcher.await.unwrap(); - - results -} - -#[tokio::test] -async fn keygen_and_aux() { - let num_parties = 3; - let (signers, verifiers) = make_signers(num_parties); - let verifiers_set = BTreeSet::from_iter(verifiers.iter().cloned()); - - let session_id = SessionId::from_seed(b"1234567890"); - - let sessions = signers - .into_iter() - .map(|signer| { - make_key_gen_session::( - &mut OsRng, - session_id, - signer, - &verifiers_set, - ) - .unwrap() - }) - .collect(); - - let (key_shares, _aux_infos): (Vec<_>, Vec<_>) = run_nodes(sessions).await.into_iter().unzip(); - - for (idx, key_share) in key_shares.iter().enumerate() { - assert_eq!(key_share.owner(), &verifiers[idx]); - assert_eq!(key_share.all_parties(), verifiers_set); - assert_eq!(key_share.verifying_key(), key_shares[0].verifying_key()); - } -} - -#[tokio::test] -async fn interactive_signing() { - let num_parties = 3; - let (signers, verifiers) = make_signers(num_parties); - let verifiers_set = BTreeSet::from_iter(verifiers.iter().cloned()); - - let key_shares = - KeyShare::::new_centralized(&mut OsRng, &verifiers_set, None); - let aux_infos = - AuxInfo::::new_centralized(&mut OsRng, &verifiers_set); - - let session_id = SessionId::from_seed(b"1234567890"); - let message = b"abcdefghijklmnopqrstuvwxyz123456"; - - let sessions = (0..num_parties) - .map(|idx| { - make_interactive_signing_session::<_, Signature, _, _>( - &mut OsRng, - session_id, - signers[idx].clone(), - &verifiers_set, - &key_shares[&verifiers[idx]], - &aux_infos[&verifiers[idx]], - message, - ) - .unwrap() - }) - .collect(); - - let signatures = run_nodes(sessions).await; - - for signature in signatures { - let (sig, rec_id) = signature.to_backend(); - let vkey = key_shares[&verifiers[0]].verifying_key().unwrap(); - - // Check that the signature can be verified - vkey.verify_prehash(message, &sig).unwrap(); - - // Check that the key can be recovered - let recovered_key = VerifyingKey::recover_from_prehash(message, &sig, rec_id).unwrap(); - assert_eq!(recovered_key, vkey); - } -} diff --git a/synedrion/tests/threshold.rs b/synedrion/tests/threshold.rs index 3b117bfb..3bd3d239 100644 --- a/synedrion/tests/threshold.rs +++ b/synedrion/tests/threshold.rs @@ -1,208 +1,29 @@ use std::collections::{BTreeMap, BTreeSet}; -use k256::ecdsa::{signature::hazmat::PrehashVerifier, Signature, SigningKey, VerifyingKey}; -use rand::Rng; +use k256::ecdsa::{signature::hazmat::PrehashVerifier, VerifyingKey}; +use manul::{ + dev::{run_sync, BinaryFormat, TestSessionParams, TestSigner, TestVerifier}, + session::signature::Keypair, +}; use rand_core::OsRng; -use tokio::sync::mpsc; -use tokio::time::{sleep, Duration}; - use synedrion::{ - make_aux_gen_session, make_interactive_signing_session, make_key_init_session, - make_key_resharing_session, DeriveChildKey, FinalizeOutcome, KeyResharingInputs, MessageBundle, - NewHolder, OldHolder, ProtocolResult, Session, SessionId, TestParams, ThresholdKeyShare, + AuxGen, DeriveChildKey, InteractiveSigning, KeyInit, KeyResharing, NewHolder, OldHolder, + TestParams, ThresholdKeyShare, }; -type MessageOut = (VerifyingKey, VerifyingKey, MessageBundle); -type MessageIn = (VerifyingKey, MessageBundle); - -fn key_to_str(key: &VerifyingKey) -> String { - hex::encode(&key.to_encoded_point(true).as_bytes()[1..5]) -} - -async fn run_session( - tx: mpsc::Sender, - rx: mpsc::Receiver, - session: Session, -) -> Res::Success { - let mut rx = rx; - - let mut session = session; - let mut cached_messages = Vec::new(); - - let key = session.verifier(); - let key_str = key_to_str(&key); - - loop { - println!( - "{key_str}: *** starting round {:?} ***", - session.current_round() - ); - - // This is kept in the main task since it's mutable, - // and we don't want to bother with synchronization. - let mut accum = session.make_accumulator(); - - // Note: generating/sending messages and verifying newly received messages - // can be done in parallel, with the results being assembled into `accum` - // sequentially in the host task. - - let destinations = session.message_destinations(); - for destination in destinations.iter() { - // In production usage, this will happen in a spawned task - // (since it can take some time to create a message), - // and the artifact will be sent back to the host task - // to be added to the accumulator. - let (message, artifact) = session.make_message(&mut OsRng, destination).unwrap(); - println!( - "{key_str}: sending a message to {}", - key_to_str(destination) - ); - tx.send((key, *destination, message)).await.unwrap(); - - // This will happen in a host task - accum.add_artifact(artifact).unwrap(); - } - - for preprocessed in cached_messages { - // In production usage, this will happen in a spawned task. - println!("{key_str}: applying a cached message"); - let result = session.process_message(&mut OsRng, preprocessed).unwrap(); - - // This will happen in a host task. - accum.add_processed_message(result).unwrap().unwrap(); - } - - while !session.can_finalize(&accum).unwrap() { - // This can be checked if a timeout expired, to see which nodes have not responded yet. - let unresponsive_parties = session.missing_messages(&accum).unwrap(); - assert!(!unresponsive_parties.is_empty()); - - println!("{key_str}: waiting for a message"); - let (from, message) = rx.recv().await.unwrap(); - - // Perform quick checks before proceeding with the verification. - let preprocessed = session - .preprocess_message(&mut accum, &from, message) - .unwrap(); - - if let Some(preprocessed) = preprocessed { - // In production usage, this will happen in a spawned task. - println!("{key_str}: applying a message from {}", key_to_str(&from)); - let result = session.process_message(&mut OsRng, preprocessed).unwrap(); - - // This will happen in a host task. - accum.add_processed_message(result).unwrap().unwrap(); - } - } - - println!("{key_str}: finalizing the round"); - - match session.finalize_round(&mut OsRng, accum).unwrap() { - FinalizeOutcome::Success(res) => break res, - FinalizeOutcome::AnotherRound { - session: new_session, - cached_messages: new_cached_messages, - } => { - session = new_session; - cached_messages = new_cached_messages; - } - } - } -} - -async fn message_dispatcher( - txs: BTreeMap>, - rx: mpsc::Receiver, -) { - let mut rx = rx; - let mut messages = Vec::::new(); - loop { - let msg = match rx.recv().await { - Some(msg) => msg, - None => break, - }; - messages.push(msg); - - while let Ok(msg) = rx.try_recv() { - messages.push(msg) - } - - while !messages.is_empty() { - // Pull a random message from the list, - // to increase the chances that they are delivered out of order. - let message_idx = rand::thread_rng().gen_range(0..messages.len()); - let (id_from, id_to, message) = messages.swap_remove(message_idx); - - txs[&id_to].send((id_from, message)).await.unwrap(); - - // Give up execution so that the tasks could process messages. - sleep(Duration::from_millis(0)).await; - - if let Ok(msg) = rx.try_recv() { - messages.push(msg); - }; - } - } -} - -fn make_signers(num_parties: usize) -> (Vec, Vec) { +fn make_signers(num_parties: usize) -> (Vec, Vec) { let signers = (0..num_parties) - .map(|_| SigningKey::random(&mut OsRng)) + .map(|idx| TestSigner::new(idx as u8)) .collect::>(); let verifiers = signers .iter() - .map(|signer| *signer.verifying_key()) + .map(|signer| signer.verifying_key()) .collect::>(); (signers, verifiers) } -async fn run_nodes( - sessions: Vec>, -) -> Vec -where - Res: ProtocolResult + Send + 'static, - Res::Success: Send, -{ - let num_parties = sessions.len(); - - let (dispatcher_tx, dispatcher_rx) = mpsc::channel::(100); - - let channels = (0..num_parties).map(|_| mpsc::channel::(100)); - let (txs, rxs): (Vec>, Vec>) = - channels.unzip(); - let tx_map = sessions - .iter() - .map(|session| session.verifier()) - .zip(txs.into_iter()) - .collect(); - - let dispatcher_task = message_dispatcher(tx_map, dispatcher_rx); - let dispatcher = tokio::spawn(dispatcher_task); - - let handles: Vec> = rxs - .into_iter() - .zip(sessions.into_iter()) - .map(|(rx, session)| { - let node_task = run_session(dispatcher_tx.clone(), rx, session); - tokio::spawn(node_task) - }) - .collect(); - - // Drop the last copy of the dispatcher's incoming channel so that it can finish. - drop(dispatcher_tx); - - let mut results = Vec::with_capacity(num_parties); - for handle in handles { - results.push(handle.await.unwrap()); - } - - dispatcher.await.unwrap(); - - results -} - -#[tokio::test] -async fn full_sequence() { +#[test] +fn full_sequence() { let t = 3; let n = 5; let (signers, verifiers) = make_signers(n); @@ -210,180 +31,167 @@ async fn full_sequence() { let all_verifiers = BTreeSet::from_iter(verifiers.iter().cloned()); let old_holders = BTreeSet::from_iter(verifiers.iter().cloned().take(t)); - let session_id = SessionId::from_seed(b"1234567890"); - // Use first `t` nodes for the initial t-of-t key generation - let sessions = signers[..t] + let entry_points = signers[..t] .iter() .map(|signer| { - make_key_init_session::( - &mut OsRng, - session_id, - signer.clone(), - &old_holders, - ) - .unwrap() + let entry_point = + KeyInit::::new(old_holders.clone()).unwrap(); + (*signer, entry_point) }) .collect(); println!("\nRunning KeyInit\n"); - let key_shares = run_nodes(sessions).await; + let key_shares = run_sync::<_, TestSessionParams>(&mut OsRng, entry_points) + .unwrap() + .results() + .unwrap(); // Convert to t-of-t threshold keyshares let t_key_shares = key_shares - .iter() - .map(ThresholdKeyShare::from_key_share) - .collect::>(); + .into_iter() + .map(|(verifier, key_share)| (verifier, ThresholdKeyShare::from_key_share(&key_share))) + .collect::>(); // Derive child shares let path = "m/0/2/1/4/2".parse().unwrap(); let child_key_shares = t_key_shares .iter() - .map(|key_share| key_share.derive_bip32(&path).unwrap()) - .collect::>(); + .map(|(verifier, key_share)| (verifier, key_share.derive_bip32(&path).unwrap())) + .collect::>(); // The full verifying key can be obtained both from the original key shares and child key shares - let child_vkey = t_key_shares[0].derive_verifying_key_bip32(&path).unwrap(); - assert_eq!(child_vkey, child_key_shares[0].verifying_key()); + let child_vkey = t_key_shares[&verifiers[0]] + .derive_verifying_key_bip32(&path) + .unwrap(); + assert_eq!(child_vkey, child_key_shares[&verifiers[0]].verifying_key()); // Reshare to `n` nodes // This will need to be published so that new holders can see it and verify the received data let new_holder = NewHolder { - verifying_key: t_key_shares[0].verifying_key(), - old_threshold: t_key_shares[0].threshold(), + verifying_key: t_key_shares[&verifiers[0]].verifying_key(), + old_threshold: t_key_shares[&verifiers[0]].threshold(), old_holders, }; // Old holders' sessions (which will also hold the newly reshared parts) - let mut sessions = (0..t) + let mut entry_points = (0..t) .map(|idx| { - let inputs = KeyResharingInputs { - old_holder: Some(OldHolder { - key_share: t_key_shares[idx].clone(), + let entry_point = KeyResharing::::new( + Some(OldHolder { + key_share: t_key_shares[&verifiers[idx]].clone(), }), - new_holder: Some(new_holder.clone()), - new_holders: all_verifiers.clone(), - new_threshold: t, - }; - make_key_resharing_session::( - &mut OsRng, - session_id, - signers[idx].clone(), - &all_verifiers, - inputs, - ) - .unwrap() + Some(new_holder.clone()), + all_verifiers.clone(), + t, + ); + (signers[idx], entry_point) }) .collect::>(); // New holders' sessions - let new_holder_sessions = (t..n) + let new_holder_entry_points = (t..n) .map(|idx| { - let inputs = KeyResharingInputs { - old_holder: None, - new_holder: Some(new_holder.clone()), - new_holders: all_verifiers.clone(), - new_threshold: t, - }; - make_key_resharing_session::( - &mut OsRng, - session_id, - signers[idx].clone(), - &all_verifiers, - inputs, - ) - .unwrap() + let entry_point = KeyResharing::::new( + None, + Some(new_holder.clone()), + all_verifiers.clone(), + t, + ); + (signers[idx], entry_point) }) .collect::>(); - sessions.extend(new_holder_sessions.into_iter()); + entry_points.extend(new_holder_entry_points); println!("\nRunning KeyReshare\n"); - let new_t_key_shares = run_nodes(sessions).await; + let new_t_key_shares = run_sync::<_, TestSessionParams>(&mut OsRng, entry_points) + .unwrap() + .results() + .unwrap(); + // All the nodes are holders now, we can unwrap the Options. let new_t_key_shares = new_t_key_shares .into_iter() - .map(|key_share| key_share.unwrap()) - .collect::>(); + .map(|(verifier, key_share)| (verifier, key_share.unwrap())) + .collect::>(); assert_eq!( - new_t_key_shares[0].verifying_key(), - t_key_shares[0].verifying_key() + new_t_key_shares[&verifiers[0]].verifying_key(), + t_key_shares[&verifiers[0]].verifying_key() ); // Check that resharing did not change the derived child key - let child_vkey_after_resharing = new_t_key_shares[0] + let child_vkey_after_resharing = new_t_key_shares[&verifiers[0]] .derive_verifying_key_bip32(&path) .unwrap(); assert_eq!(child_vkey, child_vkey_after_resharing); // Generate auxiliary data - let sessions = (0..n) + let entry_points = (0..n) .map(|idx| { - make_aux_gen_session::( - &mut OsRng, - session_id, - signers[idx].clone(), - &all_verifiers, - ) - .unwrap() + let entry_point = + AuxGen::::new(all_verifiers.clone()).unwrap(); + (signers[idx], entry_point) }) .collect::>(); println!("\nRunning AuxGen\n"); - let aux_infos = run_nodes(sessions).await; + let aux_infos = run_sync::<_, TestSessionParams>(&mut OsRng, entry_points) + .unwrap() + .results() + .unwrap(); // For signing, we select `t` parties and these parties: // - derive child key shares // - convert their threshold key shares into regular key shares. - let selected_signers = vec![signers[0].clone(), signers[2].clone(), signers[4].clone()]; + let selected_signers = [signers[0], signers[2], signers[4]]; let selected_parties = BTreeSet::from([verifiers[0], verifiers[2], verifiers[4]]); - let selected_key_shares = vec![ - new_t_key_shares[0] + let selected_key_shares = [ + new_t_key_shares[&verifiers[0]] .derive_bip32(&path) .unwrap() .to_key_share(&selected_parties), - new_t_key_shares[2] + new_t_key_shares[&verifiers[2]] .derive_bip32(&path) .unwrap() .to_key_share(&selected_parties), - new_t_key_shares[4] + new_t_key_shares[&verifiers[4]] .derive_bip32(&path) .unwrap() .to_key_share(&selected_parties), ]; - let selected_aux_infos = vec![ - aux_infos[0].clone(), - aux_infos[2].clone(), - aux_infos[4].clone(), + let selected_aux_infos = [ + aux_infos[&verifiers[0]].clone(), + aux_infos[&verifiers[2]].clone(), + aux_infos[&verifiers[4]].clone(), ]; // Perform signing with the key shares let message = b"abcdefghijklmnopqrstuvwxyz123456"; - let sessions = (0..3) + let entry_points = (0..3) .map(|idx| { - make_interactive_signing_session::<_, Signature, _, _>( - &mut OsRng, - session_id, - selected_signers[idx].clone(), - &selected_parties, - &selected_key_shares[idx], - &selected_aux_infos[idx], - message, - ) - .unwrap() + let entry_point = InteractiveSigning::new( + *message, + selected_key_shares[idx].clone(), + selected_aux_infos[idx].clone(), + ); + (selected_signers[idx], entry_point) }) .collect(); println!("\nRunning InteractiveSigning\n"); - let signatures = run_nodes(sessions).await; + let signatures = run_sync::<_, TestSessionParams>(&mut OsRng, entry_points) + .unwrap() + .results() + .unwrap(); - for signature in signatures { + for (_verifier, signature) in signatures { let (sig, rec_id) = signature.to_backend(); // Check that the signature can be verified