mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 20:29:23 +00:00
Smash the singular Ciphersuite trait into multiple
This helps identify where the various functionalities are used, or rather, not used. The `Ciphersuite` trait present in `patches/ciphersuite`, facilitating the entire FCMP++ tree, only requires the markers _and_ canonical point decoding. I've opened a PR to upstream such a trait into `group` (https://github.com/zkcrypto/group/pull/68). `WrappedGroup` is still justified for as long as `Group::generator` exists. Moving `::generator()` to its own trait, on an independent structure (upstream) would be massively appreciated. @tarcieri also wanted to update from `fn generator()` to `const GENERATOR`, which would encourage further discussion on https://github.com/zkcrypto/group/issues/32 and https://github.com/zkcrypto/group/issues/45, which have been stagnant. The `Id` trait is occasionally used yet really should be first off the chopping block. Finally, `WithPreferredHash` is only actually used around a third of the time, which more than justifies it being a separate trait. --- Updates `dalek_ff_group::Scalar` to directly re-export `curve25519_dalek::Scalar`, as without issue. `dalek_ff_group::RistrettoPoint` also could be replaced with an export of `curve25519_dalek::RistrettoPoint`, yet the coordinator relies on how we implemented `Hash` on it for the hell of it so it isn't worth it at this time. `dalek_ff_group::EdwardsPoint` can't be replaced for an re-export of `curve25519_dalek::SubgroupPoint` as it doesn't implement `zeroize`, `subtle` traits within a released, non-yanked version. Relevance to https://github.com/serai-dex/serai/issues/201 and https://github.com/dalek-cryptography/curve25519-dalek/issues/811#issuecomment-3247732746. Also updates the `Ristretto` ciphersuite to prefer `Blake2b-512` over `SHA2-512`. In order to maintain compliance with FROST's IETF standard, `modular-frost` defines its own ciphersuite for Ristretto which still uses `SHA2-512`.
This commit is contained in:
@@ -17,15 +17,12 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
std-shims = { path = "../../common/std-shims", version = "^0.1.1", default-features = false, optional = true }
|
||||
|
||||
rand_core = { version = "0.6", default-features = false }
|
||||
std-shims = { path = "../../common/std-shims", version = "0.1.4", default-features = false, optional = true }
|
||||
|
||||
zeroize = { version = "^1.5", default-features = false, features = ["derive"] }
|
||||
subtle = { version = "^2.4", default-features = false }
|
||||
|
||||
digest = { version = "0.11.0-rc.0", default-features = false, features = ["block-api"] }
|
||||
transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false }
|
||||
digest = { version = "0.11.0-rc.1", default-features = false }
|
||||
|
||||
ff = { version = "0.13", default-features = false, features = ["bits"] }
|
||||
group = { version = "0.13", default-features = false }
|
||||
@@ -33,24 +30,18 @@ group = { version = "0.13", default-features = false }
|
||||
[dev-dependencies]
|
||||
hex = { version = "0.4", default-features = false, features = ["std"] }
|
||||
|
||||
rand_core = { version = "0.6", default-features = false, features = ["std"] }
|
||||
|
||||
ff-group-tests = { version = "0.13", path = "../ff-group-tests" }
|
||||
|
||||
[features]
|
||||
alloc = ["std-shims", "digest/alloc", "ff/alloc"]
|
||||
alloc = ["std-shims", "zeroize/alloc", "digest/alloc", "ff/alloc"]
|
||||
std = [
|
||||
"alloc",
|
||||
|
||||
"std-shims/std",
|
||||
|
||||
"rand_core/std",
|
||||
|
||||
"zeroize/std",
|
||||
"subtle/std",
|
||||
|
||||
"transcript/std",
|
||||
|
||||
"ff/std",
|
||||
]
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ rand_core = { version = "0.6", default-features = false }
|
||||
|
||||
zeroize = { version = "^1.5", default-features = false, features = ["derive"] }
|
||||
|
||||
sha2 = { version = "0.11.0-rc.0", default-features = false }
|
||||
sha2 = { version = "0.11.0-rc.2", default-features = false }
|
||||
|
||||
p256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits", "hash2curve"] }
|
||||
k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits", "hash2curve"] }
|
||||
|
||||
@@ -5,7 +5,7 @@ use zeroize::Zeroize;
|
||||
|
||||
use sha2::Sha512;
|
||||
|
||||
use ciphersuite::Ciphersuite;
|
||||
use ciphersuite::{WrappedGroup, Id, WithPreferredHash, GroupCanonicalEncoding};
|
||||
|
||||
pub use k256;
|
||||
pub use p256;
|
||||
@@ -18,17 +18,20 @@ macro_rules! kp_curve {
|
||||
$Ciphersuite: ident,
|
||||
$ID: literal
|
||||
) => {
|
||||
impl Ciphersuite for $Ciphersuite {
|
||||
impl WrappedGroup for $Ciphersuite {
|
||||
type F = $lib::Scalar;
|
||||
type G = $lib::ProjectivePoint;
|
||||
type H = Sha512;
|
||||
|
||||
const ID: &'static [u8] = $ID;
|
||||
|
||||
fn generator() -> Self::G {
|
||||
$lib::ProjectivePoint::GENERATOR
|
||||
}
|
||||
}
|
||||
impl Id for $Ciphersuite {
|
||||
const ID: &'static [u8] = $ID;
|
||||
}
|
||||
impl WithPreferredHash for $Ciphersuite {
|
||||
type H = Sha512;
|
||||
}
|
||||
impl GroupCanonicalEncoding for $Ciphersuite {}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,22 +9,18 @@ use std_shims::prelude::*;
|
||||
#[cfg(feature = "alloc")]
|
||||
use std_shims::io::{self, Read};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use subtle::{CtOption, ConstantTimeEq, ConditionallySelectable};
|
||||
use zeroize::Zeroize;
|
||||
use subtle::ConstantTimeEq;
|
||||
|
||||
pub use digest;
|
||||
use digest::{array::ArraySize, block_api::BlockSizeUser, OutputSizeUser, Digest, HashMarker};
|
||||
use transcript::SecureDigest;
|
||||
use digest::{array::ArraySize, OutputSizeUser, Digest, HashMarker};
|
||||
|
||||
pub use group;
|
||||
use group::{
|
||||
ff::{Field, PrimeField, PrimeFieldBits},
|
||||
ff::{PrimeField, PrimeFieldBits},
|
||||
Group, GroupOps,
|
||||
prime::PrimeGroup,
|
||||
};
|
||||
#[cfg(feature = "alloc")]
|
||||
use group::GroupEncoding;
|
||||
|
||||
pub trait FromUniformBytes<T> {
|
||||
@@ -36,74 +32,118 @@ impl<const N: usize, F: group::ff::FromUniformBytes<N>> FromUniformBytes<[u8; N]
|
||||
}
|
||||
}
|
||||
|
||||
/// Unified trait defining a ciphersuite around an elliptic curve.
|
||||
pub trait Ciphersuite:
|
||||
/// A marker trait for fields which fleshes them out a bit more.
|
||||
pub trait F: PrimeField + PrimeFieldBits + Zeroize {}
|
||||
impl<Fi: PrimeField + PrimeFieldBits + Zeroize> F for Fi {}
|
||||
/// A marker trait for groups which fleshes them out a bit more.
|
||||
pub trait G:
|
||||
Group + GroupOps + GroupEncoding + PrimeGroup + ConstantTimeEq + ConditionallySelectable + Zeroize
|
||||
{
|
||||
}
|
||||
impl<
|
||||
Gr: Group
|
||||
+ GroupOps
|
||||
+ GroupEncoding
|
||||
+ PrimeGroup
|
||||
+ ConstantTimeEq
|
||||
+ ConditionallySelectable
|
||||
+ Zeroize,
|
||||
> G for Gr
|
||||
{
|
||||
}
|
||||
|
||||
/// A `Group` type which has been wrapped into the current type.
|
||||
///
|
||||
/// This avoids having to re-implement all of the `Group` traits on the wrapper.
|
||||
// TODO: Remove these bounds
|
||||
pub trait WrappedGroup:
|
||||
'static + Send + Sync + 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
|
||||
+ FromUniformBytes<<<Self::H as OutputSizeUser>::OutputSize as ArraySize>::ArrayType<u8>>;
|
||||
// This is available via `G::Scalar` yet `WG::G::Scalar` is ambiguous, forcing horrific accesses
|
||||
type F: F;
|
||||
/// Group element type.
|
||||
type G: Group<Scalar = Self::F> + GroupOps + PrimeGroup + Zeroize + ConstantTimeEq;
|
||||
/// Hash algorithm used with this curve.
|
||||
// Requires BlockSizeUser so it can be used within Hkdf which requires that.
|
||||
type H: Send + Clone + BlockSizeUser + Digest + HashMarker + SecureDigest;
|
||||
|
||||
/// ID for this curve.
|
||||
const ID: &'static [u8];
|
||||
|
||||
type G: Group<Scalar = Self::F> + G;
|
||||
/// 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;
|
||||
}
|
||||
impl<Gr: G<Scalar: F>> WrappedGroup for Gr {
|
||||
type F = <Gr as Group>::Scalar;
|
||||
type G = Gr;
|
||||
fn generator() -> Self::G {
|
||||
<Self::G as Group>::generator()
|
||||
}
|
||||
}
|
||||
|
||||
/// An ID for an object.
|
||||
pub trait Id {
|
||||
// The ID.
|
||||
const ID: &'static [u8];
|
||||
}
|
||||
|
||||
/// A group with a preferred hash function.
|
||||
pub trait WithPreferredHash:
|
||||
WrappedGroup<
|
||||
F: FromUniformBytes<<<Self::H as OutputSizeUser>::OutputSize as ArraySize>::ArrayType<u8>>,
|
||||
>
|
||||
{
|
||||
type H: Send + Clone + Digest + HashMarker;
|
||||
#[allow(non_snake_case)]
|
||||
fn hash_to_F(data: &[u8]) -> Self::F {
|
||||
Self::F::from_uniform_bytes(&Self::H::digest(data).into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a random non-zero scalar.
|
||||
#[allow(non_snake_case)]
|
||||
fn random_nonzero_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
|
||||
}
|
||||
|
||||
/// Read a canonical scalar from something implementing std::io::Read.
|
||||
#[cfg(feature = "alloc")]
|
||||
#[allow(non_snake_case)]
|
||||
fn read_F<R: Read>(reader: &mut R) -> io::Result<Self::F> {
|
||||
let mut encoding = <Self::F as PrimeField>::Repr::default();
|
||||
reader.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::other("non-canonical scalar"));
|
||||
encoding.as_mut().zeroize();
|
||||
res
|
||||
}
|
||||
|
||||
/// Read a canonical point from something implementing std::io::Read.
|
||||
/// A group which always encodes points canonically and supports decoding points while checking
|
||||
/// they have a canonical encoding.
|
||||
pub trait GroupCanonicalEncoding: WrappedGroup {
|
||||
/// Decode a point from its canonical encoding.
|
||||
///
|
||||
/// The provided implementation is safe so long as `GroupEncoding::to_bytes` always returns a
|
||||
/// canonical serialization.
|
||||
/// Returns `None` if the point was invalid or not the encoding wasn't canonical.
|
||||
///
|
||||
/// If `<Self::G as GroupEncoding>::from_bytes` already only accepts canonical encodings, this
|
||||
/// SHOULD be overriden with `<Self::G as GroupEncoding>::from_bytes(bytes)`.
|
||||
fn from_canonical_bytes(bytes: &<Self::G as GroupEncoding>::Repr) -> CtOption<Self::G> {
|
||||
let res = Self::G::from_bytes(bytes).unwrap_or(Self::generator());
|
||||
// Safe due to the bound points are always encoded canonically
|
||||
let canonical = res.to_bytes().as_ref().ct_eq(bytes.as_ref());
|
||||
CtOption::new(res, canonical)
|
||||
}
|
||||
}
|
||||
|
||||
/// `std::io` extensions for `GroupCanonicalEncoding.`
|
||||
#[cfg(feature = "alloc")]
|
||||
#[allow(non_snake_case)]
|
||||
pub trait GroupIo: GroupCanonicalEncoding {
|
||||
/// Read a canonical field element from something implementing `std::io::Read`.
|
||||
fn read_F<R: Read>(reader: &mut R) -> io::Result<Self::F> {
|
||||
let mut bytes = <Self::F as PrimeField>::Repr::default();
|
||||
reader.read_exact(bytes.as_mut())?;
|
||||
|
||||
// `ff` mandates this is canonical
|
||||
let res = Option::<Self::F>::from(Self::F::from_repr(bytes))
|
||||
.ok_or_else(|| io::Error::other("non-canonical scalar"));
|
||||
bytes.as_mut().zeroize();
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// Read a canonical point from something implementing `std::io::Read`.
|
||||
#[cfg(feature = "alloc")]
|
||||
#[allow(non_snake_case)]
|
||||
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
|
||||
let mut encoding = <Self::G as GroupEncoding>::Repr::default();
|
||||
reader.read_exact(encoding.as_mut())?;
|
||||
let mut bytes = <Self::G as GroupEncoding>::Repr::default();
|
||||
reader.read_exact(bytes.as_mut())?;
|
||||
|
||||
let point = Option::<Self::G>::from(Self::G::from_bytes(&encoding))
|
||||
let res = Option::<Self::G>::from(Self::from_canonical_bytes(&bytes))
|
||||
.ok_or_else(|| io::Error::other("invalid point"))?;
|
||||
if point.to_bytes().as_ref() != encoding.as_ref() {
|
||||
Err(io::Error::other("non-canonical point"))?;
|
||||
}
|
||||
Ok(point)
|
||||
bytes.as_mut().zeroize();
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
impl<Gr: GroupCanonicalEncoding> GroupIo for Gr {}
|
||||
|
||||
/// Unified trait defining a ciphersuite around an elliptic curve.
|
||||
pub trait Ciphersuite: Id + WithPreferredHash + GroupCanonicalEncoding {}
|
||||
impl<C: Id + WithPreferredHash + GroupCanonicalEncoding> Ciphersuite for C {}
|
||||
|
||||
Reference in New Issue
Block a user