From 205c7978cb3d32c256e9c27eb4f873125bb88b53 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Jul 2025 02:55:09 -0400 Subject: [PATCH 01/11] Add support for the ff/group API to FieldElement These changes are largely straightforward, with the note prior, the only arithmetic supported prior was for borrows. Now that we've added support for non-borrowed addition, the existing code is 'unnecessarily borrowing', hence the added lint. The larger question applicable is how this exposes FieldElement from several different backends, assuming they're all correct for arbitrary usage. The implementation of ConstantTimeEq has the following notes. ```rs /// Test equality between two `FieldElement`s. Since the /// internal representation is not canonical, the field elements /// are normalized to wire format before comparison. ``` While that doesn't suggest the mathematical operations are incomplete, it should be double-checked that no backend bounded the amount of operations they're correct to on the presumption the curve formulas would never exceed so many operations. --- curve25519-dalek/src/field.rs | 189 ++++++++++++++++++++++++++++++++++ curve25519-dalek/src/lib.rs | 1 + 2 files changed, 190 insertions(+) diff --git a/curve25519-dalek/src/field.rs b/curve25519-dalek/src/field.rs index db2d9aa4b..55d857d49 100644 --- a/curve25519-dalek/src/field.rs +++ b/curve25519-dalek/src/field.rs @@ -410,6 +410,195 @@ impl FieldElement { } } +#[cfg(feature = "group")] +mod group_support { + use super::*; + use core::{ + iter::{Product, Sum}, + ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, + }; + use ff::{Field, PrimeField}; + use subtle::CtOption; + + impl Default for FieldElement { + fn default() -> Self { + FieldElement::ZERO + } + } + + impl From for FieldElement { + fn from(a: u64) -> Self { + let mut bytes = [0; 32]; + bytes[..8].copy_from_slice(&a.to_le_bytes()); + Self::from_bytes(&bytes) + } + } + + impl Add for FieldElement { + type Output = Self; + fn add(self, other: FieldElement) -> Self { + &self + &other + } + } + impl Add<&FieldElement> for FieldElement { + type Output = Self; + fn add(self, other: &FieldElement) -> Self { + &self + other + } + } + impl Neg for FieldElement { + type Output = Self; + fn neg(self) -> Self { + FieldElement::ZERO - self + } + } + impl Sub for FieldElement { + type Output = Self; + fn sub(self, other: FieldElement) -> Self { + &self - &other + } + } + impl Sub<&FieldElement> for FieldElement { + type Output = Self; + fn sub(self, other: &FieldElement) -> Self { + &self - other + } + } + impl Mul for FieldElement { + type Output = Self; + fn mul(self, other: FieldElement) -> Self { + &self * &other + } + } + impl Mul<&FieldElement> for FieldElement { + type Output = Self; + fn mul(self, other: &FieldElement) -> Self { + &self * other + } + } + + impl AddAssign for FieldElement { + fn add_assign(&mut self, other: FieldElement) { + *self = *self + other; + } + } + impl SubAssign for FieldElement { + fn sub_assign(&mut self, other: FieldElement) { + *self = *self - other; + } + } + impl MulAssign for FieldElement { + fn mul_assign(&mut self, other: FieldElement) { + *self = *self * other; + } + } + + impl Sum for FieldElement { + fn sum>(iter: I) -> FieldElement { + let mut res = FieldElement::ZERO; + for item in iter { + res += item; + } + res + } + } + impl<'a> Sum<&'a FieldElement> for FieldElement { + fn sum>(iter: I) -> FieldElement { + iter.copied().sum() + } + } + + impl Product for FieldElement { + fn product>(iter: I) -> FieldElement { + let mut res = FieldElement::ONE; + for item in iter { + res *= item; + } + res + } + } + impl<'a> Product<&'a FieldElement> for FieldElement { + fn product>(iter: I) -> FieldElement { + iter.copied().product() + } + } + + impl Field for FieldElement { + const ZERO: Self = Self::ZERO; + const ONE: Self = Self::ONE; + + fn try_from_rng(rng: &mut R) -> Result { + let mut bytes = [0; 64]; + rng.try_fill_bytes(&mut bytes)?; + Ok(FieldElement::from_bytes_wide(&bytes)) + } + + fn square(&self) -> Self { + self * self + } + + fn double(&self) -> Self { + self + self + } + + fn invert(&self) -> CtOption { + CtOption::new(self.invert(), !self.is_zero()) + } + + fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { + FieldElement::sqrt_ratio_i(num, div) + } + } + + impl PrimeField for FieldElement { + type Repr = [u8; 32]; + + fn from_repr(repr: Self::Repr) -> CtOption { + let res = Self::from_bytes(&repr); + CtOption::new(res, repr.ct_eq(&res.to_bytes())) + } + + fn from_repr_vartime(repr: Self::Repr) -> Option { + Self::from_repr(repr).into() + } + + fn to_repr(&self) -> Self::Repr { + self.to_bytes() + } + + fn is_odd(&self) -> Choice { + Choice::from(self.to_bytes()[0] & 1) + } + + const MODULUS: &'static str = + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed"; + const NUM_BITS: u32 = 255; + const CAPACITY: u32 = 254; + + const TWO_INV: Self = Self::from_bytes(&[ + 247, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63, + ]); + const MULTIPLICATIVE_GENERATOR: Self = Self::from_bytes(&[ + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + const S: u32 = 2; + const ROOT_OF_UNITY: Self = Self::from_bytes(&[ + 176, 160, 14, 74, 39, 27, 238, 196, 120, 228, 47, 173, 6, 24, 67, 47, 167, 215, 251, + 61, 153, 0, 77, 43, 11, 223, 193, 79, 128, 36, 131, 43, + ]); + const ROOT_OF_UNITY_INV: Self = Self::from_bytes(&[ + 61, 95, 241, 181, 216, 228, 17, 59, 135, 27, 208, 82, 249, 231, 188, 208, 88, 40, 4, + 194, 102, 255, 178, 212, 244, 32, 62, 176, 127, 219, 124, 84, + ]); + const DELTA: Self = Self::from_bytes(&[ + 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ]); + } +} + #[cfg(test)] mod test { use crate::field::*; diff --git a/curve25519-dalek/src/lib.rs b/curve25519-dalek/src/lib.rs index 24e0fa5b8..440e1e827 100644 --- a/curve25519-dalek/src/lib.rs +++ b/curve25519-dalek/src/lib.rs @@ -30,6 +30,7 @@ unused_lifetimes, unused_qualifications )] +#![cfg_attr(feature = "group", allow(clippy::op_ref))] //------------------------------------------------------------------------ // External dependencies: From 3f174ca2ec2e9505dab3bf094a81fdd219577f9b Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Jul 2025 03:01:41 -0400 Subject: [PATCH 02/11] Publicize FieldElement, with a note it's not covered by semver and may have inconsistent internal representations --- curve25519-dalek/src/field.rs | 22 +++++++++++++--------- curve25519-dalek/src/lib.rs | 3 ++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/curve25519-dalek/src/field.rs b/curve25519-dalek/src/field.rs index 55d857d49..1268af542 100644 --- a/curve25519-dalek/src/field.rs +++ b/curve25519-dalek/src/field.rs @@ -49,35 +49,39 @@ cfg_if! { /// \\( \mathbb Z / (2\^{255} - 19)\\). /// /// The `FieldElement` type is an alias for one of the platform-specific - /// implementations. + /// implementations. Its size and internals are not guaranteed to have + /// any specific properties and are not covered by semver. /// /// Using formally-verified field arithmetic from fiat-crypto. #[cfg(curve25519_dalek_bits = "32")] - pub(crate) type FieldElement = backend::serial::fiat_u32::field::FieldElement2625; + pub type FieldElement = backend::serial::fiat_u32::field::FieldElement2625; /// A `FieldElement` represents an element of the field /// \\( \mathbb Z / (2\^{255} - 19)\\). /// /// The `FieldElement` type is an alias for one of the platform-specific - /// implementations. + /// implementations. Its size and internals are not guaranteed to have + /// any specific properties and are not covered by semver. /// /// Using formally-verified field arithmetic from fiat-crypto. #[cfg(curve25519_dalek_bits = "64")] - pub(crate) type FieldElement = backend::serial::fiat_u64::field::FieldElement51; + pub type FieldElement = backend::serial::fiat_u64::field::FieldElement51; } else if #[cfg(curve25519_dalek_bits = "64")] { /// A `FieldElement` represents an element of the field /// \\( \mathbb Z / (2\^{255} - 19)\\). /// /// The `FieldElement` type is an alias for one of the platform-specific - /// implementations. - pub(crate) type FieldElement = backend::serial::u64::field::FieldElement51; + /// implementations. Its size and internals are not guaranteed to have + /// any specific properties and are not covered by semver. + pub type FieldElement = backend::serial::u64::field::FieldElement51; } else { /// A `FieldElement` represents an element of the field /// \\( \mathbb Z / (2\^{255} - 19)\\). /// /// The `FieldElement` type is an alias for one of the platform-specific - /// implementations. - pub(crate) type FieldElement = backend::serial::u32::field::FieldElement2625; + /// implementations. Its size and internals are not guaranteed to have + /// any specific properties and are not covered by semver. + pub type FieldElement = backend::serial::u32::field::FieldElement2625; } } @@ -411,7 +415,7 @@ impl FieldElement { } #[cfg(feature = "group")] -mod group_support { +mod group { use super::*; use core::{ iter::{Product, Sum}, diff --git a/curve25519-dalek/src/lib.rs b/curve25519-dalek/src/lib.rs index 440e1e827..988157c1f 100644 --- a/curve25519-dalek/src/lib.rs +++ b/curve25519-dalek/src/lib.rs @@ -92,7 +92,8 @@ pub(crate) mod backend; pub(crate) mod window; pub use crate::{ - edwards::EdwardsPoint, montgomery::MontgomeryPoint, ristretto::RistrettoPoint, scalar::Scalar, + edwards::EdwardsPoint, field::FieldElement, montgomery::MontgomeryPoint, + ristretto::RistrettoPoint, scalar::Scalar, }; // Build time diagnostics for validation From 6ac3776536186fd6e96c7a70d445d0a5afdd8284 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Jul 2025 03:40:01 -0400 Subject: [PATCH 03/11] Add support for PrimeFieldBits Also fixes the feature flagging for the wide reduction function. --- curve25519-dalek/src/field.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/curve25519-dalek/src/field.rs b/curve25519-dalek/src/field.rs index 1268af542..01a3e16c7 100644 --- a/curve25519-dalek/src/field.rs +++ b/curve25519-dalek/src/field.rs @@ -104,7 +104,7 @@ impl ConstantTimeEq for FieldElement { impl FieldElement { /// Load a `FieldElement` from 64 bytes, by reducing modulo q. - #[cfg(feature = "digest")] + #[cfg(any(feature = "digest", feature = "group"))] pub(crate) fn from_bytes_wide(bytes: &[u8; 64]) -> Self { let mut fl = [0u8; 32]; let mut gl = [0u8; 32]; @@ -601,6 +601,23 @@ mod group { 0, 0, 0, ]); } + + #[cfg(feature = "group-bits")] + impl ff::PrimeFieldBits for FieldElement { + type ReprBits = [u8; 32]; + + fn to_le_bits(&self) -> ff::FieldBits { + self.to_repr().into() + } + + fn char_le_bits() -> ff::FieldBits { + [ + 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 237, + ] + .into() + } + } } #[cfg(test)] From 6860d517817f3b32cdbb24b671f95a4c3d05fae8 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Jul 2025 04:04:22 -0400 Subject: [PATCH 04/11] Add a dedicated FieldElement type which is safe to expose This forces a reduction after every operation via converting to bytes (canonical) and re-reading it. There's probably a better way... --- curve25519-dalek/src/field.rs | 222 +++++++++++++++++++++------------- curve25519-dalek/src/lib.rs | 3 +- 2 files changed, 138 insertions(+), 87 deletions(-) diff --git a/curve25519-dalek/src/field.rs b/curve25519-dalek/src/field.rs index 01a3e16c7..96a5d34aa 100644 --- a/curve25519-dalek/src/field.rs +++ b/curve25519-dalek/src/field.rs @@ -49,39 +49,35 @@ cfg_if! { /// \\( \mathbb Z / (2\^{255} - 19)\\). /// /// The `FieldElement` type is an alias for one of the platform-specific - /// implementations. Its size and internals are not guaranteed to have - /// any specific properties and are not covered by semver. + /// implementations. /// /// Using formally-verified field arithmetic from fiat-crypto. #[cfg(curve25519_dalek_bits = "32")] - pub type FieldElement = backend::serial::fiat_u32::field::FieldElement2625; + pub(crate) type FieldElement = backend::serial::fiat_u32::field::FieldElement2625; /// A `FieldElement` represents an element of the field /// \\( \mathbb Z / (2\^{255} - 19)\\). /// /// The `FieldElement` type is an alias for one of the platform-specific - /// implementations. Its size and internals are not guaranteed to have - /// any specific properties and are not covered by semver. + /// implementations. /// /// Using formally-verified field arithmetic from fiat-crypto. #[cfg(curve25519_dalek_bits = "64")] - pub type FieldElement = backend::serial::fiat_u64::field::FieldElement51; + pub(crate) type FieldElement = backend::serial::fiat_u64::field::FieldElement51; } else if #[cfg(curve25519_dalek_bits = "64")] { /// A `FieldElement` represents an element of the field /// \\( \mathbb Z / (2\^{255} - 19)\\). /// /// The `FieldElement` type is an alias for one of the platform-specific - /// implementations. Its size and internals are not guaranteed to have - /// any specific properties and are not covered by semver. - pub type FieldElement = backend::serial::u64::field::FieldElement51; + /// implementations. + pub(crate) type FieldElement = backend::serial::u64::field::FieldElement51; } else { /// A `FieldElement` represents an element of the field /// \\( \mathbb Z / (2\^{255} - 19)\\). /// /// The `FieldElement` type is an alias for one of the platform-specific - /// implementations. Its size and internals are not guaranteed to have - /// any specific properties and are not covered by semver. - pub type FieldElement = backend::serial::u32::field::FieldElement2625; + /// implementations. + pub(crate) type FieldElement = backend::serial::u32::field::FieldElement2625; } } @@ -416,150 +412,204 @@ impl FieldElement { #[cfg(feature = "group")] mod group { - use super::*; + use super::FieldElement; use core::{ + fmt::Debug, iter::{Product, Sum}, ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, }; use ff::{Field, PrimeField}; - use subtle::CtOption; + use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; - impl Default for FieldElement { + /// A `FieldElement` represents an element of the field + /// \\( \mathbb Z / (2\^{255} - 19)\\). + /// + /// The `FieldElement` type is an alias for one of the platform-specific + /// implementations. Its size and internals are not guaranteed to have + /// any specific properties and are not covered by semver. + #[derive(Clone, Copy, PartialEq, Eq)] + pub struct FfFieldElement(FieldElement); + + impl Default for FfFieldElement { fn default() -> Self { - FieldElement::ZERO + FfFieldElement(FieldElement::ZERO) + } + } + + impl Debug for FfFieldElement { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "FfFieldElement{{\n\tbytes: {:?},\n}}", &self.0.to_bytes()) } } - impl From for FieldElement { + impl From for FfFieldElement { fn from(a: u64) -> Self { + // Portable method to convert a u64 to a FfFieldElement, + // regardless of the internal representation let mut bytes = [0; 32]; bytes[..8].copy_from_slice(&a.to_le_bytes()); - Self::from_bytes(&bytes) + Self(FieldElement::from_bytes(&bytes)) } } - impl Add for FieldElement { + impl Add<&FfFieldElement> for FfFieldElement { type Output = Self; - fn add(self, other: FieldElement) -> Self { - &self + &other + fn add(self, other: &Self) -> Self { + let unreduced = &self.0 + &other.0; + // Force a reduction + Self(FieldElement::from_bytes(&unreduced.to_bytes())) } } - impl Add<&FieldElement> for FieldElement { + impl Add for FfFieldElement { type Output = Self; - fn add(self, other: &FieldElement) -> Self { - &self + other + fn add(self, other: Self) -> Self { + self + &other } } - impl Neg for FieldElement { - type Output = Self; - fn neg(self) -> Self { - FieldElement::ZERO - self + impl AddAssign for FfFieldElement { + fn add_assign(&mut self, other: Self) { + *self = *self + &other; } } - impl Sub for FieldElement { - type Output = Self; - fn sub(self, other: FieldElement) -> Self { - &self - &other + impl AddAssign<&FfFieldElement> for FfFieldElement { + fn add_assign(&mut self, other: &Self) { + *self = *self + other; } } - impl Sub<&FieldElement> for FieldElement { + + impl Sub<&FfFieldElement> for FfFieldElement { type Output = Self; - fn sub(self, other: &FieldElement) -> Self { - &self - other + fn sub(self, other: &Self) -> Self { + let unreduced = &self.0 - &other.0; + // Force a reduction + Self(FieldElement::from_bytes(&unreduced.to_bytes())) } } - impl Mul for FieldElement { + impl Sub for FfFieldElement { type Output = Self; - fn mul(self, other: FieldElement) -> Self { - &self * &other + fn sub(self, other: Self) -> Self { + self - &other + } + } + impl SubAssign for FfFieldElement { + fn sub_assign(&mut self, other: Self) { + *self = *self - &other; } } - impl Mul<&FieldElement> for FieldElement { + impl SubAssign<&FfFieldElement> for FfFieldElement { + fn sub_assign(&mut self, other: &Self) { + *self = *self - other; + } + } + + impl Neg for FfFieldElement { type Output = Self; - fn mul(self, other: &FieldElement) -> Self { - &self * other + fn neg(self) -> Self { + FfFieldElement::ZERO - self } } - impl AddAssign for FieldElement { - fn add_assign(&mut self, other: FieldElement) { - *self = *self + other; + impl Mul<&FfFieldElement> for FfFieldElement { + type Output = Self; + fn mul(self, other: &Self) -> Self { + let unreduced = &self.0 * &other.0; + // Force a reduction + Self(FieldElement::from_bytes(&unreduced.to_bytes())) } } - impl SubAssign for FieldElement { - fn sub_assign(&mut self, other: FieldElement) { - *self = *self - other; + impl Mul for FfFieldElement { + type Output = Self; + fn mul(self, other: Self) -> Self { + self * &other } } - impl MulAssign for FieldElement { - fn mul_assign(&mut self, other: FieldElement) { + impl MulAssign for FfFieldElement { + fn mul_assign(&mut self, other: Self) { + *self = *self * other; + } + } + impl MulAssign<&FfFieldElement> for FfFieldElement { + fn mul_assign(&mut self, other: &Self) { *self = *self * other; } } - impl Sum for FieldElement { - fn sum>(iter: I) -> FieldElement { - let mut res = FieldElement::ZERO; + impl Sum for FfFieldElement { + fn sum>(iter: I) -> Self { + let mut res = FfFieldElement::ZERO; for item in iter { res += item; } res } } - impl<'a> Sum<&'a FieldElement> for FieldElement { - fn sum>(iter: I) -> FieldElement { + impl<'a> Sum<&'a FfFieldElement> for FfFieldElement { + fn sum>(iter: I) -> Self { iter.copied().sum() } } - impl Product for FieldElement { - fn product>(iter: I) -> FieldElement { - let mut res = FieldElement::ONE; + impl Product for FfFieldElement { + fn product>(iter: I) -> Self { + let mut res = FfFieldElement::ONE; for item in iter { res *= item; } res } } - impl<'a> Product<&'a FieldElement> for FieldElement { - fn product>(iter: I) -> FieldElement { + impl<'a> Product<&'a FfFieldElement> for FfFieldElement { + fn product>(iter: I) -> Self { iter.copied().product() } } - impl Field for FieldElement { - const ZERO: Self = Self::ZERO; - const ONE: Self = Self::ONE; + impl ConstantTimeEq for FfFieldElement { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } + } + + impl ConditionallySelectable for FfFieldElement { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self(<_>::conditional_select(&a.0, &b.0, choice)) + } + } + + impl Field for FfFieldElement { + const ZERO: Self = Self(FieldElement::ZERO); + const ONE: Self = Self(FieldElement::ONE); fn try_from_rng(rng: &mut R) -> Result { let mut bytes = [0; 64]; rng.try_fill_bytes(&mut bytes)?; - Ok(FieldElement::from_bytes_wide(&bytes)) + Ok(Self(FieldElement::from_bytes_wide(&bytes))) } fn square(&self) -> Self { - self * self + *self * self } fn double(&self) -> Self { - self + self + *self + self } fn invert(&self) -> CtOption { - CtOption::new(self.invert(), !self.is_zero()) + CtOption::new(Self(self.0.invert()), !self.is_zero()) } fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { - FieldElement::sqrt_ratio_i(num, div) + let res = FieldElement::sqrt_ratio_i(&num.0, &div.0); + (res.0, Self(res.1)) } } - impl PrimeField for FieldElement { + impl PrimeField for FfFieldElement { type Repr = [u8; 32]; fn from_repr(repr: Self::Repr) -> CtOption { - let res = Self::from_bytes(&repr); - CtOption::new(res, repr.ct_eq(&res.to_bytes())) + let res = Self(FieldElement::from_bytes(&repr)); + CtOption::new(res, repr.ct_eq(&res.0.to_bytes())) } fn from_repr_vartime(repr: Self::Repr) -> Option { @@ -567,11 +617,11 @@ mod group { } fn to_repr(&self) -> Self::Repr { - self.to_bytes() + self.0.to_bytes() } fn is_odd(&self) -> Choice { - Choice::from(self.to_bytes()[0] & 1) + Choice::from(self.0.to_bytes()[0] & 1) } const MODULUS: &'static str = @@ -579,31 +629,31 @@ mod group { const NUM_BITS: u32 = 255; const CAPACITY: u32 = 254; - const TWO_INV: Self = Self::from_bytes(&[ + const TWO_INV: Self = Self(FieldElement::from_bytes(&[ 247, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 63, - ]); - const MULTIPLICATIVE_GENERATOR: Self = Self::from_bytes(&[ + ])); + const MULTIPLICATIVE_GENERATOR: Self = Self(FieldElement::from_bytes(&[ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ]); + ])); const S: u32 = 2; - const ROOT_OF_UNITY: Self = Self::from_bytes(&[ + const ROOT_OF_UNITY: Self = Self(FieldElement::from_bytes(&[ 176, 160, 14, 74, 39, 27, 238, 196, 120, 228, 47, 173, 6, 24, 67, 47, 167, 215, 251, 61, 153, 0, 77, 43, 11, 223, 193, 79, 128, 36, 131, 43, - ]); - const ROOT_OF_UNITY_INV: Self = Self::from_bytes(&[ + ])); + const ROOT_OF_UNITY_INV: Self = Self(FieldElement::from_bytes(&[ 61, 95, 241, 181, 216, 228, 17, 59, 135, 27, 208, 82, 249, 231, 188, 208, 88, 40, 4, 194, 102, 255, 178, 212, 244, 32, 62, 176, 127, 219, 124, 84, - ]); - const DELTA: Self = Self::from_bytes(&[ + ])); + const DELTA: Self = Self(FieldElement::from_bytes(&[ 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ]); + ])); } #[cfg(feature = "group-bits")] - impl ff::PrimeFieldBits for FieldElement { + impl ff::PrimeFieldBits for FfFieldElement { type ReprBits = [u8; 32]; fn to_le_bits(&self) -> ff::FieldBits { @@ -612,13 +662,15 @@ mod group { fn char_le_bits() -> ff::FieldBits { [ - 127, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 237, + 237, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, ] .into() } } } +#[cfg(feature = "group")] +pub use group::FfFieldElement; #[cfg(test)] mod test { diff --git a/curve25519-dalek/src/lib.rs b/curve25519-dalek/src/lib.rs index 988157c1f..99415db94 100644 --- a/curve25519-dalek/src/lib.rs +++ b/curve25519-dalek/src/lib.rs @@ -30,7 +30,6 @@ unused_lifetimes, unused_qualifications )] -#![cfg_attr(feature = "group", allow(clippy::op_ref))] //------------------------------------------------------------------------ // External dependencies: @@ -92,7 +91,7 @@ pub(crate) mod backend; pub(crate) mod window; pub use crate::{ - edwards::EdwardsPoint, field::FieldElement, montgomery::MontgomeryPoint, + edwards::EdwardsPoint, field::FfFieldElement as FieldElement, montgomery::MontgomeryPoint, ristretto::RistrettoPoint, scalar::Scalar, }; From 54e57681b46cc1136124592e8ea03dde5d44f56f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Jul 2025 04:06:57 -0400 Subject: [PATCH 05/11] cargo +nightly fmt --- curve25519-dalek/src/field.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/curve25519-dalek/src/field.rs b/curve25519-dalek/src/field.rs index 96a5d34aa..1bab5c216 100644 --- a/curve25519-dalek/src/field.rs +++ b/curve25519-dalek/src/field.rs @@ -438,7 +438,11 @@ mod group { impl Debug for FfFieldElement { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "FfFieldElement{{\n\tbytes: {:?},\n}}", &self.0.to_bytes()) + write!( + f, + "FfFieldElement{{\n\tbytes: {:?},\n}}", + &self.0.to_bytes() + ) } } From 997d7c7d0ed25af2c02db4ff8d3f6c9b6385c3ee Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Jul 2025 04:14:59 -0400 Subject: [PATCH 06/11] Use negate instead of subtraction from zero --- curve25519-dalek/src/field.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/curve25519-dalek/src/field.rs b/curve25519-dalek/src/field.rs index 1bab5c216..0f19f8643 100644 --- a/curve25519-dalek/src/field.rs +++ b/curve25519-dalek/src/field.rs @@ -508,8 +508,9 @@ mod group { impl Neg for FfFieldElement { type Output = Self; - fn neg(self) -> Self { - FfFieldElement::ZERO - self + fn neg(mut self) -> Self { + self.0.negate(); + Self(FieldElement::from_bytes(&self.0.to_bytes())) } } From 1537414e8557123542f818eea5414c8591199704 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Jul 2025 04:16:10 -0400 Subject: [PATCH 07/11] Add Zeroize to FfFieldElement --- curve25519-dalek/src/field.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/curve25519-dalek/src/field.rs b/curve25519-dalek/src/field.rs index 0f19f8643..907445051 100644 --- a/curve25519-dalek/src/field.rs +++ b/curve25519-dalek/src/field.rs @@ -581,6 +581,13 @@ mod group { } } + #[cfg(feature = "zeroize")] + impl zeroize::Zeroize for FfFieldElement { + fn zeroize(&mut self) { + self.0.zeroize(); + } + } + impl Field for FfFieldElement { const ZERO: Self = Self(FieldElement::ZERO); const ONE: Self = Self(FieldElement::ONE); From cb7989254a66abaaf81c2e84cccbe1deab69a6a5 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Jul 2025 04:18:19 -0400 Subject: [PATCH 08/11] Update README respective to the exposed FieldElement type --- curve25519-dalek/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curve25519-dalek/README.md b/curve25519-dalek/README.md index 1316dbac8..2754cedcb 100644 --- a/curve25519-dalek/README.md +++ b/curve25519-dalek/README.md @@ -55,7 +55,7 @@ curve25519-dalek = ">= 5.0, < 5.2" | `digest` | | Enables `RistrettoPoint::{from_hash, hash_from_bytes}` and `Scalar::{from_hash, hash_from_bytes}`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. | | `serde` | | Enables `serde` serialization/deserialization for all the point and scalar types. | | `legacy_compatibility`| | Enables `Scalar::from_bits`, which allows the user to build unreduced scalars whose arithmetic is broken. Do not use this unless you know what you're doing. | -| `group` | | Enables external `group` and `ff` crate traits. | +| `group` | | Enables external `group` and `ff` crate traits, exposing an implementation of the Ed25519 field. | | `group-bits` | | Enables `group` and impls `ff::PrimeFieldBits` for `Scalar`. | To disable the default features when using `curve25519-dalek` as a dependency, From 2f5c74979c7be89ccee84cffa9c979412cf3ca1f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Jul 2025 04:34:24 -0400 Subject: [PATCH 09/11] Fix exporting of the new FieldElement type --- curve25519-dalek/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/curve25519-dalek/src/lib.rs b/curve25519-dalek/src/lib.rs index 99415db94..f7ba2f546 100644 --- a/curve25519-dalek/src/lib.rs +++ b/curve25519-dalek/src/lib.rs @@ -91,9 +91,10 @@ pub(crate) mod backend; pub(crate) mod window; pub use crate::{ - edwards::EdwardsPoint, field::FfFieldElement as FieldElement, montgomery::MontgomeryPoint, - ristretto::RistrettoPoint, scalar::Scalar, + edwards::EdwardsPoint, montgomery::MontgomeryPoint, ristretto::RistrettoPoint, scalar::Scalar, }; +#[cfg(feature = "group")] +use field::FfFieldElement as FieldElement; // Build time diagnostics for validation #[cfg(curve25519_dalek_diagnostics = "build")] From c703a9eeb105430be1123d952cf702655e742125 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Jul 2025 04:37:39 -0400 Subject: [PATCH 10/11] clippy --- curve25519-dalek/src/field.rs | 7 +++++-- curve25519-dalek/src/lib.rs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/curve25519-dalek/src/field.rs b/curve25519-dalek/src/field.rs index 907445051..278c0c7d7 100644 --- a/curve25519-dalek/src/field.rs +++ b/curve25519-dalek/src/field.rs @@ -464,6 +464,7 @@ mod group { Self(FieldElement::from_bytes(&unreduced.to_bytes())) } } + #[allow(clippy::op_ref)] impl Add for FfFieldElement { type Output = Self; fn add(self, other: Self) -> Self { @@ -472,7 +473,7 @@ mod group { } impl AddAssign for FfFieldElement { fn add_assign(&mut self, other: Self) { - *self = *self + &other; + *self = *self + other; } } impl AddAssign<&FfFieldElement> for FfFieldElement { @@ -489,6 +490,7 @@ mod group { Self(FieldElement::from_bytes(&unreduced.to_bytes())) } } + #[allow(clippy::op_ref)] impl Sub for FfFieldElement { type Output = Self; fn sub(self, other: Self) -> Self { @@ -497,7 +499,7 @@ mod group { } impl SubAssign for FfFieldElement { fn sub_assign(&mut self, other: Self) { - *self = *self - &other; + *self = *self - other; } } impl SubAssign<&FfFieldElement> for FfFieldElement { @@ -522,6 +524,7 @@ mod group { Self(FieldElement::from_bytes(&unreduced.to_bytes())) } } + #[allow(clippy::op_ref)] impl Mul for FfFieldElement { type Output = Self; fn mul(self, other: Self) -> Self { diff --git a/curve25519-dalek/src/lib.rs b/curve25519-dalek/src/lib.rs index f7ba2f546..2b1f33ec0 100644 --- a/curve25519-dalek/src/lib.rs +++ b/curve25519-dalek/src/lib.rs @@ -94,7 +94,7 @@ pub use crate::{ edwards::EdwardsPoint, montgomery::MontgomeryPoint, ristretto::RistrettoPoint, scalar::Scalar, }; #[cfg(feature = "group")] -use field::FfFieldElement as FieldElement; +pub use field::FfFieldElement as FieldElement; // Build time diagnostics for validation #[cfg(curve25519_dalek_diagnostics = "build")] From bc85a42c0004a9fc3e6d3fa57f5c33d2b8c70ef8 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 27 Aug 2025 16:36:26 -0400 Subject: [PATCH 11/11] FromUniformBytes<64> for FfFieldElement --- curve25519-dalek/src/field.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/curve25519-dalek/src/field.rs b/curve25519-dalek/src/field.rs index 278c0c7d7..28b668fc1 100644 --- a/curve25519-dalek/src/field.rs +++ b/curve25519-dalek/src/field.rs @@ -418,7 +418,7 @@ mod group { iter::{Product, Sum}, ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, }; - use ff::{Field, PrimeField}; + use ff::{Field, FromUniformBytes, PrimeField}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; /// A `FieldElement` represents an element of the field @@ -683,6 +683,11 @@ mod group { .into() } } + impl FromUniformBytes<64> for FfFieldElement { + fn from_uniform_bytes(bytes: &[u8; 64]) -> Self { + Self(FieldElement::from_bytes_wide(bytes)) + } + } } #[cfg(feature = "group")] pub use group::FfFieldElement;