From fd7b6c84f39c46ea693c07473797fdeeb97227e3 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 12 Jul 2025 02:55:09 -0400 Subject: [PATCH 01/19] Add `expose-field` to expose `FieldElement` The underlying field elements are unsafe for public consumption as they have undefined arithmetic after a certain amount of uses. This trait solves the problem as following: - Defining a `ff::Field` wrapper which reduces after _every operation_ - Defining a bespoke `LazyField` trait which tracks consumed capacity using `typenum` - Definining a wrapper so any existing `ff::Field` may satisfy `LazyField` (actually allowing the `LazyField` trait to be considered for usage) - Defining a marker trait for any lazy field with a certain amount of capacity, so code generic to the field may still reduce how often they perform modular reductions - Implementing `LazyField` for `FieldElement` --- curve25519-dalek/Cargo.toml | 2 + curve25519-dalek/README.md | 3 +- .../src/backend/serial/fiat_u32/field.rs | 5 + .../src/backend/serial/fiat_u64/field.rs | 5 + .../src/backend/serial/u32/field.rs | 5 + .../src/backend/serial/u64/field.rs | 5 + curve25519-dalek/src/ff_field.rs | 302 ++++++++++++++++++ curve25519-dalek/src/ff_field/lazy_field.rs | 70 ++++ .../src/ff_field/lazy_field/eager.rs | 243 ++++++++++++++ .../src/ff_field/lazy_field25519.rs | 56 ++++ curve25519-dalek/src/field.rs | 2 +- curve25519-dalek/src/lib.rs | 7 + 12 files changed, 703 insertions(+), 2 deletions(-) create mode 100644 curve25519-dalek/src/ff_field.rs create mode 100644 curve25519-dalek/src/ff_field/lazy_field.rs create mode 100644 curve25519-dalek/src/ff_field/lazy_field/eager.rs create mode 100644 curve25519-dalek/src/ff_field/lazy_field25519.rs diff --git a/curve25519-dalek/Cargo.toml b/curve25519-dalek/Cargo.toml index 72bff1de7..d2113f895 100644 --- a/curve25519-dalek/Cargo.toml +++ b/curve25519-dalek/Cargo.toml @@ -68,6 +68,7 @@ serde = { version = "1.0", default-features = false, optional = true, features = "derive", ] } zeroize = { version = "1", default-features = false, optional = true } +typenum = { version = "1", default-features = false, optional = true } [target.'cfg(target_arch = "x86_64")'.dependencies] cpufeatures = "0.2.17" @@ -80,6 +81,7 @@ default = ["alloc", "precomputed-tables", "zeroize"] alloc = ["zeroize?/alloc"] precomputed-tables = [] legacy_compatibility = [] +expose-field = ["rand_core", "ff", "typenum"] group = ["dep:group", "rand_core"] group-bits = ["group", "ff/bits"] digest = ["dep:digest"] diff --git a/curve25519-dalek/README.md b/curve25519-dalek/README.md index 1316dbac8..43f479c09 100644 --- a/curve25519-dalek/README.md +++ b/curve25519-dalek/README.md @@ -55,8 +55,9 @@ 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. | +| `expose-field` | | Enables `FieldElement` satisfying `ff` traits and bespoke traits for lazy reduction | | `group` | | Enables external `group` and `ff` crate traits. | -| `group-bits` | | Enables `group` and impls `ff::PrimeFieldBits` for `Scalar`. | +| `group-bits` | | Enables `group` and impls `ff::PrimeFieldBits` for `Scalar`, and `FieldElement` if `expose-field`. | To disable the default features when using `curve25519-dalek` as a dependency, add `default-features = false` to the dependency in your `Cargo.toml`. To diff --git a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs index 411e75fef..91ba3f5a7 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs @@ -269,3 +269,8 @@ impl FieldElement2625 { output } } + +#[cfg(feature = "expose-field")] +impl crate::UnderlyingCapacity for FieldElement51 { + type Capacity = typenum::U3; +} diff --git a/curve25519-dalek/src/backend/serial/fiat_u64/field.rs b/curve25519-dalek/src/backend/serial/fiat_u64/field.rs index 8f1927542..dfd36b47f 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u64/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u64/field.rs @@ -260,3 +260,8 @@ impl FieldElement51 { output } } + +#[cfg(feature = "expose-field")] +impl crate::UnderlyingCapacity for FieldElement51 { + type Capacity = typenum::U8; +} diff --git a/curve25519-dalek/src/backend/serial/u32/field.rs b/curve25519-dalek/src/backend/serial/u32/field.rs index 2aff69684..36a571d61 100644 --- a/curve25519-dalek/src/backend/serial/u32/field.rs +++ b/curve25519-dalek/src/backend/serial/u32/field.rs @@ -605,3 +605,8 @@ impl FieldElement2625 { FieldElement2625::reduce(coeffs) } } + +#[cfg(feature = "expose-field")] +impl crate::UnderlyingCapacity for FieldElement51 { + type Capacity = typenum::U3; +} diff --git a/curve25519-dalek/src/backend/serial/u64/field.rs b/curve25519-dalek/src/backend/serial/u64/field.rs index 05340bcaa..9fe50dc61 100644 --- a/curve25519-dalek/src/backend/serial/u64/field.rs +++ b/curve25519-dalek/src/backend/serial/u64/field.rs @@ -573,3 +573,8 @@ impl FieldElement51 { square } } + +#[cfg(feature = "expose-field")] +impl crate::UnderlyingCapacity for FieldElement51 { + type Capacity = typenum::U8; +} diff --git a/curve25519-dalek/src/ff_field.rs b/curve25519-dalek/src/ff_field.rs new file mode 100644 index 000000000..7ca50c7fc --- /dev/null +++ b/curve25519-dalek/src/ff_field.rs @@ -0,0 +1,302 @@ +use core::{ + fmt::Debug, + iter::{Product, Sum}, + marker::PhantomData, + ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, +}; + +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; +use typenum::{U0, Unsigned}; + +use ff::{Field, FromUniformBytes, PrimeField}; + +use crate::field::FieldElement as Underlying; + +pub mod lazy_field; +mod lazy_field25519; +pub(crate) use lazy_field25519::UnderlyingCapacity; + +/// 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(Copy)] +pub struct FieldElement(pub(crate) Underlying, pub(crate) PhantomData); +unsafe impl Send for FieldElement {} +unsafe impl Sync for FieldElement {} + +impl FieldElement { + pub(crate) const fn from(underlying: Underlying) -> Self { + Self(underlying, PhantomData) + } +} + +impl ConstantTimeEq for FieldElement { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } +} +impl PartialEq for FieldElement { + fn eq(&self, other: &Self) -> bool { + bool::from(self.ct_eq(other)) + } +} +impl Eq for FieldElement {} + +impl Clone for FieldElement { + fn clone(&self) -> Self { + *self + } +} + +impl Default for FieldElement { + fn default() -> Self { + Self::from(Underlying::ZERO) + } +} + +impl Debug for FieldElement { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.0.fmt(f) + } +} + +impl ConditionallySelectable for FieldElement { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self::from(<_>::conditional_select(&a.0, &b.0, choice)) + } +} + +impl Add<&FieldElement> for FieldElement { + type Output = Self; + fn add(self, other: &Self) -> Self { + let unreduced = &self.0 + &other.0; + // Force a reduction + Self::from(Underlying::from_bytes(&unreduced.to_bytes())) + } +} +#[allow(clippy::op_ref)] +impl Add for FieldElement { + type Output = Self; + fn add(self, other: Self) -> Self { + self + &other + } +} +impl AddAssign for FieldElement { + fn add_assign(&mut self, other: Self) { + *self = *self + other; + } +} +impl AddAssign<&FieldElement> for FieldElement { + fn add_assign(&mut self, other: &Self) { + *self = *self + other; + } +} + +impl Sub<&FieldElement> for FieldElement { + type Output = Self; + fn sub(self, other: &Self) -> Self { + let unreduced = &self.0 - &other.0; + // Force a reduction + Self::from(Underlying::from_bytes(&unreduced.to_bytes())) + } +} +#[allow(clippy::op_ref)] +impl Sub for FieldElement { + type Output = Self; + fn sub(self, other: Self) -> Self { + self - &other + } +} +impl SubAssign for FieldElement { + fn sub_assign(&mut self, other: Self) { + *self = *self - other; + } +} +impl SubAssign<&FieldElement> for FieldElement { + fn sub_assign(&mut self, other: &Self) { + *self = *self - other; + } +} + +impl Neg for FieldElement { + type Output = Self; + fn neg(mut self) -> Self { + self.0.negate(); + Self::from(Underlying::from_bytes(&self.0.to_bytes())) + } +} + +impl Mul<&FieldElement> for FieldElement { + type Output = Self; + fn mul(self, other: &Self) -> Self { + let unreduced = &self.0 * &other.0; + // Force a reduction + Self::from(Underlying::from_bytes(&unreduced.to_bytes())) + } +} +#[allow(clippy::op_ref)] +impl Mul for FieldElement { + type Output = Self; + fn mul(self, other: Self) -> Self { + self * &other + } +} +impl MulAssign for FieldElement { + fn mul_assign(&mut self, other: Self) { + *self = *self * other; + } +} +impl MulAssign<&FieldElement> for FieldElement { + fn mul_assign(&mut self, other: &Self) { + *self = *self * other; + } +} + +impl Sum for FieldElement { + fn sum>(iter: I) -> Self { + let mut res = FieldElement::ZERO; + for item in iter { + res += item; + } + res + } +} +impl<'a> Sum<&'a FieldElement> for FieldElement { + fn sum>(iter: I) -> Self { + iter.copied().sum() + } +} + +impl Product for FieldElement { + fn product>(iter: I) -> Self { + let mut res = FieldElement::ONE; + for item in iter { + res *= item; + } + res + } +} +impl<'a> Product<&'a FieldElement> for FieldElement { + fn product>(iter: I) -> Self { + iter.copied().product() + } +} + +impl Field for FieldElement { + const ZERO: Self = Self::from(Underlying::ZERO); + const ONE: Self = Self::from(Underlying::ONE); + + fn try_from_rng(rng: &mut R) -> Result { + let mut bytes = [0; 64]; + rng.try_fill_bytes(&mut bytes)?; + Ok(Self::from(Underlying::from_bytes_wide(&bytes))) + } + + fn square(&self) -> Self { + *self * self + } + + fn double(&self) -> Self { + *self + self + } + + fn invert(&self) -> CtOption { + CtOption::new(Self::from(self.0.invert()), !self.is_zero()) + } + + fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { + let res = Underlying::sqrt_ratio_i(&num.0, &div.0); + (res.0, Self::from(res.1)) + } +} + +impl PrimeField for FieldElement { + type Repr = [u8; 32]; + + fn from_repr(repr: Self::Repr) -> CtOption { + let res = Self::from(Underlying::from_bytes(&repr)); + CtOption::new(res, repr.ct_eq(&res.0.to_bytes())) + } + + fn from_repr_vartime(repr: Self::Repr) -> Option { + Self::from_repr(repr).into() + } + + fn to_repr(&self) -> Self::Repr { + self.0.to_bytes() + } + + fn is_odd(&self) -> Choice { + Choice::from(self.0.to_bytes()[0] & 1) + } + + const MODULUS: &'static str = + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed"; + const NUM_BITS: u32 = 255; + const CAPACITY: u32 = 254; + + const TWO_INV: Self = Self::from(Underlying::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(Underlying::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(Underlying::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(Underlying::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(Underlying::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 { + type ReprBits = [u8; 32]; + + fn to_le_bits(&self) -> ff::FieldBits { + self.to_repr().into() + } + + fn char_le_bits() -> ff::FieldBits { + [ + 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() + } +} + +impl From for FieldElement { + fn from(a: u64) -> Self { + // Portable method to convert a u64 to a FieldElement, + // regardless of the internal representation + let mut bytes = [0; 32]; + bytes[..8].copy_from_slice(&a.to_le_bytes()); + Self::from(Underlying::from_bytes(&bytes)) + } +} + +impl FromUniformBytes<64> for FieldElement { + fn from_uniform_bytes(bytes: &[u8; 64]) -> Self { + Self::from(Underlying::from_bytes_wide(bytes)) + } +} + +#[cfg(feature = "zeroize")] +impl zeroize::Zeroize for FieldElement { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} diff --git a/curve25519-dalek/src/ff_field/lazy_field.rs b/curve25519-dalek/src/ff_field/lazy_field.rs new file mode 100644 index 000000000..21aac6b55 --- /dev/null +++ b/curve25519-dalek/src/ff_field/lazy_field.rs @@ -0,0 +1,70 @@ +//! Traits for working with fields which only perform reduction as needed. + +use core::{fmt::Debug, ops::Add}; + +use typenum::{B1, U0, Unsigned, type_operators::IsLessOrEqual}; + +use ff::Field; + +mod eager; +pub use eager::*; + +/// An element which can be reduced. +pub trait Reducible { + /// The reduced element. + type Output: Field + LazyField; + /// Reduce to a reduced element. + fn reduce(&self) -> Self::Output; +} + +/// An element of a field which is only reduced as instructed. +/// +/// By only reducing as instructed, when necessary, unnecessary reductions can be optimized out. +/// In order to ensure a safe API, `typenum` is used to track the number of operations performed +/// and ensure arithmetic remains well-defined. +/* + There's a oddity here where `CapacityUsed` is not bound to be less than `Capacity`. Such elements + aren't obtainable nor usable via the `add` function however, so it shouldn't be an issue? + Attempting to introduce that bound overloads the Rust type system. +*/ +pub trait LazyField: + Sized + Eq + Copy + Clone + Send + Sync + Debug + 'static + Reducible +{ + /// The amount of operations which can be performed while operations remain well-defined. + type Capacity: Unsigned; + /// The non-generic type underlying this which presumably lacks inherent capacity checks. + type Underlying; + + /// A reference to the underlying type. + /// + /// The underlying type has undefined semantics and MUST NOT be used directly. + fn as_underlying(&self) -> &Self::Underlying; + + /// Add two lazy elements, which reduce to the same field, where the result remains within the + /// capacity. + fn add< + V: Unsigned + Add>, + T: LazyField, + >( + self, + other: &T, + ) -> impl LazyField<>::Output, Capacity = Self::Capacity>; + + /// Multiply two lazy elements. + /// + /// This will always return a reduced field element. + fn mul>( + self, + other: &T, + ) -> ::Output; +} + +/// A lazy field with _at least_ the specified amount of capacity. +/// +/// When working generically with fields, the amount of capacity will differ. This method sets a +/// minimum bound on the capacity, allowing taking advantage of the bound regardless of the field. +/// +/// `LazyFieldWithCapacity` is _recommended_ due to the widespread popularity of 255-bit +/// fields. +pub trait LazyFieldWithCapacity {} +impl> LazyFieldWithCapacity for F {} diff --git a/curve25519-dalek/src/ff_field/lazy_field/eager.rs b/curve25519-dalek/src/ff_field/lazy_field/eager.rs new file mode 100644 index 000000000..839e51641 --- /dev/null +++ b/curve25519-dalek/src/ff_field/lazy_field/eager.rs @@ -0,0 +1,243 @@ +//! A safe exposure of the various `FieldElement` backends, with a unified API. + +use core::{ + fmt::Debug, + iter::{Product, Sum}, + marker::PhantomData, + ops::*, +}; + +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; +use typenum::{B1, U0, U256, Unsigned, type_operators::IsLessOrEqual}; + +use rand_core::{RngCore, TryRngCore}; + +use ff::Field; + +use super::*; + +/// A wrapper for any field so it may satisfy the `LazyField` traits. +/// +/// This allows developers to entirely use the `LazyField` traits, as all existing `Field` +/// implementations may be used with them via this compatibility shim. +#[derive(Copy, Clone, Default)] +pub struct EagerField(pub F, pub PhantomData); + +// Avoids `U: Send + Sync + Unsigned` +unsafe impl Send for EagerField {} +unsafe impl Sync for EagerField {} + +impl Debug for EagerField { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("EagerField") + .field("0", &self.0) + .field("1", &self.1) + .finish() + } +} + +impl EagerField { + const fn from(field: F) -> Self { + Self(field, PhantomData) + } +} + +impl ConditionallySelectable for EagerField { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self::from(<_>::conditional_select(&a.0, &b.0, choice)) + } +} +impl ConstantTimeEq for EagerField { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } +} +impl PartialEq for EagerField { + fn eq(&self, other: &Self) -> bool { + bool::from(self.ct_eq(other)) + } +} +impl Eq for EagerField {} +impl Neg for EagerField { + type Output = Self; + fn neg(self) -> Self { + Self::from(self.0.neg()) + } +} +impl Add for EagerField { + type Output = Self; + fn add(self, other: Self) -> Self { + Self::from(self.0.add(other.0)) + } +} +impl Sub for EagerField { + type Output = Self; + fn sub(self, other: Self) -> Self { + Self::from(self.0.sub(other.0)) + } +} +impl Mul for EagerField { + type Output = Self; + fn mul(self, other: Self) -> Self { + Self::from(self.0.mul(other.0)) + } +} +impl Sum for EagerField { + fn sum>(iter: I) -> Self { + Self::from(F::sum(iter.map(|item| item.0))) + } +} +impl Product for EagerField { + fn product>(iter: I) -> Self { + Self::from(F::product(iter.map(|item| item.0))) + } +} +impl<'a, F: Field> Add<&'a Self> for EagerField { + type Output = Self; + fn add(self, other: &'a Self) -> Self { + Self::from(self.0.add(&other.0)) + } +} +impl<'a, F: Field> Sub<&'a Self> for EagerField { + type Output = Self; + fn sub(self, other: &'a Self) -> Self { + Self::from(self.0.sub(&other.0)) + } +} +impl<'a, F: Field> Mul<&'a Self> for EagerField { + type Output = Self; + fn mul(self, other: &'a Self) -> Self { + Self::from(self.0.mul(&other.0)) + } +} +impl<'a, F: Field> Sum<&'a Self> for EagerField { + fn sum>(iter: I) -> Self { + Self::from(F::sum(iter.map(|item| &item.0))) + } +} +impl<'a, F: Field> Product<&'a Self> for EagerField { + fn product>(iter: I) -> Self { + Self::from(F::product(iter.map(|item| &item.0))) + } +} +impl AddAssign for EagerField { + fn add_assign(&mut self, other: Self) { + self.0.add_assign(other.0); + } +} +impl SubAssign for EagerField { + fn sub_assign(&mut self, other: Self) { + self.0.sub_assign(other.0); + } +} +impl MulAssign for EagerField { + fn mul_assign(&mut self, other: Self) { + self.0.mul_assign(other.0); + } +} +impl<'a, F: Field> AddAssign<&'a Self> for EagerField { + fn add_assign(&mut self, other: &'a Self) { + self.0.add_assign(&other.0); + } +} +impl<'a, F: Field> SubAssign<&'a Self> for EagerField { + fn sub_assign(&mut self, other: &'a Self) { + self.0.sub_assign(&other.0); + } +} +impl<'a, F: Field> MulAssign<&'a Self> for EagerField { + fn mul_assign(&mut self, other: &'a Self) { + self.0.mul_assign(&other.0); + } +} +impl Field for EagerField { + const ZERO: Self = Self::from(F::ZERO); + const ONE: Self = Self::from(F::ONE); + fn try_from_rng(rng: &mut R) -> Result { + F::try_from_rng(rng).map(Self::from) + } + fn square(&self) -> Self { + Self::from(self.0.square()) + } + fn double(&self) -> Self { + Self::from(self.0.double()) + } + fn invert(&self) -> CtOption { + self.0.invert().map(Self::from) + } + fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { + let (choice, root) = F::sqrt_ratio(&num.0, &div.0); + (choice, Self::from(root)) + } + fn random(rng: &mut R) -> Self { + Self::from(F::random(rng)) + } + fn is_zero(&self) -> Choice { + self.0.is_zero() + } + fn is_zero_vartime(&self) -> bool { + self.0.is_zero_vartime() + } + fn cube(&self) -> Self { + Self::from(self.0.cube()) + } + fn sqrt_alt(&self) -> (Choice, Self) { + let (choice, root) = self.0.sqrt_alt(); + (choice, Self::from(root)) + } + fn sqrt(&self) -> CtOption { + self.0.sqrt().map(Self::from) + } + fn pow>(&self, exp: S) -> Self { + Self::from(self.0.pow(exp)) + } + fn pow_vartime>(&self, exp: S) -> Self { + Self::from(self.0.pow_vartime(exp)) + } +} + +impl Reducible for EagerField { + type Output = EagerField; + fn reduce(&self) -> Self::Output { + Self::Output::from(self.0) + } +} + +impl, F: Field> LazyField + for EagerField +{ + // `U::MAX` does not exist. This should be well-defined/fully implemented and excessive + type Capacity = U256; + type Underlying = F; + + fn as_underlying(&self) -> &Self::Underlying { + &self.0 + } + + fn add< + V: Unsigned + Add>, + T: LazyField, + >( + self, + other: &T, + ) -> impl LazyField<>::Output, Capacity = Self::Capacity> { + EagerField::<>::Output, F>( + self.0 + other.as_underlying(), + PhantomData, + ) + } + + fn mul>( + self, + other: &T, + ) -> ::Output { + EagerField::from(self.0 * other.as_underlying()) + } +} + +#[cfg(feature = "zeroize")] +impl zeroize::Zeroize for EagerField { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} diff --git a/curve25519-dalek/src/ff_field/lazy_field25519.rs b/curve25519-dalek/src/ff_field/lazy_field25519.rs new file mode 100644 index 000000000..fb4d18f39 --- /dev/null +++ b/curve25519-dalek/src/ff_field/lazy_field25519.rs @@ -0,0 +1,56 @@ +use core::ops::Add; + +use typenum::{U0, Unsigned, type_operators::IsLessOrEqual}; + +use crate::{FieldElement, field::FieldElement as Underlying, lazy_field::*}; + +type ReducibleOutput = FieldElement; +impl Reducible for FieldElement +where + FieldElement: LazyField, +{ + /// The reduced element. + type Output = ReducibleOutput; + /// Reduce to a reduced element. + fn reduce(&self) -> Self::Output { + let res = ReducibleOutput::from(Underlying::from_bytes(&self.0.to_bytes())); + // For God knows what reason, Rust doesn't realize this is the same type + unsafe { *((&res as *const _) as *const _) } + } +} + +/// Sealed trait for the capacity of the `FieldElement` backend. +pub trait UnderlyingCapacity { + type Capacity: Unsigned; +} + +impl LazyField for FieldElement { + type Capacity = ::Capacity; + type Underlying = Underlying; + + fn as_underlying(&self) -> &Self::Underlying { + &self.0 + } + + fn add< + V: Unsigned + + Add< + CapacityUsed, + Output: Unsigned + IsLessOrEqual<::Capacity>, + >, + T: LazyField, + >( + self, + other: &T, + ) -> impl LazyField<>::Output, Capacity = Self::Capacity> { + FieldElement::<>::Output>::from(&self.0 + other.as_underlying()) + } + + fn mul>( + self, + other: &T, + ) -> ::Output { + let unreduced = &self.0 * other.as_underlying(); + FieldElement::from(Underlying::from_bytes(&unreduced.to_bytes())) + } +} diff --git a/curve25519-dalek/src/field.rs b/curve25519-dalek/src/field.rs index a25a73780..90dd78caf 100644 --- a/curve25519-dalek/src/field.rs +++ b/curve25519-dalek/src/field.rs @@ -100,7 +100,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", feature = "expose-field"))] pub(crate) fn from_bytes_wide(bytes: &[u8; 64]) -> Self { let mut fl = [0u8; 32]; let mut gl = [0u8; 32]; diff --git a/curve25519-dalek/src/lib.rs b/curve25519-dalek/src/lib.rs index 24e0fa5b8..a2399e902 100644 --- a/curve25519-dalek/src/lib.rs +++ b/curve25519-dalek/src/lib.rs @@ -94,6 +94,13 @@ pub use crate::{ edwards::EdwardsPoint, montgomery::MontgomeryPoint, ristretto::RistrettoPoint, scalar::Scalar, }; +#[cfg(feature = "expose-field")] +mod ff_field; +#[cfg(feature = "expose-field")] +pub(crate) use ff_field::UnderlyingCapacity; +#[cfg(feature = "expose-field")] +pub use ff_field::{FieldElement, lazy_field}; + // Build time diagnostics for validation #[cfg(curve25519_dalek_diagnostics = "build")] mod diagnostics; From fa523a03afbe58fda1c9e249bb14162ea7001c3b Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 04:55:41 -0400 Subject: [PATCH 02/19] Add a `const fn` to create a `FieldElement` --- .../src/backend/serial/fiat_u32/field.rs | 2 +- .../src/backend/serial/fiat_u64/field.rs | 2 +- curve25519-dalek/src/backend/serial/u32/field.rs | 2 +- curve25519-dalek/src/backend/serial/u64/field.rs | 4 ++-- curve25519-dalek/src/ff_field.rs | 14 ++++++++++++++ 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs index 91ba3f5a7..8c57eb09e 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs @@ -241,7 +241,7 @@ impl FieldElement2625 { /// Serialize this `FieldElement51` to a 32-byte array. The /// encoding is canonical. - pub fn to_bytes(self) -> [u8; 32] { + pub const fn to_bytes(self) -> [u8; 32] { let mut bytes = [0u8; 32]; fiat_25519_to_bytes(&mut bytes, &self.0); bytes diff --git a/curve25519-dalek/src/backend/serial/fiat_u64/field.rs b/curve25519-dalek/src/backend/serial/fiat_u64/field.rs index dfd36b47f..54d5b3e89 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u64/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u64/field.rs @@ -218,7 +218,7 @@ impl FieldElement51 { /// Serialize this `FieldElement51` to a 32-byte array. The /// encoding is canonical. - pub fn to_bytes(self) -> [u8; 32] { + pub const fn to_bytes(self) -> [u8; 32] { let mut bytes = [0u8; 32]; fiat_25519_to_bytes(&mut bytes, &self.0); bytes diff --git a/curve25519-dalek/src/backend/serial/u32/field.rs b/curve25519-dalek/src/backend/serial/u32/field.rs index 36a571d61..3c6939d86 100644 --- a/curve25519-dalek/src/backend/serial/u32/field.rs +++ b/curve25519-dalek/src/backend/serial/u32/field.rs @@ -434,7 +434,7 @@ impl FieldElement2625 { /// Serialize this `FieldElement51` to a 32-byte array. The /// encoding is canonical. #[allow(clippy::identity_op)] - pub fn to_bytes(self) -> [u8; 32] { + pub const fn to_bytes(self) -> [u8; 32] { let inp = &self.0; // Reduce the value represented by `in` to the range [0,2*p) let mut h: [u32; 10] = FieldElement2625::reduce([ diff --git a/curve25519-dalek/src/backend/serial/u64/field.rs b/curve25519-dalek/src/backend/serial/u64/field.rs index 9fe50dc61..8e1083bad 100644 --- a/curve25519-dalek/src/backend/serial/u64/field.rs +++ b/curve25519-dalek/src/backend/serial/u64/field.rs @@ -287,7 +287,7 @@ impl FieldElement51 { /// Given 64-bit input limbs, reduce to enforce the bound 2^(51 + epsilon). #[inline(always)] - fn reduce(mut limbs: [u64; 5]) -> FieldElement51 { + const fn reduce(mut limbs: [u64; 5]) -> FieldElement51 { const LOW_51_BIT_MASK: u64 = (1u64 << 51) - 1; // Since the input limbs are bounded by 2^64, the biggest @@ -365,7 +365,7 @@ impl FieldElement51 { /// Serialize this `FieldElement51` to a 32-byte array. The /// encoding is canonical. #[rustfmt::skip] // keep alignment of s[*] calculations - pub fn to_bytes(self) -> [u8; 32] { + pub const fn to_bytes(self) -> [u8; 32] { // Let h = limbs[0] + limbs[1]*2^51 + ... + limbs[4]*2^204. // // Write h = pq + r with 0 <= r < p. diff --git a/curve25519-dalek/src/ff_field.rs b/curve25519-dalek/src/ff_field.rs index 7ca50c7fc..d6a48d221 100644 --- a/curve25519-dalek/src/ff_field.rs +++ b/curve25519-dalek/src/ff_field.rs @@ -31,6 +31,20 @@ impl FieldElement { pub(crate) const fn from(underlying: Underlying) -> Self { Self(underlying, PhantomData) } + + /// Create a `FieldElement` within a `const` context. + pub const fn from_bytes(bytes: &[u8; 32]) -> Option { + let underlying = Underlying::from_bytes(bytes); + let canonical_bytes: [u8; 32] = underlying.to_bytes(); + let mut i = 0; + while i < 32 { + if canonical_bytes[i] != bytes[i] { + return None; + } + i += 1; + } + Some(Self::from(underlying)) + } } impl ConstantTimeEq for FieldElement { From be036149796b9b56e427e2063495bd46cd7772a5 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 05:04:34 -0400 Subject: [PATCH 03/19] Prevent leaking the underlying `FieldElement` implementation via the `LazyField` trait Adds a simple wrapper which stops dependents from accessing the underlying type. --- curve25519-dalek/src/ff_field.rs | 40 +++++++++++-------- .../src/ff_field/lazy_field25519.rs | 11 ++--- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/curve25519-dalek/src/ff_field.rs b/curve25519-dalek/src/ff_field.rs index d6a48d221..f849e4603 100644 --- a/curve25519-dalek/src/ff_field.rs +++ b/curve25519-dalek/src/ff_field.rs @@ -16,6 +16,14 @@ pub mod lazy_field; mod lazy_field25519; pub(crate) use lazy_field25519::UnderlyingCapacity; +/* + The `Underlying` struct is exposed via the `LazyField` trait. As the underlying field + implementations don't have safe arithmetic, we don't want to expose their arithmetic, but we must + expose _them_. We solve this by wrapping them into the following struct. +*/ +#[derive(Clone, Copy)] +pub struct OpaqueFieldElement(Underlying); + /// A `FieldElement` represents an element of the field /// \\( \mathbb Z / (2\^{255} - 19)\\). /// @@ -23,13 +31,13 @@ pub(crate) use lazy_field25519::UnderlyingCapacity; /// implementations. Its size and internals are not guaranteed to have /// any specific properties and are not covered by semver. #[derive(Copy)] -pub struct FieldElement(pub(crate) Underlying, pub(crate) PhantomData); +pub struct FieldElement(pub(crate) OpaqueFieldElement, pub(crate) PhantomData); unsafe impl Send for FieldElement {} unsafe impl Sync for FieldElement {} impl FieldElement { pub(crate) const fn from(underlying: Underlying) -> Self { - Self(underlying, PhantomData) + Self(OpaqueFieldElement(underlying), PhantomData) } /// Create a `FieldElement` within a `const` context. @@ -49,7 +57,7 @@ impl FieldElement { impl ConstantTimeEq for FieldElement { fn ct_eq(&self, other: &Self) -> Choice { - self.0.ct_eq(&other.0) + self.0.0.ct_eq(&other.0.0) } } impl PartialEq for FieldElement { @@ -73,20 +81,20 @@ impl Default for FieldElement { impl Debug for FieldElement { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.0.fmt(f) + self.0.0.fmt(f) } } impl ConditionallySelectable for FieldElement { fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { - Self::from(<_>::conditional_select(&a.0, &b.0, choice)) + Self::from(<_>::conditional_select(&a.0.0, &b.0.0, choice)) } } impl Add<&FieldElement> for FieldElement { type Output = Self; fn add(self, other: &Self) -> Self { - let unreduced = &self.0 + &other.0; + let unreduced = &self.0.0 + &other.0.0; // Force a reduction Self::from(Underlying::from_bytes(&unreduced.to_bytes())) } @@ -112,7 +120,7 @@ impl AddAssign<&FieldElement> for FieldElement { impl Sub<&FieldElement> for FieldElement { type Output = Self; fn sub(self, other: &Self) -> Self { - let unreduced = &self.0 - &other.0; + let unreduced = &self.0.0 - &other.0.0; // Force a reduction Self::from(Underlying::from_bytes(&unreduced.to_bytes())) } @@ -138,15 +146,15 @@ impl SubAssign<&FieldElement> for FieldElement { impl Neg for FieldElement { type Output = Self; fn neg(mut self) -> Self { - self.0.negate(); - Self::from(Underlying::from_bytes(&self.0.to_bytes())) + self.0.0.negate(); + Self::from(Underlying::from_bytes(&self.0.0.to_bytes())) } } impl Mul<&FieldElement> for FieldElement { type Output = Self; fn mul(self, other: &Self) -> Self { - let unreduced = &self.0 * &other.0; + let unreduced = &self.0.0 * &other.0.0; // Force a reduction Self::from(Underlying::from_bytes(&unreduced.to_bytes())) } @@ -218,11 +226,11 @@ impl Field for FieldElement { } fn invert(&self) -> CtOption { - CtOption::new(Self::from(self.0.invert()), !self.is_zero()) + CtOption::new(Self::from(self.0.0.invert()), !self.is_zero()) } fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { - let res = Underlying::sqrt_ratio_i(&num.0, &div.0); + let res = Underlying::sqrt_ratio_i(&num.0.0, &div.0.0); (res.0, Self::from(res.1)) } } @@ -232,7 +240,7 @@ impl PrimeField for FieldElement { fn from_repr(repr: Self::Repr) -> CtOption { let res = Self::from(Underlying::from_bytes(&repr)); - CtOption::new(res, repr.ct_eq(&res.0.to_bytes())) + CtOption::new(res, repr.ct_eq(&res.0.0.to_bytes())) } fn from_repr_vartime(repr: Self::Repr) -> Option { @@ -240,11 +248,11 @@ impl PrimeField for FieldElement { } fn to_repr(&self) -> Self::Repr { - self.0.to_bytes() + self.0.0.to_bytes() } fn is_odd(&self) -> Choice { - Choice::from(self.0.to_bytes()[0] & 1) + Choice::from(self.0.0.to_bytes()[0] & 1) } const MODULUS: &'static str = @@ -311,6 +319,6 @@ impl FromUniformBytes<64> for FieldElement { #[cfg(feature = "zeroize")] impl zeroize::Zeroize for FieldElement { fn zeroize(&mut self) { - self.0.zeroize(); + self.0.0.zeroize(); } } diff --git a/curve25519-dalek/src/ff_field/lazy_field25519.rs b/curve25519-dalek/src/ff_field/lazy_field25519.rs index fb4d18f39..8c2aae698 100644 --- a/curve25519-dalek/src/ff_field/lazy_field25519.rs +++ b/curve25519-dalek/src/ff_field/lazy_field25519.rs @@ -2,7 +2,8 @@ use core::ops::Add; use typenum::{U0, Unsigned, type_operators::IsLessOrEqual}; -use crate::{FieldElement, field::FieldElement as Underlying, lazy_field::*}; +use super::{FieldElement, OpaqueFieldElement, lazy_field::*}; +use crate::field::FieldElement as Underlying; type ReducibleOutput = FieldElement; impl Reducible for FieldElement @@ -13,7 +14,7 @@ where type Output = ReducibleOutput; /// Reduce to a reduced element. fn reduce(&self) -> Self::Output { - let res = ReducibleOutput::from(Underlying::from_bytes(&self.0.to_bytes())); + let res = ReducibleOutput::from(Underlying::from_bytes(&self.0.0.to_bytes())); // For God knows what reason, Rust doesn't realize this is the same type unsafe { *((&res as *const _) as *const _) } } @@ -26,7 +27,7 @@ pub trait UnderlyingCapacity { impl LazyField for FieldElement { type Capacity = ::Capacity; - type Underlying = Underlying; + type Underlying = OpaqueFieldElement; fn as_underlying(&self) -> &Self::Underlying { &self.0 @@ -43,14 +44,14 @@ impl LazyField for FieldElement impl LazyField<>::Output, Capacity = Self::Capacity> { - FieldElement::<>::Output>::from(&self.0 + other.as_underlying()) + FieldElement::<>::Output>::from(&self.0.0 + &other.as_underlying().0) } fn mul>( self, other: &T, ) -> ::Output { - let unreduced = &self.0 * other.as_underlying(); + let unreduced = &self.0.0 * &other.as_underlying().0; FieldElement::from(Underlying::from_bytes(&unreduced.to_bytes())) } } From 5db642d357daf3282189e9f1a7515bda00b50557 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 05:08:53 -0400 Subject: [PATCH 04/19] Tweak documentation --- curve25519-dalek/src/ff_field.rs | 3 +++ curve25519-dalek/src/ff_field/lazy_field.rs | 5 ++--- curve25519-dalek/src/ff_field/lazy_field25519.rs | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/curve25519-dalek/src/ff_field.rs b/curve25519-dalek/src/ff_field.rs index f849e4603..ddde60d32 100644 --- a/curve25519-dalek/src/ff_field.rs +++ b/curve25519-dalek/src/ff_field.rs @@ -30,6 +30,9 @@ pub struct OpaqueFieldElement(Underlying); /// 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. +/// +/// Usage is recommended to be done via `LazyFieldWithCapacity` which is +/// comprehensive to all backends. #[derive(Copy)] pub struct FieldElement(pub(crate) OpaqueFieldElement, pub(crate) PhantomData); unsafe impl Send for FieldElement {} diff --git a/curve25519-dalek/src/ff_field/lazy_field.rs b/curve25519-dalek/src/ff_field/lazy_field.rs index 21aac6b55..0901a2f3a 100644 --- a/curve25519-dalek/src/ff_field/lazy_field.rs +++ b/curve25519-dalek/src/ff_field/lazy_field.rs @@ -37,11 +37,10 @@ pub trait LazyField: /// A reference to the underlying type. /// - /// The underlying type has undefined semantics and MUST NOT be used directly. + /// The underlying type is allowed to have undefined semantics and MUST NOT be used directly. fn as_underlying(&self) -> &Self::Underlying; - /// Add two lazy elements, which reduce to the same field, where the result remains within the - /// capacity. + /// Add two lazy elements where the result remains within the capacity. fn add< V: Unsigned + Add>, T: LazyField, diff --git a/curve25519-dalek/src/ff_field/lazy_field25519.rs b/curve25519-dalek/src/ff_field/lazy_field25519.rs index 8c2aae698..cd3833583 100644 --- a/curve25519-dalek/src/ff_field/lazy_field25519.rs +++ b/curve25519-dalek/src/ff_field/lazy_field25519.rs @@ -21,6 +21,7 @@ where } /// Sealed trait for the capacity of the `FieldElement` backend. +// Rust believe this is in a public API, as it... technically is? so it must be `pub`. pub trait UnderlyingCapacity { type Capacity: Unsigned; } From c46c165428c92c18fd2ac81763a54ff66d35bc1f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 05:20:11 -0400 Subject: [PATCH 05/19] Don't reduce after `sub`, `neg`, `mul` All always return reduced outputs for all four backends present within curve25519-dalek. This may be an unnecessary design choice of the backends, offering potential future improvements, yet it's one we can take advantage of here. --- curve25519-dalek/src/ff_field.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/curve25519-dalek/src/ff_field.rs b/curve25519-dalek/src/ff_field.rs index ddde60d32..0c7911ed5 100644 --- a/curve25519-dalek/src/ff_field.rs +++ b/curve25519-dalek/src/ff_field.rs @@ -123,9 +123,7 @@ impl AddAssign<&FieldElement> for FieldElement { impl Sub<&FieldElement> for FieldElement { type Output = Self; fn sub(self, other: &Self) -> Self { - let unreduced = &self.0.0 - &other.0.0; - // Force a reduction - Self::from(Underlying::from_bytes(&unreduced.to_bytes())) + Self::from(&self.0.0 - &other.0.0) } } #[allow(clippy::op_ref)] @@ -149,17 +147,16 @@ impl SubAssign<&FieldElement> for FieldElement { impl Neg for FieldElement { type Output = Self; fn neg(mut self) -> Self { - self.0.0.negate(); - Self::from(Underlying::from_bytes(&self.0.0.to_bytes())) + // `negate` modifies in-place + let () = self.0.0.negate(); + Self::from(self.0.0) } } impl Mul<&FieldElement> for FieldElement { type Output = Self; fn mul(self, other: &Self) -> Self { - let unreduced = &self.0.0 * &other.0.0; - // Force a reduction - Self::from(Underlying::from_bytes(&unreduced.to_bytes())) + Self::from(&self.0.0 * &other.0.0) } } #[allow(clippy::op_ref)] From e0f24eb068591441ed4bc7e44d75b819272d65b0 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 05:23:05 -0400 Subject: [PATCH 06/19] Fix typos for 32-bit backends --- curve25519-dalek/src/backend/serial/fiat_u32/field.rs | 2 +- curve25519-dalek/src/backend/serial/u32/field.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs index 8c57eb09e..f6de317b1 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs @@ -271,6 +271,6 @@ impl FieldElement2625 { } #[cfg(feature = "expose-field")] -impl crate::UnderlyingCapacity for FieldElement51 { +impl crate::UnderlyingCapacity for FieldElement2625 { type Capacity = typenum::U3; } diff --git a/curve25519-dalek/src/backend/serial/u32/field.rs b/curve25519-dalek/src/backend/serial/u32/field.rs index 3c6939d86..f70a2edbd 100644 --- a/curve25519-dalek/src/backend/serial/u32/field.rs +++ b/curve25519-dalek/src/backend/serial/u32/field.rs @@ -607,6 +607,6 @@ impl FieldElement2625 { } #[cfg(feature = "expose-field")] -impl crate::UnderlyingCapacity for FieldElement51 { +impl crate::UnderlyingCapacity for FieldElement2625 { type Capacity = typenum::U3; } From 0aa0dfee4f7b7a4348d97e04fbbade74c01f5187 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 14:07:43 -0400 Subject: [PATCH 07/19] Rename "expose-field" to "hazmat" --- curve25519-dalek/Cargo.toml | 2 +- curve25519-dalek/src/backend/serial/fiat_u32/field.rs | 4 ++-- curve25519-dalek/src/backend/serial/fiat_u64/field.rs | 4 ++-- curve25519-dalek/src/backend/serial/u32/field.rs | 4 ++-- curve25519-dalek/src/backend/serial/u64/field.rs | 4 ++-- curve25519-dalek/src/field.rs | 2 +- curve25519-dalek/src/{ff_field.rs => hazmat.rs} | 0 curve25519-dalek/src/{ff_field => hazmat}/lazy_field.rs | 0 .../src/{ff_field => hazmat}/lazy_field/eager.rs | 0 .../src/{ff_field => hazmat}/lazy_field25519.rs | 0 curve25519-dalek/src/lib.rs | 8 ++------ 11 files changed, 12 insertions(+), 16 deletions(-) rename curve25519-dalek/src/{ff_field.rs => hazmat.rs} (100%) rename curve25519-dalek/src/{ff_field => hazmat}/lazy_field.rs (100%) rename curve25519-dalek/src/{ff_field => hazmat}/lazy_field/eager.rs (100%) rename curve25519-dalek/src/{ff_field => hazmat}/lazy_field25519.rs (100%) diff --git a/curve25519-dalek/Cargo.toml b/curve25519-dalek/Cargo.toml index d2113f895..ecf9cf363 100644 --- a/curve25519-dalek/Cargo.toml +++ b/curve25519-dalek/Cargo.toml @@ -81,7 +81,7 @@ default = ["alloc", "precomputed-tables", "zeroize"] alloc = ["zeroize?/alloc"] precomputed-tables = [] legacy_compatibility = [] -expose-field = ["rand_core", "ff", "typenum"] +hazmat = ["rand_core", "ff", "typenum"] group = ["dep:group", "rand_core"] group-bits = ["group", "ff/bits"] digest = ["dep:digest"] diff --git a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs index f6de317b1..cb09137a2 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs @@ -270,7 +270,7 @@ impl FieldElement2625 { } } -#[cfg(feature = "expose-field")] -impl crate::UnderlyingCapacity for FieldElement2625 { +#[cfg(feature = "hazmat")] +impl crate::hazmat::UnderlyingCapacity for FieldElement2625 { type Capacity = typenum::U3; } diff --git a/curve25519-dalek/src/backend/serial/fiat_u64/field.rs b/curve25519-dalek/src/backend/serial/fiat_u64/field.rs index 54d5b3e89..94b91b722 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u64/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u64/field.rs @@ -261,7 +261,7 @@ impl FieldElement51 { } } -#[cfg(feature = "expose-field")] -impl crate::UnderlyingCapacity for FieldElement51 { +#[cfg(feature = "hazmat")] +impl crate::hazmat::UnderlyingCapacity for FieldElement51 { type Capacity = typenum::U8; } diff --git a/curve25519-dalek/src/backend/serial/u32/field.rs b/curve25519-dalek/src/backend/serial/u32/field.rs index f70a2edbd..a976f19b4 100644 --- a/curve25519-dalek/src/backend/serial/u32/field.rs +++ b/curve25519-dalek/src/backend/serial/u32/field.rs @@ -606,7 +606,7 @@ impl FieldElement2625 { } } -#[cfg(feature = "expose-field")] -impl crate::UnderlyingCapacity for FieldElement2625 { +#[cfg(feature = "hazmat")] +impl crate::hazmat::UnderlyingCapacity for FieldElement2625 { type Capacity = typenum::U3; } diff --git a/curve25519-dalek/src/backend/serial/u64/field.rs b/curve25519-dalek/src/backend/serial/u64/field.rs index 8e1083bad..03dfcfdeb 100644 --- a/curve25519-dalek/src/backend/serial/u64/field.rs +++ b/curve25519-dalek/src/backend/serial/u64/field.rs @@ -574,7 +574,7 @@ impl FieldElement51 { } } -#[cfg(feature = "expose-field")] -impl crate::UnderlyingCapacity for FieldElement51 { +#[cfg(feature = "hazmat")] +impl crate::hazmat::UnderlyingCapacity for FieldElement51 { type Capacity = typenum::U8; } diff --git a/curve25519-dalek/src/field.rs b/curve25519-dalek/src/field.rs index 90dd78caf..35ef1b55a 100644 --- a/curve25519-dalek/src/field.rs +++ b/curve25519-dalek/src/field.rs @@ -100,7 +100,7 @@ impl ConstantTimeEq for FieldElement { impl FieldElement { /// Load a `FieldElement` from 64 bytes, by reducing modulo q. - #[cfg(any(feature = "digest", feature = "group", feature = "expose-field"))] + #[cfg(any(feature = "digest", feature = "group", feature = "hazmat"))] pub(crate) fn from_bytes_wide(bytes: &[u8; 64]) -> Self { let mut fl = [0u8; 32]; let mut gl = [0u8; 32]; diff --git a/curve25519-dalek/src/ff_field.rs b/curve25519-dalek/src/hazmat.rs similarity index 100% rename from curve25519-dalek/src/ff_field.rs rename to curve25519-dalek/src/hazmat.rs diff --git a/curve25519-dalek/src/ff_field/lazy_field.rs b/curve25519-dalek/src/hazmat/lazy_field.rs similarity index 100% rename from curve25519-dalek/src/ff_field/lazy_field.rs rename to curve25519-dalek/src/hazmat/lazy_field.rs diff --git a/curve25519-dalek/src/ff_field/lazy_field/eager.rs b/curve25519-dalek/src/hazmat/lazy_field/eager.rs similarity index 100% rename from curve25519-dalek/src/ff_field/lazy_field/eager.rs rename to curve25519-dalek/src/hazmat/lazy_field/eager.rs diff --git a/curve25519-dalek/src/ff_field/lazy_field25519.rs b/curve25519-dalek/src/hazmat/lazy_field25519.rs similarity index 100% rename from curve25519-dalek/src/ff_field/lazy_field25519.rs rename to curve25519-dalek/src/hazmat/lazy_field25519.rs diff --git a/curve25519-dalek/src/lib.rs b/curve25519-dalek/src/lib.rs index a2399e902..ad700ef66 100644 --- a/curve25519-dalek/src/lib.rs +++ b/curve25519-dalek/src/lib.rs @@ -94,12 +94,8 @@ pub use crate::{ edwards::EdwardsPoint, montgomery::MontgomeryPoint, ristretto::RistrettoPoint, scalar::Scalar, }; -#[cfg(feature = "expose-field")] -mod ff_field; -#[cfg(feature = "expose-field")] -pub(crate) use ff_field::UnderlyingCapacity; -#[cfg(feature = "expose-field")] -pub use ff_field::{FieldElement, lazy_field}; +#[cfg(feature = "hazmat")] +pub mod hazmat; // Build time diagnostics for validation #[cfg(curve25519_dalek_diagnostics = "build")] From 0b2e51741a81558b63a5578a3f360cd7632a20b2 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 14:10:06 -0400 Subject: [PATCH 08/19] Fix feature-gating of `FieldElement::from_bytes_wide` --- curve25519-dalek/src/field.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curve25519-dalek/src/field.rs b/curve25519-dalek/src/field.rs index 35ef1b55a..f753bec62 100644 --- a/curve25519-dalek/src/field.rs +++ b/curve25519-dalek/src/field.rs @@ -100,7 +100,7 @@ impl ConstantTimeEq for FieldElement { impl FieldElement { /// Load a `FieldElement` from 64 bytes, by reducing modulo q. - #[cfg(any(feature = "digest", feature = "group", feature = "hazmat"))] + #[cfg(any(feature = "digest", feature = "hazmat"))] pub(crate) fn from_bytes_wide(bytes: &[u8; 64]) -> Self { let mut fl = [0u8; 32]; let mut gl = [0u8; 32]; From 8c2f299200cb9591d6b4f13603320bb044173dd8 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 14:12:42 -0400 Subject: [PATCH 09/19] Update hazmat documentation --- curve25519-dalek/README.md | 2 +- curve25519-dalek/src/hazmat.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/curve25519-dalek/README.md b/curve25519-dalek/README.md index 43f479c09..484f09b5b 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. | -| `expose-field` | | Enables `FieldElement` satisfying `ff` traits and bespoke traits for lazy reduction | +| `hazmat` | | Enables `FieldElement` satisfying `ff` traits and bespoke traits for lazy reduction | | `group` | | Enables external `group` and `ff` crate traits. | | `group-bits` | | Enables `group` and impls `ff::PrimeFieldBits` for `Scalar`, and `FieldElement` if `expose-field`. | diff --git a/curve25519-dalek/src/hazmat.rs b/curve25519-dalek/src/hazmat.rs index 0c7911ed5..667806249 100644 --- a/curve25519-dalek/src/hazmat.rs +++ b/curve25519-dalek/src/hazmat.rs @@ -1,3 +1,5 @@ +//! Hazardous materials with no semver guarantees. + use core::{ fmt::Debug, iter::{Product, Sum}, From 0a8237505d2b20c8e2a2e35814004718531a797d Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 14:41:10 -0400 Subject: [PATCH 10/19] Add basic test for LazyField, EagerField --- curve25519-dalek/src/hazmat.rs | 1 + curve25519-dalek/src/hazmat/lazy_field.rs | 16 +++++-- .../src/hazmat/lazy_field/eager.rs | 7 ++- .../src/hazmat/lazy_field25519.rs | 47 ++++++++++++++++++- 4 files changed, 65 insertions(+), 6 deletions(-) diff --git a/curve25519-dalek/src/hazmat.rs b/curve25519-dalek/src/hazmat.rs index 667806249..b166a9625 100644 --- a/curve25519-dalek/src/hazmat.rs +++ b/curve25519-dalek/src/hazmat.rs @@ -18,6 +18,7 @@ pub mod lazy_field; mod lazy_field25519; pub(crate) use lazy_field25519::UnderlyingCapacity; +/// An opaque view of the field element backend. /* The `Underlying` struct is exposed via the `LazyField` trait. As the underlying field implementations don't have safe arithmetic, we don't want to expose their arithmetic, but we must diff --git a/curve25519-dalek/src/hazmat/lazy_field.rs b/curve25519-dalek/src/hazmat/lazy_field.rs index 0901a2f3a..c10bb6c62 100644 --- a/curve25519-dalek/src/hazmat/lazy_field.rs +++ b/curve25519-dalek/src/hazmat/lazy_field.rs @@ -2,7 +2,10 @@ use core::{fmt::Debug, ops::Add}; -use typenum::{B1, U0, Unsigned, type_operators::IsLessOrEqual}; +use typenum::{ + B1, U0, Unsigned, + type_operators::{Cmp, IsLessOrEqual}, +}; use ff::Field; @@ -47,7 +50,12 @@ pub trait LazyField: >( self, other: &T, - ) -> impl LazyField<>::Output, Capacity = Self::Capacity>; + ) -> impl Reducible::Output> + + LazyField< + >::Output, + Capacity = Self::Capacity, + Underlying = Self::Underlying, + >; /// Multiply two lazy elements. /// @@ -65,5 +73,5 @@ pub trait LazyField: /// /// `LazyFieldWithCapacity` is _recommended_ due to the widespread popularity of 255-bit /// fields. -pub trait LazyFieldWithCapacity {} -impl> LazyFieldWithCapacity for F {} +pub trait LazyFieldWithCapacity>: LazyField {} +impl, F: LazyField> LazyFieldWithCapacity for F {} diff --git a/curve25519-dalek/src/hazmat/lazy_field/eager.rs b/curve25519-dalek/src/hazmat/lazy_field/eager.rs index 839e51641..a4e734dd3 100644 --- a/curve25519-dalek/src/hazmat/lazy_field/eager.rs +++ b/curve25519-dalek/src/hazmat/lazy_field/eager.rs @@ -220,7 +220,12 @@ impl, F: Field> LazyFi >( self, other: &T, - ) -> impl LazyField<>::Output, Capacity = Self::Capacity> { + ) -> impl Reducible::Output> + + LazyField< + >::Output, + Capacity = Self::Capacity, + Underlying = Self::Underlying, + > { EagerField::<>::Output, F>( self.0 + other.as_underlying(), PhantomData, diff --git a/curve25519-dalek/src/hazmat/lazy_field25519.rs b/curve25519-dalek/src/hazmat/lazy_field25519.rs index cd3833583..706932056 100644 --- a/curve25519-dalek/src/hazmat/lazy_field25519.rs +++ b/curve25519-dalek/src/hazmat/lazy_field25519.rs @@ -44,7 +44,12 @@ impl LazyField for FieldElement( self, other: &T, - ) -> impl LazyField<>::Output, Capacity = Self::Capacity> { + ) -> impl Reducible::Output> + + LazyField< + >::Output, + Capacity = Self::Capacity, + Underlying = Self::Underlying, + > { FieldElement::<>::Output>::from(&self.0.0 + &other.as_underlying().0) } @@ -56,3 +61,43 @@ impl LazyField for FieldElement) + .add(&EagerField(b, PhantomData::)) + .add(&EagerField(c, PhantomData::)) + .mul( + &EagerField(d, PhantomData::) + .add(&EagerField(e, PhantomData::)) + .add(&EagerField(f, PhantomData::)) + ) + .0, + expected + ); + } +} From 67e63ee4bc7de10a7ce8a2f874e9be905fdb3679 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 14:52:54 -0400 Subject: [PATCH 11/19] Correct `U1` as the identity for `CapacityUsed`, not `U0` --- curve25519-dalek/src/hazmat.rs | 4 +- curve25519-dalek/src/hazmat/lazy_field.rs | 8 ++-- .../src/hazmat/lazy_field/eager.rs | 44 +++++++++---------- .../src/hazmat/lazy_field25519.rs | 18 ++++---- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/curve25519-dalek/src/hazmat.rs b/curve25519-dalek/src/hazmat.rs index b166a9625..40831de38 100644 --- a/curve25519-dalek/src/hazmat.rs +++ b/curve25519-dalek/src/hazmat.rs @@ -8,7 +8,7 @@ use core::{ }; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; -use typenum::{U0, Unsigned}; +use typenum::{U1, Unsigned}; use ff::{Field, FromUniformBytes, PrimeField}; @@ -37,7 +37,7 @@ pub struct OpaqueFieldElement(Underlying); /// Usage is recommended to be done via `LazyFieldWithCapacity` which is /// comprehensive to all backends. #[derive(Copy)] -pub struct FieldElement(pub(crate) OpaqueFieldElement, pub(crate) PhantomData); +pub struct FieldElement(pub(crate) OpaqueFieldElement, pub(crate) PhantomData); unsafe impl Send for FieldElement {} unsafe impl Sync for FieldElement {} diff --git a/curve25519-dalek/src/hazmat/lazy_field.rs b/curve25519-dalek/src/hazmat/lazy_field.rs index c10bb6c62..11c973e5e 100644 --- a/curve25519-dalek/src/hazmat/lazy_field.rs +++ b/curve25519-dalek/src/hazmat/lazy_field.rs @@ -3,7 +3,7 @@ use core::{fmt::Debug, ops::Add}; use typenum::{ - B1, U0, Unsigned, + B1, U1, Unsigned, type_operators::{Cmp, IsLessOrEqual}, }; @@ -15,7 +15,7 @@ pub use eager::*; /// An element which can be reduced. pub trait Reducible { /// The reduced element. - type Output: Field + LazyField; + type Output: Field + LazyField; /// Reduce to a reduced element. fn reduce(&self) -> Self::Output; } @@ -73,5 +73,5 @@ pub trait LazyField: /// /// `LazyFieldWithCapacity` is _recommended_ due to the widespread popularity of 255-bit /// fields. -pub trait LazyFieldWithCapacity>: LazyField {} -impl, F: LazyField> LazyFieldWithCapacity for F {} +pub trait LazyFieldWithCapacity>: LazyField {} +impl, F: LazyField> LazyFieldWithCapacity for F {} diff --git a/curve25519-dalek/src/hazmat/lazy_field/eager.rs b/curve25519-dalek/src/hazmat/lazy_field/eager.rs index a4e734dd3..615c906d6 100644 --- a/curve25519-dalek/src/hazmat/lazy_field/eager.rs +++ b/curve25519-dalek/src/hazmat/lazy_field/eager.rs @@ -8,7 +8,7 @@ use core::{ }; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; -use typenum::{B1, U0, U256, Unsigned, type_operators::IsLessOrEqual}; +use typenum::{B1, U1, U256, Unsigned, type_operators::IsLessOrEqual}; use rand_core::{RngCore, TryRngCore}; @@ -36,13 +36,13 @@ impl Debug for EagerField { } } -impl EagerField { +impl EagerField { const fn from(field: F) -> Self { Self(field, PhantomData) } } -impl ConditionallySelectable for EagerField { +impl ConditionallySelectable for EagerField { fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { Self::from(<_>::conditional_select(&a.0, &b.0, choice)) } @@ -58,99 +58,99 @@ impl PartialEq for EagerField { } } impl Eq for EagerField {} -impl Neg for EagerField { +impl Neg for EagerField { type Output = Self; fn neg(self) -> Self { Self::from(self.0.neg()) } } -impl Add for EagerField { +impl Add for EagerField { type Output = Self; fn add(self, other: Self) -> Self { Self::from(self.0.add(other.0)) } } -impl Sub for EagerField { +impl Sub for EagerField { type Output = Self; fn sub(self, other: Self) -> Self { Self::from(self.0.sub(other.0)) } } -impl Mul for EagerField { +impl Mul for EagerField { type Output = Self; fn mul(self, other: Self) -> Self { Self::from(self.0.mul(other.0)) } } -impl Sum for EagerField { +impl Sum for EagerField { fn sum>(iter: I) -> Self { Self::from(F::sum(iter.map(|item| item.0))) } } -impl Product for EagerField { +impl Product for EagerField { fn product>(iter: I) -> Self { Self::from(F::product(iter.map(|item| item.0))) } } -impl<'a, F: Field> Add<&'a Self> for EagerField { +impl<'a, F: Field> Add<&'a Self> for EagerField { type Output = Self; fn add(self, other: &'a Self) -> Self { Self::from(self.0.add(&other.0)) } } -impl<'a, F: Field> Sub<&'a Self> for EagerField { +impl<'a, F: Field> Sub<&'a Self> for EagerField { type Output = Self; fn sub(self, other: &'a Self) -> Self { Self::from(self.0.sub(&other.0)) } } -impl<'a, F: Field> Mul<&'a Self> for EagerField { +impl<'a, F: Field> Mul<&'a Self> for EagerField { type Output = Self; fn mul(self, other: &'a Self) -> Self { Self::from(self.0.mul(&other.0)) } } -impl<'a, F: Field> Sum<&'a Self> for EagerField { +impl<'a, F: Field> Sum<&'a Self> for EagerField { fn sum>(iter: I) -> Self { Self::from(F::sum(iter.map(|item| &item.0))) } } -impl<'a, F: Field> Product<&'a Self> for EagerField { +impl<'a, F: Field> Product<&'a Self> for EagerField { fn product>(iter: I) -> Self { Self::from(F::product(iter.map(|item| &item.0))) } } -impl AddAssign for EagerField { +impl AddAssign for EagerField { fn add_assign(&mut self, other: Self) { self.0.add_assign(other.0); } } -impl SubAssign for EagerField { +impl SubAssign for EagerField { fn sub_assign(&mut self, other: Self) { self.0.sub_assign(other.0); } } -impl MulAssign for EagerField { +impl MulAssign for EagerField { fn mul_assign(&mut self, other: Self) { self.0.mul_assign(other.0); } } -impl<'a, F: Field> AddAssign<&'a Self> for EagerField { +impl<'a, F: Field> AddAssign<&'a Self> for EagerField { fn add_assign(&mut self, other: &'a Self) { self.0.add_assign(&other.0); } } -impl<'a, F: Field> SubAssign<&'a Self> for EagerField { +impl<'a, F: Field> SubAssign<&'a Self> for EagerField { fn sub_assign(&mut self, other: &'a Self) { self.0.sub_assign(&other.0); } } -impl<'a, F: Field> MulAssign<&'a Self> for EagerField { +impl<'a, F: Field> MulAssign<&'a Self> for EagerField { fn mul_assign(&mut self, other: &'a Self) { self.0.mul_assign(&other.0); } } -impl Field for EagerField { +impl Field for EagerField { const ZERO: Self = Self::from(F::ZERO); const ONE: Self = Self::from(F::ONE); fn try_from_rng(rng: &mut R) -> Result { @@ -197,7 +197,7 @@ impl Field for EagerField { } impl Reducible for EagerField { - type Output = EagerField; + type Output = EagerField; fn reduce(&self) -> Self::Output { Self::Output::from(self.0) } diff --git a/curve25519-dalek/src/hazmat/lazy_field25519.rs b/curve25519-dalek/src/hazmat/lazy_field25519.rs index 706932056..8a6ff89a1 100644 --- a/curve25519-dalek/src/hazmat/lazy_field25519.rs +++ b/curve25519-dalek/src/hazmat/lazy_field25519.rs @@ -1,11 +1,11 @@ use core::ops::Add; -use typenum::{U0, Unsigned, type_operators::IsLessOrEqual}; +use typenum::{U1, Unsigned, type_operators::IsLessOrEqual}; use super::{FieldElement, OpaqueFieldElement, lazy_field::*}; use crate::field::FieldElement as Underlying; -type ReducibleOutput = FieldElement; +type ReducibleOutput = FieldElement; impl Reducible for FieldElement where FieldElement: LazyField, @@ -70,7 +70,7 @@ mod tests { use crate::hazmat::lazy_field::{EagerField, LazyField, LazyFieldWithCapacity, Reducible}; #[test] - fn three_add_and_then_mul() { + fn lazy_add_then_mul() { use crate::hazmat::FieldElement; use core::marker::PhantomData; use ff::Field; @@ -88,13 +88,13 @@ mod tests { assert_eq!(a.add(&b).add(&c).mul(&d.add(&e).add(&f)), expected); assert_eq!( - EagerField(a, PhantomData::) - .add(&EagerField(b, PhantomData::)) - .add(&EagerField(c, PhantomData::)) + EagerField(a, PhantomData::) + .add(&EagerField(b, PhantomData::)) + .add(&EagerField(c, PhantomData::)) .mul( - &EagerField(d, PhantomData::) - .add(&EagerField(e, PhantomData::)) - .add(&EagerField(f, PhantomData::)) + &EagerField(d, PhantomData::) + .add(&EagerField(e, PhantomData::)) + .add(&EagerField(f, PhantomData::)) ) .0, expected From 9d98b184c05edc7d0371def76b8f529c93d4757e Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 15:17:53 -0400 Subject: [PATCH 12/19] Test `LazyFieldWithCapacity` --- curve25519-dalek/src/hazmat/lazy_field.rs | 22 +++++--- .../src/hazmat/lazy_field/eager.rs | 4 +- .../src/hazmat/lazy_field25519.rs | 55 ++++++++++++++----- 3 files changed, 57 insertions(+), 24 deletions(-) diff --git a/curve25519-dalek/src/hazmat/lazy_field.rs b/curve25519-dalek/src/hazmat/lazy_field.rs index 11c973e5e..d737c7e4a 100644 --- a/curve25519-dalek/src/hazmat/lazy_field.rs +++ b/curve25519-dalek/src/hazmat/lazy_field.rs @@ -2,10 +2,7 @@ use core::{fmt::Debug, ops::Add}; -use typenum::{ - B1, U1, Unsigned, - type_operators::{Cmp, IsLessOrEqual}, -}; +use typenum::{B1, U1, Unsigned, type_operators::IsLessOrEqual}; use ff::Field; @@ -43,6 +40,9 @@ pub trait LazyField: /// The underlying type is allowed to have undefined semantics and MUST NOT be used directly. fn as_underlying(&self) -> &Self::Underlying; + // The type corresponding to a certain usage of capacity. + // type ForCapacityUsed: LazyField + /// Add two lazy elements where the result remains within the capacity. fn add< V: Unsigned + Add>, @@ -50,11 +50,11 @@ pub trait LazyField: >( self, other: &T, - ) -> impl Reducible::Output> - + LazyField< + ) -> impl LazyField< >::Output, Capacity = Self::Capacity, Underlying = Self::Underlying, + Output = ::Output, >; /// Multiply two lazy elements. @@ -73,5 +73,11 @@ pub trait LazyField: /// /// `LazyFieldWithCapacity` is _recommended_ due to the widespread popularity of 255-bit /// fields. -pub trait LazyFieldWithCapacity>: LazyField {} -impl, F: LazyField> LazyFieldWithCapacity for F {} +pub trait LazyFieldWithCapacity>: + LazyField +{ +} +impl, F: LazyField> + LazyFieldWithCapacity for F +{ +} diff --git a/curve25519-dalek/src/hazmat/lazy_field/eager.rs b/curve25519-dalek/src/hazmat/lazy_field/eager.rs index 615c906d6..0ab1e291b 100644 --- a/curve25519-dalek/src/hazmat/lazy_field/eager.rs +++ b/curve25519-dalek/src/hazmat/lazy_field/eager.rs @@ -220,11 +220,11 @@ impl, F: Field> LazyFi >( self, other: &T, - ) -> impl Reducible::Output> - + LazyField< + ) -> impl LazyField< >::Output, Capacity = Self::Capacity, Underlying = Self::Underlying, + Output = ::Output, > { EagerField::<>::Output, F>( self.0 + other.as_underlying(), diff --git a/curve25519-dalek/src/hazmat/lazy_field25519.rs b/curve25519-dalek/src/hazmat/lazy_field25519.rs index 8a6ff89a1..c3665e087 100644 --- a/curve25519-dalek/src/hazmat/lazy_field25519.rs +++ b/curve25519-dalek/src/hazmat/lazy_field25519.rs @@ -44,11 +44,11 @@ impl LazyField for FieldElement( self, other: &T, - ) -> impl Reducible::Output> - + LazyField< + ) -> impl LazyField< >::Output, Capacity = Self::Capacity, Underlying = Self::Underlying, + Output = ::Output, > { FieldElement::<>::Output>::from(&self.0.0 + &other.as_underlying().0) } @@ -64,16 +64,34 @@ impl LazyField for FieldElement>( + a: F, + b: F, + c: F, + d: F, + e: F, + f: F, + ) -> ::Output + where + U2: IsLessOrEqual, + U3: IsLessOrEqual, + { + let ab = a.add(&b); + let abc = ab.add(&c); + let de = d.add(&e); + let def = de.add(&f); + abc.mul(&def) + } #[test] fn lazy_add_then_mul() { use crate::hazmat::FieldElement; use core::marker::PhantomData; use ff::Field; + use rand_core::{OsRng, TryRngCore}; let mut rng = OsRng.unwrap_err(); @@ -85,19 +103,28 @@ mod tests { let f = FieldElement::random(&mut rng); let expected = (a + b + c) * (d + e + f); - assert_eq!(a.add(&b).add(&c).mul(&d.add(&e).add(&f)), expected); + assert_eq!( + LazyField::add(a, &b) + .add(&c) + .mul(&LazyField::add(d, &e).add(&f)), + expected + ); + assert_eq!(add_triple_then_mul(a, b, c, d, e, f), expected); + + let a = EagerField(a, PhantomData::); + let b = EagerField(b, PhantomData::); + let c = EagerField(c, PhantomData::); + let d = EagerField(d, PhantomData::); + let e = EagerField(e, PhantomData::); + let f = EagerField(f, PhantomData::); assert_eq!( - EagerField(a, PhantomData::) - .add(&EagerField(b, PhantomData::)) - .add(&EagerField(c, PhantomData::)) - .mul( - &EagerField(d, PhantomData::) - .add(&EagerField(e, PhantomData::)) - .add(&EagerField(f, PhantomData::)) - ) + LazyField::add(a, &b) + .add(&c) + .mul(&LazyField::add(d, &e).add(&f)) .0, expected ); + assert_eq!(add_triple_then_mul(a, b, c, d, e, f).0, expected); } } From b70643aa795ac290962c204d378ba15884681a6e Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 15:56:17 -0400 Subject: [PATCH 13/19] Remove stray mention to `expose-field` --- 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 484f09b5b..004bf8aab 100644 --- a/curve25519-dalek/README.md +++ b/curve25519-dalek/README.md @@ -57,7 +57,7 @@ curve25519-dalek = ">= 5.0, < 5.2" | `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. | | `hazmat` | | Enables `FieldElement` satisfying `ff` traits and bespoke traits for lazy reduction | | `group` | | Enables external `group` and `ff` crate traits. | -| `group-bits` | | Enables `group` and impls `ff::PrimeFieldBits` for `Scalar`, and `FieldElement` if `expose-field`. | +| `group-bits` | | Enables `group` and impls `ff::PrimeFieldBits` for `Scalar`, and `FieldElement` if `hazmat`. | To disable the default features when using `curve25519-dalek` as a dependency, add `default-features = false` to the dependency in your `Cargo.toml`. To From d600742b260e3284b56cf8470aed39764a4a4fe6 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 16:19:14 -0400 Subject: [PATCH 14/19] Decrease 32-bit backend capacity, increase iterations within test --- .../src/backend/serial/fiat_u32/field.rs | 2 +- .../src/backend/serial/u32/field.rs | 2 +- curve25519-dalek/src/hazmat.rs | 2 +- .../src/hazmat/lazy_field25519.rs | 65 +++++++------------ 4 files changed, 28 insertions(+), 43 deletions(-) diff --git a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs index cb09137a2..c0ae60389 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs @@ -272,5 +272,5 @@ impl FieldElement2625 { #[cfg(feature = "hazmat")] impl crate::hazmat::UnderlyingCapacity for FieldElement2625 { - type Capacity = typenum::U3; + type Capacity = typenum::U2; } diff --git a/curve25519-dalek/src/backend/serial/u32/field.rs b/curve25519-dalek/src/backend/serial/u32/field.rs index a976f19b4..c2cdeb74a 100644 --- a/curve25519-dalek/src/backend/serial/u32/field.rs +++ b/curve25519-dalek/src/backend/serial/u32/field.rs @@ -608,5 +608,5 @@ impl FieldElement2625 { #[cfg(feature = "hazmat")] impl crate::hazmat::UnderlyingCapacity for FieldElement2625 { - type Capacity = typenum::U3; + type Capacity = typenum::U2; } diff --git a/curve25519-dalek/src/hazmat.rs b/curve25519-dalek/src/hazmat.rs index 40831de38..a7b57ef8f 100644 --- a/curve25519-dalek/src/hazmat.rs +++ b/curve25519-dalek/src/hazmat.rs @@ -34,7 +34,7 @@ pub struct OpaqueFieldElement(Underlying); /// implementations. Its size and internals are not guaranteed to have /// any specific properties and are not covered by semver. /// -/// Usage is recommended to be done via `LazyFieldWithCapacity` which is +/// Usage is recommended to be done via `LazyFieldWithCapacity` which is /// comprehensive to all backends. #[derive(Copy)] pub struct FieldElement(pub(crate) OpaqueFieldElement, pub(crate) PhantomData); diff --git a/curve25519-dalek/src/hazmat/lazy_field25519.rs b/curve25519-dalek/src/hazmat/lazy_field25519.rs index c3665e087..be921eca1 100644 --- a/curve25519-dalek/src/hazmat/lazy_field25519.rs +++ b/curve25519-dalek/src/hazmat/lazy_field25519.rs @@ -65,25 +65,20 @@ impl LazyField for FieldElement>( + fn add_pair_then_mul>( a: F, b: F, c: F, d: F, - e: F, - f: F, ) -> ::Output where U2: IsLessOrEqual, - U3: IsLessOrEqual, { let ab = a.add(&b); - let abc = ab.add(&c); - let de = d.add(&e); - let def = de.add(&f); - abc.mul(&def) + let cd = c.add(&d); + ab.mul(&cd) } #[test] @@ -95,36 +90,26 @@ mod tests { let mut rng = OsRng.unwrap_err(); - let a = FieldElement::random(&mut rng); - let b = FieldElement::random(&mut rng); - let c = FieldElement::random(&mut rng); - let d = FieldElement::random(&mut rng); - let e = FieldElement::random(&mut rng); - let f = FieldElement::random(&mut rng); - let expected = (a + b + c) * (d + e + f); - - assert_eq!( - LazyField::add(a, &b) - .add(&c) - .mul(&LazyField::add(d, &e).add(&f)), - expected - ); - assert_eq!(add_triple_then_mul(a, b, c, d, e, f), expected); - - let a = EagerField(a, PhantomData::); - let b = EagerField(b, PhantomData::); - let c = EagerField(c, PhantomData::); - let d = EagerField(d, PhantomData::); - let e = EagerField(e, PhantomData::); - let f = EagerField(f, PhantomData::); - - assert_eq!( - LazyField::add(a, &b) - .add(&c) - .mul(&LazyField::add(d, &e).add(&f)) - .0, - expected - ); - assert_eq!(add_triple_then_mul(a, b, c, d, e, f).0, expected); + for _ in 0..10_000 { + let a = FieldElement::random(&mut rng); + let b = FieldElement::random(&mut rng); + let c = FieldElement::random(&mut rng); + let d = FieldElement::random(&mut rng); + let expected = (a + b) * (c + d); + + assert_eq!(LazyField::add(a, &b).mul(&LazyField::add(c, &d)), expected); + assert_eq!(add_pair_then_mul(a, b, c, d), expected); + + let a = EagerField(a, PhantomData::); + let b = EagerField(b, PhantomData::); + let c = EagerField(c, PhantomData::); + let d = EagerField(d, PhantomData::); + + assert_eq!( + LazyField::add(a, &b).mul(&LazyField::add(c, &d)).0, + expected + ); + assert_eq!(add_pair_then_mul(a, b, c, d).0, expected); + } } } From f53dbc0a9c457cfb55f241e34898f66229e84401 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 16:33:08 -0400 Subject: [PATCH 15/19] Reduce after calling `from_bytes_wide` Restores bound of `3` for 32-bit platforms. --- .../src/backend/serial/fiat_u32/field.rs | 2 +- .../src/backend/serial/u32/field.rs | 2 +- curve25519-dalek/src/hazmat.rs | 8 +++-- .../src/hazmat/lazy_field25519.rs | 35 ++++++++++++++----- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs index c0ae60389..cb09137a2 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs @@ -272,5 +272,5 @@ impl FieldElement2625 { #[cfg(feature = "hazmat")] impl crate::hazmat::UnderlyingCapacity for FieldElement2625 { - type Capacity = typenum::U2; + type Capacity = typenum::U3; } diff --git a/curve25519-dalek/src/backend/serial/u32/field.rs b/curve25519-dalek/src/backend/serial/u32/field.rs index c2cdeb74a..a976f19b4 100644 --- a/curve25519-dalek/src/backend/serial/u32/field.rs +++ b/curve25519-dalek/src/backend/serial/u32/field.rs @@ -608,5 +608,5 @@ impl FieldElement2625 { #[cfg(feature = "hazmat")] impl crate::hazmat::UnderlyingCapacity for FieldElement2625 { - type Capacity = typenum::U2; + type Capacity = typenum::U3; } diff --git a/curve25519-dalek/src/hazmat.rs b/curve25519-dalek/src/hazmat.rs index a7b57ef8f..7b1322ed3 100644 --- a/curve25519-dalek/src/hazmat.rs +++ b/curve25519-dalek/src/hazmat.rs @@ -34,7 +34,7 @@ pub struct OpaqueFieldElement(Underlying); /// implementations. Its size and internals are not guaranteed to have /// any specific properties and are not covered by semver. /// -/// Usage is recommended to be done via `LazyFieldWithCapacity` which is +/// Usage is recommended to be done via `LazyFieldWithCapacity` which is /// comprehensive to all backends. #[derive(Copy)] pub struct FieldElement(pub(crate) OpaqueFieldElement, pub(crate) PhantomData); @@ -217,7 +217,7 @@ impl Field for FieldElement { fn try_from_rng(rng: &mut R) -> Result { let mut bytes = [0; 64]; rng.try_fill_bytes(&mut bytes)?; - Ok(Self::from(Underlying::from_bytes_wide(&bytes))) + Ok(Self::from_uniform_bytes(&bytes)) } fn square(&self) -> Self { @@ -315,7 +315,9 @@ impl From for FieldElement { impl FromUniformBytes<64> for FieldElement { fn from_uniform_bytes(bytes: &[u8; 64]) -> Self { - Self::from(Underlying::from_bytes_wide(bytes)) + Self::from(Underlying::from_bytes( + &Underlying::from_bytes_wide(bytes).to_bytes(), + )) } } diff --git a/curve25519-dalek/src/hazmat/lazy_field25519.rs b/curve25519-dalek/src/hazmat/lazy_field25519.rs index be921eca1..1caef5e6f 100644 --- a/curve25519-dalek/src/hazmat/lazy_field25519.rs +++ b/curve25519-dalek/src/hazmat/lazy_field25519.rs @@ -65,20 +65,25 @@ impl LazyField for FieldElement>( + fn add_triple_then_mul>( a: F, b: F, c: F, d: F, + e: F, + f: F, ) -> ::Output where U2: IsLessOrEqual, + U3: IsLessOrEqual, { let ab = a.add(&b); - let cd = c.add(&d); - ab.mul(&cd) + let abc = ab.add(&c); + let de = d.add(&e); + let def = de.add(&f); + abc.mul(&def) } #[test] @@ -95,21 +100,33 @@ mod tests { let b = FieldElement::random(&mut rng); let c = FieldElement::random(&mut rng); let d = FieldElement::random(&mut rng); - let expected = (a + b) * (c + d); + let e = FieldElement::random(&mut rng); + let f = FieldElement::random(&mut rng); + let expected = (a + b + c) * (d + e + f); - assert_eq!(LazyField::add(a, &b).mul(&LazyField::add(c, &d)), expected); - assert_eq!(add_pair_then_mul(a, b, c, d), expected); + assert_eq!( + LazyField::add(a, &b) + .add(&c) + .mul(&LazyField::add(d, &e).add(&f)), + expected + ); + assert_eq!(add_triple_then_mul(a, b, c, d, e, f), expected); let a = EagerField(a, PhantomData::); let b = EagerField(b, PhantomData::); let c = EagerField(c, PhantomData::); let d = EagerField(d, PhantomData::); + let e = EagerField(e, PhantomData::); + let f = EagerField(f, PhantomData::); assert_eq!( - LazyField::add(a, &b).mul(&LazyField::add(c, &d)).0, + LazyField::add(a, &b) + .add(&c) + .mul(&LazyField::add(d, &e).add(&f)) + .0, expected ); - assert_eq!(add_pair_then_mul(a, b, c, d).0, expected); + assert_eq!(add_triple_then_mul(a, b, c, d, e, f).0, expected); } } } From 1d8c793bb8593d5a34cf2a2f80c41d8f1290fb29 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 15:53:20 -0400 Subject: [PATCH 16/19] Add `EdwardsPoint::from_affine_coordinates` (`const`) Builds on #816. Resolves #817. --- .../src/backend/serial/fiat_u32/field.rs | 22 +- .../src/backend/serial/fiat_u64/field.rs | 22 +- .../src/backend/serial/u32/field.rs | 218 ++++++++++-------- .../src/backend/serial/u64/field.rs | 215 +++++++++-------- curve25519-dalek/src/edwards.rs | 25 ++ curve25519-dalek/src/edwards/affine.rs | 33 ++- 6 files changed, 321 insertions(+), 214 deletions(-) diff --git a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs index cb09137a2..bf45cef7c 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs @@ -70,14 +70,20 @@ impl Zeroize for FieldElement2625 { } } -impl<'b> AddAssign<&'b FieldElement2625> for FieldElement2625 { - fn add_assign(&mut self, rhs: &'b FieldElement2625) { +impl FieldElement2625 { + pub(crate) const fn const_add_assign(&mut self, _rhs: &FieldElement2625) -> FieldElement2625 { let mut result_loose = fiat_25519_loose_field_element([0; 10]); fiat_25519_add(&mut result_loose, &self.0, &rhs.0); fiat_25519_carry(&mut self.0, &result_loose); } } +impl<'b> AddAssign<&'b FieldElement2625> for FieldElement2625 { + fn add_assign(&mut self, rhs: &'b FieldElement2625) { + self.const_add_assign(rhs) + } +} + impl<'a, 'b> Add<&'b FieldElement2625> for &'a FieldElement2625 { type Output = FieldElement2625; fn add(self, rhs: &'b FieldElement2625) -> FieldElement2625 { @@ -118,9 +124,8 @@ impl<'b> MulAssign<&'b FieldElement2625> for FieldElement2625 { } } -impl<'a, 'b> Mul<&'b FieldElement2625> for &'a FieldElement2625 { - type Output = FieldElement2625; - fn mul(self, rhs: &'b FieldElement2625) -> FieldElement2625 { +impl FieldElement2625 { + pub(crate) const fn const_mul(&self, rhs: &FieldElement2625) -> FieldElement2625 { let mut self_loose = fiat_25519_loose_field_element([0; 10]); fiat_25519_relax(&mut self_loose, &self.0); let mut rhs_loose = fiat_25519_loose_field_element([0; 10]); @@ -131,6 +136,13 @@ impl<'a, 'b> Mul<&'b FieldElement2625> for &'a FieldElement2625 { } } +impl<'a, 'b> Mul<&'b FieldElement2625> for &'a FieldElement2625 { + type Output = FieldElement2625; + fn mul(self, rhs: &'b FieldElement2625) -> FieldElement2625 { + self.const_mul(rhs) + } +} + impl<'a> Neg for &'a FieldElement2625 { type Output = FieldElement2625; fn neg(self) -> FieldElement2625 { diff --git a/curve25519-dalek/src/backend/serial/fiat_u64/field.rs b/curve25519-dalek/src/backend/serial/fiat_u64/field.rs index 94b91b722..42e345b33 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u64/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u64/field.rs @@ -59,14 +59,20 @@ impl Zeroize for FieldElement51 { } } -impl<'b> AddAssign<&'b FieldElement51> for FieldElement51 { - fn add_assign(&mut self, rhs: &'b FieldElement51) { +impl FieldElement51 { + pub(crate) const fn const_add_assign(&mut self, rhs: &FieldElement51) { let mut result_loose = fiat_25519_loose_field_element([0; 5]); fiat_25519_add(&mut result_loose, &self.0, &rhs.0); fiat_25519_carry(&mut self.0, &result_loose); } } +impl<'b> AddAssign<&'b FieldElement51> for FieldElement51 { + fn add_assign(&mut self, rhs: &'b FieldElement51) { + self.const_add_assign(rhs) + } +} + impl<'a, 'b> Add<&'b FieldElement51> for &'a FieldElement51 { type Output = FieldElement51; fn add(self, rhs: &'b FieldElement51) -> FieldElement51 { @@ -107,9 +113,8 @@ impl<'b> MulAssign<&'b FieldElement51> for FieldElement51 { } } -impl<'a, 'b> Mul<&'b FieldElement51> for &'a FieldElement51 { - type Output = FieldElement51; - fn mul(self, rhs: &'b FieldElement51) -> FieldElement51 { +impl FieldElement51 { + pub(crate) const fn const_mul(&self, rhs: &FieldElement51) -> FieldElement51 { let mut self_loose = fiat_25519_loose_field_element([0; 5]); fiat_25519_relax(&mut self_loose, &self.0); let mut rhs_loose = fiat_25519_loose_field_element([0; 5]); @@ -120,6 +125,13 @@ impl<'a, 'b> Mul<&'b FieldElement51> for &'a FieldElement51 { } } +impl<'a, 'b> Mul<&'b FieldElement51> for &'a FieldElement51 { + type Output = FieldElement51; + fn mul(self, rhs: &'b FieldElement51) -> FieldElement51 { + self.const_mul(rhs) + } +} + impl<'a> Neg for &'a FieldElement51 { type Output = FieldElement51; fn neg(self) -> FieldElement51 { diff --git a/curve25519-dalek/src/backend/serial/u32/field.rs b/curve25519-dalek/src/backend/serial/u32/field.rs index a976f19b4..e3966ad42 100644 --- a/curve25519-dalek/src/backend/serial/u32/field.rs +++ b/curve25519-dalek/src/backend/serial/u32/field.rs @@ -66,14 +66,22 @@ impl Zeroize for FieldElement2625 { } } -impl<'b> AddAssign<&'b FieldElement2625> for FieldElement2625 { - fn add_assign(&mut self, _rhs: &'b FieldElement2625) { - for i in 0..10 { +impl FieldElement2625 { + pub(crate) const fn const_add_assign(&mut self, _rhs: &FieldElement2625) -> FieldElement2625 { + let mut i = 0; + while i < 10 { self.0[i] += _rhs.0[i]; + i += 1; } } } +impl<'b> AddAssign<&'b FieldElement2625> for FieldElement2625 { + fn add_assign(&mut self, _rhs: &'b FieldElement2625) { + self.const_add_assign(_rhs) + } +} + impl<'a, 'b> Add<&'b FieldElement2625> for &'a FieldElement2625 { type Output = FieldElement2625; fn add(self, _rhs: &'b FieldElement2625) -> FieldElement2625 { @@ -121,109 +129,115 @@ impl<'b> MulAssign<&'b FieldElement2625> for FieldElement2625 { } } +impl FieldElement2625 { + #[rustfmt::skip] // keep alignment of z* calculations + pub(crate) const fn const_mul(&self, _rhs: & FieldElement2625) -> FieldElement2625 { + /// Helper function to multiply two 32-bit integers with 64 bits + /// of output. + #[inline(always)] + const fn m(x: u32, y: u32) -> u64 { + (x as u64) * (y as u64) + } + + // Alias self, _rhs for more readable formulas + let x: &[u32; 10] = &self.0; + let y: &[u32; 10] = &_rhs.0; + + // We assume that the input limbs x[i], y[i] are bounded by: + // + // x[i], y[i] < 2^(26 + b) if i even + // x[i], y[i] < 2^(25 + b) if i odd + // + // where b is a (real) parameter representing the excess bits of + // the limbs. We track the bitsizes of all variables through + // the computation and solve at the end for the allowable + // headroom bitsize b (which determines how many additions we + // can perform between reductions or multiplications). + + let y1_19 = 19 * y[1]; // This fits in a u32 + let y2_19 = 19 * y[2]; // iff 26 + b + lg(19) < 32 + let y3_19 = 19 * y[3]; // if b < 32 - 26 - 4.248 = 1.752 + let y4_19 = 19 * y[4]; + let y5_19 = 19 * y[5]; // below, b<2.5: this is a bottleneck, + let y6_19 = 19 * y[6]; // could be avoided by promoting to + let y7_19 = 19 * y[7]; // u64 here instead of in m() + let y8_19 = 19 * y[8]; + let y9_19 = 19 * y[9]; + + // What happens when we multiply x[i] with y[j] and place the + // result into the (i+j)-th limb? + // + // x[i] represents the value x[i]*2^ceil(i*51/2) + // y[j] represents the value y[j]*2^ceil(j*51/2) + // z[i+j] represents the value z[i+j]*2^ceil((i+j)*51/2) + // x[i]*y[j] represents the value x[i]*y[i]*2^(ceil(i*51/2)+ceil(j*51/2)) + // + // Since the radix is already accounted for, the result placed + // into the (i+j)-th limb should be + // + // x[i]*y[i]*2^(ceil(i*51/2)+ceil(j*51/2) - ceil((i+j)*51/2)). + // + // The value of ceil(i*51/2)+ceil(j*51/2) - ceil((i+j)*51/2) is + // 1 when both i and j are odd, and 0 otherwise. So we add + // + // x[i]*y[j] if either i or j is even + // 2*x[i]*y[j] if i and j are both odd + // + // by using precomputed multiples of x[i] for odd i: + + let x1_2 = 2 * x[1]; // This fits in a u32 iff 25 + b + 1 < 32 + let x3_2 = 2 * x[3]; // iff b < 6 + let x5_2 = 2 * x[5]; + let x7_2 = 2 * x[7]; + let x9_2 = 2 * x[9]; + + let z0 = m(x[0], y[0]) + m(x1_2, y9_19) + m(x[2], y8_19) + m(x3_2, y7_19) + m(x[4], y6_19) + m(x5_2, y5_19) + m(x[6], y4_19) + m(x7_2, y3_19) + m(x[8], y2_19) + m(x9_2, y1_19); + let z1 = m(x[0], y[1]) + m(x[1], y[0]) + m(x[2], y9_19) + m(x[3], y8_19) + m(x[4], y7_19) + m(x[5], y6_19) + m(x[6], y5_19) + m(x[7], y4_19) + m(x[8], y3_19) + m(x[9], y2_19); + let z2 = m(x[0], y[2]) + m(x1_2, y[1]) + m(x[2], y[0]) + m(x3_2, y9_19) + m(x[4], y8_19) + m(x5_2, y7_19) + m(x[6], y6_19) + m(x7_2, y5_19) + m(x[8], y4_19) + m(x9_2, y3_19); + let z3 = m(x[0], y[3]) + m(x[1], y[2]) + m(x[2], y[1]) + m(x[3], y[0]) + m(x[4], y9_19) + m(x[5], y8_19) + m(x[6], y7_19) + m(x[7], y6_19) + m(x[8], y5_19) + m(x[9], y4_19); + let z4 = m(x[0], y[4]) + m(x1_2, y[3]) + m(x[2], y[2]) + m(x3_2, y[1]) + m(x[4], y[0]) + m(x5_2, y9_19) + m(x[6], y8_19) + m(x7_2, y7_19) + m(x[8], y6_19) + m(x9_2, y5_19); + let z5 = m(x[0], y[5]) + m(x[1], y[4]) + m(x[2], y[3]) + m(x[3], y[2]) + m(x[4], y[1]) + m(x[5], y[0]) + m(x[6], y9_19) + m(x[7], y8_19) + m(x[8], y7_19) + m(x[9], y6_19); + let z6 = m(x[0], y[6]) + m(x1_2, y[5]) + m(x[2], y[4]) + m(x3_2, y[3]) + m(x[4], y[2]) + m(x5_2, y[1]) + m(x[6], y[0]) + m(x7_2, y9_19) + m(x[8], y8_19) + m(x9_2, y7_19); + let z7 = m(x[0], y[7]) + m(x[1], y[6]) + m(x[2], y[5]) + m(x[3], y[4]) + m(x[4], y[3]) + m(x[5], y[2]) + m(x[6], y[1]) + m(x[7], y[0]) + m(x[8], y9_19) + m(x[9], y8_19); + let z8 = m(x[0], y[8]) + m(x1_2, y[7]) + m(x[2], y[6]) + m(x3_2, y[5]) + m(x[4], y[4]) + m(x5_2, y[3]) + m(x[6], y[2]) + m(x7_2, y[1]) + m(x[8], y[0]) + m(x9_2, y9_19); + let z9 = m(x[0], y[9]) + m(x[1], y[8]) + m(x[2], y[7]) + m(x[3], y[6]) + m(x[4], y[5]) + m(x[5], y[4]) + m(x[6], y[3]) + m(x[7], y[2]) + m(x[8], y[1]) + m(x[9], y[0]); + + // How big is the contribution to z[i+j] from x[i], y[j]? + // + // Using the bounds above, we get: + // + // i even, j even: x[i]*y[j] < 2^(26+b)*2^(26+b) = 2*2^(51+2*b) + // i odd, j even: x[i]*y[j] < 2^(25+b)*2^(26+b) = 1*2^(51+2*b) + // i even, j odd: x[i]*y[j] < 2^(26+b)*2^(25+b) = 1*2^(51+2*b) + // i odd, j odd: 2*x[i]*y[j] < 2*2^(25+b)*2^(25+b) = 1*2^(51+2*b) + // + // We perform inline reduction mod p by replacing 2^255 by 19 + // (since 2^255 - 19 = 0 mod p). This adds a factor of 19, so + // we get the bounds (z0 is the biggest one, but calculated for + // posterity here in case finer estimation is needed later): + // + // z0 < ( 2 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 249*2^(51 + 2*b) + // z1 < ( 1 + 1 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 )*2^(51 + 2b) = 154*2^(51 + 2*b) + // z2 < ( 2 + 1 + 2 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 195*2^(51 + 2*b) + // z3 < ( 1 + 1 + 1 + 1 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 )*2^(51 + 2b) = 118*2^(51 + 2*b) + // z4 < ( 2 + 1 + 2 + 1 + 2 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 141*2^(51 + 2*b) + // z5 < ( 1 + 1 + 1 + 1 + 1 + 1 + 1*19 + 1*19 + 1*19 + 1*19 )*2^(51 + 2b) = 82*2^(51 + 2*b) + // z6 < ( 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 87*2^(51 + 2*b) + // z7 < ( 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1*19 + 1*19 )*2^(51 + 2b) = 46*2^(51 + 2*b) + // z6 < ( 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1*19 )*2^(51 + 2b) = 33*2^(51 + 2*b) + // z7 < ( 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 )*2^(51 + 2b) = 10*2^(51 + 2*b) + // + // So z[0] fits into a u64 if 51 + 2*b + lg(249) < 64 + // if b < 2.5. + FieldElement2625::reduce([z0, z1, z2, z3, z4, z5, z6, z7, z8, z9]) + } +} + impl<'a, 'b> Mul<&'b FieldElement2625> for &'a FieldElement2625 { type Output = FieldElement2625; - #[rustfmt::skip] // keep alignment of z* calculations fn mul(self, _rhs: &'b FieldElement2625) -> FieldElement2625 { - /// Helper function to multiply two 32-bit integers with 64 bits - /// of output. - #[inline(always)] - fn m(x: u32, y: u32) -> u64 { - (x as u64) * (y as u64) - } - - // Alias self, _rhs for more readable formulas - let x: &[u32; 10] = &self.0; - let y: &[u32; 10] = &_rhs.0; - - // We assume that the input limbs x[i], y[i] are bounded by: - // - // x[i], y[i] < 2^(26 + b) if i even - // x[i], y[i] < 2^(25 + b) if i odd - // - // where b is a (real) parameter representing the excess bits of - // the limbs. We track the bitsizes of all variables through - // the computation and solve at the end for the allowable - // headroom bitsize b (which determines how many additions we - // can perform between reductions or multiplications). - - let y1_19 = 19 * y[1]; // This fits in a u32 - let y2_19 = 19 * y[2]; // iff 26 + b + lg(19) < 32 - let y3_19 = 19 * y[3]; // if b < 32 - 26 - 4.248 = 1.752 - let y4_19 = 19 * y[4]; - let y5_19 = 19 * y[5]; // below, b<2.5: this is a bottleneck, - let y6_19 = 19 * y[6]; // could be avoided by promoting to - let y7_19 = 19 * y[7]; // u64 here instead of in m() - let y8_19 = 19 * y[8]; - let y9_19 = 19 * y[9]; - - // What happens when we multiply x[i] with y[j] and place the - // result into the (i+j)-th limb? - // - // x[i] represents the value x[i]*2^ceil(i*51/2) - // y[j] represents the value y[j]*2^ceil(j*51/2) - // z[i+j] represents the value z[i+j]*2^ceil((i+j)*51/2) - // x[i]*y[j] represents the value x[i]*y[i]*2^(ceil(i*51/2)+ceil(j*51/2)) - // - // Since the radix is already accounted for, the result placed - // into the (i+j)-th limb should be - // - // x[i]*y[i]*2^(ceil(i*51/2)+ceil(j*51/2) - ceil((i+j)*51/2)). - // - // The value of ceil(i*51/2)+ceil(j*51/2) - ceil((i+j)*51/2) is - // 1 when both i and j are odd, and 0 otherwise. So we add - // - // x[i]*y[j] if either i or j is even - // 2*x[i]*y[j] if i and j are both odd - // - // by using precomputed multiples of x[i] for odd i: - - let x1_2 = 2 * x[1]; // This fits in a u32 iff 25 + b + 1 < 32 - let x3_2 = 2 * x[3]; // iff b < 6 - let x5_2 = 2 * x[5]; - let x7_2 = 2 * x[7]; - let x9_2 = 2 * x[9]; - - let z0 = m(x[0], y[0]) + m(x1_2, y9_19) + m(x[2], y8_19) + m(x3_2, y7_19) + m(x[4], y6_19) + m(x5_2, y5_19) + m(x[6], y4_19) + m(x7_2, y3_19) + m(x[8], y2_19) + m(x9_2, y1_19); - let z1 = m(x[0], y[1]) + m(x[1], y[0]) + m(x[2], y9_19) + m(x[3], y8_19) + m(x[4], y7_19) + m(x[5], y6_19) + m(x[6], y5_19) + m(x[7], y4_19) + m(x[8], y3_19) + m(x[9], y2_19); - let z2 = m(x[0], y[2]) + m(x1_2, y[1]) + m(x[2], y[0]) + m(x3_2, y9_19) + m(x[4], y8_19) + m(x5_2, y7_19) + m(x[6], y6_19) + m(x7_2, y5_19) + m(x[8], y4_19) + m(x9_2, y3_19); - let z3 = m(x[0], y[3]) + m(x[1], y[2]) + m(x[2], y[1]) + m(x[3], y[0]) + m(x[4], y9_19) + m(x[5], y8_19) + m(x[6], y7_19) + m(x[7], y6_19) + m(x[8], y5_19) + m(x[9], y4_19); - let z4 = m(x[0], y[4]) + m(x1_2, y[3]) + m(x[2], y[2]) + m(x3_2, y[1]) + m(x[4], y[0]) + m(x5_2, y9_19) + m(x[6], y8_19) + m(x7_2, y7_19) + m(x[8], y6_19) + m(x9_2, y5_19); - let z5 = m(x[0], y[5]) + m(x[1], y[4]) + m(x[2], y[3]) + m(x[3], y[2]) + m(x[4], y[1]) + m(x[5], y[0]) + m(x[6], y9_19) + m(x[7], y8_19) + m(x[8], y7_19) + m(x[9], y6_19); - let z6 = m(x[0], y[6]) + m(x1_2, y[5]) + m(x[2], y[4]) + m(x3_2, y[3]) + m(x[4], y[2]) + m(x5_2, y[1]) + m(x[6], y[0]) + m(x7_2, y9_19) + m(x[8], y8_19) + m(x9_2, y7_19); - let z7 = m(x[0], y[7]) + m(x[1], y[6]) + m(x[2], y[5]) + m(x[3], y[4]) + m(x[4], y[3]) + m(x[5], y[2]) + m(x[6], y[1]) + m(x[7], y[0]) + m(x[8], y9_19) + m(x[9], y8_19); - let z8 = m(x[0], y[8]) + m(x1_2, y[7]) + m(x[2], y[6]) + m(x3_2, y[5]) + m(x[4], y[4]) + m(x5_2, y[3]) + m(x[6], y[2]) + m(x7_2, y[1]) + m(x[8], y[0]) + m(x9_2, y9_19); - let z9 = m(x[0], y[9]) + m(x[1], y[8]) + m(x[2], y[7]) + m(x[3], y[6]) + m(x[4], y[5]) + m(x[5], y[4]) + m(x[6], y[3]) + m(x[7], y[2]) + m(x[8], y[1]) + m(x[9], y[0]); - - // How big is the contribution to z[i+j] from x[i], y[j]? - // - // Using the bounds above, we get: - // - // i even, j even: x[i]*y[j] < 2^(26+b)*2^(26+b) = 2*2^(51+2*b) - // i odd, j even: x[i]*y[j] < 2^(25+b)*2^(26+b) = 1*2^(51+2*b) - // i even, j odd: x[i]*y[j] < 2^(26+b)*2^(25+b) = 1*2^(51+2*b) - // i odd, j odd: 2*x[i]*y[j] < 2*2^(25+b)*2^(25+b) = 1*2^(51+2*b) - // - // We perform inline reduction mod p by replacing 2^255 by 19 - // (since 2^255 - 19 = 0 mod p). This adds a factor of 19, so - // we get the bounds (z0 is the biggest one, but calculated for - // posterity here in case finer estimation is needed later): - // - // z0 < ( 2 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 249*2^(51 + 2*b) - // z1 < ( 1 + 1 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 )*2^(51 + 2b) = 154*2^(51 + 2*b) - // z2 < ( 2 + 1 + 2 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 195*2^(51 + 2*b) - // z3 < ( 1 + 1 + 1 + 1 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 + 1*19 )*2^(51 + 2b) = 118*2^(51 + 2*b) - // z4 < ( 2 + 1 + 2 + 1 + 2 + 1*19 + 2*19 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 141*2^(51 + 2*b) - // z5 < ( 1 + 1 + 1 + 1 + 1 + 1 + 1*19 + 1*19 + 1*19 + 1*19 )*2^(51 + 2b) = 82*2^(51 + 2*b) - // z6 < ( 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1*19 + 2*19 + 1*19 )*2^(51 + 2b) = 87*2^(51 + 2*b) - // z7 < ( 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1*19 + 1*19 )*2^(51 + 2b) = 46*2^(51 + 2*b) - // z6 < ( 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1*19 )*2^(51 + 2b) = 33*2^(51 + 2*b) - // z7 < ( 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 )*2^(51 + 2b) = 10*2^(51 + 2*b) - // - // So z[0] fits into a u64 if 51 + 2*b + lg(249) < 64 - // if b < 2.5. - FieldElement2625::reduce([z0, z1, z2, z3, z4, z5, z6, z7, z8, z9]) + self.const_mul(_rhs) } } diff --git a/curve25519-dalek/src/backend/serial/u64/field.rs b/curve25519-dalek/src/backend/serial/u64/field.rs index 03dfcfdeb..3fe6a85c5 100644 --- a/curve25519-dalek/src/backend/serial/u64/field.rs +++ b/curve25519-dalek/src/backend/serial/u64/field.rs @@ -55,14 +55,22 @@ impl Zeroize for FieldElement51 { } } -impl<'a> AddAssign<&'a FieldElement51> for FieldElement51 { - fn add_assign(&mut self, _rhs: &'a FieldElement51) { - for i in 0..5 { +impl FieldElement51 { + pub(crate) const fn const_add_assign(&mut self, _rhs: &FieldElement51) { + let mut i = 0; + while i < 5 { self.0[i] += _rhs.0[i]; + i += 1; } } } +impl<'a> AddAssign<&'a FieldElement51> for FieldElement51 { + fn add_assign(&mut self, _rhs: &'a FieldElement51) { + self.const_add_assign(_rhs) + } +} + impl<'a> Add<&'a FieldElement51> for &FieldElement51 { type Output = FieldElement51; fn add(self, _rhs: &'a FieldElement51) -> FieldElement51 { @@ -108,108 +116,115 @@ impl<'a> MulAssign<&'a FieldElement51> for FieldElement51 { } } +impl FieldElement51 { + #[rustfmt::skip] // keep alignment of c* calculations + pub(crate) const fn const_mul(&self, _rhs: & FieldElement51) -> FieldElement51 { + /// Helper function to multiply two 64-bit integers with 128 + /// bits of output. + #[inline(always)] + const fn m(x: u64, y: u64) -> u128 { (x as u128) * (y as u128) } + + // Alias self, _rhs for more readable formulas + let a: &[u64; 5] = &self.0; + let b: &[u64; 5] = &_rhs.0; + + // Precondition: assume input limbs a[i], b[i] are bounded as + // + // a[i], b[i] < 2^(51 + b) + // + // where b is a real parameter measuring the "bit excess" of the limbs. + + // 64-bit precomputations to avoid 128-bit multiplications. + // + // This fits into a u64 whenever 51 + b + lg(19) < 64. + // + // Since 51 + b + lg(19) < 51 + 4.25 + b + // = 55.25 + b, + // this fits if b < 8.75. + let b1_19 = b[1] * 19; + let b2_19 = b[2] * 19; + let b3_19 = b[3] * 19; + let b4_19 = b[4] * 19; + + // Multiply to get 128-bit coefficients of output + let c0: u128 = m(a[0], b[0]) + m(a[4], b1_19) + m(a[3], b2_19) + m(a[2], b3_19) + m(a[1], b4_19); + let mut c1: u128 = m(a[1], b[0]) + m(a[0], b[1]) + m(a[4], b2_19) + m(a[3], b3_19) + m(a[2], b4_19); + let mut c2: u128 = m(a[2], b[0]) + m(a[1], b[1]) + m(a[0], b[2]) + m(a[4], b3_19) + m(a[3], b4_19); + let mut c3: u128 = m(a[3], b[0]) + m(a[2], b[1]) + m(a[1], b[2]) + m(a[0], b[3]) + m(a[4], b4_19); + let mut c4: u128 = m(a[4], b[0]) + m(a[3], b[1]) + m(a[2], b[2]) + m(a[1], b[3]) + m(a[0] , b[4]); + + // How big are the c[i]? We have + // + // c[i] < 2^(102 + 2*b) * (1+i + (4-i)*19) + // < 2^(102 + lg(1 + 4*19) + 2*b) + // < 2^(108.27 + 2*b) + // + // The carry (c[i] >> 51) fits into a u64 when + // 108.27 + 2*b - 51 < 64 + // 2*b < 6.73 + // b < 3.365. + // + // So we require b < 3 to ensure this fits. + debug_assert!(a[0] < (1 << 54)); debug_assert!(b[0] < (1 << 54)); + debug_assert!(a[1] < (1 << 54)); debug_assert!(b[1] < (1 << 54)); + debug_assert!(a[2] < (1 << 54)); debug_assert!(b[2] < (1 << 54)); + debug_assert!(a[3] < (1 << 54)); debug_assert!(b[3] < (1 << 54)); + debug_assert!(a[4] < (1 << 54)); debug_assert!(b[4] < (1 << 54)); + + // Casting to u64 and back tells the compiler that the carry is + // bounded by 2^64, so that the addition is a u128 + u64 rather + // than u128 + u128. + + const LOW_51_BIT_MASK: u64 = (1u64 << 51) - 1; + let mut out = [0u64; 5]; + + c1 += ((c0 >> 51) as u64) as u128; + out[0] = (c0 as u64) & LOW_51_BIT_MASK; + + c2 += ((c1 >> 51) as u64) as u128; + out[1] = (c1 as u64) & LOW_51_BIT_MASK; + + c3 += ((c2 >> 51) as u64) as u128; + out[2] = (c2 as u64) & LOW_51_BIT_MASK; + + c4 += ((c3 >> 51) as u64) as u128; + out[3] = (c3 as u64) & LOW_51_BIT_MASK; + + let carry: u64 = (c4 >> 51) as u64; + out[4] = (c4 as u64) & LOW_51_BIT_MASK; + + // To see that this does not overflow, we need out[0] + carry * 19 < 2^64. + // + // c4 < a0*b4 + a1*b3 + a2*b2 + a3*b1 + a4*b0 + (carry from c3) + // < 5*(2^(51 + b) * 2^(51 + b)) + (carry from c3) + // < 2^(102 + 2*b + lg(5)) + 2^64. + // + // When b < 3 we get + // + // c4 < 2^110.33 so that carry < 2^59.33 + // + // so that + // + // out[0] + carry * 19 < 2^51 + 19 * 2^59.33 < 2^63.58 + // + // and there is no overflow. + out[0] += carry * 19; + + // Now out[1] < 2^51 + 2^(64 -51) = 2^51 + 2^13 < 2^(51 + epsilon). + out[1] += out[0] >> 51; + out[0] &= LOW_51_BIT_MASK; + + // Now out[i] < 2^(51 + epsilon) for all i. + FieldElement51(out) + } +} + impl<'a> Mul<&'a FieldElement51> for &FieldElement51 { type Output = FieldElement51; #[rustfmt::skip] // keep alignment of c* calculations fn mul(self, _rhs: &'a FieldElement51) -> FieldElement51 { - /// Helper function to multiply two 64-bit integers with 128 - /// bits of output. - #[inline(always)] - fn m(x: u64, y: u64) -> u128 { (x as u128) * (y as u128) } - - // Alias self, _rhs for more readable formulas - let a: &[u64; 5] = &self.0; - let b: &[u64; 5] = &_rhs.0; - - // Precondition: assume input limbs a[i], b[i] are bounded as - // - // a[i], b[i] < 2^(51 + b) - // - // where b is a real parameter measuring the "bit excess" of the limbs. - - // 64-bit precomputations to avoid 128-bit multiplications. - // - // This fits into a u64 whenever 51 + b + lg(19) < 64. - // - // Since 51 + b + lg(19) < 51 + 4.25 + b - // = 55.25 + b, - // this fits if b < 8.75. - let b1_19 = b[1] * 19; - let b2_19 = b[2] * 19; - let b3_19 = b[3] * 19; - let b4_19 = b[4] * 19; - - // Multiply to get 128-bit coefficients of output - let c0: u128 = m(a[0], b[0]) + m(a[4], b1_19) + m(a[3], b2_19) + m(a[2], b3_19) + m(a[1], b4_19); - let mut c1: u128 = m(a[1], b[0]) + m(a[0], b[1]) + m(a[4], b2_19) + m(a[3], b3_19) + m(a[2], b4_19); - let mut c2: u128 = m(a[2], b[0]) + m(a[1], b[1]) + m(a[0], b[2]) + m(a[4], b3_19) + m(a[3], b4_19); - let mut c3: u128 = m(a[3], b[0]) + m(a[2], b[1]) + m(a[1], b[2]) + m(a[0], b[3]) + m(a[4], b4_19); - let mut c4: u128 = m(a[4], b[0]) + m(a[3], b[1]) + m(a[2], b[2]) + m(a[1], b[3]) + m(a[0] , b[4]); - - // How big are the c[i]? We have - // - // c[i] < 2^(102 + 2*b) * (1+i + (4-i)*19) - // < 2^(102 + lg(1 + 4*19) + 2*b) - // < 2^(108.27 + 2*b) - // - // The carry (c[i] >> 51) fits into a u64 when - // 108.27 + 2*b - 51 < 64 - // 2*b < 6.73 - // b < 3.365. - // - // So we require b < 3 to ensure this fits. - debug_assert!(a[0] < (1 << 54)); debug_assert!(b[0] < (1 << 54)); - debug_assert!(a[1] < (1 << 54)); debug_assert!(b[1] < (1 << 54)); - debug_assert!(a[2] < (1 << 54)); debug_assert!(b[2] < (1 << 54)); - debug_assert!(a[3] < (1 << 54)); debug_assert!(b[3] < (1 << 54)); - debug_assert!(a[4] < (1 << 54)); debug_assert!(b[4] < (1 << 54)); - - // Casting to u64 and back tells the compiler that the carry is - // bounded by 2^64, so that the addition is a u128 + u64 rather - // than u128 + u128. - - const LOW_51_BIT_MASK: u64 = (1u64 << 51) - 1; - let mut out = [0u64; 5]; - - c1 += ((c0 >> 51) as u64) as u128; - out[0] = (c0 as u64) & LOW_51_BIT_MASK; - - c2 += ((c1 >> 51) as u64) as u128; - out[1] = (c1 as u64) & LOW_51_BIT_MASK; - - c3 += ((c2 >> 51) as u64) as u128; - out[2] = (c2 as u64) & LOW_51_BIT_MASK; - - c4 += ((c3 >> 51) as u64) as u128; - out[3] = (c3 as u64) & LOW_51_BIT_MASK; - - let carry: u64 = (c4 >> 51) as u64; - out[4] = (c4 as u64) & LOW_51_BIT_MASK; - - // To see that this does not overflow, we need out[0] + carry * 19 < 2^64. - // - // c4 < a0*b4 + a1*b3 + a2*b2 + a3*b1 + a4*b0 + (carry from c3) - // < 5*(2^(51 + b) * 2^(51 + b)) + (carry from c3) - // < 2^(102 + 2*b + lg(5)) + 2^64. - // - // When b < 3 we get - // - // c4 < 2^110.33 so that carry < 2^59.33 - // - // so that - // - // out[0] + carry * 19 < 2^51 + 19 * 2^59.33 < 2^63.58 - // - // and there is no overflow. - out[0] += carry * 19; - - // Now out[1] < 2^51 + 2^(64 -51) = 2^51 + 2^13 < 2^(51 + epsilon). - out[1] += out[0] >> 51; - out[0] &= LOW_51_BIT_MASK; - - // Now out[i] < 2^(51 + epsilon) for all i. - FieldElement51(out) + self.const_mul(_rhs) } } diff --git a/curve25519-dalek/src/edwards.rs b/curve25519-dalek/src/edwards.rs index f7d2e6906..42a0f2f92 100644 --- a/curve25519-dalek/src/edwards.rs +++ b/curve25519-dalek/src/edwards.rs @@ -763,6 +763,19 @@ impl EdwardsPoint { } } } + + /// Create an `EdwardsPoint` from its affine coordinates. + /// + /// Returns `None` if the point is off-curve. + /// + /// This function runs in variable time. + #[cfg(feature = "hazmat")] + pub const fn from_affine_coordinates(x: FieldElement, y: FieldElement) -> Option { + let Some(point) = AffinePoint::from_coordinates(x, y) else { + return None; + }; + Some(point.to_edwards()) + } } // ------------------------------------------------------------------------ @@ -1872,6 +1885,18 @@ mod test { assert_eq!(minus_basepoint.T, -(&constants::ED25519_BASEPOINT_POINT.T)); } + #[test] + fn from_affine_coordinates() { + assert_eq!(constants::ED25519_BASEPOINT_POINT.Z, FieldElement::ONE); + assert_eq!( + EdwardsPoint::from_affine_coordinates( + constants::ED25519_BASEPOINT_POINT.X, + constants::ED25519_BASEPOINT_POINT.Y + ), + Some(constants::ED25519_BASEPOINT_POINT) + ); + } + /// Test that computing 1*basepoint gives the correct basepoint. #[cfg(feature = "precomputed-tables")] #[test] diff --git a/curve25519-dalek/src/edwards/affine.rs b/curve25519-dalek/src/edwards/affine.rs index 301037ed1..7a8a5e20f 100644 --- a/curve25519-dalek/src/edwards/affine.rs +++ b/curve25519-dalek/src/edwards/affine.rs @@ -56,13 +56,42 @@ impl Eq for AffinePoint {} impl DefaultIsZeroes for AffinePoint {} impl AffinePoint { + /// Create an `AffinePoint` from its coordinates. + /// + /// Returns `None` if the point is off-curve. + /// + /// This function runs in variable time. + #[cfg(feature = "hazmat")] + pub const fn from_coordinates(x: FieldElement, y: FieldElement) -> Option { + let x_sq = x.const_mul(&x); + let y_sq = y.const_mul(&y); + + let mut lhs = crate::constants::MINUS_ONE.const_mul(&x_sq); + lhs.const_add_assign(&y_sq); + + let mut rhs = FieldElement::ONE; + rhs.const_add_assign(&crate::constants::EDWARDS_D.const_mul(&x_sq.const_mul(&y_sq))); + + let lhs: [u8; 32] = lhs.to_bytes(); + let rhs: [u8; 32] = rhs.to_bytes(); + + let mut i = 0; + while i < 32 { + if lhs[i] != rhs[i] { + return None; + } + i += 1; + } + Some(Self { x, y }) + } + /// Convert to extended coordinates. - pub fn to_edwards(self) -> EdwardsPoint { + pub const fn to_edwards(self) -> EdwardsPoint { EdwardsPoint { X: self.x, Y: self.y, Z: FieldElement::ONE, - T: &self.x * &self.y, + T: self.x.const_mul(&self.y), } } From 782c3f0a713b97acd72ab57b5f9e14a59ebb477b Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 15:58:27 -0400 Subject: [PATCH 17/19] Fix `const_add_assign` for 32-bit platforms --- curve25519-dalek/src/backend/serial/fiat_u32/field.rs | 2 +- curve25519-dalek/src/backend/serial/u32/field.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs index bf45cef7c..6b20c01ef 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs @@ -71,7 +71,7 @@ impl Zeroize for FieldElement2625 { } impl FieldElement2625 { - pub(crate) const fn const_add_assign(&mut self, _rhs: &FieldElement2625) -> FieldElement2625 { + pub(crate) const fn const_add_assign(&mut self, _rhs: &FieldElement2625) { let mut result_loose = fiat_25519_loose_field_element([0; 10]); fiat_25519_add(&mut result_loose, &self.0, &rhs.0); fiat_25519_carry(&mut self.0, &result_loose); diff --git a/curve25519-dalek/src/backend/serial/u32/field.rs b/curve25519-dalek/src/backend/serial/u32/field.rs index e3966ad42..9e0ad1f32 100644 --- a/curve25519-dalek/src/backend/serial/u32/field.rs +++ b/curve25519-dalek/src/backend/serial/u32/field.rs @@ -67,7 +67,7 @@ impl Zeroize for FieldElement2625 { } impl FieldElement2625 { - pub(crate) const fn const_add_assign(&mut self, _rhs: &FieldElement2625) -> FieldElement2625 { + pub(crate) const fn const_add_assign(&mut self, _rhs: &FieldElement2625) { let mut i = 0; while i < 10 { self.0[i] += _rhs.0[i]; From 2e83284486a12323823d552bb7a32668fd39c6bf Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 15:59:27 -0400 Subject: [PATCH 18/19] Gate `from_affine_coordinates` test to features "hazmat" --- curve25519-dalek/src/edwards.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/curve25519-dalek/src/edwards.rs b/curve25519-dalek/src/edwards.rs index 42a0f2f92..f21224b7a 100644 --- a/curve25519-dalek/src/edwards.rs +++ b/curve25519-dalek/src/edwards.rs @@ -1885,6 +1885,7 @@ mod test { assert_eq!(minus_basepoint.T, -(&constants::ED25519_BASEPOINT_POINT.T)); } + #[cfg(feature = "hazmat")] #[test] fn from_affine_coordinates() { assert_eq!(constants::ED25519_BASEPOINT_POINT.Z, FieldElement::ONE); From 938d11bcdbcea1b3b4b311926448c3df10484460 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 3 Sep 2025 16:04:07 -0400 Subject: [PATCH 19/19] Fix typo in `fiat-crypto` `u32` backend --- curve25519-dalek/src/backend/serial/fiat_u32/field.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs index 6b20c01ef..5abfd34d4 100644 --- a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs +++ b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs @@ -71,7 +71,7 @@ impl Zeroize for FieldElement2625 { } impl FieldElement2625 { - pub(crate) const fn const_add_assign(&mut self, _rhs: &FieldElement2625) { + pub(crate) const fn const_add_assign(&mut self, rhs: &FieldElement2625) { let mut result_loose = fiat_25519_loose_field_element([0; 10]); fiat_25519_add(&mut result_loose, &self.0, &rhs.0); fiat_25519_carry(&mut self.0, &result_loose);