modular_frost/
algorithm.rs

1use core::{marker::PhantomData, fmt::Debug};
2use std::io::{self, Read, Write};
3
4use zeroize::Zeroizing;
5use rand_core::{RngCore, CryptoRng};
6
7use transcript::Transcript;
8
9use crate::{Participant, ThresholdKeys, ThresholdView, Curve, FrostError};
10pub use schnorr::SchnorrSignature;
11
12/// Write an addendum to a writer.
13pub trait WriteAddendum {
14  fn write<W: Write>(&self, writer: &mut W) -> io::Result<()>;
15}
16
17impl WriteAddendum for () {
18  fn write<W: Write>(&self, _: &mut W) -> io::Result<()> {
19    Ok(())
20  }
21}
22
23/// Trait alias for the requirements to be used as an addendum.
24pub trait Addendum: Send + Sync + Clone + PartialEq + Debug + WriteAddendum {}
25impl<A: Send + Sync + Clone + PartialEq + Debug + WriteAddendum> Addendum for A {}
26
27/// Algorithm trait usable by the FROST signing machine to produce signatures..
28pub trait Algorithm<C: Curve>: Send + Sync {
29  /// The transcript format this algorithm uses. This likely should NOT be the IETF-compatible
30  /// transcript included in this crate.
31  type Transcript: Sync + Clone + Debug + Transcript;
32  /// Serializable addendum, used in algorithms requiring more data than just the nonces.
33  type Addendum: Addendum;
34  /// The resulting type of the signatures this algorithm will produce.
35  type Signature: Clone + PartialEq + Debug;
36
37  /// Obtain a mutable borrow of the underlying transcript.
38  fn transcript(&mut self) -> &mut Self::Transcript;
39
40  /// Obtain the list of nonces to generate, as specified by the generators to create commitments
41  /// against per-nonce.
42  ///
43  /// The Algorithm is responsible for all transcripting of these nonce specifications/generators.
44  ///
45  /// The prover will be passed the commitments, and the commitments will be sent to all other
46  /// participants. No guarantees the commitments are internally consistent (have the same discrete
47  /// logarithm across generators) are made. Any Algorithm which specifies multiple generators for
48  /// a single nonce must handle that itself.
49  fn nonces(&self) -> Vec<Vec<C::G>>;
50
51  /// Generate an addendum to FROST"s preprocessing stage.
52  fn preprocess_addendum<R: RngCore + CryptoRng>(
53    &mut self,
54    rng: &mut R,
55    keys: &ThresholdKeys<C>,
56  ) -> Self::Addendum;
57
58  /// Read an addendum from a reader.
59  fn read_addendum<R: Read>(&self, reader: &mut R) -> io::Result<Self::Addendum>;
60
61  /// Process the addendum for the specified participant. Guaranteed to be called in order.
62  fn process_addendum(
63    &mut self,
64    params: &ThresholdView<C>,
65    l: Participant,
66    reader: Self::Addendum,
67  ) -> Result<(), FrostError>;
68
69  /// Sign a share with the given secret/nonce.
70  /// The secret will already have been its lagrange coefficient applied so it is the necessary
71  /// key share.
72  /// The nonce will already have been processed into the combined form d + (e * p).
73  fn sign_share(
74    &mut self,
75    params: &ThresholdView<C>,
76    nonce_sums: &[Vec<C::G>],
77    nonces: Vec<Zeroizing<C::F>>,
78    msg: &[u8],
79  ) -> C::F;
80
81  /// Verify a signature.
82  #[must_use]
83  fn verify(&self, group_key: C::G, nonces: &[Vec<C::G>], sum: C::F) -> Option<Self::Signature>;
84
85  /// Verify a specific share given as a response.
86  /// This function should return a series of pairs whose products should sum to zero for a valid
87  /// share. Any error raised is treated as the share being invalid.
88  #[allow(clippy::type_complexity, clippy::result_unit_err)]
89  fn verify_share(
90    &self,
91    verification_share: C::G,
92    nonces: &[Vec<C::G>],
93    share: C::F,
94  ) -> Result<Vec<(C::F, C::G)>, ()>;
95}
96
97mod sealed {
98  pub use super::*;
99
100  /// IETF-compliant transcript. This is incredibly naive and should not be used within larger
101  /// protocols.
102  #[derive(Clone, Debug)]
103  pub struct IetfTranscript(pub(crate) Vec<u8>);
104  impl Transcript for IetfTranscript {
105    type Challenge = Vec<u8>;
106
107    fn new(_: &'static [u8]) -> IetfTranscript {
108      IetfTranscript(vec![])
109    }
110
111    fn domain_separate(&mut self, _: &[u8]) {}
112
113    fn append_message<M: AsRef<[u8]>>(&mut self, _: &'static [u8], message: M) {
114      self.0.extend(message.as_ref());
115    }
116
117    fn challenge(&mut self, _: &'static [u8]) -> Vec<u8> {
118      self.0.clone()
119    }
120
121    // FROST won't use this and this shouldn't be used outside of FROST
122    fn rng_seed(&mut self, _: &'static [u8]) -> [u8; 32] {
123      unimplemented!()
124    }
125  }
126}
127pub(crate) use sealed::IetfTranscript;
128
129/// HRAm usable by the included Schnorr signature algorithm to generate challenges.
130pub trait Hram<C: Curve>: Send + Sync + Clone {
131  /// HRAm function to generate a challenge.
132  /// H2 from the IETF draft, despite having a different argument set (not being pre-formatted).
133  #[allow(non_snake_case)]
134  fn hram(R: &C::G, A: &C::G, m: &[u8]) -> C::F;
135}
136
137/// Schnorr signature algorithm ((R, s) where s = r + cx).
138#[derive(Clone)]
139pub struct Schnorr<C: Curve, T: Sync + Clone + Debug + Transcript, H: Hram<C>> {
140  transcript: T,
141  c: Option<C::F>,
142  _hram: PhantomData<H>,
143}
144
145/// IETF-compliant Schnorr signature algorithm.
146///
147/// This algorithm specifically uses the transcript format defined in the FROST IETF draft.
148/// It's a naive transcript format not viable for usage in larger protocols, yet is presented here
149/// in order to provide compatibility.
150///
151/// Usage of this with key offsets will break the intended compatibility as the IETF draft does not
152/// specify a protocol for offsets.
153pub type IetfSchnorr<C, H> = Schnorr<C, IetfTranscript, H>;
154
155impl<C: Curve, T: Sync + Clone + Debug + Transcript, H: Hram<C>> Schnorr<C, T, H> {
156  /// Construct a Schnorr algorithm continuing the specified transcript.
157  pub fn new(transcript: T) -> Schnorr<C, T, H> {
158    Schnorr { transcript, c: None, _hram: PhantomData }
159  }
160}
161
162impl<C: Curve, H: Hram<C>> IetfSchnorr<C, H> {
163  /// Construct a IETF-compatible Schnorr algorithm.
164  ///
165  /// Please see the `IetfSchnorr` documentation for the full details of this.
166  pub fn ietf() -> IetfSchnorr<C, H> {
167    Schnorr::new(IetfTranscript(vec![]))
168  }
169}
170
171impl<C: Curve, T: Sync + Clone + Debug + Transcript, H: Hram<C>> Algorithm<C> for Schnorr<C, T, H> {
172  type Transcript = T;
173  type Addendum = ();
174  type Signature = SchnorrSignature<C>;
175
176  fn transcript(&mut self) -> &mut Self::Transcript {
177    &mut self.transcript
178  }
179
180  fn nonces(&self) -> Vec<Vec<C::G>> {
181    vec![vec![C::generator()]]
182  }
183
184  fn preprocess_addendum<R: RngCore + CryptoRng>(&mut self, _: &mut R, _: &ThresholdKeys<C>) {}
185
186  fn read_addendum<R: Read>(&self, _: &mut R) -> io::Result<Self::Addendum> {
187    Ok(())
188  }
189
190  fn process_addendum(
191    &mut self,
192    _: &ThresholdView<C>,
193    _: Participant,
194    (): (),
195  ) -> Result<(), FrostError> {
196    Ok(())
197  }
198
199  fn sign_share(
200    &mut self,
201    params: &ThresholdView<C>,
202    nonce_sums: &[Vec<C::G>],
203    mut nonces: Vec<Zeroizing<C::F>>,
204    msg: &[u8],
205  ) -> C::F {
206    let c = H::hram(&nonce_sums[0][0], &params.group_key(), msg);
207    self.c = Some(c);
208    SchnorrSignature::<C>::sign(params.secret_share(), nonces.swap_remove(0), c).s
209  }
210
211  #[must_use]
212  fn verify(&self, group_key: C::G, nonces: &[Vec<C::G>], sum: C::F) -> Option<Self::Signature> {
213    let sig = SchnorrSignature { R: nonces[0][0], s: sum };
214    Some(sig).filter(|sig| sig.verify(group_key, self.c.unwrap()))
215  }
216
217  fn verify_share(
218    &self,
219    verification_share: C::G,
220    nonces: &[Vec<C::G>],
221    share: C::F,
222  ) -> Result<Vec<(C::F, C::G)>, ()> {
223    Ok(
224      SchnorrSignature::<C> { R: nonces[0][0], s: share }
225        .batch_statements(verification_share, self.c.unwrap())
226        .to_vec(),
227    )
228  }
229}