dkg/
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::{
6  ops::Deref,
7  fmt::{self, Debug},
8};
9use std_shims::{sync::Arc, vec, vec::Vec, collections::HashMap, io};
10
11use zeroize::{Zeroize, Zeroizing};
12
13use ciphersuite::{
14  group::{
15    ff::{Field, PrimeField},
16    GroupEncoding,
17  },
18  Ciphersuite,
19};
20
21/// The ID of a participant, defined as a non-zero u16.
22#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Zeroize)]
23#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize))]
24pub struct Participant(u16);
25impl Participant {
26  /// Create a new Participant identifier from a u16.
27  pub const fn new(i: u16) -> Option<Participant> {
28    if i == 0 {
29      None
30    } else {
31      Some(Participant(i))
32    }
33  }
34
35  /// Convert a Participant identifier to bytes.
36  #[allow(clippy::wrong_self_convention)]
37  pub const fn to_bytes(&self) -> [u8; 2] {
38    self.0.to_le_bytes()
39  }
40}
41
42impl From<Participant> for u16 {
43  fn from(participant: Participant) -> u16 {
44    participant.0
45  }
46}
47
48impl fmt::Display for Participant {
49  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50    write!(f, "{}", self.0)
51  }
52}
53
54/// Errors encountered when working with threshold keys.
55#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
56pub enum DkgError {
57  /// A parameter was zero.
58  #[error("a parameter was 0 (threshold {t}, participants {n})")]
59  ZeroParameter {
60    /// The specified threshold.
61    t: u16,
62    /// The specified total amount of participants.
63    n: u16,
64  },
65
66  /// The threshold exceeded the amount of participants.
67  #[error("invalid threshold (max {n}, got {t})")]
68  InvalidThreshold {
69    /// The specified threshold.
70    t: u16,
71    /// The specified total amount of participants.
72    n: u16,
73  },
74
75  /// Invalid participant identifier.
76  #[error("invalid participant (1 <= participant <= {n}, yet participant is {participant})")]
77  InvalidParticipant {
78    /// The total amount of participants.
79    n: u16,
80    /// The specified participant.
81    participant: Participant,
82  },
83
84  /// An incorrect amount of participants was specified.
85  #[error("incorrect amount of verification shares (n = {n} yet {shares} provided)")]
86  IncorrectAmountOfVerificationShares {
87    /// The amount of participants.
88    n: u16,
89    /// The amount of shares provided.
90    shares: usize,
91  },
92
93  /// An inapplicable method of interpolation was specified.
94  #[error("inapplicable method of interpolation ({0})")]
95  InapplicableInterpolation(&'static str),
96
97  /// An incorrect amount of participants was specified.
98  #[error("incorrect amount of participants. {t} <= amount <= {n}, yet amount is {amount}")]
99  IncorrectAmountOfParticipants {
100    /// The threshold required.
101    t: u16,
102    /// The total amount of participants.
103    n: u16,
104    /// The amount of participants specified.
105    amount: usize,
106  },
107
108  /// A participant was duplicated.
109  #[error("a participant ({0}) was duplicated")]
110  DuplicatedParticipant(Participant),
111
112  /// Not participating in declared signing set.
113  #[error("not participating in declared signing set")]
114  NotParticipating,
115}
116
117// Manually implements BorshDeserialize so we can enforce it's a valid index
118#[cfg(feature = "borsh")]
119impl borsh::BorshDeserialize for Participant {
120  fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
121    Participant::new(u16::deserialize_reader(reader)?)
122      .ok_or_else(|| io::Error::other("invalid participant"))
123  }
124}
125
126/// Parameters for a multisig.
127#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
128#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize))]
129pub struct ThresholdParams {
130  /// Participants needed to sign on behalf of the group.
131  t: u16,
132  /// Amount of participants.
133  n: u16,
134  /// Index of the participant being acted for.
135  i: Participant,
136}
137
138/// An iterator over all participant indexes.
139struct AllParticipantIndexes {
140  i: u16,
141  n: u16,
142}
143impl Iterator for AllParticipantIndexes {
144  type Item = Participant;
145  fn next(&mut self) -> Option<Participant> {
146    if self.i > self.n {
147      None?;
148    }
149    let res = Participant::new(self.i).unwrap();
150
151    // If i == n == u16::MAX, we cause `i > n` by setting `n` to `0` so the iterator becomes empty
152    if self.i == u16::MAX {
153      self.n = 0;
154    } else {
155      self.i += 1;
156    }
157
158    Some(res)
159  }
160}
161
162impl ThresholdParams {
163  /// Create a new set of parameters.
164  pub const fn new(t: u16, n: u16, i: Participant) -> Result<ThresholdParams, DkgError> {
165    if (t == 0) || (n == 0) {
166      return Err(DkgError::ZeroParameter { t, n });
167    }
168
169    if t > n {
170      return Err(DkgError::InvalidThreshold { t, n });
171    }
172    if i.0 > n {
173      return Err(DkgError::InvalidParticipant { n, participant: i });
174    }
175
176    Ok(ThresholdParams { t, n, i })
177  }
178
179  /// The threshold for a multisig with these parameters.
180  pub const fn t(&self) -> u16 {
181    self.t
182  }
183  /// The amount of participants for a multisig with these parameters.
184  pub const fn n(&self) -> u16 {
185    self.n
186  }
187  /// The participant index of the share with these parameters.
188  pub const fn i(&self) -> Participant {
189    self.i
190  }
191
192  /// An iterator over all participant indexes.
193  pub fn all_participant_indexes(&self) -> impl Iterator<Item = Participant> {
194    AllParticipantIndexes { i: 1, n: self.n }
195  }
196}
197
198#[cfg(feature = "borsh")]
199impl borsh::BorshDeserialize for ThresholdParams {
200  fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
201    let t = u16::deserialize_reader(reader)?;
202    let n = u16::deserialize_reader(reader)?;
203    let i = Participant::deserialize_reader(reader)?;
204    ThresholdParams::new(t, n, i).map_err(|e| io::Error::other(format!("{e:?}")))
205  }
206}
207
208/// A method of interpolation.
209#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
210pub enum Interpolation<F: Zeroize + PrimeField> {
211  /// A list of constant coefficients, one for each of the secret key shares.
212  /*
213    There's no benefit to using a full linear combination here, as the additive term would have
214    an entirely known evaluation with a fixed, public coefficient of `1`. Accordingly, the entire
215    key can simply be offset with the additive term to achieve the same effect.
216  */
217  Constant(Vec<F>),
218  /// Lagrange interpolation.
219  Lagrange,
220}
221
222impl<F: Zeroize + PrimeField> Interpolation<F> {
223  /// The interpolation factor for this participant, within this signing set.
224  fn interpolation_factor(&self, i: Participant, included: &[Participant]) -> F {
225    match self {
226      Interpolation::Constant(c) => c[usize::from(u16::from(i) - 1)],
227      Interpolation::Lagrange => {
228        let i_f = F::from(u64::from(u16::from(i)));
229
230        let mut num = F::ONE;
231        let mut denom = F::ONE;
232        for l in included {
233          if i == *l {
234            continue;
235          }
236
237          let share = F::from(u64::from(u16::from(*l)));
238          num *= share;
239          denom *= share - i_f;
240        }
241
242        // Safe as this will only be 0 if we're part of the above loop
243        // (which we have an if case to avoid)
244        num * denom.invert().unwrap()
245      }
246    }
247  }
248}
249
250/// A key share for a thresholdized secret key.
251///
252/// This is the 'core' structure containing all relevant data, expected to be wrapped into an
253/// heap-allocated pointer to minimize copies on the stack (`ThresholdKeys`, the publicly exposed
254/// type).
255#[derive(Clone, PartialEq, Eq)]
256struct ThresholdCore<C: Ciphersuite> {
257  params: ThresholdParams,
258  group_key: C::G,
259  verification_shares: HashMap<Participant, C::G>,
260  interpolation: Interpolation<C::F>,
261  secret_share: Zeroizing<C::F>,
262}
263
264impl<C: Ciphersuite> fmt::Debug for ThresholdCore<C> {
265  fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
266    fmt
267      .debug_struct("ThresholdCore")
268      .field("params", &self.params)
269      .field("group_key", &self.group_key)
270      .field("verification_shares", &self.verification_shares)
271      .field("interpolation", &self.interpolation)
272      .finish_non_exhaustive()
273  }
274}
275
276impl<C: Ciphersuite> Zeroize for ThresholdCore<C> {
277  fn zeroize(&mut self) {
278    self.params.zeroize();
279    self.group_key.zeroize();
280    for share in self.verification_shares.values_mut() {
281      share.zeroize();
282    }
283    self.interpolation.zeroize();
284    self.secret_share.zeroize();
285  }
286}
287
288/// Threshold keys usable for signing.
289#[derive(Clone, Debug, Zeroize)]
290pub struct ThresholdKeys<C: Ciphersuite> {
291  // Core keys.
292  #[zeroize(skip)]
293  core: Arc<Zeroizing<ThresholdCore<C>>>,
294
295  // Scalar applied to these keys.
296  scalar: C::F,
297  // Offset applied to these keys.
298  offset: C::F,
299}
300
301/// View of keys, interpolated and with the expected linear combination taken for usage.
302#[derive(Clone)]
303pub struct ThresholdView<C: Ciphersuite> {
304  interpolation: Interpolation<C::F>,
305  scalar: C::F,
306  offset: C::F,
307  group_key: C::G,
308  included: Vec<Participant>,
309  secret_share: Zeroizing<C::F>,
310  original_verification_shares: HashMap<Participant, C::G>,
311  verification_shares: HashMap<Participant, C::G>,
312}
313
314impl<C: Ciphersuite> fmt::Debug for ThresholdView<C> {
315  fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
316    fmt
317      .debug_struct("ThresholdView")
318      .field("interpolation", &self.interpolation)
319      .field("scalar", &self.scalar)
320      .field("offset", &self.offset)
321      .field("group_key", &self.group_key)
322      .field("included", &self.included)
323      .field("original_verification_shares", &self.original_verification_shares)
324      .field("verification_shares", &self.verification_shares)
325      .finish_non_exhaustive()
326  }
327}
328
329impl<C: Ciphersuite> Zeroize for ThresholdView<C> {
330  fn zeroize(&mut self) {
331    self.scalar.zeroize();
332    self.offset.zeroize();
333    self.group_key.zeroize();
334    self.included.zeroize();
335    self.secret_share.zeroize();
336    for share in self.original_verification_shares.values_mut() {
337      share.zeroize();
338    }
339    for share in self.verification_shares.values_mut() {
340      share.zeroize();
341    }
342  }
343}
344
345impl<C: Ciphersuite> ThresholdKeys<C> {
346  /// Create a new set of ThresholdKeys.
347  pub fn new(
348    params: ThresholdParams,
349    interpolation: Interpolation<C::F>,
350    secret_share: Zeroizing<C::F>,
351    verification_shares: HashMap<Participant, C::G>,
352  ) -> Result<ThresholdKeys<C>, DkgError> {
353    if verification_shares.len() != usize::from(params.n()) {
354      Err(DkgError::IncorrectAmountOfVerificationShares {
355        n: params.n(),
356        shares: verification_shares.len(),
357      })?;
358    }
359    for participant in verification_shares.keys().copied() {
360      if u16::from(participant) > params.n() {
361        Err(DkgError::InvalidParticipant { n: params.n(), participant })?;
362      }
363    }
364
365    match &interpolation {
366      Interpolation::Constant(_) => {
367        if params.t() != params.n() {
368          Err(DkgError::InapplicableInterpolation("constant interpolation for keys where t != n"))?;
369        }
370      }
371      Interpolation::Lagrange => {}
372    }
373
374    let t = (1 ..= params.t()).map(Participant).collect::<Vec<_>>();
375    let group_key =
376      t.iter().map(|i| verification_shares[i] * interpolation.interpolation_factor(*i, &t)).sum();
377
378    Ok(ThresholdKeys {
379      core: Arc::new(Zeroizing::new(ThresholdCore {
380        params,
381        interpolation,
382        secret_share,
383        group_key,
384        verification_shares,
385      })),
386      scalar: C::F::ONE,
387      offset: C::F::ZERO,
388    })
389  }
390
391  /// Scale the keys by a given scalar to allow for various account and privacy schemes.
392  ///
393  /// This scalar is ephemeral and will not be included when these keys are serialized. The
394  /// scalar is applied on top of any already-existing scalar/offset.
395  ///
396  /// Returns `None` if the scalar is equal to `0`.
397  #[must_use]
398  pub fn scale(mut self, scalar: C::F) -> Option<ThresholdKeys<C>> {
399    if bool::from(scalar.is_zero()) {
400      None?;
401    }
402    self.scalar *= scalar;
403    self.offset *= scalar;
404    Some(self)
405  }
406
407  /// Offset the keys by a given scalar to allow for various account and privacy schemes.
408  ///
409  /// This offset is ephemeral and will not be included when these keys are serialized. The
410  /// offset is applied on top of any already-existing scalar/offset.
411  #[must_use]
412  pub fn offset(mut self, offset: C::F) -> ThresholdKeys<C> {
413    self.offset += offset;
414    self
415  }
416
417  /// Return the current scalar in-use for these keys.
418  pub fn current_scalar(&self) -> C::F {
419    self.scalar
420  }
421
422  /// Return the current offset in-use for these keys.
423  pub fn current_offset(&self) -> C::F {
424    self.offset
425  }
426
427  /// Return the parameters for these keys.
428  pub fn params(&self) -> ThresholdParams {
429    self.core.params
430  }
431
432  /// Return the original group key, without any tweaks applied.
433  pub fn original_group_key(&self) -> C::G {
434    self.core.group_key
435  }
436
437  /// Return the interpolation method for these keys.
438  pub fn interpolation(&self) -> &Interpolation<C::F> {
439    &self.core.interpolation
440  }
441
442  /// Return the group key, with the expected linear combination taken.
443  pub fn group_key(&self) -> C::G {
444    (self.core.group_key * self.scalar) + (C::generator() * self.offset)
445  }
446
447  /// Return the underlying secret share for these keys, without any tweaks applied.
448  pub fn original_secret_share(&self) -> &Zeroizing<C::F> {
449    &self.core.secret_share
450  }
451
452  /// Return the original (untweaked) verification share for the specified participant.
453  ///
454  /// This will panic if the participant index is invalid for these keys.
455  pub fn original_verification_share(&self, l: Participant) -> C::G {
456    self.core.verification_shares[&l]
457  }
458
459  /// Obtain a view of these keys, interpolated for the specified signing set, with the specified
460  /// linear combination taken.
461  pub fn view(&self, mut included: Vec<Participant>) -> Result<ThresholdView<C>, DkgError> {
462    if (included.len() < self.params().t.into()) ||
463      (usize::from(self.params().n()) < included.len())
464    {
465      Err(DkgError::IncorrectAmountOfParticipants {
466        t: self.params().t,
467        n: self.params().n,
468        amount: included.len(),
469      })?;
470    }
471    included.sort();
472    {
473      let mut found = included[0] == self.params().i();
474      for i in 1 .. included.len() {
475        if included[i - 1] == included[i] {
476          Err(DkgError::DuplicatedParticipant(included[i]))?;
477        }
478        found |= included[i] == self.params().i();
479      }
480      if !found {
481        Err(DkgError::NotParticipating)?;
482      }
483    }
484    {
485      let last = *included.last().unwrap();
486      if u16::from(last) > self.params().n() {
487        Err(DkgError::InvalidParticipant { n: self.params().n(), participant: last })?;
488      }
489    }
490
491    // The interpolation occurs multiplicatively, letting us scale by the scalar now
492    let secret_share_scaled = Zeroizing::new(self.scalar * self.original_secret_share().deref());
493    let mut secret_share = Zeroizing::new(
494      self.core.interpolation.interpolation_factor(self.params().i(), &included) *
495        secret_share_scaled.deref(),
496    );
497
498    let mut verification_shares = HashMap::with_capacity(included.len());
499    for i in &included {
500      let verification_share = self.core.verification_shares[i];
501      let verification_share = verification_share *
502        self.scalar *
503        self.core.interpolation.interpolation_factor(*i, &included);
504      verification_shares.insert(*i, verification_share);
505    }
506
507    /*
508      The offset is included by adding it to the participant with the lowest ID.
509
510      This is done after interpolating to ensure, regardless of the method of interpolation, that
511      the method of interpolation does not scale the offset. For Lagrange interpolation, we could
512      add the offset to every key share before interpolating, yet for Constant interpolation, we
513      _have_ to add it as we do here (which also works even when we intend to perform Lagrange
514      interpolation).
515    */
516    if included[0] == self.params().i() {
517      *secret_share += self.offset;
518    }
519    *verification_shares.get_mut(&included[0]).unwrap() += C::generator() * self.offset;
520
521    Ok(ThresholdView {
522      interpolation: self.core.interpolation.clone(),
523      scalar: self.scalar,
524      offset: self.offset,
525      group_key: self.group_key(),
526      secret_share,
527      original_verification_shares: self.core.verification_shares.clone(),
528      verification_shares,
529      included,
530    })
531  }
532
533  /// Write these keys to a type satisfying `std::io::Write`.
534  ///
535  /// This will not include the ephemeral scalar/offset.
536  pub fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
537    writer.write_all(&u32::try_from(C::ID.len()).unwrap().to_le_bytes())?;
538    writer.write_all(C::ID)?;
539    writer.write_all(&self.core.params.t.to_le_bytes())?;
540    writer.write_all(&self.core.params.n.to_le_bytes())?;
541    writer.write_all(&self.core.params.i.to_bytes())?;
542    match &self.core.interpolation {
543      Interpolation::Constant(c) => {
544        writer.write_all(&[0])?;
545        for c in c {
546          writer.write_all(c.to_repr().as_ref())?;
547        }
548      }
549      Interpolation::Lagrange => writer.write_all(&[1])?,
550    };
551    let mut share_bytes = self.core.secret_share.to_repr();
552    writer.write_all(share_bytes.as_ref())?;
553    share_bytes.as_mut().zeroize();
554    for l in 1 ..= self.core.params.n {
555      writer.write_all(
556        self.core.verification_shares[&Participant::new(l).unwrap()].to_bytes().as_ref(),
557      )?;
558    }
559    Ok(())
560  }
561
562  /// Serialize these keys to a `Vec<u8>`.
563  ///
564  /// This will not include the ephemeral scalar/offset.
565  pub fn serialize(&self) -> Zeroizing<Vec<u8>> {
566    let mut serialized = Zeroizing::new(vec![]);
567    self.write::<Vec<u8>>(serialized.as_mut()).unwrap();
568    serialized
569  }
570
571  /// Read keys from a type satisfying `std::io::Read`.
572  pub fn read<R: io::Read>(reader: &mut R) -> io::Result<ThresholdKeys<C>> {
573    {
574      let different = || io::Error::other("deserializing ThresholdKeys for another curve");
575
576      let mut id_len = [0; 4];
577      reader.read_exact(&mut id_len)?;
578      if u32::try_from(C::ID.len()).unwrap().to_le_bytes() != id_len {
579        Err(different())?;
580      }
581
582      let mut id = vec![0; C::ID.len()];
583      reader.read_exact(&mut id)?;
584      if id != C::ID {
585        Err(different())?;
586      }
587    }
588
589    let (t, n, i) = {
590      let mut read_u16 = || -> io::Result<u16> {
591        let mut value = [0; 2];
592        reader.read_exact(&mut value)?;
593        Ok(u16::from_le_bytes(value))
594      };
595      (
596        read_u16()?,
597        read_u16()?,
598        Participant::new(read_u16()?).ok_or(io::Error::other("invalid participant index"))?,
599      )
600    };
601
602    let mut interpolation = [0];
603    reader.read_exact(&mut interpolation)?;
604    let interpolation = match interpolation[0] {
605      0 => Interpolation::Constant({
606        let mut res = Vec::with_capacity(usize::from(n));
607        for _ in 0 .. n {
608          res.push(C::read_F(reader)?);
609        }
610        res
611      }),
612      1 => Interpolation::Lagrange,
613      _ => Err(io::Error::other("invalid interpolation method"))?,
614    };
615
616    let secret_share = Zeroizing::new(C::read_F(reader)?);
617
618    let mut verification_shares = HashMap::new();
619    for l in (1 ..= n).map(Participant) {
620      verification_shares.insert(l, <C as Ciphersuite>::read_G(reader)?);
621    }
622
623    ThresholdKeys::new(
624      ThresholdParams::new(t, n, i).map_err(io::Error::other)?,
625      interpolation,
626      secret_share,
627      verification_shares,
628    )
629    .map_err(io::Error::other)
630  }
631}
632
633impl<C: Ciphersuite> ThresholdView<C> {
634  /// Return the scalar applied to this view.
635  pub fn scalar(&self) -> C::F {
636    self.scalar
637  }
638
639  /// Return the offset applied to this view.
640  pub fn offset(&self) -> C::F {
641    self.offset
642  }
643
644  /// Return the group key.
645  pub fn group_key(&self) -> C::G {
646    self.group_key
647  }
648
649  /// Return the included signers.
650  pub fn included(&self) -> &[Participant] {
651    &self.included
652  }
653
654  /// Return the interpolation factor for a signer.
655  pub fn interpolation_factor(&self, participant: Participant) -> Option<C::F> {
656    if !self.included.contains(&participant) {
657      None?
658    }
659    Some(self.interpolation.interpolation_factor(participant, &self.included))
660  }
661
662  /// Return the interpolated secret share, with the expected linear combination taken.
663  pub fn secret_share(&self) -> &Zeroizing<C::F> {
664    &self.secret_share
665  }
666
667  /// Return the original (untweaked) verification share for the specified participant.
668  ///
669  /// This will panic if the participant index is invalid for these keys.
670  pub fn original_verification_share(&self, l: Participant) -> C::G {
671    self.original_verification_shares[&l]
672  }
673
674  /// Return the interpolated verification share, with the expected linear combination taken,
675  /// for the specified participant.
676  ///
677  /// This will panic if the participant was not included in the signing set.
678  pub fn verification_share(&self, l: Participant) -> C::G {
679    self.verification_shares[&l]
680  }
681}