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