mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
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`.
150 lines
4.6 KiB
Rust
150 lines
4.6 KiB
Rust
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
#![doc = include_str!("lib.md")]
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
|
|
use core::fmt::Debug;
|
|
#[cfg(feature = "alloc")]
|
|
#[allow(unused_imports)]
|
|
use std_shims::prelude::*;
|
|
#[cfg(feature = "alloc")]
|
|
use std_shims::io::{self, Read};
|
|
|
|
use subtle::{CtOption, ConstantTimeEq, ConditionallySelectable};
|
|
use zeroize::Zeroize;
|
|
|
|
pub use digest;
|
|
use digest::{array::ArraySize, OutputSizeUser, Digest, HashMarker};
|
|
|
|
pub use group;
|
|
use group::{
|
|
ff::{PrimeField, PrimeFieldBits},
|
|
Group, GroupOps,
|
|
prime::PrimeGroup,
|
|
};
|
|
use group::GroupEncoding;
|
|
|
|
pub trait FromUniformBytes<T> {
|
|
fn from_uniform_bytes(bytes: &T) -> Self;
|
|
}
|
|
impl<const N: usize, F: group::ff::FromUniformBytes<N>> FromUniformBytes<[u8; N]> for F {
|
|
fn from_uniform_bytes(bytes: &[u8; N]) -> Self {
|
|
F::from_uniform_bytes(bytes)
|
|
}
|
|
}
|
|
|
|
/// 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 `WG::G::Scalar` is ambiguous, forcing horrific accesses
|
|
type F: F;
|
|
/// Group element type.
|
|
type G: Group<Scalar = Self::F> + G;
|
|
/// Generator for the group.
|
|
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())
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
///
|
|
/// 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 bytes = <Self::G as GroupEncoding>::Repr::default();
|
|
reader.read_exact(bytes.as_mut())?;
|
|
|
|
let res = Option::<Self::G>::from(Self::from_canonical_bytes(&bytes))
|
|
.ok_or_else(|| io::Error::other("invalid 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 {}
|