Create a dedicated crate for the DKG (#141)

* Add dkg crate

* Remove F_len and G_len

They're generally no longer used.

* Replace hash_to_vec with a provided method around associated type H: Digest

Part of trying to minimize this trait so it can be moved elsewhere. Vec, 
which isn't std, may have been a blocker.

* Encrypt secret shares within the FROST library

Reduces requirements on callers in order to be correct.

* Update usage of Zeroize within FROST

* Inline functions in key_gen

There was no reason to have them separated as they were. sign probably 
has the same statement available, yet that isn't the focus right now.

* Add a ciphersuite package which provides hash_to_F

* Set the Ciphersuite version to something valid

* Have ed448 export Scalar/FieldElement/Point at the top level

* Move FROST over to Ciphersuite

* Correct usage of ff in ciphersuite

* Correct documentation handling

* Move Schnorr signatures to their own crate

* Remove unused feature from schnorr

* Fix Schnorr tests

* Split DKG into a separate crate

* Add serialize to Commitments and SecretShare

Helper for buf = vec![]; .write(buf).unwrap(); buf

* Move FROST over to the new dkg crate

* Update Monero lib to latest FROST

* Correct ethereum's usage of features

* Add serialize to GeneratorProof

* Add serialize helper function to FROST

* Rename AddendumSerialize to WriteAddendum

* Update processor

* Slight fix to processor
This commit is contained in:
Luke Parker
2022-10-29 03:54:42 -05:00
committed by GitHub
parent cbceaff678
commit 2379855b31
50 changed files with 2076 additions and 1601 deletions

View File

@@ -1,4 +1,3 @@
use core::fmt::Debug;
use std::io::{self, Read};
use rand_core::{RngCore, CryptoRng};
@@ -6,17 +5,23 @@ use rand_core::{RngCore, CryptoRng};
use zeroize::Zeroize;
use subtle::ConstantTimeEq;
use ff::{Field, PrimeField, PrimeFieldBits};
use group::{Group, GroupOps, GroupEncoding, prime::PrimeGroup};
use digest::Digest;
#[cfg(any(test, feature = "dalek"))]
use group::{
ff::{Field, PrimeField},
Group,
};
pub use ciphersuite::Ciphersuite;
#[cfg(any(feature = "ristretto", feature = "ed25519"))]
mod dalek;
#[cfg(any(test, feature = "ristretto"))]
#[cfg(feature = "ristretto")]
pub use dalek::{Ristretto, IetfRistrettoHram};
#[cfg(feature = "ed25519")]
pub use dalek::{Ed25519, IetfEd25519Hram};
#[cfg(feature = "kp256")]
#[cfg(any(feature = "secp256k1", feature = "p256"))]
mod kp256;
#[cfg(feature = "secp256k1")]
pub use kp256::{Secp256k1, IetfSecp256k1Hram};
@@ -28,33 +33,23 @@ mod ed448;
#[cfg(feature = "ed448")]
pub use ed448::{Ed448, Ietf8032Ed448Hram, IetfEd448Hram};
/// Unified trait to manage an elliptic curve.
// This should be moved into its own crate if the need for generic cryptography over ff/group
// continues, which is the exact reason ff/group exists (to provide a generic interface)
// elliptic-curve exists, yet it doesn't really serve the same role, nor does it use &[u8]/Vec<u8>
// It uses GenericArray which will hopefully be deprecated as Rust evolves and doesn't offer enough
// advantages in the modern day to be worth the hassle -- Kayaba
pub trait Curve: Clone + Copy + PartialEq + Eq + Debug + Zeroize {
/// Scalar field element type.
// This is available via G::Scalar yet `C::G::Scalar` is ambiguous, forcing horrific accesses
type F: PrimeField + PrimeFieldBits + Zeroize;
/// Group element type.
type G: Group<Scalar = Self::F> + GroupOps + PrimeGroup + Zeroize + ConstantTimeEq;
/// ID for this curve.
const ID: &'static [u8];
/// Generator for the group.
// While group does provide this in its API, privacy coins may want to use a custom basepoint
fn generator() -> Self::G;
/// FROST Ciphersuite, except for 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_to_vec(dst: &[u8], data: &[u8]) -> Vec<u8>;
fn hash_to_vec(dst: &[u8], data: &[u8]) -> Vec<u8> {
Self::H::digest(&[Self::CONTEXT, dst, data].concat()).as_ref().to_vec()
}
/// 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;
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]) -> Vec<u8> {
@@ -68,17 +63,7 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug + Zeroize {
/// Hash the commitments and message to calculate the binding factor. H1 from the IETF draft.
fn hash_binding_factor(binding: &[u8]) -> Self::F {
Self::hash_to_F(b"rho", binding)
}
#[allow(non_snake_case)]
fn random_F<R: RngCore + CryptoRng>(rng: &mut R) -> Self::F {
let mut res;
while {
res = Self::F::random(&mut *rng);
res.ct_eq(&Self::F::zero()).into()
} {}
res
<Self as Curve>::hash_to_F(b"rho", binding)
}
/// Securely generate a random nonce. H3 from the IETF draft.
@@ -92,7 +77,7 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug + Zeroize {
let mut res;
while {
seed.extend(repr.as_ref());
res = Self::hash_to_F(b"nonce", &seed);
res = <Self as Curve>::hash_to_F(b"nonce", &seed);
res.ct_eq(&Self::F::zero()).into()
} {
rng.fill_bytes(&mut seed);
@@ -106,40 +91,11 @@ pub trait Curve: Clone + Copy + PartialEq + Eq + Debug + Zeroize {
}
#[allow(non_snake_case)]
fn F_len() -> usize {
<Self::F as PrimeField>::Repr::default().as_ref().len()
}
#[allow(non_snake_case)]
fn G_len() -> usize {
<Self::G as GroupEncoding>::Repr::default().as_ref().len()
}
#[allow(non_snake_case)]
fn read_F<R: Read>(r: &mut R) -> io::Result<Self::F> {
let mut encoding = <Self::F as PrimeField>::Repr::default();
r.read_exact(encoding.as_mut())?;
// ff mandates this is canonical
let res = Option::<Self::F>::from(Self::F::from_repr(encoding))
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "non-canonical scalar"));
for b in encoding.as_mut() {
b.zeroize();
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::new(io::ErrorKind::Other, "identity point"))?;
}
res
}
#[allow(non_snake_case)]
fn read_G<R: Read>(r: &mut R) -> io::Result<Self::G> {
let mut encoding = <Self::G as GroupEncoding>::Repr::default();
r.read_exact(encoding.as_mut())?;
let point = Option::<Self::G>::from(Self::G::from_bytes(&encoding))
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid point"))?;
// Ban the identity, per the FROST spec, and non-canonical points
if (point.is_identity().into()) || (point.to_bytes().as_ref() != encoding.as_ref()) {
Err(io::Error::new(io::ErrorKind::Other, "non-canonical or identity point"))?;
}
Ok(point)
Ok(res)
}
}