1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use core::ops::Deref;
use std::io::{self, Read};

use rand_core::{RngCore, CryptoRng};

use zeroize::{Zeroize, Zeroizing};
use subtle::ConstantTimeEq;

use digest::{Digest, Output};

pub use ciphersuite::{
  group::{
    ff::{Field, PrimeField},
    Group,
  },
  Ciphersuite,
};

#[cfg(any(feature = "ristretto", feature = "ed25519"))]
mod dalek;
#[cfg(feature = "ristretto")]
pub use dalek::{Ristretto, IetfRistrettoHram};
#[cfg(feature = "ed25519")]
pub use dalek::{Ed25519, IetfEd25519Hram};

#[cfg(any(feature = "secp256k1", feature = "p256"))]
mod kp256;
#[cfg(feature = "secp256k1")]
pub use kp256::{Secp256k1, IetfSecp256k1Hram};
#[cfg(feature = "p256")]
pub use kp256::{P256, IetfP256Hram};

#[cfg(feature = "ed448")]
mod ed448;
#[cfg(feature = "ed448")]
pub use ed448::{Ed448, IetfEd448Hram};
#[cfg(all(test, feature = "ed448"))]
pub(crate) use ed448::Ietf8032Ed448Hram;

/// FROST Ciphersuite.
///
/// This exclude the signing algorithm specific H2, making this solely the curve, its associated
/// hash function, and the functions derived from it.
pub trait Curve: Ciphersuite {
  /// Context string for this curve.
  const CONTEXT: &'static [u8];

  /// Hash the given dst and data to a byte vector. Used to instantiate H4 and H5.
  fn hash(dst: &[u8], data: &[u8]) -> Output<Self::H> {
    Self::H::digest([Self::CONTEXT, dst, data].concat())
  }

  /// Field element from hash. Used during key gen and by other crates under Serai as a general
  /// utility. Used to instantiate H1 and H3.
  #[allow(non_snake_case)]
  fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
    <Self as Ciphersuite>::hash_to_F(&[Self::CONTEXT, dst].concat(), msg)
  }

  /// Hash the message for the binding factor. H4 from the IETF draft.
  fn hash_msg(msg: &[u8]) -> Output<Self::H> {
    Self::hash(b"msg", msg)
  }

  /// Hash the commitments for the binding factor. H5 from the IETF draft.
  fn hash_commitments(commitments: &[u8]) -> Output<Self::H> {
    Self::hash(b"com", commitments)
  }

  /// Hash the commitments and message to calculate the binding factor. H1 from the IETF draft.
  //
  // This may return 0, which is invalid according to the FROST preprint, as all binding factors
  // are expected to be in the multiplicative subgroup. This isn't a practical issue, as there's a
  // negligible probability of this returning 0.
  //
  // When raised in
  // https://github.com/cfrg/draft-irtf-cfrg-frost/issues/451#issuecomment-1715985505,
  // the negligible probbility was seen as sufficient reason not to edit the spec to be robust in
  // this regard.
  //
  // While that decision may be disagreeable, this library cannot implement a robust scheme while
  // following the specification. Following the specification is preferred to being robust against
  // an impractical probability enabling a complex attack (made infeasible by the impractical
  // probability required).
  //
  // We could still panic on the 0-hash, preferring correctness to liveliness. Finding the 0-hash
  // is as computationally complex as simply calculating the group key's discrete log however,
  // making it not worth having a panic (as this library is expected not to panic).
  fn hash_binding_factor(binding: &[u8]) -> Self::F {
    <Self as Curve>::hash_to_F(b"rho", binding)
  }

  /// Securely generate a random nonce. H3 from the IETF draft.
  fn random_nonce<R: RngCore + CryptoRng>(
    secret: &Zeroizing<Self::F>,
    rng: &mut R,
  ) -> Zeroizing<Self::F> {
    let mut seed = Zeroizing::new(vec![0; 32]);
    rng.fill_bytes(seed.as_mut());

    let mut repr = secret.to_repr();

    // Perform rejection sampling until we reach a non-zero nonce
    // While the IETF spec doesn't explicitly require this, generating a zero nonce will produce
    // commitments which will be rejected for being zero (and if they were used, leak the secret
    // share)
    // Rejection sampling here will prevent an honest participant from ever generating 'malicious'
    // values and ensure safety
    let mut res;
    while {
      seed.extend(repr.as_ref());
      res = Zeroizing::new(<Self as Curve>::hash_to_F(b"nonce", seed.deref()));
      res.ct_eq(&Self::F::ZERO).into()
    } {
      seed = Zeroizing::new(vec![0; 32]);
      rng.fill_bytes(&mut seed);
    }
    repr.as_mut().zeroize();

    res
  }

  /// Read a point from a reader, rejecting identity.
  #[allow(non_snake_case)]
  fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
    let res = <Self as Ciphersuite>::read_G(reader)?;
    if res.is_identity().into() {
      Err(io::Error::other("identity point"))?;
    }
    Ok(res)
  }
}