mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-10 05:09:22 +00:00
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:
@@ -1,10 +1,9 @@
|
||||
use zeroize::Zeroize;
|
||||
use digest::Digest;
|
||||
|
||||
use sha2::{Digest, Sha512};
|
||||
|
||||
use group::Group;
|
||||
use dalek_ff_group::Scalar;
|
||||
|
||||
use ciphersuite::Ciphersuite;
|
||||
|
||||
use crate::{curve::Curve, algorithm::Hram};
|
||||
|
||||
macro_rules! dalek_curve {
|
||||
@@ -13,49 +12,22 @@ macro_rules! dalek_curve {
|
||||
|
||||
$Curve: ident,
|
||||
$Hram: ident,
|
||||
$Point: ident,
|
||||
|
||||
$ID: literal,
|
||||
$CONTEXT: literal,
|
||||
$chal: literal,
|
||||
$chal: literal
|
||||
) => {
|
||||
use dalek_ff_group::$Point;
|
||||
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = $feature)))]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct $Curve;
|
||||
impl $Curve {
|
||||
fn hash(dst: &[u8], data: &[u8]) -> Sha512 {
|
||||
Sha512::new().chain_update(&[$CONTEXT.as_ref(), dst, data].concat())
|
||||
}
|
||||
}
|
||||
pub use ciphersuite::$Curve;
|
||||
|
||||
impl Curve for $Curve {
|
||||
type F = Scalar;
|
||||
type G = $Point;
|
||||
|
||||
const ID: &'static [u8] = $ID;
|
||||
|
||||
fn generator() -> Self::G {
|
||||
$Point::generator()
|
||||
}
|
||||
|
||||
fn hash_to_vec(dst: &[u8], data: &[u8]) -> Vec<u8> {
|
||||
Self::hash(dst, data).finalize().to_vec()
|
||||
}
|
||||
|
||||
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
|
||||
Scalar::from_hash(Self::hash(dst, data))
|
||||
}
|
||||
const CONTEXT: &'static [u8] = $CONTEXT;
|
||||
}
|
||||
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = $feature)))]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct $Hram;
|
||||
impl Hram<$Curve> for $Hram {
|
||||
#[allow(non_snake_case)]
|
||||
fn hram(R: &$Point, A: &$Point, m: &[u8]) -> Scalar {
|
||||
let mut hash = Sha512::new();
|
||||
fn hram(R: &<$Curve as Ciphersuite>::G, A: &<$Curve as Ciphersuite>::G, m: &[u8]) -> Scalar {
|
||||
let mut hash = <$Curve as Ciphersuite>::H::new();
|
||||
if $chal.len() != 0 {
|
||||
hash.update(&[$CONTEXT.as_ref(), $chal].concat());
|
||||
}
|
||||
@@ -67,24 +39,8 @@ macro_rules! dalek_curve {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "ristretto"))]
|
||||
dalek_curve!(
|
||||
"ristretto",
|
||||
Ristretto,
|
||||
IetfRistrettoHram,
|
||||
RistrettoPoint,
|
||||
b"ristretto",
|
||||
b"FROST-RISTRETTO255-SHA512-v11",
|
||||
b"chal",
|
||||
);
|
||||
#[cfg(feature = "ristretto")]
|
||||
dalek_curve!("ristretto", Ristretto, IetfRistrettoHram, b"FROST-RISTRETTO255-SHA512-v11", b"chal");
|
||||
|
||||
#[cfg(feature = "ed25519")]
|
||||
dalek_curve!(
|
||||
"ed25519",
|
||||
Ed25519,
|
||||
IetfEd25519Hram,
|
||||
EdwardsPoint,
|
||||
b"edwards25519",
|
||||
b"FROST-ED25519-SHA512-v11",
|
||||
b"",
|
||||
);
|
||||
dalek_curve!("ed25519", Ed25519, IetfEd25519Hram, b"FROST-ED25519-SHA512-v11", b"");
|
||||
|
||||
@@ -1,41 +1,17 @@
|
||||
use zeroize::Zeroize;
|
||||
use digest::Digest;
|
||||
|
||||
use sha3::{digest::ExtendableOutput, Shake256};
|
||||
use group::GroupEncoding;
|
||||
|
||||
use group::{Group, GroupEncoding};
|
||||
use minimal_ed448::{scalar::Scalar, point::Point};
|
||||
use minimal_ed448::{Scalar, Point};
|
||||
|
||||
pub use ciphersuite::{Shake256_114, Ed448};
|
||||
|
||||
use crate::{curve::Curve, algorithm::Hram};
|
||||
|
||||
const CONTEXT: &[u8] = b"FROST-ED448-SHAKE256-v11";
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct Ed448;
|
||||
impl Ed448 {
|
||||
fn hash(prefix: &[u8], context: &[u8], dst: &[u8], data: &[u8]) -> [u8; 114] {
|
||||
let mut res = [0; 114];
|
||||
Shake256::digest_xof(&[prefix, context, dst, data].concat(), &mut res);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl Curve for Ed448 {
|
||||
type F = Scalar;
|
||||
type G = Point;
|
||||
|
||||
const ID: &'static [u8] = b"ed448";
|
||||
|
||||
fn generator() -> Self::G {
|
||||
Point::generator()
|
||||
}
|
||||
|
||||
fn hash_to_vec(dst: &[u8], data: &[u8]) -> Vec<u8> {
|
||||
Self::hash(b"", CONTEXT, dst, data).as_ref().to_vec()
|
||||
}
|
||||
|
||||
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
|
||||
Scalar::wide_reduce(Self::hash(b"", CONTEXT, dst, data))
|
||||
}
|
||||
const CONTEXT: &'static [u8] = CONTEXT;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
@@ -43,12 +19,19 @@ pub struct Ietf8032Ed448Hram;
|
||||
impl Ietf8032Ed448Hram {
|
||||
#[allow(non_snake_case)]
|
||||
pub fn hram(context: &[u8], R: &Point, A: &Point, m: &[u8]) -> Scalar {
|
||||
Scalar::wide_reduce(Ed448::hash(
|
||||
&[b"SigEd448".as_ref(), &[0, u8::try_from(context.len()).unwrap()]].concat(),
|
||||
context,
|
||||
b"",
|
||||
&[R.to_bytes().as_ref(), A.to_bytes().as_ref(), m].concat(),
|
||||
))
|
||||
Scalar::wide_reduce(
|
||||
Shake256_114::digest(
|
||||
&[
|
||||
&[b"SigEd448".as_ref(), &[0, u8::try_from(context.len()).unwrap()]].concat(),
|
||||
context,
|
||||
&[R.to_bytes().as_ref(), A.to_bytes().as_ref(), m].concat(),
|
||||
]
|
||||
.concat(),
|
||||
)
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
use zeroize::Zeroize;
|
||||
use group::GroupEncoding;
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use group::{
|
||||
ff::{Field, PrimeField},
|
||||
GroupEncoding,
|
||||
};
|
||||
|
||||
use elliptic_curve::{
|
||||
generic_array::GenericArray,
|
||||
bigint::{Encoding, U384},
|
||||
hash2curve::{Expander, ExpandMsg, ExpandMsgXmd},
|
||||
};
|
||||
use ciphersuite::Ciphersuite;
|
||||
|
||||
use crate::{curve::Curve, algorithm::Hram};
|
||||
|
||||
@@ -19,87 +8,37 @@ macro_rules! kp_curve {
|
||||
(
|
||||
$feature: literal,
|
||||
|
||||
$lib: ident,
|
||||
$Curve: ident,
|
||||
$Hram: ident,
|
||||
|
||||
$ID: literal,
|
||||
$CONTEXT: literal
|
||||
) => {
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = $feature)))]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||
pub struct $Curve;
|
||||
impl $Curve {
|
||||
fn hash(dst: &[u8], data: &[u8]) -> Sha256 {
|
||||
Sha256::new().chain_update(&[$CONTEXT.as_ref(), dst, data].concat())
|
||||
}
|
||||
}
|
||||
pub use ciphersuite::$Curve;
|
||||
|
||||
impl Curve for $Curve {
|
||||
type F = $lib::Scalar;
|
||||
type G = $lib::ProjectivePoint;
|
||||
|
||||
const ID: &'static [u8] = $ID;
|
||||
|
||||
fn generator() -> Self::G {
|
||||
$lib::ProjectivePoint::GENERATOR
|
||||
}
|
||||
|
||||
fn hash_to_vec(dst: &[u8], data: &[u8]) -> Vec<u8> {
|
||||
Self::hash(dst, data).finalize().to_vec()
|
||||
}
|
||||
|
||||
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
|
||||
let mut dst = &[$CONTEXT, dst].concat();
|
||||
let oversize = Sha256::digest([b"H2C-OVERSIZE-DST-".as_ref(), dst].concat()).to_vec();
|
||||
if dst.len() > 255 {
|
||||
dst = &oversize;
|
||||
}
|
||||
|
||||
// While one of these two libraries does support directly hashing to the Scalar field, the
|
||||
// other doesn't. While that's probably an oversight, this is a universally working method
|
||||
let mut modulus = vec![0; 16];
|
||||
modulus.extend((Self::F::zero() - Self::F::one()).to_bytes());
|
||||
let modulus = U384::from_be_slice(&modulus).wrapping_add(&U384::ONE);
|
||||
|
||||
let mut unreduced = U384::from_be_bytes({
|
||||
let mut bytes = [0; 48];
|
||||
ExpandMsgXmd::<Sha256>::expand_message(&[msg], dst, 48).unwrap().fill_bytes(&mut bytes);
|
||||
bytes
|
||||
})
|
||||
.reduce(&modulus)
|
||||
.unwrap()
|
||||
.to_be_bytes();
|
||||
|
||||
let mut array = *GenericArray::from_slice(&unreduced[16 ..]);
|
||||
let res = $lib::Scalar::from_repr(array).unwrap();
|
||||
unreduced.zeroize();
|
||||
array.zeroize();
|
||||
res
|
||||
}
|
||||
const CONTEXT: &'static [u8] = $CONTEXT;
|
||||
}
|
||||
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = $feature)))]
|
||||
#[derive(Clone)]
|
||||
pub struct $Hram;
|
||||
impl Hram<$Curve> for $Hram {
|
||||
#[allow(non_snake_case)]
|
||||
fn hram(R: &$lib::ProjectivePoint, A: &$lib::ProjectivePoint, m: &[u8]) -> $lib::Scalar {
|
||||
$Curve::hash_to_F(b"chal", &[R.to_bytes().as_ref(), A.to_bytes().as_ref(), m].concat())
|
||||
fn hram(
|
||||
R: &<$Curve as Ciphersuite>::G,
|
||||
A: &<$Curve as Ciphersuite>::G,
|
||||
m: &[u8],
|
||||
) -> <$Curve as Ciphersuite>::F {
|
||||
<$Curve as Curve>::hash_to_F(
|
||||
b"chal",
|
||||
&[R.to_bytes().as_ref(), A.to_bytes().as_ref(), m].concat(),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "p256")]
|
||||
kp_curve!("p256", p256, P256, IetfP256Hram, b"P-256", b"FROST-P256-SHA256-v11");
|
||||
kp_curve!("p256", P256, IetfP256Hram, b"FROST-P256-SHA256-v11");
|
||||
|
||||
#[cfg(feature = "secp256k1")]
|
||||
kp_curve!(
|
||||
"secp256k1",
|
||||
k256,
|
||||
Secp256k1,
|
||||
IetfSecp256k1Hram,
|
||||
b"secp256k1",
|
||||
b"FROST-secp256k1-SHA256-v11"
|
||||
);
|
||||
kp_curve!("secp256k1", Secp256k1, IetfSecp256k1Hram, b"FROST-secp256k1-SHA256-v11");
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user