diff --git a/crypto/dkg/src/lib.rs b/crypto/dkg/src/lib.rs index b4a290d2..40e1282f 100644 --- a/crypto/dkg/src/lib.rs +++ b/crypto/dkg/src/lib.rs @@ -22,8 +22,8 @@ use ciphersuite::{ /// Encryption types and utilities used to secure DKG messages. pub mod encryption; -mod musig; -pub use musig::musig; +/// MuSig-style key aggregation. +pub mod musig; /// The distributed key generation protocol described in the /// [FROST paper](https://eprint.iacr.org/2020/852). diff --git a/crypto/dkg/src/musig.rs b/crypto/dkg/src/musig.rs index 452a3d38..dc033cef 100644 --- a/crypto/dkg/src/musig.rs +++ b/crypto/dkg/src/musig.rs @@ -6,19 +6,13 @@ use zeroize::Zeroizing; use transcript::{Transcript, RecommendedTranscript}; use ciphersuite::{ - group::{Group, GroupEncoding}, + group::{ff::Field, Group, GroupEncoding}, Ciphersuite, }; use crate::{Participant, DkgError, ThresholdParams, ThresholdCore, lagrange}; -/// A n-of-n non-interactive DKG which does not guarantee the usability of the resulting key. -/// -/// Creating a key with duplicated public keys returns an error. -pub fn musig( - private_key: &Zeroizing, - keys: &[C::G], -) -> Result, DkgError<()>> { +fn check_keys(keys: &[C::G]) -> Result> { if keys.is_empty() { Err(DkgError::InvalidSigningSet)?; } @@ -32,6 +26,45 @@ pub fn musig( Err(DkgError::InvalidSigningSet)?; } + Ok(keys_len) +} + +fn binding_factor_transcript(keys: &[C::G]) -> RecommendedTranscript { + let mut transcript = RecommendedTranscript::new(b"DKG MuSig v0.5"); + transcript.domain_separate(b"musig_binding_factors"); + for key in keys { + transcript.append_message(b"key", key.to_bytes()); + } + transcript +} + +fn binding_factor(mut transcript: RecommendedTranscript, i: u16) -> C::F { + transcript.append_message(b"participant", i.to_le_bytes()); + C::hash_to_F(b"DKG-MuSig-binding_factor", &transcript.challenge(b"binding_factor")) +} + +/// The group key resulting from using this library's MuSig key gen. +/// +/// Creating an aggregate key with a list containing duplicated public keys returns an error. +pub fn musig_key(keys: &[C::G]) -> Result> { + let keys_len = check_keys::(keys)?; + let transcript = binding_factor_transcript::(keys); + let mut res = C::G::identity(); + for i in 1 ..= keys_len { + res += keys[usize::from(i - 1)] * binding_factor::(transcript.clone(), i); + } + Ok(res) +} + +/// A n-of-n non-interactive DKG which does not guarantee the usability of the resulting key. +/// +/// Creating an aggregate key with a list containing duplicated public keys returns an error. +pub fn musig( + private_key: &Zeroizing, + keys: &[C::G], +) -> Result, DkgError<()>> { + let keys_len = check_keys::(keys)?; + let our_pub_key = C::generator() * private_key.deref(); let Some(pos) = keys.iter().position(|key| *key == our_pub_key) else { // Not present in signing set @@ -47,18 +80,10 @@ pub fn musig( )?; // Calculate the binding factor per-key - let mut transcript = RecommendedTranscript::new(b"DKG MuSig v0.5"); - transcript.domain_separate(b"musig_binding_factors"); - for key in keys { - transcript.append_message(b"key", key.to_bytes()); - } - + let transcript = binding_factor_transcript::(keys); let mut binding = Vec::with_capacity(keys.len()); for i in 1 ..= keys_len { - let mut transcript = transcript.clone(); - transcript.append_message(b"participant", i.to_le_bytes()); - binding - .push(C::hash_to_F(b"DKG-MuSig-binding_factor", &transcript.challenge(b"binding_factor"))); + binding.push(binding_factor::(transcript.clone(), i)); } // Multiply our private key by our binding factor @@ -67,26 +92,32 @@ pub fn musig( // Calculate verification shares let mut verification_shares = HashMap::new(); - // When this library generates shares for a specific signing set, it applies the lagrange - // coefficient + // When this library offers a ThresholdView for a specific signing set, it applies the lagrange + // factor // Since this is a n-of-n scheme, there's only one possible signing set, and one possible // lagrange factor - // Define the group key as the sum of all verification shares, post-lagrange - // While we could invert our lagrange factor and multiply it by our secret share, so the group - // key wasn't post-lagrange, the inversion is ~300 multiplications and we'd have to apply similar - // inversions + multiplications to all verification shares - // Accordingly, it'd never be more performant, though it would simplify group key calculation + // In the name of simplicity, we define the group key as the sum of all bound keys + // Accordingly, the secret share must be multiplied by the inverse of the lagrange factor, along + // with all verification shares + // This is less performant than simply defining the group key as the sum of all post-lagrange + // bound keys, yet the simplicity is preferred let included = (1 ..= keys_len) // This error also shouldn't be possible, for the same reasons as documented above .map(|l| Participant::new(l).ok_or(DkgError::InvalidSigningSet)) .collect::, _>>()?; let mut group_key = C::G::identity(); for (l, p) in included.iter().enumerate() { - let verification_share = keys[l] * binding[l]; - group_key += verification_share * lagrange::(*p, &included); - verification_shares.insert(*p, verification_share); + let bound = keys[l] * binding[l]; + group_key += bound; + + let lagrange_inv = lagrange::(*p, &included).invert().unwrap(); + if params.i() == *p { + *secret_share *= lagrange_inv; + } + verification_shares.insert(*p, bound * lagrange_inv); } debug_assert_eq!(C::generator() * secret_share.deref(), verification_shares[¶ms.i()]); + debug_assert_eq!(musig_key::(keys).unwrap(), group_key); Ok(ThresholdCore { params, secret_share, group_key, verification_shares }) } diff --git a/crypto/dkg/src/tests/mod.rs b/crypto/dkg/src/tests/mod.rs index 8de9a683..43aaf9ca 100644 --- a/crypto/dkg/src/tests/mod.rs +++ b/crypto/dkg/src/tests/mod.rs @@ -6,7 +6,7 @@ use rand_core::{RngCore, CryptoRng}; use ciphersuite::{group::ff::Field, Ciphersuite}; -use crate::{Participant, ThresholdCore, ThresholdKeys, lagrange, musig as musig_fn}; +use crate::{Participant, ThresholdCore, ThresholdKeys, lagrange, musig::musig as musig_fn}; mod musig; pub use musig::test_musig; diff --git a/crypto/dkg/src/tests/musig.rs b/crypto/dkg/src/tests/musig.rs index 1b037717..0eac8340 100644 --- a/crypto/dkg/src/tests/musig.rs +++ b/crypto/dkg/src/tests/musig.rs @@ -6,7 +6,8 @@ use rand_core::{RngCore, CryptoRng}; use ciphersuite::{group::ff::Field, Ciphersuite}; use crate::{ - ThresholdKeys, musig, + ThresholdKeys, + musig::{musig_key, musig}, tests::{PARTICIPANTS, recover_key}, }; @@ -29,7 +30,7 @@ pub fn test_musig(rng: &mut R) { { let mut created_keys = HashMap::new(); let mut verification_shares = HashMap::new(); - let mut group_key = None; + let group_key = musig_key::(&pub_keys).unwrap(); for (i, key) in keys.iter().enumerate() { let these_keys = musig::(key, &pub_keys).unwrap(); assert_eq!(these_keys.params().t(), PARTICIPANTS); @@ -39,10 +40,7 @@ pub fn test_musig(rng: &mut R) { verification_shares .insert(these_keys.params().i(), C::generator() * **these_keys.secret_share()); - if group_key.is_none() { - group_key = Some(these_keys.group_key()); - } - assert_eq!(these_keys.group_key(), group_key.unwrap()); + assert_eq!(these_keys.group_key(), group_key); created_keys.insert(these_keys.params().i(), ThresholdKeys::new(these_keys)); } @@ -51,7 +49,7 @@ pub fn test_musig(rng: &mut R) { assert_eq!(keys.verification_shares(), verification_shares); } - assert_eq!(C::generator() * recover_key(&created_keys), group_key.unwrap()); + assert_eq!(C::generator() * recover_key(&created_keys), group_key); } }