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)
}
}