Move embedwards25519 over to short-weierstrass

This commit is contained in:
Luke Parker
2025-08-28 21:56:28 -04:00
parent f2d399ba1e
commit da190759a9
17 changed files with 252 additions and 536 deletions

View File

@@ -7,12 +7,17 @@ use std_shims::prelude::*;
#[cfg(any(feature = "alloc", feature = "std"))]
use std_shims::io::{self, Read};
use generic_array::typenum::{Sum, Diff, Quot, U, U1, U2};
use prime_field::{subtle::Choice, zeroize::Zeroize};
use ciphersuite::group::{
ff::{PrimeField, FromUniformBytes},
ff::{Field, PrimeField, FromUniformBytes},
Group,
};
use curve25519_dalek::Scalar as DalekScalar;
pub use dalek_ff_group::Scalar as FieldElement;
use short_weierstrass::{ShortWeierstrass, Affine, Projective};
prime_field::odd_prime_field!(
Scalar,
"0fffffffffffffffffffffffffffffffe53f4debb78ff96877063f0306eef96b",
@@ -20,33 +25,52 @@ prime_field::odd_prime_field!(
false
);
pub use dalek_ff_group::Scalar as FieldElement;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Embedwards25519;
mod point;
pub use point::Point;
#[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!(
"ead3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010"
)));
const B: FieldElement = FieldElement(DalekScalar::from_bits(hex_literal::hex!(
"5f07603a853f20370b682036210d463e64903a23ea669d07ca26cfc13f594209"
)));
const GENERATOR: Affine<Self> = Affine::from_xy_unchecked(
FieldElement::ONE,
FieldElement(DalekScalar::from_bits(hex_literal::hex!(
"2e4118080a484a3dfbafe2199a0e36b7193581d676c0dadfa376b0265616020c"
))),
);
type Scalar = Scalar;
pub(crate) fn u8_from_bool(bit_ref: &mut bool) -> u8 {
use core::hint::black_box;
use prime_field::zeroize::Zeroize;
type Repr = [u8; 32];
// Use an all-zero encoding for the identity as `0` isn't the `x` coordinate of an on-curve point
const IDENTITY: [u8; 32] = [0; 32];
fn compress(x: Self::FieldElement, odd_y: Choice) -> Self::Repr {
// The LE `x` coordinate, with if `y` is odd in the unused 256th bit
let mut res = [0; 32];
res.as_mut().copy_from_slice(x.to_repr().as_ref());
res[31] |= odd_y.unwrap_u8() << 7;
res
}
fn decode_compressed(bytes: &Self::Repr) -> (<Self::FieldElement as PrimeField>::Repr, Choice) {
// Extract and clear the sign bit
let mut bytes = *bytes;
let odd_y = Choice::from(bytes[31] >> 7);
bytes[31] &= u8::MAX >> 1;
let bit_ref = black_box(bit_ref);
// Copy from the point's representation to the field's
let mut repr = <Self::FieldElement as PrimeField>::Repr::default();
repr.as_mut().copy_from_slice(&bytes);
let mut bit = black_box(*bit_ref);
let res = black_box(u8::from(bit));
bit.zeroize();
debug_assert!((res | 1) == 1);
bit_ref.zeroize();
res
(repr, odd_y)
}
}
/// Ciphersuite for Embedwards25519.
///
/// hash_to_F is implemented with a naive concatenation of the dst and data, allowing transposition
/// between the two. This means `dst: b"abc", data: b"def"`, will produce the same scalar as
/// `dst: "abcdef", data: b""`. Please use carefully, not letting dsts be substrings of each other.
#[derive(Clone, Copy, PartialEq, Eq, Debug, zeroize::Zeroize)]
pub struct Embedwards25519;
pub type Point = Projective<Embedwards25519>;
impl ciphersuite::Ciphersuite for Embedwards25519 {
type F = Scalar;
type G = Point;
@@ -58,6 +82,10 @@ impl ciphersuite::Ciphersuite for Embedwards25519 {
Point::generator()
}
/// `hash_to_F` is implemented with a naive concatenation of the `dst` and `data`, allowing
/// transposition between the two. This means `dst: b"abc", data: b"def"`, will produce the same
/// scalar as `dst: "abcdef", data: b""`. Please use carefully, not letting `dst` valuess be
/// substrings of each other.
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
use blake2::Digest;
<Scalar as FromUniformBytes<64>>::from_uniform_bytes(
@@ -67,7 +95,7 @@ impl ciphersuite::Ciphersuite for Embedwards25519 {
// We override the provided impl, which compares against the reserialization, because
// we already require canonicity
#[cfg(any(feature = "alloc", feature = "std"))]
#[cfg(feature = "alloc")]
#[allow(non_snake_case)]
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
use ciphersuite::group::GroupEncoding;
@@ -81,9 +109,38 @@ impl ciphersuite::Ciphersuite for Embedwards25519 {
}
}
impl generalized_bulletproofs_ec_gadgets::DiscreteLogParameters for Embedwards25519 {
type ScalarBits = U<{ Scalar::NUM_BITS as usize }>;
type XCoefficients = Quot<Sum<Self::ScalarBits, U1>, U2>;
type XCoefficientsMinusOne = Diff<Self::XCoefficients, U1>;
type YxCoefficients = Diff<Quot<Sum<Sum<Self::ScalarBits, U1>, U1>, U2>, U2>;
#[cfg(feature = "alloc")]
impl generalized_bulletproofs_ec_gadgets::DiscreteLogParameter for Embedwards25519 {
type ScalarBits = generic_array::typenum::U<{ <Scalar as PrimeField>::NUM_BITS as usize }>;
}
#[test]
fn test_curve() {
ff_group_tests::group::test_prime_group_bits::<_, Point>(&mut rand_core::OsRng);
}
#[test]
fn generator() {
use ciphersuite::group::{Group, GroupEncoding};
assert_eq!(
Point::generator(),
Point::from_bytes(&hex_literal::hex!(
"0100000000000000000000000000000000000000000000000000000000000000"
))
.unwrap()
);
}
#[test]
fn zero_x_is_off_curve() {
assert!(bool::from(
Affine::<Embedwards25519>::decompress(FieldElement::ZERO, 1.into()).is_none()
));
}
// Checks random won't infinitely loop
#[test]
fn random() {
use ciphersuite::group::Group;
Point::random(&mut rand_core::OsRng);
}