dkg_musig/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3#![cfg_attr(not(feature = "std"), no_std)]
4
5use core::ops::Deref;
6use std_shims::{
7  vec,
8  vec::Vec,
9  collections::{HashSet, HashMap},
10};
11
12use zeroize::Zeroizing;
13
14use ciphersuite::{group::GroupEncoding, Ciphersuite};
15
16pub use dkg::*;
17
18#[cfg(test)]
19mod tests;
20
21/// Errors encountered when working with threshold keys.
22#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
23pub enum MusigError<C: Ciphersuite> {
24  /// No keys were provided.
25  #[error("no keys provided")]
26  NoKeysProvided,
27  /// Too many keys were provided.
28  #[error("too many keys (allowed {max}, provided {provided})")]
29  TooManyKeysProvided {
30    /// The maximum amount of keys allowed.
31    max: u16,
32    /// The amount of keys provided.
33    provided: usize,
34  },
35  /// A participant was duplicated.
36  #[error("a participant was duplicated")]
37  DuplicatedParticipant(C::G),
38  /// Participating, yet our public key wasn't found in the list of keys.
39  #[error("private key's public key wasn't present in the list of public keys")]
40  NotPresent,
41  /// An error propagated from the underlying `dkg` crate.
42  #[error("error from dkg ({0})")]
43  DkgError(DkgError),
44}
45
46fn check_keys<C: Ciphersuite>(keys: &[C::G]) -> Result<u16, MusigError<C>> {
47  if keys.is_empty() {
48    Err(MusigError::NoKeysProvided)?;
49  }
50
51  let keys_len = u16::try_from(keys.len())
52    .map_err(|_| MusigError::TooManyKeysProvided { max: u16::MAX, provided: keys.len() })?;
53
54  let mut set = HashSet::with_capacity(keys.len());
55  for key in keys {
56    let bytes = key.to_bytes().as_ref().to_vec();
57    if !set.insert(bytes) {
58      Err(MusigError::DuplicatedParticipant(*key))?;
59    }
60  }
61
62  Ok(keys_len)
63}
64
65fn binding_factor_transcript<C: Ciphersuite>(
66  context: [u8; 32],
67  keys_len: u16,
68  keys: &[C::G],
69) -> Vec<u8> {
70  debug_assert_eq!(usize::from(keys_len), keys.len());
71
72  let mut transcript = vec![];
73  transcript.extend(&context);
74  transcript.extend(keys_len.to_le_bytes());
75  for key in keys {
76    transcript.extend(key.to_bytes().as_ref());
77  }
78  transcript
79}
80
81fn binding_factor<C: Ciphersuite>(mut transcript: Vec<u8>, i: u16) -> C::F {
82  transcript.extend(i.to_le_bytes());
83  C::hash_to_F(b"dkg-musig", &transcript)
84}
85
86#[allow(clippy::type_complexity)]
87fn musig_key_multiexp<C: Ciphersuite>(
88  context: [u8; 32],
89  keys: &[C::G],
90) -> Result<Vec<(C::F, C::G)>, MusigError<C>> {
91  let keys_len = check_keys::<C>(keys)?;
92  let transcript = binding_factor_transcript::<C>(context, keys_len, keys);
93  let mut multiexp = Vec::with_capacity(keys.len());
94  for i in 1 ..= keys_len {
95    multiexp.push((binding_factor::<C>(transcript.clone(), i), keys[usize::from(i - 1)]));
96  }
97  Ok(multiexp)
98}
99
100/// The group key resulting from using this library's MuSig key aggregation.
101///
102/// This function executes in variable time and MUST NOT be used with secret data.
103pub fn musig_key_vartime<C: Ciphersuite>(
104  context: [u8; 32],
105  keys: &[C::G],
106) -> Result<C::G, MusigError<C>> {
107  Ok(multiexp::multiexp_vartime(&musig_key_multiexp(context, keys)?))
108}
109
110/// The group key resulting from using this library's MuSig key aggregation.
111pub fn musig_key<C: Ciphersuite>(context: [u8; 32], keys: &[C::G]) -> Result<C::G, MusigError<C>> {
112  Ok(multiexp::multiexp(&musig_key_multiexp(context, keys)?))
113}
114
115/// A n-of-n non-interactive DKG which does not guarantee the usability of the resulting key.
116pub fn musig<C: Ciphersuite>(
117  context: [u8; 32],
118  private_key: Zeroizing<C::F>,
119  keys: &[C::G],
120) -> Result<ThresholdKeys<C>, MusigError<C>> {
121  let our_pub_key = C::generator() * private_key.deref();
122  let Some(our_i) = keys.iter().position(|key| *key == our_pub_key) else {
123    Err(MusigError::DkgError(DkgError::NotParticipating))?
124  };
125
126  let keys_len: u16 = check_keys::<C>(keys)?;
127
128  let params = ThresholdParams::new(
129    keys_len,
130    keys_len,
131    // The `+ 1` won't fail as `keys.len() <= u16::MAX`, so any index is `< u16::MAX`
132    Participant::new(
133      u16::try_from(our_i).expect("keys.len() <= u16::MAX yet index of keys > u16::MAX?") + 1,
134    )
135    .expect("i + 1 != 0"),
136  )
137  .map_err(MusigError::DkgError)?;
138
139  let transcript = binding_factor_transcript::<C>(context, keys_len, keys);
140  let mut binding_factors = Vec::with_capacity(keys.len());
141  let mut multiexp = Vec::with_capacity(keys.len());
142  let mut verification_shares = HashMap::with_capacity(keys.len());
143  for (i, key) in (1 ..= keys_len).zip(keys.iter().copied()) {
144    let binding_factor = binding_factor::<C>(transcript.clone(), i);
145    binding_factors.push(binding_factor);
146    multiexp.push((binding_factor, key));
147
148    let i = Participant::new(i).expect("non-zero u16 wasn't a valid Participant index?");
149    verification_shares.insert(i, key);
150  }
151  let group_key = multiexp::multiexp(&multiexp);
152  debug_assert_eq!(our_pub_key, verification_shares[&params.i()]);
153  debug_assert_eq!(musig_key_vartime::<C>(context, keys).unwrap(), group_key);
154
155  ThresholdKeys::new(
156    params,
157    Interpolation::Constant(binding_factors),
158    private_key,
159    verification_shares,
160  )
161  .map_err(MusigError::DkgError)
162}