diff --git a/curve25519-dalek/Cargo.toml b/curve25519-dalek/Cargo.toml index 72bff1de7..ecf9cf363 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 = [] +hazmat = ["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..004bf8aab 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. | +| `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`. | +| `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 diff --git a/curve25519-dalek/src/backend/serial/fiat_u32/field.rs b/curve25519-dalek/src/backend/serial/fiat_u32/field.rs index 411e75fef..cb09137a2 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 @@ -269,3 +269,8 @@ impl FieldElement2625 { output } } + +#[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 8f1927542..94b91b722 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 @@ -260,3 +260,8 @@ impl FieldElement51 { output } } + +#[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 2aff69684..a976f19b4 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([ @@ -605,3 +605,8 @@ impl FieldElement2625 { FieldElement2625::reduce(coeffs) } } + +#[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 05340bcaa..03dfcfdeb 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. @@ -573,3 +573,8 @@ impl FieldElement51 { square } } + +#[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 a25a73780..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(feature = "digest")] + #[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]; diff --git a/curve25519-dalek/src/hazmat.rs b/curve25519-dalek/src/hazmat.rs new file mode 100644 index 000000000..7b1322ed3 --- /dev/null +++ b/curve25519-dalek/src/hazmat.rs @@ -0,0 +1,329 @@ +//! Hazardous materials with no semver guarantees. + +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::{U1, 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; + +/// 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 + 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)\\). +/// +/// 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 {} +unsafe impl Sync for FieldElement {} + +impl FieldElement { + pub(crate) const fn from(underlying: Underlying) -> Self { + Self(OpaqueFieldElement(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 { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.0.ct_eq(&other.0.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.0.fmt(f) + } +} + +impl ConditionallySelectable for FieldElement { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + 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.0 + &other.0.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 { + Self::from(&self.0.0 - &other.0.0) + } +} +#[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 { + // `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 { + Self::from(&self.0.0 * &other.0.0) + } +} +#[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_uniform_bytes(&bytes)) + } + + fn square(&self) -> Self { + *self * self + } + + fn double(&self) -> Self { + *self + self + } + + fn invert(&self) -> CtOption { + 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.0, &div.0.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.0.to_bytes())) + } + + fn from_repr_vartime(repr: Self::Repr) -> Option { + Self::from_repr(repr).into() + } + + fn to_repr(&self) -> Self::Repr { + self.0.0.to_bytes() + } + + fn is_odd(&self) -> Choice { + Choice::from(self.0.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( + &Underlying::from_bytes_wide(bytes).to_bytes(), + )) + } +} + +#[cfg(feature = "zeroize")] +impl zeroize::Zeroize for FieldElement { + fn zeroize(&mut self) { + self.0.0.zeroize(); + } +} diff --git a/curve25519-dalek/src/hazmat/lazy_field.rs b/curve25519-dalek/src/hazmat/lazy_field.rs new file mode 100644 index 000000000..d737c7e4a --- /dev/null +++ b/curve25519-dalek/src/hazmat/lazy_field.rs @@ -0,0 +1,83 @@ +//! Traits for working with fields which only perform reduction as needed. + +use core::{fmt::Debug, ops::Add}; + +use typenum::{B1, U1, 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 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>, + T: LazyField, + >( + self, + other: &T, + ) -> impl LazyField< + >::Output, + Capacity = Self::Capacity, + Underlying = Self::Underlying, + Output = ::Output, + >; + + /// 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>: + 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 new file mode 100644 index 000000000..0ab1e291b --- /dev/null +++ b/curve25519-dalek/src/hazmat/lazy_field/eager.rs @@ -0,0 +1,248 @@ +//! 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, U1, 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, + Underlying = Self::Underlying, + Output = ::Output, + > { + 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/hazmat/lazy_field25519.rs b/curve25519-dalek/src/hazmat/lazy_field25519.rs new file mode 100644 index 000000000..1caef5e6f --- /dev/null +++ b/curve25519-dalek/src/hazmat/lazy_field25519.rs @@ -0,0 +1,132 @@ +use core::ops::Add; + +use typenum::{U1, Unsigned, type_operators::IsLessOrEqual}; + +use super::{FieldElement, OpaqueFieldElement, lazy_field::*}; +use crate::field::FieldElement as Underlying; + +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.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. +// Rust believe this is in a public API, as it... technically is? so it must be `pub`. +pub trait UnderlyingCapacity { + type Capacity: Unsigned; +} + +impl LazyField for FieldElement { + type Capacity = ::Capacity; + type Underlying = OpaqueFieldElement; + + 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, + Underlying = Self::Underlying, + Output = ::Output, + > { + FieldElement::<>::Output>::from(&self.0.0 + &other.as_underlying().0) + } + + fn mul>( + self, + other: &T, + ) -> ::Output { + let unreduced = &self.0.0 * &other.as_underlying().0; + FieldElement::from(Underlying::from_bytes(&unreduced.to_bytes())) + } +} + +#[cfg(test)] +mod tests { + use crate::hazmat::lazy_field::{EagerField, LazyField, LazyFieldWithCapacity, Reducible}; + use typenum::{B1, U2, U3, type_operators::IsLessOrEqual}; + + 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 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(); + + 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 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); + } + } +} diff --git a/curve25519-dalek/src/lib.rs b/curve25519-dalek/src/lib.rs index 24e0fa5b8..ad700ef66 100644 --- a/curve25519-dalek/src/lib.rs +++ b/curve25519-dalek/src/lib.rs @@ -94,6 +94,9 @@ pub use crate::{ edwards::EdwardsPoint, montgomery::MontgomeryPoint, ristretto::RistrettoPoint, scalar::Scalar, }; +#[cfg(feature = "hazmat")] +pub mod hazmat; + // Build time diagnostics for validation #[cfg(curve25519_dalek_diagnostics = "build")] mod diagnostics;