diff --git a/curve25519-dalek/Cargo.toml b/curve25519-dalek/Cargo.toml index fe53e4c45..9cec000aa 100644 --- a/curve25519-dalek/Cargo.toml +++ b/curve25519-dalek/Cargo.toml @@ -54,6 +54,7 @@ digest = { version = "0.10", default-features = false, optional = true } subtle = { version = "2.6.0", default-features = false, features = ["const-generics"]} serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] } zeroize = { version = "1", default-features = false, optional = true } +elliptic-curve = { version = "0.13", optional = true } [target.'cfg(target_arch = "x86_64")'.dependencies] cpufeatures = "0.2.17" @@ -66,8 +67,9 @@ default = ["alloc", "precomputed-tables", "zeroize"] alloc = ["zeroize?/alloc"] precomputed-tables = [] legacy_compatibility = [] -group = ["dep:group", "rand_core"] +group = ["dep:group", "rand_core", "digest"] group-bits = ["group", "ff/bits"] +elliptic-curve = ["group", "dep:ff", "dep:elliptic-curve"] [target.'cfg(all(not(curve25519_dalek_backend = "fiat"), not(curve25519_dalek_backend = "serial"), target_arch = "x86_64"))'.dependencies] curve25519-dalek-derive = { version = "0.1", path = "../curve25519-dalek-derive" } diff --git a/curve25519-dalek/src/ed25519.rs b/curve25519-dalek/src/ed25519.rs new file mode 100644 index 000000000..f46f05373 --- /dev/null +++ b/curve25519-dalek/src/ed25519.rs @@ -0,0 +1,25 @@ +use elliptic_curve::{bigint::U256, consts::U32, Curve, CurveArithmetic, FieldBytesEncoding}; + +use crate::{constants::BASEPOINT_ORDER_PRIVATE, edwards::affine::AffinePoint, EdwardsPoint, Scalar}; + +/// QUESTION: I don't know where to put this singleton. Maybe in the crate's root? +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub struct Ed25519; + +impl Curve for Ed25519 { + type FieldBytesSize = U32; + + type Uint = U256; + + const ORDER: Self::Uint = U256::from_le_slice(&BASEPOINT_ORDER_PRIVATE.bytes); +} + +impl CurveArithmetic for Ed25519 { + type AffinePoint = AffinePoint; + + type ProjectivePoint = EdwardsPoint; + + type Scalar = Scalar; +} + +impl FieldBytesEncoding for U256 {} diff --git a/curve25519-dalek/src/edwards.rs b/curve25519-dalek/src/edwards.rs index 7da4678ed..4e2023558 100644 --- a/curve25519-dalek/src/edwards.rs +++ b/curve25519-dalek/src/edwards.rs @@ -93,7 +93,7 @@ // affine and projective cakes and eat both of them too. #![allow(non_snake_case)] -mod affine; +pub(crate) mod affine; use cfg_if::cfg_if; use core::array::TryFromSliceError; @@ -105,7 +105,7 @@ use core::ops::{AddAssign, SubAssign}; use core::ops::{Mul, MulAssign}; #[cfg(feature = "digest")] -use digest::{generic_array::typenum::U64, Digest}; +use digest::{generic_array::typenum::{U64, U32}, Digest}; #[cfg(feature = "group")] use { @@ -122,7 +122,7 @@ use subtle::ConditionallySelectable; use subtle::ConstantTimeEq; #[cfg(feature = "zeroize")] -use zeroize::Zeroize; +use zeroize::DefaultIsZeroes; use crate::constants; @@ -433,24 +433,10 @@ impl Default for EdwardsPoint { // ------------------------------------------------------------------------ #[cfg(feature = "zeroize")] -impl Zeroize for CompressedEdwardsY { - /// Reset this `CompressedEdwardsY` to the compressed form of the identity element. - fn zeroize(&mut self) { - self.0.zeroize(); - self.0[0] = 1; - } -} +impl DefaultIsZeroes for CompressedEdwardsY {} #[cfg(feature = "zeroize")] -impl Zeroize for EdwardsPoint { - /// Reset this `EdwardsPoint` to the identity element. - fn zeroize(&mut self) { - self.X.zeroize(); - self.Y = FieldElement::ONE; - self.Z = FieldElement::ONE; - self.T.zeroize(); - } -} +impl DefaultIsZeroes for EdwardsPoint {} // ------------------------------------------------------------------------ // Validity checks (for debugging, not CT) @@ -859,6 +845,24 @@ impl EdwardsPoint { } } +// ------------------------------------------------------------------------ +// Elliptic curve traits +// ------------------------------------------------------------------------ + +#[cfg(feature = "elliptic-curve")] +impl elliptic_curve::ops::LinearCombination for EdwardsPoint { + fn lincomb(x: &Self, k: &Self::Scalar, y: &Self, l: &Self::Scalar) -> Self { + EdwardsPoint::multiscalar_mul([k, l], [x, y]) + } +} +#[cfg(feature = "elliptic-curve")] +impl elliptic_curve::ops::MulByGenerator for EdwardsPoint { + fn mul_by_generator(scalar: &Self::Scalar) -> Self { + Self::mul_base(scalar) + } +} + + // ------------------------------------------------------------------------ // Multiscalar Multiplication impls // ------------------------------------------------------------------------ @@ -1358,6 +1362,15 @@ impl Debug for EdwardsPoint { // group traits // ------------------------------------------------------------------------ +#[cfg(feature = "group")] +impl group::Curve for EdwardsPoint { + type AffineRepr = AffinePoint; + + fn to_affine(&self) -> Self::AffineRepr { + EdwardsPoint::to_affine(*self) + } +} + // Use the full trait path to avoid Group::identity overlapping Identity::identity in the // rest of the module (e.g. tests). #[cfg(feature = "group")] @@ -1388,10 +1401,10 @@ impl group::Group for EdwardsPoint { #[cfg(feature = "group")] impl GroupEncoding for EdwardsPoint { - type Repr = [u8; 32]; + type Repr = digest::generic_array::GenericArray; fn from_bytes(bytes: &Self::Repr) -> CtOption { - let repr = CompressedEdwardsY(*bytes); + let repr = CompressedEdwardsY(<[u8; 32]>::from(*bytes)); let (is_valid_y_coord, X, Y, Z) = decompress::step_1(&repr); CtOption::new(decompress::step_2(&repr, X, Y, Z), is_valid_y_coord) } @@ -1402,7 +1415,7 @@ impl GroupEncoding for EdwardsPoint { } fn to_bytes(&self) -> Self::Repr { - self.compress().to_bytes() + self.compress().to_bytes().into() } } @@ -1599,11 +1612,7 @@ impl ConditionallySelectable for SubgroupPoint { } #[cfg(all(feature = "group", feature = "zeroize"))] -impl Zeroize for SubgroupPoint { - fn zeroize(&mut self) { - self.0.zeroize(); - } -} +impl DefaultIsZeroes for SubgroupPoint {} #[cfg(feature = "group")] impl group::Group for SubgroupPoint { @@ -1655,7 +1664,7 @@ impl GroupEncoding for SubgroupPoint { } fn to_bytes(&self) -> Self::Repr { - self.0.compress().to_bytes() + self.0.compress().to_bytes().into() } } @@ -1680,6 +1689,82 @@ impl CofactorGroup for EdwardsPoint { } } +// ------------------------------------------------------------------------ +// Interop between CompressedEdwardsY and EdwardsPoint for group traits +// ------------------------------------------------------------------------ + +// Again, we assume throughout that CompressedEdwardsY is a valid point (this is not what we +// want, just something that somewhat works until we know what to do). + +impl From for EdwardsPoint { + fn from(value: AffinePoint) -> Self { + value.to_edwards() + } +} + +impl From<&AffinePoint> for EdwardsPoint { + fn from(value: &AffinePoint) -> Self { + value.to_edwards() + } +} + + +impl From for AffinePoint { + fn from(value: EdwardsPoint) -> Self { + value.to_affine() + } +} + +impl From<&EdwardsPoint> for AffinePoint { + fn from(value: &EdwardsPoint) -> Self { + value.to_affine() + } +} + +impl Add<&AffinePoint> for &EdwardsPoint { + type Output = EdwardsPoint; + + fn add(self, other: &AffinePoint) -> EdwardsPoint { + self + EdwardsPoint::from(other) + } +} + +define_add_variants!( + LHS = EdwardsPoint, + RHS = AffinePoint, + Output = EdwardsPoint +); + +impl AddAssign<&AffinePoint> for EdwardsPoint { + fn add_assign(&mut self, rhs: &AffinePoint) { + *self += EdwardsPoint::from(rhs); + } +} + +define_add_assign_variants!(LHS = EdwardsPoint, RHS = AffinePoint); + +impl Sub<&AffinePoint> for &EdwardsPoint { + type Output = EdwardsPoint; + + fn sub(self, other: &AffinePoint) -> EdwardsPoint { + self - EdwardsPoint::from(other) + } +} + +define_sub_variants!( + LHS = EdwardsPoint, + RHS = AffinePoint, + Output = EdwardsPoint +); + +impl SubAssign<&AffinePoint> for EdwardsPoint { + fn sub_assign(&mut self, rhs: &AffinePoint) { + *self -= EdwardsPoint::from(rhs); + } +} + +define_sub_assign_variants!(LHS = EdwardsPoint, RHS = AffinePoint); + // ------------------------------------------------------------------------ // Tests // ------------------------------------------------------------------------ diff --git a/curve25519-dalek/src/edwards/affine.rs b/curve25519-dalek/src/edwards/affine.rs index aa68d8eae..524444920 100644 --- a/curve25519-dalek/src/edwards/affine.rs +++ b/curve25519-dalek/src/edwards/affine.rs @@ -93,6 +93,19 @@ impl Mul<&AffinePoint> for Scalar { } } +#[cfg(feature = "elliptic-curve")] +impl elliptic_curve::point::AffineCoordinates for AffinePoint { + type FieldRepr = digest::generic_array::GenericArray; + + fn x(&self) -> Self::FieldRepr { + self.x.to_bytes().into() + } + + fn y_is_odd(&self) -> Choice { + Choice::from(self.y.to_bytes()[0] & 1) + } +} + #[cfg(test)] mod tests { use super::{AffinePoint, EdwardsPoint, Identity}; diff --git a/curve25519-dalek/src/lib.rs b/curve25519-dalek/src/lib.rs index 58f4d784d..031a60806 100644 --- a/curve25519-dalek/src/lib.rs +++ b/curve25519-dalek/src/lib.rs @@ -105,3 +105,6 @@ pub use crate::{ // Build time diagnostics for validation #[cfg(curve25519_dalek_diagnostics = "build")] mod diagnostics; + +#[cfg(feature = "elliptic-curve")] +mod ed25519; diff --git a/curve25519-dalek/src/scalar.rs b/curve25519-dalek/src/scalar.rs index 5971f5c8d..9fe6e1977 100644 --- a/curve25519-dalek/src/scalar.rs +++ b/curve25519-dalek/src/scalar.rs @@ -134,7 +134,10 @@ use rand_core::RngCore; use rand_core::CryptoRngCore; #[cfg(feature = "digest")] -use digest::generic_array::typenum::U64; +use digest::generic_array::{ + typenum::{U32, U64}, + GenericArray, +}; #[cfg(feature = "digest")] use digest::Digest; @@ -144,7 +147,7 @@ use subtle::ConstantTimeEq; use subtle::CtOption; #[cfg(feature = "zeroize")] -use zeroize::Zeroize; +use zeroize::DefaultIsZeroes; use crate::backend; use crate::constants; @@ -553,9 +556,11 @@ impl From for Scalar { } #[cfg(feature = "zeroize")] -impl Zeroize for Scalar { - fn zeroize(&mut self) { - self.bytes.zeroize(); +impl DefaultIsZeroes for Scalar {} + +impl AsRef for Scalar { + fn as_ref(&self) -> &Scalar { + self } } @@ -831,7 +836,7 @@ impl Scalar { } #[cfg(feature = "zeroize")] - Zeroize::zeroize(&mut scratch); + zeroize::Zeroize::zeroize(&mut scratch); ret } @@ -1208,6 +1213,137 @@ impl UnpackedScalar { } } +// ---------------------------------------------------------------------- +// Elliptic Curve Arithmetic traits +// ---------------------------------------------------------------------- + +// QUESTION: This trait is needed for `CurveArithmetic`, hence why it is bounded behind the +// "elliptic-curve" feature. If this is a fine trait to expose to the user, we can consider +// removing the feature flag and implement all of its variants (with u8, &u8, u16, etc.) and the +// non-assign `Shr` trait. We probably don't want to impl `Shl` as this can overflow the scalar +// and break the invariants. If we don't want to expose this trait, we can also make it +// `#[doc(hidden)]`. For reference, crates in the `RustCrypto/elliptic-curves` repository +// implement: +// - `Shr for &Scalar` +// - `Shr for Scalar` +// - `ShrAssign for Scalar` +// +// QUESTION: I did not use constant-time because other elliptic-curves dont +// (e.g., https://docs.rs/p256/0.13.2/src/p256/arithmetic/scalar.rs.html#426-432). +// Is constant-time necessary? +#[cfg(feature = "elliptic-curve")] +impl core::ops::ShrAssign for Scalar { + fn shr_assign(&mut self, rhs: usize) { + use elliptic_curve::bigint::Encoding; + let repr = elliptic_curve::bigint::U256::from_le_bytes(self.bytes); + self.bytes = (repr.shr_vartime(rhs)).to_le_bytes(); + } +} + +#[cfg(feature = "elliptic-curve")] +impl elliptic_curve::ops::Reduce for Scalar { + type Bytes = GenericArray; + + fn reduce(n: elliptic_curve::bigint::U256) -> Self { + use elliptic_curve::bigint::Encoding; + Scalar::from_bytes_mod_order(n.to_le_bytes()) + } + + fn reduce_bytes(bytes: &Self::Bytes) -> Self { + Scalar::from_bytes_mod_order(<[u8; 32]>::from(*bytes)) + } +} + +// QUESTION: Even though the trait asks for "&self is _greater that or equal_ to `n / 2`", +// every implementation I have looked at (e.g., +// https://docs.rs/p256/latest/src/p256/arithmetic/scalar.rs.html#413-415) uses `ct_gt`. +// I decided to do the same, it this correct? +#[cfg(feature = "elliptic-curve")] +impl elliptic_curve::scalar::IsHigh for Scalar { + fn is_high(&self) -> Choice { + use elliptic_curve::bigint::{Encoding, U256}; + use subtle::ConstantTimeGreater; + U256::from_le_bytes(self.bytes).ct_gt(&U256::from_le_bytes( + (constants::BASEPOINT_ORDER_PRIVATE * Self::TWO_INV).bytes, + )) + } +} + +#[cfg(feature = "elliptic-curve")] +impl elliptic_curve::ops::Invert for Scalar { + type Output = CtOption; + + fn invert(&self) -> Self::Output { + let invert = self.invert(); + CtOption::new(invert, !self.is_zero()) + } +} + +#[cfg(feature = "elliptic-curve")] +impl PartialOrd for Scalar { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[cfg(feature = "elliptic-curve")] +impl Ord for Scalar { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + use elliptic_curve::bigint::{Encoding, U256}; + U256::from_le_bytes(self.bytes).cmp( + &U256::from_le_bytes(other.bytes) + ) + } +} + +#[cfg(feature = "elliptic-curve")] +impl From for elliptic_curve::bigint::U256 { + fn from(value: Scalar) -> Self { + use elliptic_curve::bigint::Encoding; + elliptic_curve::bigint::U256::from_le_bytes(value.bytes) + } +} + +#[cfg(feature = "elliptic-curve")] +impl From> for Scalar { + fn from(value: elliptic_curve::ScalarPrimitive) -> Self { + use elliptic_curve::bigint::Encoding; + Scalar { + bytes: value.to_uint().to_le_bytes(), + } + } +} + +#[cfg(feature = "elliptic-curve")] +impl From for elliptic_curve::ScalarPrimitive { + fn from(mut value: Scalar) -> Self { + value.bytes.reverse(); + let bytes: GenericArray<_, _> = value.bytes.into(); + + elliptic_curve::ScalarPrimitive::from_bytes(&bytes) + .expect("Scalar should have valid bytes") + } +} + +#[cfg(feature = "digest")] +impl From for GenericArray { + fn from(value: Scalar) -> Self { + value.bytes.into() + } +} + +#[cfg(feature = "elliptic-curve")] +impl elliptic_curve::scalar::FromUintUnchecked for Scalar { + type Uint = elliptic_curve::bigint::U256; + + fn from_uint_unchecked(uint: Self::Uint) -> Self { + use elliptic_curve::bigint::Encoding; + Self { + bytes: uint.to_le_bytes(), + } + } +} + #[cfg(feature = "group")] impl Field for Scalar { const ZERO: Self = Self::ZERO; @@ -1253,10 +1389,10 @@ impl Field for Scalar { #[cfg(feature = "group")] impl PrimeField for Scalar { - type Repr = [u8; 32]; + type Repr = GenericArray; fn from_repr(repr: Self::Repr) -> CtOption { - Self::from_canonical_bytes(repr) + Self::from_canonical_bytes(repr.into()) } fn from_repr_vartime(repr: Self::Repr) -> Option { @@ -1265,7 +1401,7 @@ impl PrimeField for Scalar { return None; } - let candidate = Scalar { bytes: repr }; + let candidate = Scalar { bytes: repr.into() }; if candidate == candidate.reduce() { Some(candidate) @@ -1275,7 +1411,7 @@ impl PrimeField for Scalar { } fn to_repr(&self) -> Self::Repr { - self.to_bytes() + self.to_bytes().into() } fn is_odd(&self) -> Choice { @@ -1328,7 +1464,7 @@ impl PrimeFieldBits for Scalar { type ReprBits = [u8; 32]; fn to_le_bits(&self) -> FieldBits { - self.to_repr().into() + <[u8; 32]>::from(self.to_repr()).into() } fn char_le_bits() -> FieldBits { @@ -1996,10 +2132,10 @@ pub(crate) mod test { assert!([X, -X].contains(&x_sq.sqrt().unwrap())); assert_eq!(Scalar::from_repr_vartime(X.to_repr()), Some(X)); - assert_eq!(Scalar::from_repr_vartime([0xff; 32]), None); + assert_eq!(Scalar::from_repr_vartime([0xff; 32].into()), None); assert_eq!(Scalar::from_repr(X.to_repr()).unwrap(), X); - assert!(bool::from(Scalar::from_repr([0xff; 32]).is_none())); + assert!(bool::from(Scalar::from_repr([0xff; 32].into()).is_none())); } #[test] diff --git a/ed25519-dalek/README.md b/ed25519-dalek/README.md index 364d08538..5381629e3 100644 --- a/ed25519-dalek/README.md +++ b/ed25519-dalek/README.md @@ -22,7 +22,7 @@ This crate is `#[no_std]` compatible with `default-features = false`. | `zeroize` | ✓ | Implements `Zeroize` and `ZeroizeOnDrop` for `SigningKey` | | `rand_core` | | Enables `SigningKey::generate` | | `batch` | | Enables `verify_batch` for verifying many signatures quickly. Also enables `rand_core`. | -| `digest` | | Enables `Context`, `SigningKey::{with_context, sign_prehashed}` and `VerifyingKey::{with_context, verify_prehashed, verify_prehashed_strict}` for Ed25519ph prehashed signatures | +| `digest` | | Enables `Context`, `SigningKey::{with_context, sign_prehashed}` and `VerifyingKey::{with_context, verify_prehashed, verify_prehashed_strict}` for Ed25519ph prehashed signatures. Also implements `KeySizeUser` for `SigningKey` and `VerifyingKey`, and implements `KeyInit` for `SigningKey`. | | `asm` | | Enables assembly optimizations in the SHA-512 compression functions | | `pkcs8` | | Enables [PKCS#8](https://en.wikipedia.org/wiki/PKCS_8) serialization/deserialization for `SigningKey` and `VerifyingKey` | | `pem` | | Enables PEM serialization support for PKCS#8 private keys and SPKI public keys. Also enables `alloc`. | diff --git a/ed25519-dalek/src/signing.rs b/ed25519-dalek/src/signing.rs index 5416274ba..41e678f29 100644 --- a/ed25519-dalek/src/signing.rs +++ b/ed25519-dalek/src/signing.rs @@ -20,6 +20,9 @@ use rand_core::CryptoRngCore; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; +#[cfg(feature = "digest")] +use curve25519_dalek::digest::{crypto_common::{KeySizeUser, KeyInit, Key}, typenum::U32}; + use sha2::Sha512; use subtle::{Choice, ConstantTimeEq}; @@ -549,6 +552,18 @@ impl SigningKey { } } +#[cfg(feature = "digest")] +impl KeySizeUser for SigningKey { + type KeySize = U32; +} + +#[cfg(feature = "digest")] +impl KeyInit for SigningKey { + fn new(key: &Key) -> Self { + Self::from_bytes(key.as_ref()) + } +} + impl AsRef for SigningKey { fn as_ref(&self) -> &VerifyingKey { &self.verifying_key diff --git a/ed25519-dalek/src/verifying.rs b/ed25519-dalek/src/verifying.rs index 48e6c1650..a03c82468 100644 --- a/ed25519-dalek/src/verifying.rs +++ b/ed25519-dalek/src/verifying.rs @@ -9,6 +9,9 @@ //! ed25519 public keys. +#[cfg(feature = "digest")] +use curve25519_dalek::digest::{crypto_common::KeySizeUser, typenum::U32}; + use core::fmt::Debug; use core::hash::{Hash, Hasher}; @@ -114,6 +117,11 @@ impl From for VerifyingKey { } } +#[cfg(feature = "digest")] +impl KeySizeUser for VerifyingKey { + type KeySize = U32; +} + impl VerifyingKey { /// Convert this public key to a byte array. #[inline]