modular_frost/tests/
nonces.rs

1use std::io::{self, Read};
2
3use zeroize::Zeroizing;
4
5use rand_core::{RngCore, CryptoRng, SeedableRng};
6use rand_chacha::ChaCha20Rng;
7
8use transcript::{Transcript, RecommendedTranscript};
9
10use ciphersuite::group::{ff::Field, Group, GroupEncoding};
11
12use crate::{
13  Curve, Participant, ThresholdView, ThresholdKeys, FrostError,
14  algorithm::Algorithm,
15  tests::{key_gen, algorithm_machines, sign},
16};
17
18#[derive(Clone)]
19struct MultiNonce<C: Curve> {
20  transcript: RecommendedTranscript,
21  nonces: Option<Vec<Vec<C::G>>>,
22}
23
24impl<C: Curve> MultiNonce<C> {
25  fn new() -> MultiNonce<C> {
26    MultiNonce {
27      transcript: RecommendedTranscript::new(b"FROST MultiNonce Algorithm Test"),
28      nonces: None,
29    }
30  }
31}
32
33fn nonces<C: Curve>() -> Vec<Vec<C::G>> {
34  vec![
35    vec![C::generator(), C::generator().double()],
36    vec![C::generator(), C::generator() * C::F::from(3), C::generator() * C::F::from(4)],
37  ]
38}
39
40fn verify_nonces<C: Curve>(nonces: &[Vec<C::G>]) {
41  assert_eq!(nonces.len(), 2);
42
43  // Each nonce should be a series of commitments, over some generators, which share a discrete log
44  // Since they share a discrete log, their only distinction should be the generator
45  // Above, the generators were created with a known relationship
46  // Accordingly, we can check here that relationship holds to make sure these commitments are well
47  // formed
48  assert_eq!(nonces[0].len(), 2);
49  assert_eq!(nonces[0][0].double(), nonces[0][1]);
50
51  assert_eq!(nonces[1].len(), 3);
52  assert_eq!(nonces[1][0] * C::F::from(3), nonces[1][1]);
53  assert_eq!(nonces[1][0] * C::F::from(4), nonces[1][2]);
54
55  assert!(nonces[0][0] != nonces[1][0]);
56}
57
58impl<C: Curve> Algorithm<C> for MultiNonce<C> {
59  type Transcript = RecommendedTranscript;
60  type Addendum = ();
61  type Signature = ();
62
63  fn transcript(&mut self) -> &mut Self::Transcript {
64    &mut self.transcript
65  }
66
67  fn nonces(&self) -> Vec<Vec<C::G>> {
68    nonces::<C>()
69  }
70
71  fn preprocess_addendum<R: RngCore + CryptoRng>(&mut self, _: &mut R, _: &ThresholdKeys<C>) {}
72
73  fn read_addendum<R: Read>(&self, _: &mut R) -> io::Result<Self::Addendum> {
74    Ok(())
75  }
76
77  fn process_addendum(
78    &mut self,
79    _: &ThresholdView<C>,
80    _: Participant,
81    (): (),
82  ) -> Result<(), FrostError> {
83    Ok(())
84  }
85
86  fn sign_share(
87    &mut self,
88    _: &ThresholdView<C>,
89    nonce_sums: &[Vec<C::G>],
90    nonces: Vec<Zeroizing<C::F>>,
91    _: &[u8],
92  ) -> C::F {
93    // Verify the nonce sums are as expected
94    verify_nonces::<C>(nonce_sums);
95
96    // Verify we actually have two nonces and that they're distinct
97    assert_eq!(nonces.len(), 2);
98    assert!(nonces[0] != nonces[1]);
99
100    // Save the nonce sums for later so we can check they're consistent with the call to verify
101    assert!(self.nonces.is_none());
102    self.nonces = Some(nonce_sums.to_vec());
103
104    // Sum the nonces so we can later check they actually have a relationship to nonce_sums
105    let mut res = C::F::ZERO;
106
107    // Weight each nonce
108    // This is probably overkill, since their unweighted forms would practically still require
109    // some level of crafting to pass a naive sum via malleability, yet this makes it more robust
110    for nonce in nonce_sums {
111      self.transcript.domain_separate(b"nonce");
112      for commitment in nonce {
113        self.transcript.append_message(b"commitment", commitment.to_bytes());
114      }
115    }
116    let mut rng = ChaCha20Rng::from_seed(self.transcript.clone().rng_seed(b"weight"));
117
118    for nonce in nonces {
119      res += *nonce * C::F::random(&mut rng);
120    }
121    res
122  }
123
124  #[must_use]
125  fn verify(&self, _: C::G, nonces: &[Vec<C::G>], sum: C::F) -> Option<Self::Signature> {
126    verify_nonces::<C>(nonces);
127    assert_eq!(&self.nonces.clone().unwrap(), nonces);
128
129    // Make sure the nonce sums actually relate to the nonces
130    let mut res = C::G::identity();
131    let mut rng = ChaCha20Rng::from_seed(self.transcript.clone().rng_seed(b"weight"));
132    for nonce in nonces {
133      res += nonce[0] * C::F::random(&mut rng);
134    }
135    assert_eq!(res, C::generator() * sum);
136
137    Some(())
138  }
139
140  fn verify_share(&self, _: C::G, _: &[Vec<C::G>], _: C::F) -> Result<Vec<(C::F, C::G)>, ()> {
141    panic!("share verification triggered");
142  }
143}
144
145/// Test a multi-nonce, multi-generator algorithm.
146// Specifically verifies this library can:
147// 1) Generate multiple nonces
148// 2) Provide the group nonces (nonce_sums) across multiple generators, still with the same
149//    discrete log
150// 3) Provide algorithms with nonces which match the group nonces
151pub fn test_multi_nonce<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
152  let keys = key_gen::<R, C>(&mut *rng);
153  let machines = algorithm_machines(&mut *rng, &MultiNonce::<C>::new(), &keys);
154  sign(&mut *rng, &MultiNonce::<C>::new(), keys.clone(), machines, &[]);
155}