modular_frost_mirror/
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(Clone, 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: Clone;
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. This cached preprocess MUST only be used once. Reuse
207  /// of it enables recovery of your private key share. Third-party recovery of a cached preprocess
208  /// also enables recovery of your private key share, so this MUST be treated with the same
209  /// security as your private key share.
210  fn cache(self) -> CachedPreprocess;
211
212  /// Create a sign machine from a cached preprocess.
213
214  /// After this, the preprocess must be deleted so it's never reused. Any reuse will presumably
215  /// cause the signer to leak their secret share.
216  fn from_cache(
217    params: Self::Params,
218    keys: Self::Keys,
219    cache: CachedPreprocess,
220  ) -> (Self, Self::Preprocess);
221
222  /// Read a Preprocess message. Despite taking self, this does not save the preprocess.
223  /// It must be externally cached and passed into sign.
224  fn read_preprocess<R: Read>(&self, reader: &mut R) -> io::Result<Self::Preprocess>;
225
226  /// Sign a message.
227  /// Takes in the participants' preprocess messages. Returns the signature share to be broadcast
228  /// to all participants, over an authenticated channel. The parties who participate here will
229  /// become the signing set for this session.
230  fn sign(
231    self,
232    commitments: HashMap<Participant, Self::Preprocess>,
233    msg: &[u8],
234  ) -> Result<(Self::SignatureMachine, Self::SignatureShare), FrostError>;
235}
236
237/// Next step of the state machine for the signing process.
238#[derive(Zeroize)]
239pub struct AlgorithmSignMachine<C: Curve, A: Algorithm<C>> {
240  params: Params<C, A>,
241  seed: CachedPreprocess,
242
243  pub(crate) nonces: Vec<Nonce<C>>,
244  // Skips the preprocess due to being too large a bound to feasibly enforce on users
245  #[zeroize(skip)]
246  pub(crate) preprocess: Preprocess<C, A::Addendum>,
247  pub(crate) blame_entropy: [u8; 32],
248}
249
250impl<C: Curve, A: Algorithm<C>> SignMachine<A::Signature> for AlgorithmSignMachine<C, A> {
251  type Params = A;
252  type Keys = ThresholdKeys<C>;
253  type Preprocess = Preprocess<C, A::Addendum>;
254  type SignatureShare = SignatureShare<C>;
255  type SignatureMachine = AlgorithmSignatureMachine<C, A>;
256
257  fn cache(self) -> CachedPreprocess {
258    self.seed
259  }
260
261  fn from_cache(
262    algorithm: A,
263    keys: ThresholdKeys<C>,
264    cache: CachedPreprocess,
265  ) -> (Self, Self::Preprocess) {
266    AlgorithmMachine::new(algorithm, keys).seeded_preprocess(cache)
267  }
268
269  fn read_preprocess<R: Read>(&self, reader: &mut R) -> io::Result<Self::Preprocess> {
270    Ok(Preprocess {
271      commitments: Commitments::read::<_>(reader, &self.params.algorithm.nonces())?,
272      addendum: self.params.algorithm.read_addendum(reader)?,
273    })
274  }
275
276  fn sign(
277    mut self,
278    mut preprocesses: HashMap<Participant, Preprocess<C, A::Addendum>>,
279    msg: &[u8],
280  ) -> Result<(Self::SignatureMachine, SignatureShare<C>), FrostError> {
281    let multisig_params = self.params.multisig_params();
282
283    let mut included = Vec::with_capacity(preprocesses.len() + 1);
284    included.push(multisig_params.i());
285    for l in preprocesses.keys() {
286      included.push(*l);
287    }
288    included.sort_unstable();
289
290    // Included < threshold
291    if included.len() < usize::from(multisig_params.t()) {
292      Err(FrostError::InvalidSigningSet("not enough signers"))?;
293    }
294    // OOB index
295    if u16::from(included[included.len() - 1]) > multisig_params.n() {
296      Err(FrostError::InvalidParticipant(multisig_params.n(), included[included.len() - 1]))?;
297    }
298    // Same signer included multiple times
299    for i in 0 .. (included.len() - 1) {
300      if included[i] == included[i + 1] {
301        Err(FrostError::DuplicatedParticipant(included[i]))?;
302      }
303    }
304
305    let view = self.params.keys.view(included.clone()).unwrap();
306    validate_map(&preprocesses, &included, multisig_params.i())?;
307
308    {
309      // Domain separate FROST
310      self.params.algorithm.transcript().domain_separate(b"FROST");
311    }
312
313    let nonces = self.params.algorithm.nonces();
314    #[allow(non_snake_case)]
315    let mut B = BindingFactor(HashMap::<Participant, _>::with_capacity(included.len()));
316    {
317      // Parse the preprocesses
318      for l in &included {
319        {
320          self
321            .params
322            .algorithm
323            .transcript()
324            .append_message(b"participant", C::F::from(u64::from(u16::from(*l))).to_repr());
325        }
326
327        if *l == self.params.keys.params().i() {
328          let commitments = self.preprocess.commitments.clone();
329          commitments.transcript(self.params.algorithm.transcript());
330
331          let addendum = self.preprocess.addendum.clone();
332          {
333            let mut buf = vec![];
334            addendum.write(&mut buf).unwrap();
335            self.params.algorithm.transcript().append_message(b"addendum", buf);
336          }
337
338          B.insert(*l, commitments);
339          self.params.algorithm.process_addendum(&view, *l, addendum)?;
340        } else {
341          let preprocess = preprocesses.remove(l).unwrap();
342          preprocess.commitments.transcript(self.params.algorithm.transcript());
343          {
344            let mut buf = vec![];
345            preprocess.addendum.write(&mut buf).unwrap();
346            self.params.algorithm.transcript().append_message(b"addendum", buf);
347          }
348
349          B.insert(*l, preprocess.commitments);
350          self.params.algorithm.process_addendum(&view, *l, preprocess.addendum)?;
351        }
352      }
353
354      // Re-format into the FROST-expected rho transcript
355      let mut rho_transcript = A::Transcript::new(b"FROST_rho");
356      rho_transcript.append_message(
357        b"group_key",
358        (self.params.keys.group_key() +
359          (C::generator() * self.params.keys.current_offset().unwrap_or(C::F::ZERO)))
360        .to_bytes(),
361      );
362      rho_transcript.append_message(b"message", C::hash_msg(msg));
363      rho_transcript.append_message(
364        b"preprocesses",
365        C::hash_commitments(self.params.algorithm.transcript().challenge(b"preprocesses").as_ref()),
366      );
367
368      // Generate the per-signer binding factors
369      B.calculate_binding_factors(&rho_transcript);
370
371      // Merge the rho transcript back into the global one to ensure its advanced, while
372      // simultaneously committing to everything
373      self
374        .params
375        .algorithm
376        .transcript()
377        .append_message(b"rho_transcript", rho_transcript.challenge(b"merge"));
378    }
379
380    #[allow(non_snake_case)]
381    let Rs = B.nonces(&nonces);
382
383    let our_binding_factors = B.binding_factors(multisig_params.i());
384    let nonces = self
385      .nonces
386      .drain(..)
387      .enumerate()
388      .map(|(n, nonces)| {
389        let [base, mut actual] = nonces.0;
390        *actual *= our_binding_factors[n];
391        *actual += base.deref();
392        actual
393      })
394      .collect::<Vec<_>>();
395
396    let share = self.params.algorithm.sign_share(&view, &Rs, nonces, msg);
397
398    Ok((
399      AlgorithmSignatureMachine {
400        params: self.params.clone(),
401        view,
402        B,
403        Rs,
404        share,
405        blame_entropy: self.blame_entropy,
406      },
407      SignatureShare(share),
408    ))
409  }
410}
411
412/// Trait for the final machine of a two-round signing protocol.
413pub trait SignatureMachine<S>: Send + Sync {
414  /// SignatureShare message for this machine.
415  type SignatureShare: Clone + PartialEq + Writable;
416
417  /// Read a Signature Share message.
418  fn read_share<R: Read>(&self, reader: &mut R) -> io::Result<Self::SignatureShare>;
419
420  /// Complete signing.
421  /// Takes in everyone elses' shares. Returns the signature.
422  fn complete(self, shares: HashMap<Participant, Self::SignatureShare>) -> Result<S, FrostError>;
423}
424
425/// Final step of the state machine for the signing process.
426///
427/// This may panic if an invalid algorithm is provided.
428#[allow(non_snake_case)]
429pub struct AlgorithmSignatureMachine<C: Curve, A: Algorithm<C>> {
430  params: Params<C, A>,
431  view: ThresholdView<C>,
432  B: BindingFactor<C>,
433  Rs: Vec<Vec<C::G>>,
434  share: C::F,
435  blame_entropy: [u8; 32],
436}
437
438impl<C: Curve, A: Algorithm<C>> SignatureMachine<A::Signature> for AlgorithmSignatureMachine<C, A> {
439  type SignatureShare = SignatureShare<C>;
440
441  fn read_share<R: Read>(&self, reader: &mut R) -> io::Result<SignatureShare<C>> {
442    Ok(SignatureShare(C::read_F(reader)?))
443  }
444
445  fn complete(
446    self,
447    mut shares: HashMap<Participant, SignatureShare<C>>,
448  ) -> Result<A::Signature, FrostError> {
449    let params = self.params.multisig_params();
450    validate_map(&shares, self.view.included(), params.i())?;
451
452    let mut responses = HashMap::new();
453    responses.insert(params.i(), self.share);
454    let mut sum = self.share;
455    for (l, share) in shares.drain() {
456      responses.insert(l, share.0);
457      sum += share.0;
458    }
459
460    // Perform signature validation instead of individual share validation
461    // For the success route, which should be much more frequent, this should be faster
462    // It also acts as an integrity check of this library's signing function
463    if let Some(sig) = self.params.algorithm.verify(self.view.group_key(), &self.Rs, sum) {
464      return Ok(sig);
465    }
466
467    // We could remove blame_entropy by taking in an RNG here
468    // Considering we don't need any RNG for a valid signature, and we only use the RNG here for
469    // performance reasons, it doesn't feel worthwhile to include as an argument to every
470    // implementor of the trait
471    let mut rng = ChaCha20Rng::from_seed(self.blame_entropy);
472    let mut batch = BatchVerifier::new(self.view.included().len());
473    for l in self.view.included() {
474      if let Ok(statements) = self.params.algorithm.verify_share(
475        self.view.verification_share(*l),
476        &self.B.bound(*l),
477        responses[l],
478      ) {
479        batch.queue(&mut rng, *l, statements);
480      } else {
481        Err(FrostError::InvalidShare(*l))?;
482      }
483    }
484
485    if let Err(l) = batch.verify_vartime_with_vartime_blame() {
486      Err(FrostError::InvalidShare(l))?;
487    }
488
489    // If everyone has a valid share, and there were enough participants, this should've worked
490    // The only known way to cause this, for valid parameters/algorithms, is to deserialize a
491    // semantically invalid FrostKeys
492    Err(FrostError::InternalError("everyone had a valid share yet the signature was still invalid"))
493  }
494}