modular_frost/
sign.rs

1use core::{ops::Deref, fmt::Debug};
2use std::{
3  io::{self, Read, Write},
4  collections::HashMap,
5};
6
7use rand_core::{RngCore, CryptoRng, SeedableRng};
8use rand_chacha::ChaCha20Rng;
9
10use zeroize::{Zeroize, Zeroizing};
11
12use transcript::Transcript;
13
14use ciphersuite::group::{
15  ff::{Field, PrimeField},
16  GroupEncoding,
17};
18use multiexp::BatchVerifier;
19
20use crate::{
21  curve::Curve,
22  Participant, FrostError, ThresholdParams, ThresholdKeys, ThresholdView,
23  algorithm::{WriteAddendum, Addendum, Algorithm},
24  validate_map,
25};
26
27pub(crate) use crate::nonce::*;
28
29/// Trait enabling writing preprocesses and signature shares.
30pub trait Writable {
31  fn write<W: Write>(&self, writer: &mut W) -> io::Result<()>;
32
33  fn serialize(&self) -> Vec<u8> {
34    let mut buf = vec![];
35    self.write(&mut buf).unwrap();
36    buf
37  }
38}
39
40impl<T: Writable> Writable for Vec<T> {
41  fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
42    for w in self {
43      w.write(writer)?;
44    }
45    Ok(())
46  }
47}
48
49// Pairing of an Algorithm with a ThresholdKeys instance.
50#[derive(Zeroize)]
51struct Params<C: Curve, A: Algorithm<C>> {
52  // Skips the algorithm due to being too large a bound to feasibly enforce on users
53  #[zeroize(skip)]
54  algorithm: A,
55  keys: ThresholdKeys<C>,
56}
57
58impl<C: Curve, A: Algorithm<C>> Params<C, A> {
59  fn new(algorithm: A, keys: ThresholdKeys<C>) -> Params<C, A> {
60    Params { algorithm, keys }
61  }
62
63  fn multisig_params(&self) -> ThresholdParams {
64    self.keys.params()
65  }
66}
67
68/// Preprocess for an instance of the FROST signing protocol.
69#[derive(Clone, PartialEq, Eq)]
70pub struct Preprocess<C: Curve, A: Addendum> {
71  pub(crate) commitments: Commitments<C>,
72  /// The addendum used by the algorithm.
73  pub addendum: A,
74}
75
76impl<C: Curve, A: Addendum> Writable for Preprocess<C, A> {
77  fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
78    self.commitments.write(writer)?;
79    self.addendum.write(writer)
80  }
81}
82
83/// A cached preprocess.
84///
85/// A preprocess MUST only be used once. Reuse will enable third-party recovery of your private
86/// key share. Additionally, this MUST be handled with the same security as your private key share,
87/// as knowledge of it also enables recovery.
88// Directly exposes the [u8; 32] member to void needing to route through std::io interfaces.
89// Still uses Zeroizing internally so when users grab it, they have a higher likelihood of
90// appreciating how to handle it and don't immediately start copying it just by grabbing it.
91#[derive(Zeroize)]
92pub struct CachedPreprocess(pub Zeroizing<[u8; 32]>);
93
94/// Trait for the initial state machine of a two-round signing protocol.
95pub trait PreprocessMachine: Send {
96  /// Preprocess message for this machine.
97  type Preprocess: Clone + PartialEq + Writable;
98  /// Signature produced by this machine.
99  type Signature: Clone + PartialEq + Debug;
100  /// SignMachine this PreprocessMachine turns into.
101  type SignMachine: SignMachine<Self::Signature, Preprocess = Self::Preprocess>;
102
103  /// Perform the preprocessing round required in order to sign.
104  /// Returns a preprocess message to be broadcast to all participants, over an authenticated
105  /// channel.
106  fn preprocess<R: RngCore + CryptoRng>(self, rng: &mut R)
107    -> (Self::SignMachine, Self::Preprocess);
108}
109
110/// State machine which manages signing for an arbitrary signature algorithm.
111pub struct AlgorithmMachine<C: Curve, A: Algorithm<C>> {
112  params: Params<C, A>,
113}
114
115impl<C: Curve, A: Algorithm<C>> AlgorithmMachine<C, A> {
116  /// Creates a new machine to generate a signature with the specified keys.
117  pub fn new(algorithm: A, keys: ThresholdKeys<C>) -> AlgorithmMachine<C, A> {
118    AlgorithmMachine { params: Params::new(algorithm, keys) }
119  }
120
121  fn seeded_preprocess(
122    self,
123    seed: CachedPreprocess,
124  ) -> (AlgorithmSignMachine<C, A>, Preprocess<C, A::Addendum>) {
125    let mut params = self.params;
126
127    let mut rng = ChaCha20Rng::from_seed(*seed.0);
128    let (nonces, commitments) =
129      Commitments::new::<_>(&mut rng, params.keys.secret_share(), &params.algorithm.nonces());
130    let addendum = params.algorithm.preprocess_addendum(&mut rng, &params.keys);
131
132    let preprocess = Preprocess { commitments, addendum };
133
134    // Also obtain entropy to randomly sort the included participants if we need to identify blame
135    let mut blame_entropy = [0; 32];
136    rng.fill_bytes(&mut blame_entropy);
137    (
138      AlgorithmSignMachine { params, seed, nonces, preprocess: preprocess.clone(), blame_entropy },
139      preprocess,
140    )
141  }
142
143  #[cfg(any(test, feature = "tests"))]
144  pub(crate) fn unsafe_override_preprocess(
145    self,
146    nonces: Vec<Nonce<C>>,
147    preprocess: Preprocess<C, A::Addendum>,
148  ) -> AlgorithmSignMachine<C, A> {
149    AlgorithmSignMachine {
150      params: self.params,
151      seed: CachedPreprocess(Zeroizing::new([0; 32])),
152
153      nonces,
154      preprocess,
155      // Uses 0s since this is just used to protect against a malicious participant from
156      // deliberately increasing the amount of time needed to identify them (and is accordingly
157      // not necessary to function)
158      blame_entropy: [0; 32],
159    }
160  }
161}
162
163impl<C: Curve, A: Algorithm<C>> PreprocessMachine for AlgorithmMachine<C, A> {
164  type Preprocess = Preprocess<C, A::Addendum>;
165  type Signature = A::Signature;
166  type SignMachine = AlgorithmSignMachine<C, A>;
167
168  fn preprocess<R: RngCore + CryptoRng>(
169    self,
170    rng: &mut R,
171  ) -> (Self::SignMachine, Preprocess<C, A::Addendum>) {
172    let mut seed = CachedPreprocess(Zeroizing::new([0; 32]));
173    rng.fill_bytes(seed.0.as_mut());
174    self.seeded_preprocess(seed)
175  }
176}
177
178/// Share of a signature produced via FROST.
179#[derive(Clone, PartialEq, Eq)]
180pub struct SignatureShare<C: Curve>(C::F);
181impl<C: Curve> Writable for SignatureShare<C> {
182  fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
183    writer.write_all(self.0.to_repr().as_ref())
184  }
185}
186#[cfg(any(test, feature = "tests"))]
187impl<C: Curve> SignatureShare<C> {
188  pub(crate) fn invalidate(&mut self) {
189    self.0 += C::F::ONE;
190  }
191}
192
193/// Trait for the second machine of a two-round signing protocol.
194pub trait SignMachine<S>: Send + Sync + Sized {
195  /// Params used to instantiate this machine which can be used to rebuild from a cache.
196  type Params;
197  /// Keys used for signing operations.
198  type Keys;
199  /// Preprocess message for this machine.
200  type Preprocess: Clone + PartialEq + Writable;
201  /// SignatureShare message for this machine.
202  type SignatureShare: Clone + PartialEq + Writable;
203  /// SignatureMachine this SignMachine turns into.
204  type SignatureMachine: SignatureMachine<S, SignatureShare = Self::SignatureShare>;
205
206  /// Cache this preprocess for usage later.
207  ///
208  /// This cached preprocess MUST only be used once. Reuse of it enables recovery of your private
209  /// key share. Third-party recovery of a cached preprocess also enables recovery of your private
210  /// key share, so this MUST be treated with the same security as your private key share.
211  fn cache(self) -> CachedPreprocess;
212
213  /// Create a sign machine from a cached preprocess.
214  ///
215  /// After this, the preprocess must be deleted so it's never reused. Any reuse will presumably
216  /// cause the signer to leak their secret share.
217  fn from_cache(
218    params: Self::Params,
219    keys: Self::Keys,
220    cache: CachedPreprocess,
221  ) -> (Self, Self::Preprocess);
222
223  /// Read a Preprocess message.
224  ///
225  /// Despite taking self, this does not save the preprocess. It must be externally cached and
226  /// passed into sign.
227  fn read_preprocess<R: Read>(&self, reader: &mut R) -> io::Result<Self::Preprocess>;
228
229  /// Sign a message.
230  ///
231  /// Takes in the participants' preprocess messages. Returns the signature share to be broadcast
232  /// to all participants, over an authenticated channel. The parties who participate here will
233  /// become the signing set for this session.
234  fn sign(
235    self,
236    commitments: HashMap<Participant, Self::Preprocess>,
237    msg: &[u8],
238  ) -> Result<(Self::SignatureMachine, Self::SignatureShare), FrostError>;
239}
240
241/// Next step of the state machine for the signing process.
242#[derive(Zeroize)]
243pub struct AlgorithmSignMachine<C: Curve, A: Algorithm<C>> {
244  params: Params<C, A>,
245  seed: CachedPreprocess,
246
247  pub(crate) nonces: Vec<Nonce<C>>,
248  // Skips the preprocess due to being too large a bound to feasibly enforce on users
249  #[zeroize(skip)]
250  pub(crate) preprocess: Preprocess<C, A::Addendum>,
251  pub(crate) blame_entropy: [u8; 32],
252}
253
254impl<C: Curve, A: Algorithm<C>> SignMachine<A::Signature> for AlgorithmSignMachine<C, A> {
255  type Params = A;
256  type Keys = ThresholdKeys<C>;
257  type Preprocess = Preprocess<C, A::Addendum>;
258  type SignatureShare = SignatureShare<C>;
259  type SignatureMachine = AlgorithmSignatureMachine<C, A>;
260
261  fn cache(self) -> CachedPreprocess {
262    self.seed
263  }
264
265  fn from_cache(
266    algorithm: A,
267    keys: ThresholdKeys<C>,
268    cache: CachedPreprocess,
269  ) -> (Self, Self::Preprocess) {
270    AlgorithmMachine::new(algorithm, keys).seeded_preprocess(cache)
271  }
272
273  fn read_preprocess<R: Read>(&self, reader: &mut R) -> io::Result<Self::Preprocess> {
274    Ok(Preprocess {
275      commitments: Commitments::read::<_>(reader, &self.params.algorithm.nonces())?,
276      addendum: self.params.algorithm.read_addendum(reader)?,
277    })
278  }
279
280  fn sign(
281    mut self,
282    mut preprocesses: HashMap<Participant, Preprocess<C, A::Addendum>>,
283    msg: &[u8],
284  ) -> Result<(Self::SignatureMachine, SignatureShare<C>), FrostError> {
285    let multisig_params = self.params.multisig_params();
286
287    let mut included = Vec::with_capacity(preprocesses.len() + 1);
288    included.push(multisig_params.i());
289    for l in preprocesses.keys() {
290      included.push(*l);
291    }
292    included.sort_unstable();
293
294    // Included < threshold
295    if included.len() < usize::from(multisig_params.t()) {
296      Err(FrostError::InvalidSigningSet("not enough signers"))?;
297    }
298    // OOB index
299    if u16::from(included[included.len() - 1]) > multisig_params.n() {
300      Err(FrostError::InvalidParticipant(multisig_params.n(), included[included.len() - 1]))?;
301    }
302    // Same signer included multiple times
303    for i in 0 .. (included.len() - 1) {
304      if included[i] == included[i + 1] {
305        Err(FrostError::DuplicatedParticipant(included[i]))?;
306      }
307    }
308
309    let view = self.params.keys.view(included.clone()).unwrap();
310    validate_map(&preprocesses, &included, multisig_params.i())?;
311
312    {
313      // Domain separate FROST
314      self.params.algorithm.transcript().domain_separate(b"FROST");
315    }
316
317    let nonces = self.params.algorithm.nonces();
318    #[allow(non_snake_case)]
319    let mut B = BindingFactor(HashMap::<Participant, _>::with_capacity(included.len()));
320    {
321      // Parse the preprocesses
322      for l in &included {
323        {
324          self
325            .params
326            .algorithm
327            .transcript()
328            .append_message(b"participant", C::F::from(u64::from(u16::from(*l))).to_repr());
329        }
330
331        if *l == self.params.keys.params().i() {
332          let commitments = self.preprocess.commitments.clone();
333          commitments.transcript(self.params.algorithm.transcript());
334
335          let addendum = self.preprocess.addendum.clone();
336          {
337            let mut buf = vec![];
338            addendum.write(&mut buf).unwrap();
339            self.params.algorithm.transcript().append_message(b"addendum", buf);
340          }
341
342          B.insert(*l, commitments);
343          self.params.algorithm.process_addendum(&view, *l, addendum)?;
344        } else {
345          let preprocess = preprocesses.remove(l).unwrap();
346          preprocess.commitments.transcript(self.params.algorithm.transcript());
347          {
348            let mut buf = vec![];
349            preprocess.addendum.write(&mut buf).unwrap();
350            self.params.algorithm.transcript().append_message(b"addendum", buf);
351          }
352
353          B.insert(*l, preprocess.commitments);
354          self.params.algorithm.process_addendum(&view, *l, preprocess.addendum)?;
355        }
356      }
357
358      // Re-format into the FROST-expected rho transcript
359      let mut rho_transcript = A::Transcript::new(b"FROST_rho");
360      rho_transcript.append_message(b"group_key", self.params.keys.group_key().to_bytes());
361      rho_transcript.append_message(b"message", C::hash_msg(msg));
362      rho_transcript.append_message(
363        b"preprocesses",
364        C::hash_commitments(self.params.algorithm.transcript().challenge(b"preprocesses").as_ref()),
365      );
366
367      // Generate the per-signer binding factors
368      B.calculate_binding_factors(&rho_transcript);
369
370      // Merge the rho transcript back into the global one to ensure its advanced, while
371      // simultaneously committing to everything
372      self
373        .params
374        .algorithm
375        .transcript()
376        .append_message(b"rho_transcript", rho_transcript.challenge(b"merge"));
377    }
378
379    #[allow(non_snake_case)]
380    let Rs = B.nonces(&nonces);
381
382    let our_binding_factors = B.binding_factors(multisig_params.i());
383    let nonces = self
384      .nonces
385      .drain(..)
386      .enumerate()
387      .map(|(n, nonces)| {
388        let [base, mut actual] = nonces.0;
389        *actual *= our_binding_factors[n];
390        *actual += base.deref();
391        actual
392      })
393      .collect::<Vec<_>>();
394
395    let share = self.params.algorithm.sign_share(&view, &Rs, nonces, msg);
396
397    Ok((
398      AlgorithmSignatureMachine {
399        params: self.params,
400        view,
401        B,
402        Rs,
403        share,
404        blame_entropy: self.blame_entropy,
405      },
406      SignatureShare(share),
407    ))
408  }
409}
410
411/// Trait for the final machine of a two-round signing protocol.
412pub trait SignatureMachine<S>: Send + Sync {
413  /// SignatureShare message for this machine.
414  type SignatureShare: Clone + PartialEq + Writable;
415
416  /// Read a Signature Share message.
417  fn read_share<R: Read>(&self, reader: &mut R) -> io::Result<Self::SignatureShare>;
418
419  /// Complete signing.
420  /// Takes in everyone elses' shares. Returns the signature.
421  fn complete(self, shares: HashMap<Participant, Self::SignatureShare>) -> Result<S, FrostError>;
422}
423
424/// Final step of the state machine for the signing process.
425///
426/// This may panic if an invalid algorithm is provided.
427#[allow(non_snake_case)]
428pub struct AlgorithmSignatureMachine<C: Curve, A: Algorithm<C>> {
429  params: Params<C, A>,
430  view: ThresholdView<C>,
431  B: BindingFactor<C>,
432  Rs: Vec<Vec<C::G>>,
433  share: C::F,
434  blame_entropy: [u8; 32],
435}
436
437impl<C: Curve, A: Algorithm<C>> SignatureMachine<A::Signature> for AlgorithmSignatureMachine<C, A> {
438  type SignatureShare = SignatureShare<C>;
439
440  fn read_share<R: Read>(&self, reader: &mut R) -> io::Result<SignatureShare<C>> {
441    Ok(SignatureShare(C::read_F(reader)?))
442  }
443
444  fn complete(
445    self,
446    mut shares: HashMap<Participant, SignatureShare<C>>,
447  ) -> Result<A::Signature, FrostError> {
448    let params = self.params.multisig_params();
449    validate_map(&shares, self.view.included(), params.i())?;
450
451    let mut responses = HashMap::new();
452    responses.insert(params.i(), self.share);
453    let mut sum = self.share;
454    for (l, share) in shares.drain() {
455      responses.insert(l, share.0);
456      sum += share.0;
457    }
458
459    // Perform signature validation instead of individual share validation
460    // For the success route, which should be much more frequent, this should be faster
461    // It also acts as an integrity check of this library's signing function
462    if let Some(sig) = self.params.algorithm.verify(self.view.group_key(), &self.Rs, sum) {
463      return Ok(sig);
464    }
465
466    // We could remove blame_entropy by taking in an RNG here
467    // Considering we don't need any RNG for a valid signature, and we only use the RNG here for
468    // performance reasons, it doesn't feel worthwhile to include as an argument to every
469    // implementor of the trait
470    let mut rng = ChaCha20Rng::from_seed(self.blame_entropy);
471    let mut batch = BatchVerifier::new(self.view.included().len());
472    for l in self.view.included() {
473      if let Ok(statements) = self.params.algorithm.verify_share(
474        self.view.verification_share(*l),
475        &self.B.bound(*l),
476        responses[l],
477      ) {
478        batch.queue(&mut rng, *l, statements);
479      } else {
480        Err(FrostError::InvalidShare(*l))?;
481      }
482    }
483
484    if let Err(l) = batch.verify_vartime_with_vartime_blame() {
485      Err(FrostError::InvalidShare(l))?;
486    }
487
488    // If everyone has a valid share, and there were enough participants, this should've worked
489    // The only known way to cause this, for valid parameters/algorithms, is to deserialize a
490    // semantically invalid FrostKeys
491    Err(FrostError::InternalError("everyone had a valid share yet the signature was still invalid"))
492  }
493}