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:
Luke Parker
2025-09-03 12:25:37 -04:00
parent 215e41fdb6
commit a141deaf36
124 changed files with 1003 additions and 1211 deletions

View File

@@ -32,7 +32,9 @@ rand_chacha = { version = "0.3", default-features = false, features = ["std"] }
blake2 = { version = "0.11.0-rc.0", default-features = false, features = ["alloc"] }
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", default-features = false, features = ["std"] }
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std"] }
dkg = { package = "dkg-evrf", path = "../../crypto/dkg/evrf", default-features = false, features = ["std", "ristretto"] }
embedwards25519 = { path = "../../crypto/embedwards25519", default-features = false, features = ["std"] }
dkg = { package = "dkg-evrf", path = "../../crypto/dkg/evrf", default-features = false, features = ["std"] }
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false, features = ["ristretto"] }
# Substrate
serai-validator-sets-primitives = { path = "../../substrate/validator-sets/primitives", default-features = false, features = ["std"] }

View File

@@ -3,7 +3,7 @@ use std::collections::HashMap;
use zeroize::Zeroizing;
use ciphersuite::{group::GroupEncoding, Ciphersuite};
use ciphersuite::{group::GroupEncoding, *};
use dkg::*;
use serai_validator_sets_primitives::Session;
@@ -11,15 +11,15 @@ use serai_validator_sets_primitives::Session;
use borsh::{BorshSerialize, BorshDeserialize};
use serai_db::{Get, DbTxn};
use crate::KeyGenParams;
use crate::{Ristretto, KeyGenParams};
pub(crate) struct Params<P: KeyGenParams> {
pub(crate) t: u16,
pub(crate) n: u16,
pub(crate) substrate_evrf_public_keys:
Vec<<<Ristretto as Curves>::EmbeddedCurve as Ciphersuite>::G>,
Vec<<<Ristretto as Curves>::EmbeddedCurve as WrappedGroup>::G>,
pub(crate) network_evrf_public_keys:
Vec<<<P::ExternalNetworkCiphersuite as Curves>::EmbeddedCurve as Ciphersuite>::G>,
Vec<<<P::ExternalNetworkCiphersuite as Curves>::EmbeddedCurve as WrappedGroup>::G>,
}
#[derive(BorshSerialize, BorshDeserialize)]
@@ -85,17 +85,16 @@ impl<P: KeyGenParams> KeyGenDb<P> {
.substrate_evrf_public_keys
.into_iter()
.map(|key| {
<<Ristretto as Curves>::EmbeddedCurve as Ciphersuite>::read_G(&mut key.as_slice())
.unwrap()
<<Ristretto as Curves>::EmbeddedCurve as GroupIo>::read_G(&mut key.as_slice()).unwrap()
})
.collect(),
network_evrf_public_keys: params
.network_evrf_public_keys
.into_iter()
.map(|key| {
<<P::ExternalNetworkCiphersuite as Curves>::EmbeddedCurve as Ciphersuite>::read_G::<
&[u8],
>(&mut key.as_ref())
<<P::ExternalNetworkCiphersuite as Curves>::EmbeddedCurve as GroupIo>::read_G::<&[u8]>(
&mut key.as_ref(),
)
.unwrap()
})
.collect(),

View File

@@ -13,7 +13,7 @@ use blake2::{Digest, Blake2s256};
use transcript::{Transcript, RecommendedTranscript};
use ciphersuite::{
group::{Group, GroupEncoding},
Ciphersuite,
*,
};
use dkg::*;
@@ -28,6 +28,14 @@ use generators::generators;
mod db;
use db::{Params, Participations, KeyGenDb};
/// Ristretto, and an elliptic curve defined over its scalar field (embedwards25519).
pub struct Ristretto;
impl Curves for Ristretto {
type ToweringCurve = frost::curve::Ristretto;
type EmbeddedCurve = embedwards25519::Embedwards25519;
type EmbeddedCurveParameters = embedwards25519::Embedwards25519;
}
/// Parameters for a key generation.
pub trait KeyGenParams {
/// The ID for this instantiation.
@@ -49,7 +57,7 @@ pub trait KeyGenParams {
///
/// A default implementation is provided which calls the traditional `to_bytes`.
fn encode_key(
key: <<Self::ExternalNetworkCiphersuite as Curves>::ToweringCurve as Ciphersuite>::G,
key: <<Self::ExternalNetworkCiphersuite as Curves>::ToweringCurve as WrappedGroup>::G,
) -> Vec<u8> {
key.to_bytes().as_ref().to_vec()
}
@@ -59,11 +67,10 @@ pub trait KeyGenParams {
/// A default implementation is provided which calls the traditional `from_bytes`.
fn decode_key(
mut key: &[u8],
) -> Option<<<Self::ExternalNetworkCiphersuite as Curves>::ToweringCurve as Ciphersuite>::G> {
let res = <<Self::ExternalNetworkCiphersuite as Curves>::ToweringCurve as Ciphersuite>::read_G(
&mut key,
)
.ok()?;
) -> Option<<<Self::ExternalNetworkCiphersuite as Curves>::ToweringCurve as WrappedGroup>::G> {
let res =
<<Self::ExternalNetworkCiphersuite as Curves>::ToweringCurve as GroupIo>::read_G(&mut key)
.ok()?;
if !key.is_empty() {
None?;
}
@@ -101,14 +108,14 @@ pub trait KeyGenParams {
*/
fn coerce_keys<C: 'static + Curves>(
key_bytes: &[impl AsRef<[u8]>],
) -> (Vec<<C::EmbeddedCurve as Ciphersuite>::G>, Vec<Participant>) {
fn evrf_key<C: 'static + Curves>(key: &[u8]) -> Option<<C::EmbeddedCurve as Ciphersuite>::G> {
let mut repr = <<C::EmbeddedCurve as Ciphersuite>::G as GroupEncoding>::Repr::default();
) -> (Vec<<C::EmbeddedCurve as WrappedGroup>::G>, Vec<Participant>) {
fn evrf_key<C: 'static + Curves>(key: &[u8]) -> Option<<C::EmbeddedCurve as WrappedGroup>::G> {
let mut repr = <<C::EmbeddedCurve as WrappedGroup>::G as GroupEncoding>::Repr::default();
if repr.as_ref().len() != key.len() {
None?;
}
repr.as_mut().copy_from_slice(key);
let point = Option::<<C::EmbeddedCurve as Ciphersuite>::G>::from(<_>::from_bytes(&repr))?;
let point = Option::<<C::EmbeddedCurve as WrappedGroup>::G>::from(<_>::from_bytes(&repr))?;
if bool::from(point.is_identity()) {
None?;
}
@@ -131,10 +138,10 @@ fn coerce_keys<C: 'static + Curves>(
// Generate a random key
let mut rng = ChaCha20Rng::from_seed(Blake2s256::digest(key).into());
loop {
let mut repr = <<C::EmbeddedCurve as Ciphersuite>::G as GroupEncoding>::Repr::default();
let mut repr = <<C::EmbeddedCurve as WrappedGroup>::G as GroupEncoding>::Repr::default();
rng.fill_bytes(repr.as_mut());
if let Some(key) =
Option::<<C::EmbeddedCurve as Ciphersuite>::G>::from(<_>::from_bytes(&repr))
Option::<<C::EmbeddedCurve as WrappedGroup>::G>::from(<_>::from_bytes(&repr))
{
break key;
}
@@ -149,18 +156,20 @@ fn coerce_keys<C: 'static + Curves>(
/// An instance of the Serai key generation protocol.
#[derive(Debug)]
pub struct KeyGen<P: KeyGenParams> {
substrate_evrf_private_key: Zeroizing<<<Ristretto as Curves>::EmbeddedCurve as Ciphersuite>::F>,
substrate_evrf_private_key: Zeroizing<<<Ristretto as Curves>::EmbeddedCurve as WrappedGroup>::F>,
network_evrf_private_key:
Zeroizing<<<P::ExternalNetworkCiphersuite as Curves>::EmbeddedCurve as Ciphersuite>::F>,
Zeroizing<<<P::ExternalNetworkCiphersuite as Curves>::EmbeddedCurve as WrappedGroup>::F>,
}
impl<P: KeyGenParams> KeyGen<P> {
/// Create a new key generation instance.
#[allow(clippy::new_ret_no_self)]
pub fn new(
substrate_evrf_private_key: Zeroizing<<<Ristretto as Curves>::EmbeddedCurve as Ciphersuite>::F>,
substrate_evrf_private_key: Zeroizing<
<<Ristretto as Curves>::EmbeddedCurve as WrappedGroup>::F,
>,
network_evrf_private_key: Zeroizing<
<<P::ExternalNetworkCiphersuite as Curves>::EmbeddedCurve as Ciphersuite>::F,
<<P::ExternalNetworkCiphersuite as Curves>::EmbeddedCurve as WrappedGroup>::F,
>,
) -> KeyGen<P> {
KeyGen { substrate_evrf_private_key, network_evrf_private_key }
@@ -214,8 +223,8 @@ impl<P: KeyGenParams> KeyGen<P> {
fn participate<C: 'static + Curves>(
context: [u8; 32],
threshold: u16,
evrf_public_keys: &[<C::EmbeddedCurve as Ciphersuite>::G],
evrf_private_key: &Zeroizing<<C::EmbeddedCurve as Ciphersuite>::F>,
evrf_public_keys: &[<C::EmbeddedCurve as WrappedGroup>::G],
evrf_private_key: &Zeroizing<<C::EmbeddedCurve as WrappedGroup>::F>,
output: &mut impl io::Write,
) {
let participation = Dkg::<C>::participate(
@@ -411,7 +420,7 @@ impl<P: KeyGenParams> KeyGen<P> {
session: Session,
true_if_substrate_false_if_network: bool,
threshold: u16,
evrf_public_keys: &[<C::EmbeddedCurve as Ciphersuite>::G],
evrf_public_keys: &[<C::EmbeddedCurve as WrappedGroup>::G],
substrate_participations: &mut HashMap<Participant, Vec<u8>>,
network_participations: &mut HashMap<Participant, Vec<u8>>,
) -> Result<Dkg<C>, Vec<ProcessorMessage>> {