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

@@ -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",
]

View File

@@ -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"] }

View File

@@ -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 {}
};
}

View File

@@ -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 {}

View File

@@ -1,6 +1,6 @@
[package]
name = "dalek-ff-group"
version = "0.4.6"
version = "0.5.0"
description = "ff/group bindings around curve25519-dalek"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dalek-ff-group"
@@ -22,15 +22,13 @@ subtle = { version = "^2.4", default-features = false }
rand_core = { version = "0.6", default-features = false }
digest = { version = "0.10", default-features = false }
sha2 = { version = "0.11.0-rc.0", default-features = false }
sha2 = { version = "0.11.0-rc.2", default-features = false, features = ["zeroize"] }
blake2 = { version = "0.11.0-rc.2", default-features = false, features = ["zeroize"] }
prime-field = { path = "../prime-field", default-features = false }
ciphersuite = { version = "0.4.2", path = "../ciphersuite", default-features = false }
crypto-bigint = { version = "0.6", default-features = false, features = ["zeroize"] }
curve25519-dalek = { version = ">= 4.0, < 4.2", default-features = false, features = ["zeroize", "digest", "group", "precomputed-tables"] }
curve25519-dalek = { version = ">= 4.0, < 4.2", default-features = false, features = ["zeroize", "digest", "group-bits", "precomputed-tables"] }
[dev-dependencies]
hex = "0.4"
@@ -38,6 +36,6 @@ rand_core = { version = "0.6", default-features = false, features = ["std"] }
ff-group-tests = { path = "../ff-group-tests" }
[features]
alloc = ["zeroize/alloc", "digest/alloc", "prime-field/alloc", "ciphersuite/alloc", "crypto-bigint/alloc", "curve25519-dalek/alloc"]
std = ["alloc", "zeroize/std", "subtle/std", "rand_core/std", "digest/std", "prime-field/std", "ciphersuite/std"]
alloc = ["zeroize/alloc", "prime-field/alloc", "ciphersuite/alloc", "curve25519-dalek/alloc"]
std = ["alloc", "zeroize/std", "subtle/std", "rand_core/std", "prime-field/std", "ciphersuite/std"]
default = ["std"]

View File

@@ -1,49 +1,48 @@
use zeroize::Zeroize;
use sha2::Sha512;
use blake2::Blake2b512;
use ciphersuite::{group::Group, Ciphersuite};
use ::ciphersuite::{group::Group, *};
use crate::Scalar;
macro_rules! dalek_curve {
(
$feature: literal,
$Ciphersuite: ident,
$Point: ident,
$ID: literal
) => {
use crate::$Point;
impl Ciphersuite for $Ciphersuite {
type F = Scalar;
type G = $Point;
type H = Sha512;
const ID: &'static [u8] = $ID;
fn generator() -> Self::G {
$Point::generator()
}
}
};
}
use crate::*;
/// Ciphersuite for Ristretto.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Ristretto;
dalek_curve!("ristretto", Ristretto, RistrettoPoint, b"ristretto");
#[test]
fn test_ristretto() {
ff_group_tests::group::test_prime_group_bits::<_, RistrettoPoint>(&mut rand_core::OsRng);
impl WrappedGroup for Ristretto {
type F = Scalar;
type G = RistrettoPoint;
fn generator() -> Self::G {
<RistrettoPoint as Group>::generator()
}
}
impl Id for Ristretto {
const ID: &[u8] = b"ristretto";
}
impl WithPreferredHash for Ristretto {
type H = Blake2b512;
}
impl GroupCanonicalEncoding for Ristretto {
fn from_canonical_bytes(bytes: &<Self::G as GroupEncoding>::Repr) -> CtOption<Self::G> {
Self::G::from_bytes(bytes)
}
}
/// Ciphersuite for Ed25519, inspired by RFC-8032.
/// Ciphersuite for Ed25519.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Ed25519;
dalek_curve!("ed25519", Ed25519, EdwardsPoint, b"edwards25519");
#[test]
fn test_ed25519() {
ff_group_tests::group::test_prime_group_bits::<_, EdwardsPoint>(&mut rand_core::OsRng);
impl WrappedGroup for Ed25519 {
type F = Scalar;
type G = EdwardsPoint;
fn generator() -> Self::G {
<EdwardsPoint as Group>::generator()
}
}
impl Id for Ed25519 {
const ID: &[u8] = b"ed25519";
}
impl WithPreferredHash for Ed25519 {
type H = Sha512;
}
impl GroupCanonicalEncoding for Ed25519 {}

View File

@@ -7,7 +7,7 @@
use core::{
borrow::Borrow,
ops::{Deref, Add, AddAssign, Sub, SubAssign, Neg, Mul, MulAssign},
iter::{Iterator, Sum, Product},
iter::{Iterator, Sum},
hash::{Hash, Hasher},
};
@@ -15,25 +15,16 @@ use zeroize::Zeroize;
use subtle::{ConstantTimeEq, ConditionallySelectable};
use rand_core::RngCore;
use digest::{consts::U64, Digest, HashMarker};
use subtle::{Choice, CtOption};
pub use curve25519_dalek as dalek;
use dalek::{
constants::{self, BASEPOINT_ORDER},
scalar::Scalar as DScalar,
edwards::{EdwardsPoint as DEdwardsPoint, EdwardsBasepointTable, CompressedEdwardsY},
ristretto::{RistrettoPoint as DRistrettoPoint, RistrettoBasepointTable, CompressedRistretto},
use curve25519_dalek::{
edwards::{EdwardsPoint as DEdwardsPoint, CompressedEdwardsY},
ristretto::{RistrettoPoint as DRistrettoPoint, CompressedRistretto},
};
pub use constants::{ED25519_BASEPOINT_TABLE, RISTRETTO_BASEPOINT_TABLE};
pub use curve25519_dalek::Scalar;
use ::ciphersuite::group::{
ff::{Field, PrimeField, FieldBits, PrimeFieldBits, FromUniformBytes},
Group, GroupEncoding,
prime::PrimeGroup,
};
use ::ciphersuite::group::{Group, GroupEncoding, prime::PrimeGroup};
mod ciphersuite;
pub use crate::ciphersuite::{Ed25519, Ristretto};
@@ -97,7 +88,41 @@ macro_rules! constant_time {
}
};
}
pub(crate) use constant_time;
macro_rules! math_op_without_wrapping {
(
$Value: ident,
$Other: ident,
$Op: ident,
$op_fn: ident,
$Assign: ident,
$assign_fn: ident,
$function: expr
) => {
impl $Op<$Other> for $Value {
type Output = $Value;
fn $op_fn(self, other: $Other) -> Self::Output {
Self($function(self.0, other))
}
}
impl $Assign<$Other> for $Value {
fn $assign_fn(&mut self, other: $Other) {
self.0 = $function(self.0, other);
}
}
impl<'a> $Op<&'a $Other> for $Value {
type Output = $Value;
fn $op_fn(self, other: &'a $Other) -> Self::Output {
Self($function(self.0, other))
}
}
impl<'a> $Assign<&'a $Other> for $Value {
fn $assign_fn(&mut self, other: &'a $Other) {
self.0 = $function(self.0, other);
}
}
};
}
macro_rules! math_op {
(
@@ -133,20 +158,12 @@ macro_rules! math_op {
}
};
}
pub(crate) use math_op;
macro_rules! math {
($Value: ident, $Factor: ident, $add: expr, $sub: expr, $mul: expr) => {
math_op!($Value, $Value, Add, add, AddAssign, add_assign, $add);
math_op!($Value, $Value, Sub, sub, SubAssign, sub_assign, $sub);
math_op!($Value, $Factor, Mul, mul, MulAssign, mul_assign, $mul);
};
}
pub(crate) use math;
macro_rules! math_neg {
($Value: ident, $Factor: ident, $add: expr, $sub: expr, $mul: expr) => {
math!($Value, $Factor, $add, $sub, $mul);
math_op!($Value, $Value, Add, add, AddAssign, add_assign, $add);
math_op!($Value, $Value, Sub, sub, SubAssign, sub_assign, $sub);
math_op_without_wrapping!($Value, $Factor, Mul, mul, MulAssign, mul_assign, $mul);
impl Neg for $Value {
type Output = Self;
@@ -157,187 +174,6 @@ macro_rules! math_neg {
};
}
/// Wrapper around the dalek Scalar type.
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)]
pub struct Scalar(pub DScalar);
deref_borrow!(Scalar, DScalar);
constant_time!(Scalar, DScalar);
math_neg!(Scalar, Scalar, DScalar::add, DScalar::sub, DScalar::mul);
macro_rules! from_wrapper {
($uint: ident) => {
impl From<$uint> for Scalar {
fn from(a: $uint) -> Scalar {
Scalar(DScalar::from(a))
}
}
};
}
from_wrapper!(u8);
from_wrapper!(u16);
from_wrapper!(u32);
from_wrapper!(u64);
from_wrapper!(u128);
impl Scalar {
pub fn pow(&self, other: Scalar) -> Scalar {
let mut table = [Scalar::ONE; 16];
table[1] = *self;
for i in 2 .. 16 {
table[i] = table[i - 1] * self;
}
let mut res = Scalar::ONE;
let mut bits = 0;
for (i, mut bit) in other.to_le_bits().iter_mut().rev().enumerate() {
bits <<= 1;
let mut bit = u8_from_bool(&mut bit);
bits |= bit;
bit.zeroize();
if ((i + 1) % 4) == 0 {
if i != 3 {
for _ in 0 .. 4 {
res *= res;
}
}
let mut scale_by = Scalar::ONE;
#[allow(clippy::needless_range_loop)]
for i in 0 .. 16 {
#[allow(clippy::cast_possible_truncation)] // Safe since 0 .. 16
{
scale_by = <_>::conditional_select(&scale_by, &table[i], bits.ct_eq(&(i as u8)));
}
}
res *= scale_by;
bits = 0;
}
}
res
}
/// Perform wide reduction on a 64-byte array to create a Scalar without bias.
pub fn from_bytes_mod_order_wide(bytes: &[u8; 64]) -> Scalar {
Self(DScalar::from_bytes_mod_order_wide(bytes))
}
/// Derive a Scalar without bias from a digest via wide reduction.
pub fn from_hash<D: Digest<OutputSize = U64> + HashMarker>(hash: D) -> Scalar {
let mut output = [0u8; 64];
output.copy_from_slice(&hash.finalize());
let res = Scalar(DScalar::from_bytes_mod_order_wide(&output));
output.zeroize();
res
}
}
impl Field for Scalar {
const ZERO: Scalar = Scalar(DScalar::ZERO);
const ONE: Scalar = Scalar(DScalar::ONE);
fn random(rng: impl RngCore) -> Self {
Self(<DScalar as Field>::random(rng))
}
fn square(&self) -> Self {
Self(self.0.square())
}
fn double(&self) -> Self {
Self(self.0.double())
}
fn invert(&self) -> CtOption<Self> {
<DScalar as Field>::invert(&self.0).map(Self)
}
fn sqrt(&self) -> CtOption<Self> {
self.0.sqrt().map(Self)
}
fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) {
let (choice, res) = DScalar::sqrt_ratio(num, div);
(choice, Self(res))
}
}
impl PrimeField for Scalar {
type Repr = [u8; 32];
const MODULUS: &'static str = <DScalar as PrimeField>::MODULUS;
const NUM_BITS: u32 = <DScalar as PrimeField>::NUM_BITS;
const CAPACITY: u32 = <DScalar as PrimeField>::CAPACITY;
const TWO_INV: Scalar = Scalar(<DScalar as PrimeField>::TWO_INV);
const MULTIPLICATIVE_GENERATOR: Scalar =
Scalar(<DScalar as PrimeField>::MULTIPLICATIVE_GENERATOR);
const S: u32 = <DScalar as PrimeField>::S;
const ROOT_OF_UNITY: Scalar = Scalar(<DScalar as PrimeField>::ROOT_OF_UNITY);
const ROOT_OF_UNITY_INV: Scalar = Scalar(<DScalar as PrimeField>::ROOT_OF_UNITY_INV);
const DELTA: Scalar = Scalar(<DScalar as PrimeField>::DELTA);
fn from_repr(bytes: [u8; 32]) -> CtOption<Self> {
<DScalar as PrimeField>::from_repr(bytes).map(Scalar)
}
fn to_repr(&self) -> [u8; 32] {
self.0.to_repr()
}
fn is_odd(&self) -> Choice {
self.0.is_odd()
}
fn from_u128(num: u128) -> Self {
Scalar(DScalar::from_u128(num))
}
}
impl PrimeFieldBits for Scalar {
type ReprBits = [u8; 32];
fn to_le_bits(&self) -> FieldBits<Self::ReprBits> {
self.to_repr().into()
}
fn char_le_bits() -> FieldBits<Self::ReprBits> {
BASEPOINT_ORDER.to_bytes().into()
}
}
impl FromUniformBytes<64> for Scalar {
fn from_uniform_bytes(bytes: &[u8; 64]) -> Self {
Self::from_bytes_mod_order_wide(bytes)
}
}
impl Sum<Scalar> for Scalar {
fn sum<I: Iterator<Item = Scalar>>(iter: I) -> Scalar {
Self(DScalar::sum(iter))
}
}
impl<'a> Sum<&'a Scalar> for Scalar {
fn sum<I: Iterator<Item = &'a Scalar>>(iter: I) -> Scalar {
Self(DScalar::sum(iter))
}
}
impl Product<Scalar> for Scalar {
fn product<I: Iterator<Item = Scalar>>(iter: I) -> Scalar {
Self(DScalar::product(iter))
}
}
impl<'a> Product<&'a Scalar> for Scalar {
fn product<I: Iterator<Item = &'a Scalar>>(iter: I) -> Scalar {
Self(DScalar::product(iter))
}
}
macro_rules! dalek_group {
(
$Point: ident,
@@ -347,9 +183,6 @@ macro_rules! dalek_group {
$Table: ident,
$DCompressed: ident,
$BASEPOINT_POINT: ident,
$BASEPOINT_TABLE: ident
) => {
/// Wrapper around the dalek Point type.
///
@@ -363,9 +196,6 @@ macro_rules! dalek_group {
constant_time!($Point, $DPoint);
math_neg!($Point, Scalar, $DPoint::add, $DPoint::sub, $DPoint::mul);
/// The basepoint for this curve.
pub const $BASEPOINT_POINT: $Point = $Point(constants::$BASEPOINT_POINT);
impl Sum<$Point> for $Point {
fn sum<I: Iterator<Item = $Point>>(iter: I) -> $Point {
Self($DPoint::sum(iter))
@@ -396,7 +226,7 @@ macro_rules! dalek_group {
Self($DPoint::identity())
}
fn generator() -> Self {
$BASEPOINT_POINT
Self(<$DPoint as Group>::generator())
}
fn is_identity(&self) -> Choice {
self.0.ct_eq(&$DPoint::identity())
@@ -430,13 +260,6 @@ macro_rules! dalek_group {
impl PrimeGroup for $Point {}
impl Mul<Scalar> for &$Table {
type Output = $Point;
fn mul(self, b: Scalar) -> $Point {
$Point(&b.0 * self)
}
}
// Support being used as a key in a table
// While it is expensive as a key, due to the field operations required, there's frequently
// use cases for public key -> value lookups
@@ -456,24 +279,14 @@ dalek_group!(
|point: DEdwardsPoint| point.is_torsion_free(),
EdwardsBasepointTable,
CompressedEdwardsY,
ED25519_BASEPOINT_POINT,
ED25519_BASEPOINT_TABLE
);
impl EdwardsPoint {
pub fn mul_by_cofactor(&self) -> EdwardsPoint {
EdwardsPoint(self.0.mul_by_cofactor())
}
}
dalek_group!(
RistrettoPoint,
DRistrettoPoint,
|_| true,
RistrettoBasepointTable,
CompressedRistretto,
RISTRETTO_BASEPOINT_POINT,
RISTRETTO_BASEPOINT_TABLE
);
#[test]

View File

@@ -10,12 +10,12 @@ use rand_core::{RngCore, CryptoRng};
use ciphersuite::{
group::ff::{Field, PrimeField},
Ciphersuite,
GroupIo, Id,
};
pub use dkg::*;
/// Create a key via a dealer key generation protocol.
pub fn key_gen<R: RngCore + CryptoRng, C: Ciphersuite>(
pub fn key_gen<R: RngCore + CryptoRng, C: GroupIo + Id>(
rng: &mut R,
threshold: u16,
participants: u16,

View File

@@ -31,13 +31,13 @@ ciphersuite = { path = "../../ciphersuite", version = "^0.4.1", default-features
multiexp = { path = "../../multiexp", version = "0.4", default-features = false }
generic-array = { version = "1", default-features = false, features = ["alloc"] }
blake2 = { version = "0.11.0-rc.0", default-features = false }
blake2 = { version = "0.11.0-rc.2", default-features = false }
rand_chacha = { version = "0.3", default-features = false }
generalized-bulletproofs = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false }
ec-divisors = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false }
generalized-bulletproofs-circuit-abstraction = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false }
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false }
generalized-bulletproofs = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", default-features = false }
ec-divisors = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", default-features = false }
generalized-bulletproofs-circuit-abstraction = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", default-features = false }
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", default-features = false }
dkg = { path = "..", default-features = false }
@@ -52,7 +52,7 @@ rand = { version = "0.8", default-features = false, features = ["std"] }
ciphersuite = { path = "../../ciphersuite", default-features = false, features = ["std"] }
embedwards25519 = { path = "../../embedwards25519", default-features = false, features = ["std"] }
dalek-ff-group = { path = "../../dalek-ff-group", default-features = false, features = ["std"] }
generalized-bulletproofs = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", features = ["tests"] }
generalized-bulletproofs = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", features = ["tests"] }
dkg-recovery = { path = "../recovery" }
[features]
@@ -86,6 +86,5 @@ std = [
]
secp256k1 = ["ciphersuite-kp256", "secq256k1"]
ed25519 = ["dalek-ff-group", "embedwards25519"]
ristretto = ["dalek-ff-group", "embedwards25519"]
tests = ["rand_core/getrandom"]
default = ["std"]

View File

@@ -17,7 +17,7 @@ type Blake2s256Keyed = Blake2sMac<U32>;
use ciphersuite::{
group::{ff::FromUniformBytes, GroupEncoding},
Ciphersuite,
WrappedGroup, Id, GroupIo,
};
use ec_divisors::DivisorCurve;
@@ -27,10 +27,10 @@ use generalized_bulletproofs_ec_gadgets::*;
/// A pair of curves to perform the eVRF with.
pub trait Curves {
/// The towering curve, for which the resulting key is on.
type ToweringCurve: Ciphersuite<F: FromUniformBytes<64>>;
type ToweringCurve: Id + GroupIo<F: FromUniformBytes<64>>;
/// The embedded curve which participants represent their public keys over.
type EmbeddedCurve: Ciphersuite<
G: DivisorCurve<FieldElement = <Self::ToweringCurve as Ciphersuite>::F>,
type EmbeddedCurve: GroupIo<
G: DivisorCurve<FieldElement = <Self::ToweringCurve as WrappedGroup>::F>,
>;
/// The parameters to use the embedded curve with the discrete-log gadget.
type EmbeddedCurveParameters: DiscreteLogParameters;
@@ -49,14 +49,14 @@ impl<C: Curves> Generators<C> {
pub fn new(max_threshold: u16, max_participants: u16) -> Generators<C> {
let entropy = <Blake2s256Keyed as KeyInit>::new(&{
let mut key = Array::<u8, <Blake2s256Keyed as KeySizeUser>::KeySize>::default();
let key_len = key.len().min(<C::ToweringCurve as Ciphersuite>::ID.len());
let key_len = key.len().min(<C::ToweringCurve as Id>::ID.len());
{
let key: &mut [u8] = key.as_mut();
key[.. key_len].copy_from_slice(&<C::ToweringCurve as Ciphersuite>::ID[.. key_len])
key[.. key_len].copy_from_slice(&<C::ToweringCurve as Id>::ID[.. key_len])
}
key
})
.chain_update(<C::ToweringCurve as Ciphersuite>::generator().to_bytes())
.chain_update(<C::ToweringCurve as WrappedGroup>::generator().to_bytes())
.finalize()
.into_bytes();
let mut rng = ChaCha20Rng::from_seed(entropy.into());
@@ -71,7 +71,8 @@ impl<C: Curves> Generators<C> {
h_bold.push(crate::sample_point::<C::ToweringCurve>(&mut rng));
}
Self(
BpGenerators::new(<C::ToweringCurve as Ciphersuite>::generator(), h, g_bold, h_bold).unwrap(),
BpGenerators::new(<C::ToweringCurve as WrappedGroup>::generator(), h, g_bold, h_bold)
.unwrap(),
)
}
}
@@ -95,13 +96,3 @@ impl Curves for Ed25519 {
type EmbeddedCurve = embedwards25519::Embedwards25519;
type EmbeddedCurveParameters = embedwards25519::Embedwards25519;
}
/// Ristretto, and an elliptic curve defined over its scalar field (embedwards25519).
#[cfg(any(test, feature = "ristretto"))]
pub struct Ristretto;
#[cfg(any(test, feature = "ristretto"))]
impl Curves for Ristretto {
type ToweringCurve = dalek_ff_group::Ristretto;
type EmbeddedCurve = embedwards25519::Embedwards25519;
type EmbeddedCurveParameters = embedwards25519::Embedwards25519;
}

View File

@@ -21,7 +21,7 @@ use ciphersuite::{
ff::{Field, PrimeField},
Group, GroupEncoding,
},
Ciphersuite,
WrappedGroup, GroupIo,
};
use multiexp::multiexp_vartime;
@@ -49,7 +49,7 @@ mod tests;
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Participation<C: Curves> {
proof: Vec<u8>,
encrypted_secret_shares: HashMap<Participant, <C::ToweringCurve as Ciphersuite>::F>,
encrypted_secret_shares: HashMap<Participant, <C::ToweringCurve as WrappedGroup>::F>,
}
impl<C: Curves> Participation<C> {
@@ -79,7 +79,7 @@ impl<C: Curves> Participation<C> {
let mut encrypted_secret_shares = HashMap::with_capacity(usize::from(n));
for i in Participant::iter().take(usize::from(n)) {
encrypted_secret_shares.insert(i, <C::ToweringCurve as Ciphersuite>::read_F(reader)?);
encrypted_secret_shares.insert(i, <C::ToweringCurve as GroupIo>::read_F(reader)?);
}
Ok(Self { proof, encrypted_secret_shares })
@@ -151,14 +151,14 @@ pub enum VerifyResult<C: Curves> {
pub struct Dkg<C: Curves> {
t: u16,
n: u16,
evrf_public_keys: Vec<<C::EmbeddedCurve as Ciphersuite>::G>,
verification_shares: HashMap<Participant, <C::ToweringCurve as Ciphersuite>::G>,
evrf_public_keys: Vec<<C::EmbeddedCurve as WrappedGroup>::G>,
verification_shares: HashMap<Participant, <C::ToweringCurve as WrappedGroup>::G>,
#[allow(clippy::type_complexity)]
encrypted_secret_shares: HashMap<
Participant,
HashMap<
Participant,
([<C::EmbeddedCurve as Ciphersuite>::G; 2], <C::ToweringCurve as Ciphersuite>::F),
([<C::EmbeddedCurve as WrappedGroup>::G; 2], <C::ToweringCurve as WrappedGroup>::F),
>,
>,
}
@@ -167,7 +167,7 @@ impl<C: Curves> Dkg<C> {
// Form the initial transcript for the proofs.
fn initial_transcript(
invocation: [u8; 32],
evrf_public_keys: &[<C::EmbeddedCurve as Ciphersuite>::G],
evrf_public_keys: &[<C::EmbeddedCurve as WrappedGroup>::G],
t: u16,
) -> [u8; 32] {
let mut transcript = Blake2s256::new();
@@ -188,8 +188,8 @@ impl<C: Curves> Dkg<C> {
generators: &Generators<C>,
context: [u8; 32],
t: 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>,
) -> Result<Participation<C>, Error> {
let Ok(n) = u16::try_from(evrf_public_keys.len()) else {
Err(Error::TooManyParticipants { provided: evrf_public_keys.len() })?
@@ -202,7 +202,8 @@ impl<C: Curves> Dkg<C> {
};
// This also ensures the private key is not 0, due to the prior check the identity point wasn't
// present
let evrf_public_key = <C::EmbeddedCurve as Ciphersuite>::generator() * evrf_private_key.deref();
let evrf_public_key =
<C::EmbeddedCurve as WrappedGroup>::generator() * evrf_private_key.deref();
if !evrf_public_keys.contains(&evrf_public_key) {
Err(Error::NotAParticipant)?;
};
@@ -231,7 +232,7 @@ impl<C: Curves> Dkg<C> {
let mut encrypted_secret_shares = HashMap::with_capacity(usize::from(n));
for (l, encryption_key) in Participant::iter().take(usize::from(n)).zip(encryption_keys) {
let share = polynomial::<<C::ToweringCurve as Ciphersuite>::F>(&coefficients, l);
let share = polynomial::<<C::ToweringCurve as WrappedGroup>::F>(&coefficients, l);
encrypted_secret_shares.insert(l, *share + *encryption_key);
}
@@ -243,26 +244,26 @@ impl<C: Curves> Dkg<C> {
#[allow(clippy::type_complexity)]
fn verifiable_encryption_statements<C: Curves>(
rng: &mut (impl RngCore + CryptoRng),
coefficients: &[<C::ToweringCurve as Ciphersuite>::G],
encryption_key_commitments: &[<C::ToweringCurve as Ciphersuite>::G],
encrypted_secret_shares: &HashMap<Participant, <C::ToweringCurve as Ciphersuite>::F>,
coefficients: &[<C::ToweringCurve as WrappedGroup>::G],
encryption_key_commitments: &[<C::ToweringCurve as WrappedGroup>::G],
encrypted_secret_shares: &HashMap<Participant, <C::ToweringCurve as WrappedGroup>::F>,
) -> (
<C::ToweringCurve as Ciphersuite>::F,
Vec<(<C::ToweringCurve as Ciphersuite>::F, <C::ToweringCurve as Ciphersuite>::G)>,
<C::ToweringCurve as WrappedGroup>::F,
Vec<(<C::ToweringCurve as WrappedGroup>::F, <C::ToweringCurve as WrappedGroup>::G)>,
) {
let mut g_scalar = <C::ToweringCurve as Ciphersuite>::F::ZERO;
let mut g_scalar = <C::ToweringCurve as WrappedGroup>::F::ZERO;
let mut pairs = Vec::with_capacity(coefficients.len() + encryption_key_commitments.len());
// Push on the commitments to the polynomial being secret-shared
for coefficient in coefficients {
// This uses `0` as we'll add to it later, given its fixed position
pairs.push((<C::ToweringCurve as Ciphersuite>::F::ZERO, *coefficient));
pairs.push((<C::ToweringCurve as WrappedGroup>::F::ZERO, *coefficient));
}
for (i, encrypted_secret_share) in encrypted_secret_shares {
let encryption_key_commitment = encryption_key_commitments[usize::from(u16::from(*i)) - 1];
let weight = <C::ToweringCurve as Ciphersuite>::F::random(&mut *rng);
let weight = <C::ToweringCurve as WrappedGroup>::F::random(&mut *rng);
/*
The encrypted secret share scaling `G`, minus the encryption key commitment, minus the
@@ -274,7 +275,7 @@ fn verifiable_encryption_statements<C: Curves>(
pairs.push((weight, encryption_key_commitment));
// Calculate the commitment to the secret share via the commitments to the polynomial
{
let i = <C::ToweringCurve as Ciphersuite>::F::from(u64::from(u16::from(*i)));
let i = <C::ToweringCurve as WrappedGroup>::F::from(u64::from(u16::from(*i)));
(0 .. coefficients.len()).fold(weight, |exp, j| {
pairs[j].0 += exp;
exp * i
@@ -300,7 +301,7 @@ impl<C: Curves> Dkg<C> {
generators: &Generators<C>,
context: [u8; 32],
t: u16,
evrf_public_keys: &[<C::EmbeddedCurve as Ciphersuite>::G],
evrf_public_keys: &[<C::EmbeddedCurve as WrappedGroup>::G],
participations: &HashMap<Participant, Participation<C>>,
) -> Result<VerifyResult<C>, Error> {
let Ok(n) = u16::try_from(evrf_public_keys.len()) else {
@@ -386,7 +387,7 @@ impl<C: Curves> Dkg<C> {
{
let mut share_verification_statements_actual = HashMap::with_capacity(valid.len());
if !{
let mut g_scalar = <C::ToweringCurve as Ciphersuite>::F::ZERO;
let mut g_scalar = <C::ToweringCurve as WrappedGroup>::F::ZERO;
let mut pairs = Vec::with_capacity(valid.len() * (usize::from(t) + evrf_public_keys.len()));
for (i, (encrypted_secret_shares, data)) in &valid {
let (this_g_scalar, mut these_pairs) = verifiable_encryption_statements::<C>(
@@ -417,9 +418,11 @@ impl<C: Curves> Dkg<C> {
let sum_encrypted_secret_share = sum_encrypted_secret_shares
.get(j)
.copied()
.unwrap_or(<C::ToweringCurve as Ciphersuite>::F::ZERO);
let sum_mask =
sum_masks.get(j).copied().unwrap_or(<C::ToweringCurve as Ciphersuite>::G::identity());
.unwrap_or(<C::ToweringCurve as WrappedGroup>::F::ZERO);
let sum_mask = sum_masks
.get(j)
.copied()
.unwrap_or(<C::ToweringCurve as WrappedGroup>::G::identity());
sum_encrypted_secret_shares.insert(*j, sum_encrypted_secret_share + enc_share);
let j_index = usize::from(u16::from(*j)) - 1;
@@ -487,7 +490,7 @@ impl<C: Curves> Dkg<C> {
for i in Participant::iter().take(usize::from(n)) {
verification_shares.insert(
i,
(<C::ToweringCurve as Ciphersuite>::generator() * sum_encrypted_secret_shares[&i]) -
(<C::ToweringCurve as WrappedGroup>::generator() * sum_encrypted_secret_shares[&i]) -
sum_masks[&i],
);
}
@@ -506,9 +509,10 @@ impl<C: Curves> Dkg<C> {
/// This will return _all_ keys belong to the participant.
pub fn keys(
&self,
evrf_private_key: &Zeroizing<<C::EmbeddedCurve as Ciphersuite>::F>,
evrf_private_key: &Zeroizing<<C::EmbeddedCurve as WrappedGroup>::F>,
) -> Vec<ThresholdKeys<C::ToweringCurve>> {
let evrf_public_key = <C::EmbeddedCurve as Ciphersuite>::generator() * evrf_private_key.deref();
let evrf_public_key =
<C::EmbeddedCurve as WrappedGroup>::generator() * evrf_private_key.deref();
let mut is = Vec::with_capacity(1);
for (i, evrf_key) in Participant::iter().zip(self.evrf_public_keys.iter()) {
if *evrf_key == evrf_public_key {
@@ -518,14 +522,14 @@ impl<C: Curves> Dkg<C> {
let mut res = Vec::with_capacity(is.len());
for i in is {
let mut secret_share = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::ZERO);
let mut secret_share = Zeroizing::new(<C::ToweringCurve as WrappedGroup>::F::ZERO);
for shares in self.encrypted_secret_shares.values() {
let (ecdh_commitments, encrypted_secret_share) = shares[&i];
let mut ecdh = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::ZERO);
let mut ecdh = Zeroizing::new(<C::ToweringCurve as WrappedGroup>::F::ZERO);
for point in ecdh_commitments {
let (mut x, mut y) =
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(point * evrf_private_key.deref()).unwrap();
<C::EmbeddedCurve as WrappedGroup>::G::to_xy(point * evrf_private_key.deref()).unwrap();
*ecdh += x;
x.zeroize();
y.zeroize();
@@ -534,7 +538,7 @@ impl<C: Curves> Dkg<C> {
}
debug_assert_eq!(
self.verification_shares[&i],
<C::ToweringCurve as Ciphersuite>::G::generator() * secret_share.deref()
<C::ToweringCurve as WrappedGroup>::generator() * secret_share.deref()
);
res.push(

View File

@@ -8,7 +8,7 @@ use zeroize::Zeroizing;
use rand_core::{RngCore, CryptoRng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use ciphersuite::{group::ff::Field, Ciphersuite};
use ciphersuite::{group::ff::Field, WrappedGroup};
use generalized_bulletproofs::{
Generators, BatchVerifier, PedersenCommitment, PedersenVectorCommitment,
@@ -28,8 +28,8 @@ mod tape;
use tape::*;
type EmbeddedPoint<C> = (
<<<C as Curves>::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement,
<<<C as Curves>::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement,
<<<C as Curves>::EmbeddedCurve as WrappedGroup>::G as DivisorCurve>::FieldElement,
<<<C as Curves>::EmbeddedCurve as WrappedGroup>::G as DivisorCurve>::FieldElement,
);
#[allow(non_snake_case)]
@@ -37,14 +37,15 @@ struct Circuit<
'a,
C: Curves,
CG: Iterator<
Item = ChallengedGenerator<<C::ToweringCurve as Ciphersuite>::F, C::EmbeddedCurveParameters>,
Item = ChallengedGenerator<<C::ToweringCurve as WrappedGroup>::F, C::EmbeddedCurveParameters>,
>,
> {
curve_spec: &'a CurveSpec<<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement>,
curve_spec: &'a CurveSpec<<<C::EmbeddedCurve as WrappedGroup>::G as DivisorCurve>::FieldElement>,
circuit: &'a mut BpCircuit<C::ToweringCurve>,
challenge: DiscreteLogChallenge<<C::ToweringCurve as Ciphersuite>::F, C::EmbeddedCurveParameters>,
challenge:
DiscreteLogChallenge<<C::ToweringCurve as WrappedGroup>::F, C::EmbeddedCurveParameters>,
challenged_G:
ChallengedGenerator<<C::ToweringCurve as Ciphersuite>::F, C::EmbeddedCurveParameters>,
ChallengedGenerator<<C::ToweringCurve as WrappedGroup>::F, C::EmbeddedCurveParameters>,
challenged_generators: &'a mut CG,
tape: Tape,
pedersen_commitment_tape: PedersenCommitmentTape,
@@ -54,7 +55,7 @@ impl<
'a,
C: Curves,
CG: Iterator<
Item = ChallengedGenerator<<C::ToweringCurve as Ciphersuite>::F, C::EmbeddedCurveParameters>,
Item = ChallengedGenerator<<C::ToweringCurve as WrappedGroup>::F, C::EmbeddedCurveParameters>,
>,
> Circuit<'a, C, CG>
{
@@ -92,7 +93,7 @@ impl<
&self.challenge,
&challenged_generator,
);
lincomb = lincomb.term(<C::ToweringCurve as Ciphersuite>::F::ONE, point.x());
lincomb = lincomb.term(<C::ToweringCurve as WrappedGroup>::F::ONE, point.x());
}
/*
Constrain the sum of the two `x` coordinates to be equal to the value committed to in a
@@ -137,7 +138,7 @@ impl<
&self.challenge,
&challenged_public_key,
);
lincomb = lincomb.term(<C::ToweringCurve as Ciphersuite>::F::ONE, point.x());
lincomb = lincomb.term(<C::ToweringCurve as WrappedGroup>::F::ONE, point.x());
debug_assert!(point_with_dlogs.next().is_none());
}
@@ -152,20 +153,20 @@ impl<
/// The result of proving.
pub(super) struct ProveResult<C: Curves> {
/// The coefficients for use in the DKG.
pub(super) coefficients: Vec<Zeroizing<<C::ToweringCurve as Ciphersuite>::F>>,
pub(super) coefficients: Vec<Zeroizing<<C::ToweringCurve as WrappedGroup>::F>>,
/// The masks to encrypt secret shares with.
pub(super) encryption_keys: Vec<Zeroizing<<C::ToweringCurve as Ciphersuite>::F>>,
pub(super) encryption_keys: Vec<Zeroizing<<C::ToweringCurve as WrappedGroup>::F>>,
/// The proof itself.
pub(super) proof: Vec<u8>,
}
pub(super) struct Verified<C: Curves> {
/// The commitments to the coefficients used within the DKG.
pub(super) coefficients: Vec<<C::ToweringCurve as Ciphersuite>::G>,
pub(super) coefficients: Vec<<C::ToweringCurve as WrappedGroup>::G>,
/// The ephemeral public keys to perform ECDHs with
pub(super) ecdh_commitments: Vec<[<C::EmbeddedCurve as Ciphersuite>::G; 2]>,
pub(super) ecdh_commitments: Vec<[<C::EmbeddedCurve as WrappedGroup>::G; 2]>,
/// The commitments to the masks used to encrypt secret shares with.
pub(super) encryption_key_commitments: Vec<<C::ToweringCurve as Ciphersuite>::G>,
pub(super) encryption_key_commitments: Vec<<C::ToweringCurve as WrappedGroup>::G>,
}
impl<C: Curves> fmt::Debug for Verified<C> {
@@ -175,7 +176,7 @@ impl<C: Curves> fmt::Debug for Verified<C> {
}
type GeneratorTable<C> = generalized_bulletproofs_ec_gadgets::GeneratorTable<
<<<C as Curves>::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement,
<<<C as Curves>::EmbeddedCurve as WrappedGroup>::G as DivisorCurve>::FieldElement,
<C as Curves>::EmbeddedCurveParameters,
>;
@@ -219,7 +220,7 @@ impl<C: Curves> Proof<C> {
}
fn circuit(
curve_spec: &CurveSpec<<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement>,
curve_spec: &CurveSpec<<<C::EmbeddedCurve as WrappedGroup>::G as DivisorCurve>::FieldElement>,
evrf_public_key: EmbeddedPoint<C>,
coefficients: usize,
ecdh_commitments: &[[EmbeddedPoint<C>; 2]],
@@ -281,7 +282,7 @@ impl<C: Curves> Proof<C> {
fn sample_coefficients_evrf_points(
seed: [u8; 32],
coefficients: usize,
) -> Vec<<C::EmbeddedCurve as Ciphersuite>::G> {
) -> Vec<<C::EmbeddedCurve as WrappedGroup>::G> {
let mut rng = ChaCha20Rng::from_seed(seed);
let quantity = 2 * coefficients;
let mut res = Vec::with_capacity(quantity);
@@ -293,28 +294,29 @@ impl<C: Curves> Proof<C> {
/// Create the required tables for the generators.
fn generator_tables(
coefficients_evrf_points: &[<C::EmbeddedCurve as Ciphersuite>::G],
participants: &[<<C as Curves>::EmbeddedCurve as Ciphersuite>::G],
coefficients_evrf_points: &[<C::EmbeddedCurve as WrappedGroup>::G],
participants: &[<<C as Curves>::EmbeddedCurve as WrappedGroup>::G],
) -> Vec<GeneratorTable<C>> {
let curve_spec = CurveSpec {
a: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::a(),
b: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::b(),
a: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G::a(),
b: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G::b(),
};
let mut generator_tables =
Vec::with_capacity(1 + coefficients_evrf_points.len() + participants.len());
{
let (x, y) =
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(<C::EmbeddedCurve as Ciphersuite>::generator())
.unwrap();
let (x, y) = <C::EmbeddedCurve as WrappedGroup>::G::to_xy(
<C::EmbeddedCurve as WrappedGroup>::generator(),
)
.unwrap();
generator_tables.push(GeneratorTable::<C>::new(&curve_spec, x, y));
}
for generator in coefficients_evrf_points {
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::G::to_xy(*generator).unwrap();
let (x, y) = <C::EmbeddedCurve as WrappedGroup>::G::to_xy(*generator).unwrap();
generator_tables.push(GeneratorTable::<C>::new(&curve_spec, x, y));
}
for generator in participants {
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::G::to_xy(*generator).unwrap();
let (x, y) = <C::EmbeddedCurve as WrappedGroup>::G::to_xy(*generator).unwrap();
generator_tables.push(GeneratorTable::<C>::new(&curve_spec, x, y));
}
generator_tables
@@ -325,12 +327,12 @@ impl<C: Curves> Proof<C> {
generators: &Generators<C::ToweringCurve>,
transcript: [u8; 32],
coefficients: usize,
participant_public_keys: &[<<C as Curves>::EmbeddedCurve as Ciphersuite>::G],
evrf_private_key: &Zeroizing<<<C as Curves>::EmbeddedCurve as Ciphersuite>::F>,
participant_public_keys: &[<<C as Curves>::EmbeddedCurve as WrappedGroup>::G],
evrf_private_key: &Zeroizing<<<C as Curves>::EmbeddedCurve as WrappedGroup>::F>,
) -> Result<ProveResult<C>, AcProveError> {
let curve_spec = CurveSpec {
a: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::a(),
b: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::b(),
a: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G::a(),
b: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G::b(),
};
let coefficients_evrf_points = Self::sample_coefficients_evrf_points(transcript, coefficients);
@@ -340,7 +342,7 @@ impl<C: Curves> Proof<C> {
// Push a discrete logarithm onto the tape
let discrete_log =
|vector_commitment_tape: &mut Vec<_>,
dlog: &ScalarDecomposition<<<C as Curves>::EmbeddedCurve as Ciphersuite>::F>| {
dlog: &ScalarDecomposition<<<C as Curves>::EmbeddedCurve as WrappedGroup>::F>| {
for coefficient in dlog.decomposition() {
vector_commitment_tape.push(<_>::from(*coefficient));
}
@@ -351,8 +353,8 @@ impl<C: Curves> Proof<C> {
// Returns the point for which the claim was made.
let discrete_log_claim =
|vector_commitment_tape: &mut Vec<_>,
dlog: &ScalarDecomposition<<<C as Curves>::EmbeddedCurve as Ciphersuite>::F>,
generator: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G| {
dlog: &ScalarDecomposition<<<C as Curves>::EmbeddedCurve as WrappedGroup>::F>,
generator: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G| {
{
let divisor =
Zeroizing::new(dlog.scalar_mul_divisor(generator).normalize_x_coefficient());
@@ -368,12 +370,12 @@ impl<C: Curves> Proof<C> {
.y_coefficients
.first()
.copied()
.unwrap_or(<C::ToweringCurve as Ciphersuite>::F::ZERO),
.unwrap_or(<C::ToweringCurve as WrappedGroup>::F::ZERO),
);
}
let dh = generator * dlog.scalar();
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::G::to_xy(dh).unwrap();
let (x, y) = <C::EmbeddedCurve as WrappedGroup>::G::to_xy(dh).unwrap();
vector_commitment_tape.push(x);
vector_commitment_tape.push(y);
(dh, (x, y))
@@ -387,7 +389,7 @@ impl<C: Curves> Proof<C> {
let mut coefficients = Vec::with_capacity(coefficients);
let evrf_public_key = {
let evrf_private_key =
ScalarDecomposition::<<C::EmbeddedCurve as Ciphersuite>::F>::new(**evrf_private_key)
ScalarDecomposition::<<C::EmbeddedCurve as WrappedGroup>::F>::new(**evrf_private_key)
.expect("eVRF private key was zero");
discrete_log(&mut vector_commitment_tape, &evrf_private_key);
@@ -396,12 +398,12 @@ impl<C: Curves> Proof<C> {
let (_, evrf_public_key) = discrete_log_claim(
&mut vector_commitment_tape,
&evrf_private_key,
<<C as Curves>::EmbeddedCurve as Ciphersuite>::generator(),
<<C as Curves>::EmbeddedCurve as WrappedGroup>::generator(),
);
// Push the divisor for each point we use in the eVRF
for pair in coefficients_evrf_points.chunks(2) {
let mut coefficient = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::ZERO);
let mut coefficient = Zeroizing::new(<C::ToweringCurve as WrappedGroup>::F::ZERO);
for point in pair {
let (_, (dh_x, _)) =
discrete_log_claim(&mut vector_commitment_tape, &evrf_private_key, *point);
@@ -418,15 +420,16 @@ impl<C: Curves> Proof<C> {
let mut ecdh_commitments = Vec::with_capacity(2 * participant_public_keys.len());
let mut ecdh_commitments_xy = Vec::with_capacity(participant_public_keys.len());
for participant_public_key in participant_public_keys {
let mut ecdh_commitments_xy_i =
[(<C::ToweringCurve as Ciphersuite>::F::ZERO, <C::ToweringCurve as Ciphersuite>::F::ZERO);
2];
let mut encryption_key = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::ZERO);
let mut ecdh_commitments_xy_i = [(
<C::ToweringCurve as WrappedGroup>::F::ZERO,
<C::ToweringCurve as WrappedGroup>::F::ZERO,
); 2];
let mut encryption_key = Zeroizing::new(<C::ToweringCurve as WrappedGroup>::F::ZERO);
for ecdh_commitments_xy_i_j_dest in &mut ecdh_commitments_xy_i {
let mut ecdh_ephemeral_secret;
loop {
ecdh_ephemeral_secret =
Zeroizing::new(<C::EmbeddedCurve as Ciphersuite>::F::random(&mut *rng));
Zeroizing::new(<C::EmbeddedCurve as WrappedGroup>::F::random(&mut *rng));
// 0 would produce the identity, which isn't representable within the discrete-log proof.
if bool::from(!ecdh_ephemeral_secret.is_zero()) {
break;
@@ -434,7 +437,7 @@ impl<C: Curves> Proof<C> {
}
let ecdh_ephemeral_secret =
ScalarDecomposition::<<C::EmbeddedCurve as Ciphersuite>::F>::new(*ecdh_ephemeral_secret)
ScalarDecomposition::<<C::EmbeddedCurve as WrappedGroup>::F>::new(*ecdh_ephemeral_secret)
.expect("ECDH ephemeral secret zero");
discrete_log(&mut vector_commitment_tape, &ecdh_ephemeral_secret);
@@ -442,7 +445,7 @@ impl<C: Curves> Proof<C> {
let (ecdh_commitment, ecdh_commitment_xy_i_j) = discrete_log_claim(
&mut vector_commitment_tape,
&ecdh_ephemeral_secret,
<<C as Curves>::EmbeddedCurve as Ciphersuite>::generator(),
<<C as Curves>::EmbeddedCurve as WrappedGroup>::generator(),
);
ecdh_commitments.push(ecdh_commitment);
*ecdh_commitments_xy_i_j_dest = ecdh_commitment_xy_i_j;
@@ -470,7 +473,7 @@ impl<C: Curves> Proof<C> {
for chunk in vector_commitment_tape.chunks(generators_to_use) {
vector_commitments.push(PedersenVectorCommitment {
g_values: chunk.into(),
mask: <C::ToweringCurve as Ciphersuite>::F::random(&mut *rng),
mask: <C::ToweringCurve as WrappedGroup>::F::random(&mut *rng),
});
}
@@ -479,13 +482,13 @@ impl<C: Curves> Proof<C> {
for coefficient in &coefficients {
commitments.push(PedersenCommitment {
value: **coefficient,
mask: <C::ToweringCurve as Ciphersuite>::F::random(&mut *rng),
mask: <C::ToweringCurve as WrappedGroup>::F::random(&mut *rng),
});
}
for enc_mask in &encryption_keys {
commitments.push(PedersenCommitment {
value: **enc_mask,
mask: <C::ToweringCurve as Ciphersuite>::F::random(&mut *rng),
mask: <C::ToweringCurve as WrappedGroup>::F::random(&mut *rng),
});
}
@@ -536,13 +539,13 @@ impl<C: Curves> Proof<C> {
}
// Prove the openings of the commitments were correct
let mut x = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::ZERO);
let mut x = Zeroizing::new(<C::ToweringCurve as WrappedGroup>::F::ZERO);
for commitment in commitments {
*x += commitment.mask * transcript.challenge::<C::ToweringCurve>();
}
// Produce a Schnorr PoK for the weighted-sum of the Pedersen commitments' blinding factors
let r = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::random(&mut *rng));
let r = Zeroizing::new(<C::ToweringCurve as WrappedGroup>::F::random(&mut *rng));
transcript.push_point(&(generators.h() * r.deref()));
let c = transcript.challenge::<C::ToweringCurve>();
transcript.push_scalar((c * x.deref()) + r.deref());
@@ -557,14 +560,14 @@ impl<C: Curves> Proof<C> {
verifier: &mut BatchVerifier<C::ToweringCurve>,
transcript: [u8; 32],
coefficients: usize,
participant_public_keys: &[<<C as Curves>::EmbeddedCurve as Ciphersuite>::G],
evrf_public_key: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G,
participant_public_keys: &[<<C as Curves>::EmbeddedCurve as WrappedGroup>::G],
evrf_public_key: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G,
proof: &[u8],
) -> Result<Verified<C>, ()> {
let (mut transcript, ecdh_commitments, pedersen_commitments) = {
let curve_spec = CurveSpec {
a: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::a(),
b: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::b(),
a: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G::a(),
b: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G::b(),
};
let coefficients_evrf_points =
@@ -600,9 +603,9 @@ impl<C: Curves> Proof<C> {
ecdh_commitments.push(ecdh_commitments_i);
// This inherently bans using the identity point, as it won't have an affine representation
ecdh_commitments_xy.push([
<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::to_xy(ecdh_commitments_i[0])
<<C::EmbeddedCurve as WrappedGroup>::G as DivisorCurve>::to_xy(ecdh_commitments_i[0])
.ok_or(())?,
<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::to_xy(ecdh_commitments_i[1])
<<C::EmbeddedCurve as WrappedGroup>::G as DivisorCurve>::to_xy(ecdh_commitments_i[1])
.ok_or(())?,
]);
}
@@ -610,7 +613,7 @@ impl<C: Curves> Proof<C> {
let mut circuit = BpCircuit::verify();
Self::circuit(
&curve_spec,
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(evrf_public_key).ok_or(())?,
<C::EmbeddedCurve as WrappedGroup>::G::to_xy(evrf_public_key).ok_or(())?,
coefficients,
&ecdh_commitments_xy,
&generator_tables.iter().collect::<Vec<_>>(),

View File

@@ -4,11 +4,11 @@ use zeroize::Zeroizing;
use rand_core::OsRng;
use rand::seq::SliceRandom;
use ciphersuite::{group::ff::Field, Ciphersuite};
use ciphersuite::{group::ff::Field, WrappedGroup};
use embedwards25519::Embedwards25519;
use dkg_recovery::recover_key;
use crate::{Participant, Curves, Generators, VerifyResult, Dkg, Ristretto};
use crate::{Participant, Curves, Generators, VerifyResult, Dkg, Ed25519};
mod proof;
@@ -17,14 +17,14 @@ const PARTICIPANTS: u16 = 5;
#[test]
fn dkg() {
let generators = Generators::<Ristretto>::new(THRESHOLD, PARTICIPANTS);
let generators = Generators::<Ed25519>::new(THRESHOLD, PARTICIPANTS);
let context = [0; 32];
let mut priv_keys = vec![];
let mut pub_keys = vec![];
for i in 0 .. PARTICIPANTS {
let priv_key = <Embedwards25519 as Ciphersuite>::F::random(&mut OsRng);
pub_keys.push(<Embedwards25519 as Ciphersuite>::generator() * priv_key);
let priv_key = <Embedwards25519 as WrappedGroup>::F::random(&mut OsRng);
pub_keys.push(<Embedwards25519 as WrappedGroup>::generator() * priv_key);
priv_keys.push((Participant::new(1 + i).unwrap(), Zeroizing::new(priv_key)));
}
@@ -34,27 +34,15 @@ fn dkg() {
for (i, priv_key) in priv_keys.iter().take(usize::from(THRESHOLD)) {
participations.insert(
*i,
Dkg::<Ristretto>::participate(
&mut OsRng,
&generators,
context,
THRESHOLD,
&pub_keys,
priv_key,
)
.unwrap(),
Dkg::<Ed25519>::participate(&mut OsRng, &generators, context, THRESHOLD, &pub_keys, priv_key)
.unwrap(),
);
}
let VerifyResult::Valid(dkg) = Dkg::<Ristretto>::verify(
&mut OsRng,
&generators,
context,
THRESHOLD,
&pub_keys,
&participations,
)
.unwrap() else {
let VerifyResult::Valid(dkg) =
Dkg::<Ed25519>::verify(&mut OsRng, &generators, context, THRESHOLD, &pub_keys, &participations)
.unwrap()
else {
panic!("verify didn't return VerifyResult::Valid")
};
@@ -80,7 +68,7 @@ fn dkg() {
// TODO: Test for all possible combinations of keys
assert_eq!(
<<Ristretto as Curves>::ToweringCurve as Ciphersuite>::generator() *
<<Ed25519 as Curves>::ToweringCurve as WrappedGroup>::generator() *
*recover_key(&all_keys.values().cloned().collect::<Vec<_>>()).unwrap(),
group_key.unwrap()
);

View File

@@ -6,13 +6,13 @@ use zeroize::Zeroizing;
use ciphersuite::{
group::{ff::Field, Group},
Ciphersuite,
WrappedGroup,
};
use generalized_bulletproofs::{Generators, tests::insecure_test_generators};
use crate::{
Curves, Ristretto,
Curves, Ed25519,
proof::*,
tests::{THRESHOLD, PARTICIPANTS},
};
@@ -20,9 +20,9 @@ use crate::{
fn proof<C: Curves>() {
let generators = insecure_test_generators(&mut OsRng, 2048).unwrap();
let embedded_private_key =
Zeroizing::new(<C::EmbeddedCurve as Ciphersuite>::F::random(&mut OsRng));
Zeroizing::new(<C::EmbeddedCurve as WrappedGroup>::F::random(&mut OsRng));
let ecdh_public_keys: [_; PARTICIPANTS as usize] =
core::array::from_fn(|_| <C::EmbeddedCurve as Ciphersuite>::G::random(&mut OsRng));
core::array::from_fn(|_| <C::EmbeddedCurve as WrappedGroup>::G::random(&mut OsRng));
let time = Instant::now();
let res = Proof::<C>::prove(
&mut OsRng,
@@ -54,5 +54,5 @@ fn proof<C: Curves>() {
#[test]
fn ristretto_proof() {
proof::<Ristretto>();
proof::<Ed25519>();
}

View File

@@ -5,7 +5,7 @@ use rand_core::{RngCore, CryptoRng};
use ciphersuite::{
group::{ff::PrimeField, Group, GroupEncoding},
Ciphersuite,
GroupIo,
};
use dkg::Participant;
@@ -13,7 +13,7 @@ use dkg::Participant;
/// Sample a random, unbiased point on the elliptic curve with an unknown discrete logarithm.
///
/// This keeps it simple by using rejection sampling.
pub(crate) fn sample_point<C: Ciphersuite>(rng: &mut (impl RngCore + CryptoRng)) -> C::G {
pub(crate) fn sample_point<C: GroupIo>(rng: &mut (impl RngCore + CryptoRng)) -> C::G {
let mut repr = <C::G as GroupEncoding>::Repr::default();
loop {
rng.fill_bytes(repr.as_mut());

View File

@@ -4,7 +4,7 @@ use zeroize::Zeroizing;
use rand_core::OsRng;
use dalek_ff_group::Ristretto;
use ciphersuite::{group::ff::Field, Ciphersuite};
use ciphersuite::WrappedGroup;
use dkg_recovery::recover_key;
use crate::*;
@@ -17,21 +17,21 @@ pub fn test_musig() {
let mut keys = vec![];
let mut pub_keys = vec![];
for _ in 0 .. PARTICIPANTS {
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
pub_keys.push(<Ristretto as Ciphersuite>::generator() * *key);
let key = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut OsRng));
pub_keys.push(<Ristretto as WrappedGroup>::generator() * *key);
keys.push(key);
}
const CONTEXT: [u8; 32] = *b"MuSig Test ";
// Empty signing set
musig::<Ristretto>(CONTEXT, Zeroizing::new(<Ristretto as Ciphersuite>::F::ZERO), &[])
musig::<Ristretto>(CONTEXT, Zeroizing::new(<Ristretto as WrappedGroup>::F::ZERO), &[])
.unwrap_err();
// Signing set we're not part of
musig::<Ristretto>(
CONTEXT,
Zeroizing::new(<Ristretto as Ciphersuite>::F::ZERO),
&[<Ristretto as Ciphersuite>::generator()],
Zeroizing::new(<Ristretto as WrappedGroup>::F::ZERO),
&[<Ristretto as WrappedGroup>::generator()],
)
.unwrap_err();
@@ -48,7 +48,7 @@ pub fn test_musig() {
verification_shares.insert(
these_keys.params().i(),
<Ristretto as Ciphersuite>::generator() * **these_keys.original_secret_share(),
<Ristretto as WrappedGroup>::generator() * **these_keys.original_secret_share(),
);
assert_eq!(these_keys.group_key(), group_key);
@@ -63,7 +63,7 @@ pub fn test_musig() {
}
assert_eq!(
<Ristretto as Ciphersuite>::generator() *
<Ristretto as WrappedGroup>::generator() *
*recover_key(&created_keys.values().cloned().collect::<Vec<_>>()).unwrap(),
group_key
);

View File

@@ -8,7 +8,7 @@ use alloc::vec::Vec;
use zeroize::Zeroizing;
use ciphersuite::Ciphersuite;
use ciphersuite::{GroupIo, Id};
pub use dkg::*;
@@ -34,7 +34,7 @@ pub enum RecoveryError {
}
/// Recover a shared secret from a collection of `dkg::ThresholdKeys`.
pub fn recover_key<C: Ciphersuite>(
pub fn recover_key<C: GroupIo + Id>(
keys: &[ThresholdKeys<C>],
) -> Result<Zeroizing<C::F>, RecoveryError> {
let included = keys.iter().map(|keys| keys.params().i()).collect::<Vec<_>>();

View File

@@ -17,7 +17,7 @@ use ciphersuite::{
ff::{Field, PrimeField},
GroupEncoding,
},
Ciphersuite,
GroupIo, Id,
};
/// The ID of a participant, defined as a non-zero u16.
@@ -268,7 +268,7 @@ impl<F: Zeroize + PrimeField> Interpolation<F> {
/// heap-allocated pointer to minimize copies on the stack (`ThresholdKeys`, the publicly exposed
/// type).
#[derive(Clone, PartialEq, Eq)]
struct ThresholdCore<C: Ciphersuite> {
struct ThresholdCore<C: GroupIo + Id> {
params: ThresholdParams,
group_key: C::G,
verification_shares: HashMap<Participant, C::G>,
@@ -276,7 +276,7 @@ struct ThresholdCore<C: Ciphersuite> {
secret_share: Zeroizing<C::F>,
}
impl<C: Ciphersuite> fmt::Debug for ThresholdCore<C> {
impl<C: GroupIo + Id> fmt::Debug for ThresholdCore<C> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt
.debug_struct("ThresholdCore")
@@ -288,7 +288,7 @@ impl<C: Ciphersuite> fmt::Debug for ThresholdCore<C> {
}
}
impl<C: Ciphersuite> Zeroize for ThresholdCore<C> {
impl<C: GroupIo + Id> Zeroize for ThresholdCore<C> {
fn zeroize(&mut self) {
self.params.zeroize();
self.group_key.zeroize();
@@ -302,7 +302,7 @@ impl<C: Ciphersuite> Zeroize for ThresholdCore<C> {
/// Threshold keys usable for signing.
#[derive(Clone, Debug, Zeroize)]
pub struct ThresholdKeys<C: Ciphersuite> {
pub struct ThresholdKeys<C: GroupIo + Id> {
// Core keys.
#[zeroize(skip)]
core: Arc<Zeroizing<ThresholdCore<C>>>,
@@ -315,7 +315,7 @@ pub struct ThresholdKeys<C: Ciphersuite> {
/// View of keys, interpolated and with the expected linear combination taken for usage.
#[derive(Clone)]
pub struct ThresholdView<C: Ciphersuite> {
pub struct ThresholdView<C: GroupIo + Id> {
interpolation: Interpolation<C::F>,
scalar: C::F,
offset: C::F,
@@ -326,7 +326,7 @@ pub struct ThresholdView<C: Ciphersuite> {
verification_shares: HashMap<Participant, C::G>,
}
impl<C: Ciphersuite> fmt::Debug for ThresholdView<C> {
impl<C: GroupIo + Id> fmt::Debug for ThresholdView<C> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt
.debug_struct("ThresholdView")
@@ -341,7 +341,7 @@ impl<C: Ciphersuite> fmt::Debug for ThresholdView<C> {
}
}
impl<C: Ciphersuite> Zeroize for ThresholdView<C> {
impl<C: GroupIo + Id> Zeroize for ThresholdView<C> {
fn zeroize(&mut self) {
self.scalar.zeroize();
self.offset.zeroize();
@@ -357,7 +357,7 @@ impl<C: Ciphersuite> Zeroize for ThresholdView<C> {
}
}
impl<C: Ciphersuite> ThresholdKeys<C> {
impl<C: GroupIo + Id> ThresholdKeys<C> {
/// Create a new set of ThresholdKeys.
pub fn new(
params: ThresholdParams,
@@ -632,7 +632,7 @@ impl<C: Ciphersuite> ThresholdKeys<C> {
let mut verification_shares = HashMap::new();
for l in (1 ..= n).map(Participant) {
verification_shares.insert(l, <C as Ciphersuite>::read_G(reader)?);
verification_shares.insert(l, C::read_G(reader)?);
}
ThresholdKeys::new(
@@ -645,7 +645,7 @@ impl<C: Ciphersuite> ThresholdKeys<C> {
}
}
impl<C: Ciphersuite> ThresholdView<C> {
impl<C: GroupIo + Id> ThresholdView<C> {
/// Return the scalar applied to this view.
pub fn scalar(&self) -> C::F {
self.scalar

View File

@@ -19,7 +19,7 @@ workspace = true
[dependencies]
zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] }
sha3 = { version = "0.11.0-rc.0", default-features = false }
sha3 = { version = "0.11.0-rc.2", default-features = false }
crypto-bigint = { version = "0.6", default-features = false, features = ["zeroize"] }
prime-field = { path = "../prime-field", default-features = false }

View File

@@ -1,4 +1,4 @@
use zeroize::Zeroize;
use prime_field::subtle::CtOption;
use sha3::{
digest::{
@@ -8,9 +8,9 @@ use sha3::{
Shake256,
};
use ciphersuite::{group::Group, Ciphersuite};
use ciphersuite::{group::GroupEncoding, Id, WithPreferredHash, GroupCanonicalEncoding};
use crate::{Scalar, Point};
use crate::Point;
/// Shake256, fixed to a 114-byte output, as used by Ed448.
#[derive(Clone, Default)]
@@ -49,21 +49,14 @@ impl FixedOutput for Shake256_114 {
}
impl HashMarker for Shake256_114 {}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Ed448;
impl Ciphersuite for Ed448 {
type F = Scalar;
type G = Point;
impl Id for Point {
const ID: &[u8] = b"ed448";
}
impl WithPreferredHash for Point {
type H = Shake256_114;
const ID: &'static [u8] = b"ed448";
fn generator() -> Self::G {
Point::generator()
}
impl GroupCanonicalEncoding for Point {
fn from_canonical_bytes(bytes: &<Self::G as GroupEncoding>::Repr) -> CtOption<Self::G> {
Self::G::from_bytes(bytes)
}
}
#[test]
fn test_ed448() {
ff_group_tests::group::test_prime_group_bits::<_, Point>(&mut rand_core::OsRng);
}

View File

@@ -29,7 +29,6 @@ mod point;
pub use point::Point;
mod ciphersuite;
pub use crate::ciphersuite::Ed448;
pub(crate) fn u8_from_bool(bit_ref: &mut bool) -> u8 {
use core::hint::black_box;

View File

@@ -25,12 +25,11 @@ typenum = { version = "1", default-features = false }
prime-field = { path = "../prime-field", default-features = false }
short-weierstrass = { path = "../short-weierstrass", default-features = false }
curve25519-dalek = { version = "4", default-features = false, features = ["legacy_compatibility"] }
dalek-ff-group = { path = "../dalek-ff-group", version = "0.4", default-features = false }
blake2 = { version = "0.11.0-rc.0", default-features = false }
blake2 = { version = "0.11.0-rc.2", default-features = false }
ciphersuite = { path = "../ciphersuite", version = "0.4", default-features = false }
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true }
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", default-features = false, optional = true }
[dev-dependencies]
hex = "0.4"

View File

@@ -5,17 +5,14 @@
#[cfg(feature = "alloc")]
#[allow(unused_imports)]
use std_shims::prelude::*;
#[cfg(feature = "alloc")]
use std_shims::io::{self, Read};
use prime_field::{subtle::Choice, zeroize::Zeroize};
use ciphersuite::group::{
ff::{Field, PrimeField},
Group,
use prime_field::{
subtle::{Choice, CtOption},
zeroize::Zeroize,
};
use ciphersuite::group::{ff::PrimeField, Group, GroupEncoding};
use curve25519_dalek::Scalar as DalekScalar;
pub use dalek_ff_group::Scalar as FieldElement;
pub use curve25519_dalek::Scalar as FieldElement;
use short_weierstrass::{ShortWeierstrass, Affine, Projective};
@@ -32,18 +29,18 @@ pub struct Embedwards25519;
#[allow(deprecated)] // No other way to construct arbitrary `FieldElement` at compile-time :/
impl ShortWeierstrass for Embedwards25519 {
type FieldElement = FieldElement;
const A: FieldElement = FieldElement(DalekScalar::from_bits(hex_literal::hex!(
const A: FieldElement = FieldElement::from_bits(hex_literal::hex!(
"ead3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010"
)));
const B: FieldElement = FieldElement(DalekScalar::from_bits(hex_literal::hex!(
));
const B: FieldElement = FieldElement::from_bits(hex_literal::hex!(
"5f07603a853f20370b682036210d463e64903a23ea669d07ca26cfc13f594209"
)));
));
const PRIME_ORDER: bool = true;
const GENERATOR: Affine<Self> = Affine::from_xy_unchecked(
FieldElement::ONE,
FieldElement(DalekScalar::from_bits(hex_literal::hex!(
FieldElement::from_bits(hex_literal::hex!(
"2e4118080a484a3dfbafe2199a0e36b7193581d676c0dadfa376b0265616020c"
))),
)),
);
type Scalar = Scalar;
@@ -80,30 +77,23 @@ impl ShortWeierstrass for Embedwards25519 {
pub type Point = Projective<Embedwards25519>;
impl ciphersuite::Ciphersuite for Embedwards25519 {
impl ciphersuite::WrappedGroup for Embedwards25519 {
type F = Scalar;
type G = Point;
type H = blake2::Blake2b512;
const ID: &'static [u8] = b"embedwards25519";
fn generator() -> Self::G {
Point::generator()
<Point as Group>::generator()
}
// We override the provided impl, which compares against the reserialization, because
// we already require canonicity
#[cfg(feature = "alloc")]
#[allow(non_snake_case)]
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
use ciphersuite::group::GroupEncoding;
let mut encoding = <Self::G as GroupEncoding>::Repr::default();
reader.read_exact(encoding.as_mut())?;
let point = Option::<Self::G>::from(Self::G::from_bytes(&encoding))
.ok_or_else(|| io::Error::other("invalid point"))?;
Ok(point)
}
impl ciphersuite::Id for Embedwards25519 {
const ID: &[u8] = b"embedwards25519";
}
impl ciphersuite::WithPreferredHash for Embedwards25519 {
type H = blake2::Blake2b512;
}
impl ciphersuite::GroupCanonicalEncoding for Embedwards25519 {
fn from_canonical_bytes(bytes: &<Self::G as GroupEncoding>::Repr) -> CtOption<Self::G> {
Self::G::from_bytes(bytes)
}
}
@@ -119,9 +109,8 @@ fn test_curve() {
#[test]
fn generator() {
use ciphersuite::group::{Group, GroupEncoding};
assert_eq!(
Point::generator(),
<Point as Group>::generator(),
Point::from_bytes(&hex_literal::hex!(
"0100000000000000000000000000000000000000000000000000000000000000"
))
@@ -139,6 +128,5 @@ fn zero_x_is_off_curve() {
// Checks random won't infinitely loop
#[test]
fn random() {
use ciphersuite::group::Group;
Point::random(&mut rand_core::OsRng);
}

View File

@@ -1,6 +1,6 @@
[package]
name = "modular-frost"
version = "0.10.1"
version = "0.11.0"
description = "Modular implementation of FROST over ff/group"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/frost"
@@ -29,7 +29,7 @@ hex = { version = "0.4", default-features = false, features = ["std"], optional
transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false, features = ["std", "recommended"] }
dalek-ff-group = { path = "../dalek-ff-group", version = "0.4", default-features = false, features = ["std"], optional = true }
dalek-ff-group = { path = "../dalek-ff-group", version = "0.5", default-features = false, features = ["std"], optional = true }
minimal-ed448 = { path = "../ed448", version = "0.4", default-features = false, features = ["std"], optional = true }
ciphersuite = { path = "../ciphersuite", version = "^0.4.1", default-features = false, features = ["std"] }

View File

@@ -1,24 +1,24 @@
use ciphersuite::{digest::Digest, FromUniformBytes, Ciphersuite};
use subtle::CtOption;
use zeroize::Zeroize;
use ciphersuite::{
digest::Digest, group::GroupEncoding, FromUniformBytes, WrappedGroup, WithPreferredHash,
};
use dalek_ff_group::Scalar;
use crate::{curve::Curve, algorithm::Hram};
macro_rules! dalek_curve {
(
$feature: literal,
$Curve: ident,
$Hram: ident,
$CONTEXT: literal,
$chal: literal
) => {
pub use dalek_ff_group::$Curve;
impl Curve for $Curve {
const CONTEXT: &'static [u8] = $CONTEXT;
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
let mut digest = <Self as Ciphersuite>::H::new();
let mut digest = <Self as WithPreferredHash>::H::new();
digest.update(Self::CONTEXT);
digest.update(dst);
digest.update(msg);
@@ -31,8 +31,12 @@ macro_rules! dalek_curve {
pub struct $Hram;
impl Hram<$Curve> for $Hram {
#[allow(non_snake_case)]
fn hram(R: &<$Curve as Ciphersuite>::G, A: &<$Curve as Ciphersuite>::G, m: &[u8]) -> Scalar {
let mut hash = <$Curve as Ciphersuite>::H::new();
fn hram(
R: &<$Curve as WrappedGroup>::G,
A: &<$Curve as WrappedGroup>::G,
m: &[u8],
) -> Scalar {
let mut hash = <$Curve as WithPreferredHash>::H::new();
if $chal.len() != 0 {
hash.update($CONTEXT);
hash.update($chal);
@@ -46,8 +50,31 @@ macro_rules! dalek_curve {
};
}
#[cfg(feature = "ristretto")]
dalek_curve!("ristretto", Ristretto, IetfRistrettoHram, b"FROST-RISTRETTO255-SHA512-v1", b"chal");
/*
FROST defined Ristretto as using SHA2-512, while Blake2b512 is considered "preferred" by
`dalek-ff-group`. We define our own ciphersuite for it accordingly.
*/
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Ristretto;
impl WrappedGroup for Ristretto {
type F = Scalar;
type G = dalek_ff_group::RistrettoPoint;
fn generator() -> Self::G {
dalek_ff_group::Ristretto::generator()
}
}
impl ciphersuite::Id for Ristretto {
const ID: &[u8] = b"FROST-RISTRETTO255";
}
impl WithPreferredHash for Ristretto {
type H = <Ed25519 as WithPreferredHash>::H;
}
impl ciphersuite::GroupCanonicalEncoding for Ristretto {
fn from_canonical_bytes(bytes: &<Self::G as GroupEncoding>::Repr) -> CtOption<Self::G> {
dalek_ff_group::Ristretto::from_canonical_bytes(bytes)
}
}
dalek_curve!(Ristretto, IetfRistrettoHram, b"FROST-RISTRETTO255-SHA512-v1", b"chal");
#[cfg(feature = "ed25519")]
dalek_curve!("ed25519", Ed25519, IetfEd25519Hram, b"FROST-ED25519-SHA512-v1", b"");
pub use dalek_ff_group::Ed25519;
dalek_curve!(Ed25519, IetfEd25519Hram, b"FROST-ED25519-SHA512-v1", b"");

View File

@@ -1,6 +1,6 @@
pub use ciphersuite::{digest::Digest, group::GroupEncoding, FromUniformBytes, Ciphersuite};
use minimal_ed448::{Scalar, Point};
pub use minimal_ed448::Ed448;
pub use ciphersuite::{digest::Digest, group::GroupEncoding, FromUniformBytes, WithPreferredHash};
use minimal_ed448::Scalar;
pub use minimal_ed448::Point as Ed448;
use crate::{curve::Curve, algorithm::Hram};
@@ -9,7 +9,7 @@ const CONTEXT: &[u8] = b"FROST-ED448-SHAKE256-v1";
impl Curve for Ed448 {
const CONTEXT: &'static [u8] = CONTEXT;
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
let mut digest = <Self as Ciphersuite>::H::new();
let mut digest = <Self as WithPreferredHash>::H::new();
digest.update(Self::CONTEXT);
digest.update(dst);
digest.update(msg);
@@ -22,8 +22,8 @@ impl Curve for Ed448 {
pub(crate) struct Ietf8032Ed448Hram;
impl Ietf8032Ed448Hram {
#[allow(non_snake_case)]
pub(crate) fn hram(context: &[u8], R: &Point, A: &Point, m: &[u8]) -> Scalar {
let mut digest = <Ed448 as Ciphersuite>::H::new();
pub(crate) fn hram(context: &[u8], R: &Ed448, A: &Ed448, m: &[u8]) -> Scalar {
let mut digest = <Ed448 as WithPreferredHash>::H::new();
digest.update(b"SigEd448");
digest.update([0, u8::try_from(context.len()).unwrap()]);
digest.update(context);
@@ -39,7 +39,7 @@ impl Ietf8032Ed448Hram {
pub struct IetfEd448Hram;
impl Hram<Ed448> for IetfEd448Hram {
#[allow(non_snake_case)]
fn hram(R: &Point, A: &Point, m: &[u8]) -> Scalar {
fn hram(R: &Ed448, A: &Ed448, m: &[u8]) -> Scalar {
Ietf8032Ed448Hram::hram(&[], R, A, m)
}
}

View File

@@ -7,7 +7,7 @@ use ciphersuite::{
ff::{Field, PrimeField},
GroupEncoding,
},
Ciphersuite,
WrappedGroup,
};
use elliptic_curve::{
@@ -20,7 +20,7 @@ use elliptic_curve::{
use crate::{curve::Curve, algorithm::Hram};
#[allow(non_snake_case)]
fn hash_to_F<C: Ciphersuite<F: PrimeField<Repr = GenericArray<u8, U32>>>>(
fn hash_to_F<C: WrappedGroup<F: PrimeField<Repr = GenericArray<u8, U32>>>>(
dst: &[u8],
msg: &[u8],
) -> C::F {
@@ -112,10 +112,10 @@ macro_rules! kp_curve {
impl Hram<$Curve> for $Hram {
#[allow(non_snake_case)]
fn hram(
R: &<$Curve as Ciphersuite>::G,
A: &<$Curve as Ciphersuite>::G,
R: &<$Curve as WrappedGroup>::G,
A: &<$Curve as WrappedGroup>::G,
m: &[u8],
) -> <$Curve as Ciphersuite>::F {
) -> <$Curve as WrappedGroup>::F {
<$Curve as Curve>::hash_to_F(
b"chal",
&[R.to_bytes().as_ref(), A.to_bytes().as_ref(), m].concat(),
@@ -132,7 +132,7 @@ kp_curve!("p256", P256, IetfP256Hram, b"FROST-P256-SHA256-v1");
kp_curve!("secp256k1", Secp256k1, IetfSecp256k1Hram, b"FROST-secp256k1-SHA256-v1");
#[cfg(test)]
fn test_oversize_dst<C: Ciphersuite<F: PrimeField<Repr = GenericArray<u8, U32>>>>() {
fn test_oversize_dst<C: WrappedGroup<F: PrimeField<Repr = GenericArray<u8, U32>>>>() {
use sha2::Digest;
// The draft specifies DSTs >255 bytes should be hashed into a 32-byte DST

View File

@@ -6,21 +6,16 @@ use rand_core::{RngCore, CryptoRng};
use zeroize::{Zeroize, Zeroizing};
use subtle::ConstantTimeEq;
pub use ciphersuite::{
digest::Digest,
group::{
ff::{Field, PrimeField},
Group,
},
Ciphersuite,
use ciphersuite::group::{
ff::{Field, PrimeField},
Group,
};
pub use ciphersuite::{digest::Digest, WrappedGroup, GroupIo, Ciphersuite};
#[cfg(any(feature = "ristretto", feature = "ed25519"))]
mod dalek;
#[cfg(feature = "ristretto")]
pub use dalek::{Ristretto, IetfRistrettoHram};
#[cfg(feature = "ed25519")]
pub use dalek::{Ed25519, IetfEd25519Hram};
#[cfg(any(feature = "ristretto", feature = "ed25519"))]
pub use dalek::*;
#[cfg(any(feature = "secp256k1", feature = "p256"))]
mod kp256;
@@ -38,11 +33,11 @@ pub(crate) use ed448::Ietf8032Ed448Hram;
/// FROST Ciphersuite.
///
/// This exclude the signing algorithm specific H2, making this solely the curve, its associated
/// This excludes the signing algorithm specific H2, making this solely the curve, its associated
/// hash function, and the functions derived from it.
pub trait Curve: Ciphersuite {
pub trait Curve: GroupIo + Ciphersuite {
/// Context string for this curve.
const CONTEXT: &'static [u8];
const CONTEXT: &[u8];
/// Hash the given dst and data to a byte vector. Used to instantiate H4 and H5.
fn hash(dst: &[u8], data: &[u8]) -> impl AsRef<[u8]> {
@@ -121,7 +116,7 @@ pub trait Curve: Ciphersuite {
/// Read a point from a reader, rejecting identity.
#[allow(non_snake_case)]
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
let res = <Self as Ciphersuite>::read_G(reader)?;
let res = <Self as GroupIo>::read_G(reader)?;
if res.is_identity().into() {
Err(io::Error::other("identity point"))?;
}

View File

@@ -11,10 +11,9 @@ use zeroize::{Zeroize, Zeroizing};
use transcript::Transcript;
use ciphersuite::group::{
ff::{Field, PrimeField},
GroupEncoding,
};
use ciphersuite::group::{ff::PrimeField, GroupEncoding};
#[cfg(any(test, feature = "tests"))]
use ciphersuite::group::ff::Field;
use multiexp::BatchVerifier;
use crate::{

View File

@@ -1,6 +1,6 @@
use rand_core::OsRng;
use ciphersuite::Ciphersuite;
use ciphersuite::GroupIo;
use schnorr::SchnorrSignature;

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use rand_core::{RngCore, CryptoRng};
use ciphersuite::Ciphersuite;
use ciphersuite::{GroupIo, Id};
pub use dkg_recovery::recover_key;
use crate::{
@@ -28,7 +28,7 @@ pub const PARTICIPANTS: u16 = 5;
pub const THRESHOLD: u16 = ((PARTICIPANTS * 2) / 3) + 1;
/// Create a key, for testing purposes.
pub fn key_gen<R: RngCore + CryptoRng, C: Ciphersuite>(
pub fn key_gen<R: RngCore + CryptoRng, C: GroupIo + Id>(
rng: &mut R,
) -> HashMap<Participant, ThresholdKeys<C>> {
let res = dkg_dealer::key_gen::<R, C>(rng, THRESHOLD, PARTICIPANTS).unwrap();

View File

@@ -22,6 +22,7 @@ std-shims = { path = "../../common/std-shims", version = "^0.1.1", default-featu
rand_core = { version = "0.6", default-features = false }
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
digest = { version = "0.11.0-rc.1", default-features = false, features = ["block-api"] }
transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false, optional = true }

View File

@@ -5,74 +5,34 @@ use std_shims::{
use zeroize::Zeroize;
use transcript::{Transcript, SecureDigest, DigestTranscript};
use transcript::{Transcript, DigestTranscript};
use ciphersuite::{
group::{
ff::{Field, PrimeField},
Group, GroupEncoding,
},
Ciphersuite,
FromUniformBytes, GroupIo, WithPreferredHash,
};
use multiexp::multiexp_vartime;
use crate::SchnorrSignature;
// Returns a unbiased scalar weight to use on a signature in order to prevent malleability
fn weight<D: Send + Clone + SecureDigest, F: PrimeField>(digest: &mut DigestTranscript<D>) -> F {
let mut bytes = digest.challenge(b"aggregation_weight");
debug_assert_eq!(bytes.len() % 8, 0);
// This should be guaranteed thanks to SecureDigest
debug_assert!(bytes.len() >= 32);
let mut res = F::ZERO;
let mut i = 0;
// Derive a scalar from enough bits of entropy that bias is < 2^128
// This can't be const due to its usage of a generic
// Also due to the usize::try_from, yet that could be replaced with an `as`
#[allow(non_snake_case)]
let BYTES: usize = usize::try_from((F::NUM_BITS + 128).div_ceil(8)).unwrap();
let mut remaining = BYTES;
// We load bits in as u64s
const WORD_LEN_IN_BITS: usize = 64;
const WORD_LEN_IN_BYTES: usize = WORD_LEN_IN_BITS / 8;
let mut first = true;
while i < remaining {
// Shift over the already loaded bits
if !first {
for _ in 0 .. WORD_LEN_IN_BITS {
res += res;
}
}
first = false;
// Add the next 64 bits
res += F::from(u64::from_be_bytes(bytes[i .. (i + WORD_LEN_IN_BYTES)].try_into().unwrap()));
i += WORD_LEN_IN_BYTES;
// If we've exhausted this challenge, get another
if i == bytes.len() {
bytes = digest.challenge(b"aggregation_weight_continued");
remaining -= i;
i = 0;
}
}
res
fn weight<C: WithPreferredHash>(digest: &mut DigestTranscript<C::H>) -> C::F {
let bytes = digest.challenge(b"aggregation_weight");
C::F::from_uniform_bytes(&bytes.into())
}
/// Aggregate Schnorr signature as defined in <https://eprint.iacr.org/2021/350>.
#[allow(non_snake_case)]
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct SchnorrAggregate<C: Ciphersuite> {
pub struct SchnorrAggregate<C: GroupIo + WithPreferredHash> {
Rs: Vec<C::G>,
s: C::F,
}
impl<C: Ciphersuite> SchnorrAggregate<C> {
impl<C: GroupIo + WithPreferredHash> SchnorrAggregate<C> {
/// Read a SchnorrAggregate from something implementing Read.
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut len = [0; 4];
@@ -137,7 +97,7 @@ impl<C: Ciphersuite> SchnorrAggregate<C> {
let mut pairs = Vec::with_capacity((2 * keys_and_challenges.len()) + 1);
for (i, (key, challenge)) in keys_and_challenges.iter().enumerate() {
let z = weight(&mut digest);
let z = weight::<C>(&mut digest);
pairs.push((z, self.Rs[i]));
pairs.push((z * challenge, *key));
}
@@ -148,13 +108,22 @@ impl<C: Ciphersuite> SchnorrAggregate<C> {
/// A signature aggregator capable of consuming signatures in order to produce an aggregate.
#[allow(non_snake_case)]
#[derive(Clone, Debug, Zeroize)]
pub struct SchnorrAggregator<C: Ciphersuite> {
#[derive(Clone, Debug)]
pub struct SchnorrAggregator<C: GroupIo + WithPreferredHash> {
digest: DigestTranscript<C::H>,
sigs: Vec<SchnorrSignature<C>>,
}
impl<C: GroupIo + WithPreferredHash> Zeroize for SchnorrAggregator<C>
where
C::H: digest::block_api::BlockSizeUser,
{
fn zeroize(&mut self) {
self.digest.zeroize();
self.sigs.zeroize();
}
}
impl<C: Ciphersuite> SchnorrAggregator<C> {
impl<C: GroupIo + WithPreferredHash> SchnorrAggregator<C> {
/// Create a new aggregator.
///
/// The DST used here must prevent a collision with whatever hash function produced the
@@ -180,7 +149,7 @@ impl<C: Ciphersuite> SchnorrAggregator<C> {
let mut aggregate = SchnorrAggregate { Rs: Vec::with_capacity(self.sigs.len()), s: C::F::ZERO };
for i in 0 .. self.sigs.len() {
aggregate.Rs.push(self.sigs[i].R);
aggregate.s += self.sigs[i].s * weight::<_, C::F>(&mut self.digest);
aggregate.s += self.sigs[i].s * weight::<C>(&mut self.digest);
}
Some(aggregate)
}

View File

@@ -20,7 +20,7 @@ use ciphersuite::{
ff::{Field, PrimeField},
Group, GroupEncoding,
},
Ciphersuite,
GroupIo,
};
use multiexp::{multiexp_vartime, BatchVerifier};
@@ -33,20 +33,20 @@ mod tests;
/// A Schnorr signature of the form (R, s) where s = r + cx.
///
/// These are intended to be strict. It is generic over Ciphersuite which is for PrimeGroups,
/// These are intended to be strict. It is generic over `GroupIo` which is for `PrimeGroup`s,
/// and mandates canonical encodings in its read function.
///
/// RFC 8032 has an alternative verification formula, 8R = 8s - 8cX, which is intended to handle
/// torsioned nonces/public keys. Due to this library's strict requirements, such signatures will
/// not be verifiable with this library.
/// RFC 8032 has an alternative verification formula for Ed25519, `8R = 8s - 8cX`, which is
/// intended to handle torsioned nonces/public keys. Due to this library's strict requirements,
/// such signatures will not be verifiable with this library.
#[allow(non_snake_case)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct SchnorrSignature<C: Ciphersuite> {
pub struct SchnorrSignature<C: GroupIo> {
pub R: C::G,
pub s: C::F,
}
impl<C: Ciphersuite> SchnorrSignature<C> {
impl<C: GroupIo> SchnorrSignature<C> {
/// Read a SchnorrSignature from something implementing Read.
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
Ok(SchnorrSignature { R: C::read_G(reader)?, s: C::read_F(reader)? })

View File

@@ -6,7 +6,7 @@ use rand_core::OsRng;
use dalek_ff_group::Ed25519;
use ciphersuite::{
group::{ff::Field, Group},
Ciphersuite,
GroupIo, WithPreferredHash,
};
use multiexp::BatchVerifier;
@@ -16,10 +16,10 @@ use crate::aggregate::{SchnorrAggregator, SchnorrAggregate};
mod rfc8032;
pub(crate) fn sign<C: Ciphersuite>() {
let private_key = Zeroizing::new(C::random_nonzero_F(&mut OsRng));
let nonce = Zeroizing::new(C::random_nonzero_F(&mut OsRng));
let challenge = C::random_nonzero_F(&mut OsRng); // Doesn't bother to craft an HRAm
pub(crate) fn sign<C: GroupIo>() {
let private_key = Zeroizing::new(C::F::random(&mut OsRng));
let nonce = Zeroizing::new(C::F::random(&mut OsRng));
let challenge = C::F::random(&mut OsRng); // Doesn't bother to craft an HRAm
assert!(SchnorrSignature::<C>::sign(&private_key, nonce, challenge)
.verify(C::generator() * private_key.deref(), challenge));
}
@@ -27,22 +27,22 @@ pub(crate) fn sign<C: Ciphersuite>() {
// The above sign function verifies signing works
// This verifies invalid signatures don't pass, using zero signatures, which should effectively be
// random
pub(crate) fn verify<C: Ciphersuite>() {
pub(crate) fn verify<C: GroupIo>() {
assert!(!SchnorrSignature::<C> { R: C::G::identity(), s: C::F::ZERO }
.verify(C::generator() * C::random_nonzero_F(&mut OsRng), C::random_nonzero_F(&mut OsRng)));
.verify(C::generator() * C::F::random(&mut OsRng), C::F::random(&mut OsRng)));
}
pub(crate) fn batch_verify<C: Ciphersuite>() {
pub(crate) fn batch_verify<C: GroupIo>() {
// Create 5 signatures
let mut keys = vec![];
let mut challenges = vec![];
let mut sigs = vec![];
for i in 0 .. 5 {
keys.push(Zeroizing::new(C::random_nonzero_F(&mut OsRng)));
challenges.push(C::random_nonzero_F(&mut OsRng));
keys.push(Zeroizing::new(C::F::random(&mut OsRng)));
challenges.push(C::F::random(&mut OsRng));
sigs.push(SchnorrSignature::<C>::sign(
&keys[i],
Zeroizing::new(C::random_nonzero_F(&mut OsRng)),
Zeroizing::new(C::F::random(&mut OsRng)),
challenges[i],
));
}
@@ -78,7 +78,7 @@ pub(crate) fn batch_verify<C: Ciphersuite>() {
}
#[cfg(feature = "aggregate")]
pub(crate) fn aggregate<C: Ciphersuite>() {
pub(crate) fn aggregate<C: GroupIo + WithPreferredHash>() {
const DST: &[u8] = b"Schnorr Aggregator Test";
// Create 5 signatures
@@ -86,14 +86,14 @@ pub(crate) fn aggregate<C: Ciphersuite>() {
let mut challenges = vec![];
let mut aggregator = SchnorrAggregator::<C>::new(DST);
for i in 0 .. 5 {
keys.push(Zeroizing::new(C::random_nonzero_F(&mut OsRng)));
keys.push(Zeroizing::new(C::F::random(&mut OsRng)));
// In practice, this MUST be a secure challenge binding to the nonce, key, and any message
challenges.push(C::random_nonzero_F(&mut OsRng));
challenges.push(C::F::random(&mut OsRng));
aggregator.aggregate(
challenges[i],
SchnorrSignature::<C>::sign(
&keys[i],
Zeroizing::new(C::random_nonzero_F(&mut OsRng)),
Zeroizing::new(C::F::random(&mut OsRng)),
challenges[i],
),
);

View File

@@ -6,7 +6,7 @@
use sha2::{Digest, Sha512};
use dalek_ff_group::{Scalar, Ed25519};
use ciphersuite::{group::GroupEncoding, Ciphersuite};
use ciphersuite::{group::GroupEncoding, GroupIo};
use crate::SchnorrSignature;

View File

@@ -24,10 +24,9 @@ transcript = { package = "flexible-transcript", path = "../transcript", version
group = "0.13"
dalek-ff-group = { path = "../dalek-ff-group" }
ciphersuite = { path = "../ciphersuite", version = "^0.4.1", features = ["std"] }
schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "^0.5.1" }
frost = { path = "../frost", package = "modular-frost", version = "^0.10.0", features = ["ristretto"] }
frost = { path = "../frost", package = "modular-frost", version = "0.11.0", features = ["ristretto"] }
schnorrkel = { version = "0.11" }

View File

@@ -9,16 +9,16 @@ use zeroize::Zeroizing;
use transcript::{Transcript, MerlinTranscript};
use dalek_ff_group::Ristretto;
use ciphersuite::{
group::{ff::PrimeField, GroupEncoding},
Ciphersuite,
WrappedGroup,
};
use schnorr::SchnorrSignature;
use ::frost::{
Participant, ThresholdKeys, ThresholdView, FrostError,
algorithm::{Hram, Algorithm, Schnorr},
curve::Ristretto,
};
/// The [modular-frost](https://docs.rs/modular-frost) library.
@@ -28,8 +28,8 @@ pub mod frost {
use schnorrkel::{PublicKey, Signature, context::SigningTranscript, signing_context};
type RistrettoPoint = <Ristretto as Ciphersuite>::G;
type Scalar = <Ristretto as Ciphersuite>::F;
type RistrettoPoint = <Ristretto as WrappedGroup>::G;
type Scalar = <Ristretto as WrappedGroup>::F;
#[cfg(test)]
mod tests;
@@ -83,7 +83,7 @@ impl Algorithm<Ristretto> for Schnorrkel {
self.schnorr.transcript()
}
fn nonces(&self) -> Vec<Vec<<Ristretto as Ciphersuite>::G>> {
fn nonces(&self) -> Vec<Vec<<Ristretto as WrappedGroup>::G>> {
self.schnorr.nonces()
}

View File

@@ -18,13 +18,13 @@ hex-literal = { version = "0.4", default-features = false }
std-shims = { version = "0.1", path = "../../common/std-shims", default-features = false, optional = true }
k256 = { version = "0.13", default-features = false, features = ["arithmetic"] }
sha2 = { version = "0.11.0-rc.0", default-features = false }
k256 = { version = "0.13", default-features = false, features = ["arithmetic", "expose-field"] }
prime-field = { path = "../prime-field", default-features = false }
short-weierstrass = { path = "../short-weierstrass", default-features = false }
sha2 = { version = "0.11.0-rc.0", default-features = false }
ciphersuite = { path = "../ciphersuite", version = "0.4", default-features = false }
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true }
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", default-features = false, optional = true }
[dev-dependencies]
hex = "0.4"

View File

@@ -5,17 +5,12 @@
#[cfg(feature = "alloc")]
#[allow(unused_imports)]
use std_shims::prelude::*;
#[cfg(feature = "alloc")]
use std_shims::io::{self, Read};
use sha2::{
digest::array::{typenum::U33, Array},
Sha512,
};
use sha2::digest::array::{typenum::U33, Array};
use k256::elliptic_curve::{
subtle::{Choice, ConstantTimeEq, ConditionallySelectable},
subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable},
zeroize::Zeroize,
group::{ff::PrimeField, Group},
group::{ff::PrimeField, Group, GroupEncoding},
sec1::Tag,
};
@@ -109,32 +104,25 @@ impl ShortWeierstrass for Secq256k1 {
pub type Point = Projective<Secq256k1>;
impl ciphersuite::Ciphersuite for Secq256k1 {
impl ciphersuite::WrappedGroup for Secq256k1 {
type F = Scalar;
type G = Point;
type H = Sha512;
const ID: &'static [u8] = b"secq256k1";
fn generator() -> Self::G {
Point::generator()
<Point as Group>::generator()
}
// We override the provided impl, which compares against the reserialization, because
// we already require canonicity
#[cfg(feature = "alloc")]
#[allow(non_snake_case)]
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
use ciphersuite::group::GroupEncoding;
let mut encoding = <Self::G as GroupEncoding>::Repr::default();
reader.read_exact(encoding.as_mut())?;
let point = Option::<Self::G>::from(Self::G::from_bytes(&encoding))
.ok_or_else(|| io::Error::other("invalid point"))?;
Ok(point)
}
impl ciphersuite::Id for Secq256k1 {
const ID: &[u8] = b"secq256k1";
}
impl ciphersuite::GroupCanonicalEncoding for Secq256k1 {
fn from_canonical_bytes(bytes: &<Self::G as GroupEncoding>::Repr) -> CtOption<Self::G> {
Self::G::from_bytes(bytes)
}
}
impl ciphersuite::WithPreferredHash for Secq256k1 {
type H = sha2::Sha512;
}
#[cfg(feature = "alloc")]
impl generalized_bulletproofs_ec_gadgets::DiscreteLogParameter for Secq256k1 {
@@ -150,7 +138,7 @@ fn test_curve() {
fn generator() {
use ciphersuite::group::GroupEncoding;
assert_eq!(
Point::generator(),
<Point as Group>::generator(),
Point::from_bytes(&Array(hex_literal::hex!(
"020000000000000000000000000000000000000000000000000000000000000001"
)))

View File

@@ -21,7 +21,7 @@ rand_core = { version = "0.6", default-features = false }
ff = { version = "0.13", default-features = false, features = ["bits"] }
group = { version = "0.13", default-features = false }
ec-divisors = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true }
ec-divisors = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", default-features = false, optional = true }
[features]
alloc = ["zeroize/alloc", "rand_core/alloc", "ff/alloc", "group/alloc", "ec-divisors"]

View File

@@ -413,7 +413,6 @@ impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>> PrimeGroup for Projective<C> {
#[cfg(feature = "alloc")]
mod alloc {
use core::borrow::Borrow;
use ff::{PrimeField, PrimeFieldBits};
use crate::{ShortWeierstrass, Affine, Projective};
@@ -421,7 +420,8 @@ mod alloc {
type FieldElement = C::FieldElement;
type XyPoint = ec_divisors::Projective<Self>;
fn interpolator_for_scalar_mul() -> impl Borrow<ec_divisors::Interpolator<C::FieldElement>> {
type BorrowedInterpolator = ec_divisors::Interpolator<C::FieldElement>;
fn interpolator_for_scalar_mul() -> Self::BorrowedInterpolator {
ec_divisors::Interpolator::new((<C::Scalar as PrimeField>::NUM_BITS as usize).div_ceil(2) + 2)
}

View File

@@ -19,14 +19,14 @@ workspace = true
[dependencies]
zeroize = { version = "^1.5", default-features = false }
digest = { version = "0.11.0-rc.0", default-features = false, features = ["block-api"] }
digest = { version = "0.11.0-rc.1", default-features = false, features = ["block-api"] }
blake2 = { version = "0.11.0-rc.0", default-features = false, optional = true }
blake2 = { version = "0.11.0-rc.2", default-features = false, optional = true }
merlin = { version = "3", default-features = false, optional = true }
[dev-dependencies]
sha2 = { version = "0.11.0-rc.0", default-features = false }
blake2 = { version = "0.11.0-rc.0", default-features = false }
sha2 = { version = "0.11.0-rc.2", default-features = false }
blake2 = { version = "0.11.0-rc.2", default-features = false }
[features]
std = ["zeroize/std", "merlin?/std"]

View File

@@ -4,13 +4,7 @@
use zeroize::Zeroize;
use digest::{
typenum::{
consts::U32, marker_traits::NonZero, type_operators::IsGreaterOrEqual, operator_aliases::GrEq,
},
block_api::BlockSizeUser,
Digest, Output, HashMarker,
};
use digest::{block_api::BlockSizeUser, Digest, Output, HashMarker};
#[cfg(feature = "merlin")]
mod merlin;
@@ -75,24 +69,11 @@ impl DigestTranscriptMember {
}
}
/// A trait defining cryptographic Digests with at least a 256-bit output size, assuming at least a
/// 128-bit level of security accordingly.
pub trait SecureDigest: Digest + HashMarker {}
impl<D: Digest + HashMarker> SecureDigest for D
where
// This just lets us perform the comparison
D::OutputSize: IsGreaterOrEqual<U32>,
// Perform the comparison and make sure it's true (not zero), meaning D::OutputSize is >= U32
// This should be U32 as it's length in bytes, not bits
GrEq<D::OutputSize, U32>: NonZero,
{
}
/// A simple transcript format constructed around the specified hash algorithm.
#[derive(Clone, Debug)]
pub struct DigestTranscript<D: Send + Clone + SecureDigest>(D);
pub struct DigestTranscript<D: Send + Clone + Digest + HashMarker>(D);
impl<D: Send + Clone + SecureDigest> DigestTranscript<D> {
impl<D: Send + Clone + Digest + HashMarker> DigestTranscript<D> {
fn append(&mut self, kind: DigestTranscriptMember, value: &[u8]) {
self.0.update([kind.as_u8()]);
// Assumes messages don't exceed 16 exabytes
@@ -101,7 +82,7 @@ impl<D: Send + Clone + SecureDigest> DigestTranscript<D> {
}
}
impl<D: Send + Clone + SecureDigest> Transcript for DigestTranscript<D> {
impl<D: Send + Clone + Digest + HashMarker> Transcript for DigestTranscript<D> {
type Challenge = Output<D>;
fn new(name: &'static [u8]) -> Self {
@@ -140,7 +121,7 @@ impl<D: Send + Clone + SecureDigest> Transcript for DigestTranscript<D> {
// Digest doesn't implement Zeroize
// Implement Zeroize for DigestTranscript by writing twice the block size to the digest in an
// attempt to overwrite the internal hash state/any leftover bytes
impl<D: Send + Clone + SecureDigest> Zeroize for DigestTranscript<D>
impl<D: Send + Clone + Digest + HashMarker> Zeroize for DigestTranscript<D>
where
D: BlockSizeUser,
{
@@ -159,7 +140,7 @@ where
// These writes may be optimized out if they're never read
// Attempt to get them marked as read
fn mark_read<D: Send + Clone + SecureDigest>(transcript: &DigestTranscript<D>) {
fn mark_read<D: Send + Clone + Digest + HashMarker>(transcript: &DigestTranscript<D>) {
// Just get a challenge from the state
let mut challenge = core::hint::black_box(transcript.0.clone().finalize());
challenge.as_mut().zeroize();