diff --git a/.github/workflows/crypto-tests.yml b/.github/workflows/crypto-tests.yml index 2e853e71..cf3f00b4 100644 --- a/.github/workflows/crypto-tests.yml +++ b/.github/workflows/crypto-tests.yml @@ -37,6 +37,7 @@ jobs: -p dleq \ -p dkg \ -p dkg-recovery \ + -p dkg-dealer \ -p dkg-promote \ -p dkg-musig \ -p dkg-pedpop \ diff --git a/Cargo.lock b/Cargo.lock index 44d8697b..c121c8a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2220,6 +2220,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "dkg-dealer" +version = "0.6.0" +dependencies = [ + "ciphersuite", + "dkg", + "rand_core", + "std-shims", + "zeroize", +] + [[package]] name = "dkg-musig" version = "0.6.0" @@ -4877,6 +4888,7 @@ dependencies = [ "dalek-ff-group", "digest 0.10.7", "dkg", + "dkg-dealer", "dkg-recovery", "flexible-transcript", "hex", @@ -8373,6 +8385,7 @@ dependencies = [ "ciphersuite", "dalek-ff-group", "dkg", + "dkg-dealer", "dkg-musig", "dkg-recovery", "dleq", diff --git a/Cargo.toml b/Cargo.toml index d1b1862e..db9f078f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "crypto/dleq", "crypto/dkg", "crypto/dkg/recovery", + "crypto/dkg/dealer", "crypto/dkg/promote", "crypto/dkg/musig", "crypto/dkg/pedpop", diff --git a/crypto/dkg/dealer/Cargo.toml b/crypto/dkg/dealer/Cargo.toml new file mode 100644 index 00000000..b1f35e89 --- /dev/null +++ b/crypto/dkg/dealer/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "dkg-dealer" +version = "0.6.0" +description = "Produce dkg::ThresholdKeys with a dealer key generation" +license = "MIT" +repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dkg/dealer" +authors = ["Luke Parker "] +keywords = ["dkg", "multisig", "threshold", "ff", "group"] +edition = "2021" +rust-version = "1.80" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true + +[dependencies] +zeroize = { version = "^1.5", default-features = false } +rand_core = { version = "0.6", default-features = false } + +std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false } + +ciphersuite = { path = "../../ciphersuite", version = "^0.4.1", default-features = false } +dkg = { path = "../", default-features = false } + +[features] +std = [ + "zeroize/std", + "rand_core/std", + "std-shims/std", + "ciphersuite/std", + "dkg/std", +] +default = ["std"] diff --git a/crypto/dkg/dealer/LICENSE b/crypto/dkg/dealer/LICENSE new file mode 100644 index 00000000..6f7adff3 --- /dev/null +++ b/crypto/dkg/dealer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-2025 Luke Parker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crypto/dkg/dealer/README.md b/crypto/dkg/dealer/README.md new file mode 100644 index 00000000..be597461 --- /dev/null +++ b/crypto/dkg/dealer/README.md @@ -0,0 +1,13 @@ +# Distributed Key Generation - Dealer + +This crate implements a dealer key generation protocol for the +[`dkg`](https://docs.rs/dkg) crate's types. This provides a single point of +failure when the key is being generated and is NOT recommended for use outside +of tests. + +This crate was originally part of (in some form) the `dkg` crate, which was +[audited by Cypher Stack in March 2023]( + https://github.com/serai-dex/serai/raw/e1bb2c191b7123fd260d008e31656d090d559d21/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf +), culminating in commit [669d2dbffc1dafb82a09d9419ea182667115df06]( + https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06 +). Any subsequent changes have not undergone auditing. diff --git a/crypto/dkg/dealer/src/lib.rs b/crypto/dkg/dealer/src/lib.rs new file mode 100644 index 00000000..f00d5d85 --- /dev/null +++ b/crypto/dkg/dealer/src/lib.rs @@ -0,0 +1,68 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![no_std] + +use core::ops::Deref; +use std_shims::{vec::Vec, collections::HashMap}; + +use zeroize::{Zeroize, Zeroizing}; +use rand_core::{RngCore, CryptoRng}; + +use ciphersuite::{ + group::ff::{Field, PrimeField}, + Ciphersuite, +}; +pub use dkg::*; + +/// Create a key via a dealer key generation protocol. +pub fn key_gen( + rng: &mut R, + threshold: u16, + participants: u16, +) -> Result>, DkgError> { + let mut coefficients = Vec::with_capacity(usize::from(participants)); + // `.max(1)` so we always generate the 0th coefficient which we'll share + for _ in 0 .. threshold.max(1) { + coefficients.push(Zeroizing::new(C::F::random(&mut *rng))); + } + + fn polynomial( + coefficients: &[Zeroizing], + l: Participant, + ) -> Zeroizing { + let l = F::from(u64::from(u16::from(l))); + // This should never be reached since Participant is explicitly non-zero + assert!(l != F::ZERO, "zero participant passed to polynomial"); + let mut share = Zeroizing::new(F::ZERO); + for (idx, coefficient) in coefficients.iter().rev().enumerate() { + *share += coefficient.deref(); + if idx != (coefficients.len() - 1) { + *share *= l; + } + } + share + } + + let group_key = C::generator() * coefficients[0].deref(); + let mut secret_shares = HashMap::with_capacity(participants as usize); + let mut verification_shares = HashMap::with_capacity(participants as usize); + for i in 1 ..= participants { + let i = Participant::new(i).expect("non-zero u16 wasn't a valid Participant index"); + let secret_share = polynomial(&coefficients, i); + secret_shares.insert(i, secret_share.clone()); + verification_shares.insert(i, C::generator() * *secret_share); + } + + let mut res = HashMap::with_capacity(participants as usize); + for (i, secret_share) in secret_shares { + let keys = ThresholdKeys::new( + ThresholdParams::new(threshold, participants, i)?, + Interpolation::Lagrange, + secret_share, + verification_shares.clone(), + )?; + debug_assert_eq!(keys.group_key(), group_key); + res.insert(i, keys); + } + Ok(res) +} diff --git a/crypto/dkg/recovery/Cargo.toml b/crypto/dkg/recovery/Cargo.toml index e2e7485c..96df11fc 100644 --- a/crypto/dkg/recovery/Cargo.toml +++ b/crypto/dkg/recovery/Cargo.toml @@ -21,7 +21,7 @@ zeroize = { version = "^1.5", default-features = false } thiserror = { version = "2", default-features = false } -ciphersuite = { path = "../../ciphersuite", version = "^0.4.1", default-features = false, features = ["alloc"] } +ciphersuite = { path = "../../ciphersuite", version = "^0.4.1", default-features = false } dkg = { path = "../", default-features = false } [features] diff --git a/crypto/frost/Cargo.toml b/crypto/frost/Cargo.toml index 74b3318f..9e2f6ddd 100644 --- a/crypto/frost/Cargo.toml +++ b/crypto/frost/Cargo.toml @@ -41,6 +41,7 @@ schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "^0.5 dkg = { path = "../dkg", version = "0.6", default-features = false, features = ["std"] } dkg-recovery = { path = "../dkg/recovery", default-features = false, features = ["std"], optional = true } +dkg-dealer = { path = "../dkg/dealer", default-features = false, features = ["std"], optional = true } [dev-dependencies] hex = "0.4" @@ -48,6 +49,7 @@ serde_json = { version = "1", default-features = false, features = ["std"] } dkg = { path = "../dkg", default-features = false, features = ["std"] } dkg-recovery = { path = "../dkg/recovery", default-features = false, features = ["std"] } +dkg-dealer = { path = "../dkg/dealer", default-features = false, features = ["std"] } [features] ed25519 = ["dalek-ff-group", "ciphersuite/ed25519"] @@ -58,4 +60,4 @@ p256 = ["ciphersuite/p256"] ed448 = ["minimal-ed448", "ciphersuite/ed448"] -tests = ["hex", "rand_core/getrandom", "dkg-recovery"] +tests = ["hex", "rand_core/getrandom", "dkg-dealer" ,"dkg-recovery"] diff --git a/crypto/frost/README.md b/crypto/frost/README.md index e6ed2b0a..bf290acf 100644 --- a/crypto/frost/README.md +++ b/crypto/frost/README.md @@ -12,6 +12,10 @@ This library offers ciphersuites compatible with the [IETF draft](https://github.com/cfrg/draft-irtf-cfrg-frost). Currently, version 15 is supported. +A variety of testing utilities are provided under the `tests` feature. These +are provided with no guarantees and may have completely arbitrary behavior, +including panicking for completely well-reasoned input. + This library was [audited by Cypher Stack in March 2023](https://github.com/serai-dex/serai/raw/e1bb2c191b7123fd260d008e31656d090d559d21/audits/Cypher%20Stack%20crypto%20March%202023/Audit.pdf), culminating in commit diff --git a/crypto/frost/src/tests/mod.rs b/crypto/frost/src/tests/mod.rs index 1b2afa12..79ac9ded 100644 --- a/crypto/frost/src/tests/mod.rs +++ b/crypto/frost/src/tests/mod.rs @@ -1,18 +1,12 @@ -use core::ops::Deref; use std::collections::HashMap; -use zeroize::{Zeroize, Zeroizing}; use rand_core::{RngCore, CryptoRng}; -use ciphersuite::{ - group::ff::{Field, PrimeField}, - Ciphersuite, -}; -use dkg::Interpolation; +use ciphersuite::Ciphersuite; pub use dkg_recovery::recover_key; use crate::{ - Curve, Participant, ThresholdParams, ThresholdKeys, FrostError, + Curve, Participant, ThresholdKeys, FrostError, algorithm::{Algorithm, Hram, IetfSchnorr}, sign::{Writable, PreprocessMachine, SignMachine, SignatureMachine, AlgorithmMachine}, }; @@ -37,49 +31,11 @@ pub const THRESHOLD: u16 = ((PARTICIPANTS * 2) / 3) + 1; pub fn key_gen( rng: &mut R, ) -> HashMap> { - let coefficients: [_; THRESHOLD as usize] = - core::array::from_fn(|_| Zeroizing::new(C::F::random(&mut *rng))); - - fn polynomial( - coefficients: &[Zeroizing], - l: Participant, - ) -> Zeroizing { - let l = F::from(u64::from(u16::from(l))); - // This should never be reached since Participant is explicitly non-zero - assert!(l != F::ZERO, "zero participant passed to polynomial"); - let mut share = Zeroizing::new(F::ZERO); - for (idx, coefficient) in coefficients.iter().rev().enumerate() { - *share += coefficient.deref(); - if idx != (coefficients.len() - 1) { - *share *= l; - } - } - share - } - - let group_key = C::generator() * *coefficients[0]; - let mut secret_shares = HashMap::with_capacity(PARTICIPANTS as usize); - let mut verification_shares = HashMap::with_capacity(PARTICIPANTS as usize); - for i in 1 ..= PARTICIPANTS { - let i = Participant::new(i).unwrap(); - let secret_share = polynomial(&coefficients, i); - secret_shares.insert(i, secret_share.clone()); - verification_shares.insert(i, C::generator() * *secret_share); - } - - let mut res = HashMap::with_capacity(PARTICIPANTS as usize); - for i in 1 ..= PARTICIPANTS { - let i = Participant::new(i).unwrap(); - let keys = ThresholdKeys::new( - ThresholdParams::new(THRESHOLD, PARTICIPANTS, i).unwrap(), - Interpolation::Lagrange, - secret_shares.remove(&i).unwrap(), - verification_shares.clone(), - ) - .unwrap(); - assert_eq!(keys.group_key(), group_key); - res.insert(i, keys); - } + let res = dkg_dealer::key_gen::(rng, THRESHOLD, PARTICIPANTS).unwrap(); + assert_eq!( + C::generator() * *recover_key(&res.values().cloned().collect::>()).unwrap(), + res.values().next().unwrap().group_key() + ); res } diff --git a/tests/no-std/Cargo.toml b/tests/no-std/Cargo.toml index da21e0ff..fa0649f5 100644 --- a/tests/no-std/Cargo.toml +++ b/tests/no-std/Cargo.toml @@ -31,6 +31,7 @@ schnorr-signatures = { path = "../../crypto/schnorr", default-features = false } dkg = { path = "../../crypto/dkg", default-features = false } dkg-recovery = { path = "../../crypto/dkg/recovery", default-features = false } +dkg-dealer = { path = "../../crypto/dkg/dealer", default-features = false } dkg-musig = { path = "../../crypto/dkg/musig", default-features = false } # modular-frost = { path = "../../crypto/frost", default-features = false } # frost-schnorrkel = { path = "../../crypto/schnorrkel", default-features = false } diff --git a/tests/no-std/src/lib.rs b/tests/no-std/src/lib.rs index 7b9c2cca..fe0cff64 100644 --- a/tests/no-std/src/lib.rs +++ b/tests/no-std/src/lib.rs @@ -14,6 +14,7 @@ pub use schnorr_signatures; pub use dkg; pub use dkg_recovery; +pub use dkg_dealer; pub use dkg_musig; /* pub use modular_frost;