diff --git a/elliptic-curve/src/ecdh.rs b/elliptic-curve/src/ecdh.rs index b66929263..48e4d6aa7 100644 --- a/elliptic-curve/src/ecdh.rs +++ b/elliptic-curve/src/ecdh.rs @@ -1,7 +1,7 @@ //! Elliptic Curve Diffie-Hellman (Ephemeral) Support. //! //! This module contains a generic ECDH implementation which is usable with -//! any elliptic curve which implements the [`Arithmetic`] trait (presently +//! any elliptic curve which implements the [`ProjectiveArithmetic`] trait (presently //! the `k256` and `p256` crates) //! //! # Usage @@ -23,13 +23,14 @@ use crate::{ consts::U1, generic_array::ArrayLength, - point::Generator, scalar::NonZeroScalar, sec1::{self, FromEncodedPoint, UncompressedPointSize, UntaggedPointSize}, weierstrass::Curve, - Arithmetic, Error, FieldBytes, + AffinePoint, Error, FieldBytes, ProjectiveArithmetic, Scalar, }; use core::ops::{Add, Mul}; +use ff::PrimeField; +use group::{Curve as _, Group}; use rand_core::{CryptoRng, RngCore}; use zeroize::Zeroize; @@ -44,38 +45,43 @@ pub type PublicKey = sec1::EncodedPoint; /// to avoid being persisted. pub struct EphemeralSecret where - C: Curve + Arithmetic, - C::Scalar: Zeroize, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField> + Zeroize, { scalar: NonZeroScalar, } impl EphemeralSecret where - C: Curve + Arithmetic, - C::Scalar: Clone + Zeroize, - C::AffinePoint: FromEncodedPoint + Mul, Output = C::AffinePoint> + Zeroize, - PublicKey: From, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField> + Clone + Zeroize, + AffinePoint: FromEncodedPoint + Mul, Output = AffinePoint> + Zeroize, + PublicKey: From>, UntaggedPointSize: Add + ArrayLength, UncompressedPointSize: ArrayLength, { /// Generate a cryptographically random [`EphemeralSecret`]. pub fn random(rng: impl CryptoRng + RngCore) -> Self { - let scalar = NonZeroScalar::random(rng); - Self { scalar } + Self { + scalar: NonZeroScalar::random(rng), + } } /// Get the public key associated with this ephemeral secret. /// /// The `compress` flag enables point compression. pub fn public_key(&self) -> PublicKey { - PublicKey::from(C::AffinePoint::generator() * self.scalar) + (C::ProjectivePoint::generator() * &self.scalar) + .to_affine() + .into() } /// Compute a Diffie-Hellman shared secret from an ephemeral secret and the /// public key of the other participant in the exchange. pub fn diffie_hellman(&self, public_key: &PublicKey) -> Result, Error> { - let affine_point = C::AffinePoint::from_encoded_point(public_key); + let affine_point = AffinePoint::::from_encoded_point(public_key); if affine_point.is_some().into() { let shared_secret = affine_point.unwrap() * self.scalar; @@ -88,10 +94,11 @@ where impl From<&EphemeralSecret> for PublicKey where - C: Curve + Arithmetic, - C::Scalar: Clone + Zeroize, - C::AffinePoint: FromEncodedPoint + Mul, Output = C::AffinePoint> + Zeroize, - PublicKey: From, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField> + Clone + Zeroize, + AffinePoint: FromEncodedPoint + Mul, Output = AffinePoint> + Zeroize, + PublicKey: From>, UntaggedPointSize: Add + ArrayLength, UncompressedPointSize: ArrayLength, { @@ -102,8 +109,9 @@ where impl Zeroize for EphemeralSecret where - C: Curve + Arithmetic, - C::Scalar: Zeroize, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField> + Zeroize, { fn zeroize(&mut self) { self.scalar.zeroize() @@ -112,8 +120,9 @@ where impl Drop for EphemeralSecret where - C: Curve + Arithmetic, - C::Scalar: Zeroize, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField> + Zeroize, { fn drop(&mut self) { self.zeroize(); @@ -133,15 +142,22 @@ where /// /// Instead, the resulting value should be used as input to a Key Derivation /// Function (KDF) or cryptographic hash function to produce a symmetric key. -pub struct SharedSecret { +pub struct SharedSecret +where + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField>, +{ /// Computed secret value secret_bytes: FieldBytes, } impl SharedSecret where - C: Curve + Arithmetic, - C::AffinePoint: Zeroize, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField>, + AffinePoint: Zeroize, UntaggedPointSize: Add + ArrayLength, UncompressedPointSize: ArrayLength, { @@ -165,7 +181,9 @@ where impl Zeroize for SharedSecret where - C: Curve + Arithmetic, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField>, { fn zeroize(&mut self) { self.secret_bytes.zeroize() @@ -174,7 +192,9 @@ where impl Drop for SharedSecret where - C: Curve + Arithmetic, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField>, { fn drop(&mut self) { self.zeroize(); diff --git a/elliptic-curve/src/lib.rs b/elliptic-curve/src/lib.rs index 89b6a1288..53c7a72dc 100644 --- a/elliptic-curve/src/lib.rs +++ b/elliptic-curve/src/lib.rs @@ -27,11 +27,13 @@ extern crate std; pub mod error; pub mod ops; -pub mod point; pub mod sec1; pub mod util; pub mod weierstrass; +#[cfg(feature = "arithmetic")] +#[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))] +pub mod point; #[cfg(feature = "arithmetic")] #[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))] pub mod scalar; @@ -50,13 +52,17 @@ pub use generic_array::{self, typenum::consts}; pub use rand_core; pub use subtle; -// TODO(tarcieri): source this via ff crate: https://github.com/zkcrypto/ff/pull/40 #[cfg(feature = "arithmetic")] -pub use bitvec::view::BitView; +pub use self::{ + point::{AffinePoint, ProjectiveArithmetic, ProjectivePoint}, + scalar::Scalar, +}; +#[cfg(feature = "arithmetic")] +pub use bitvec::view::BitView; // TODO: https://github.com/zkcrypto/ff/pull/40 #[cfg(feature = "arithmetic")] -pub use ff; +pub use ff::{self, Field}; #[cfg(feature = "arithmetic")] -pub use group; +pub use group::{self, Group}; #[cfg(feature = "digest")] pub use digest::{self, Digest}; @@ -71,15 +77,6 @@ pub use zeroize; use core::{fmt::Debug, ops::Add}; use generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; -use subtle::{ConditionallySelectable, CtOption}; - -#[cfg(feature = "arithmetic")] -use core::ops::Mul; -#[cfg(feature = "arithmetic")] -use subtle::ConstantTimeEq; - -/// Byte representation of a base/scalar field element of a given curve. -pub type FieldBytes = GenericArray::FieldSize>; /// Elliptic curve. /// @@ -98,32 +95,8 @@ pub trait Curve: Clone + Debug + Default + Eq + Ord + Send + Sync { type FieldSize: ArrayLength + Add + Eq + Ord + Unsigned; } -/// Elliptic curve with arithmetic implementation. -#[cfg(feature = "arithmetic")] -#[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))] -pub trait Arithmetic: Curve { - /// Scalar field element modulo the curve's order. - type Scalar: ff::PrimeField - + ConstantTimeEq - + Default - + FromFieldBytes - + Into>; - - /// Elliptic curve point in affine coordinates. - type AffinePoint: ConditionallySelectable - + Mul, Output = Self::AffinePoint> - + point::Generator; - - /// Elliptic curve point in projective coordinates. - type ProjectivePoint: group::Curve - + group::Group; -} - -/// Decode the given serialized field element -pub trait FromFieldBytes: ConditionallySelectable + Sized { - /// Try to decode this object from bytes - fn from_field_bytes(bytes: &FieldBytes) -> CtOption; -} +/// Byte representation of a base/scalar field element of a given curve. +pub type FieldBytes = GenericArray::FieldSize>; /// Instantiate this type from the output of a digest. /// diff --git a/elliptic-curve/src/point.rs b/elliptic-curve/src/point.rs index 5a0ae58ae..c0b067508 100644 --- a/elliptic-curve/src/point.rs +++ b/elliptic-curve/src/point.rs @@ -1,13 +1,22 @@ -//! Traits for elliptic curve points +//! Elliptic curve points. -/// Point compression settings -pub trait Compression { - /// Should point compression be applied by default? - const COMPRESS_POINTS: bool; -} +use crate::{Curve, FieldBytes, Scalar}; -/// Obtain the generator point. -pub trait Generator { - /// Get the generator point for this elliptic curve - fn generator() -> Self; +/// Elliptic curve with projective arithmetic implementation. +pub trait ProjectiveArithmetic: Curve +where + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: ff::PrimeField>, +{ + /// Elliptic curve point in projective coordinates. + type ProjectivePoint: group::Curve; } + +/// Affine point type for a given curve with a [`ProjectiveArithmetic`] +/// implementation. +pub type AffinePoint = + <::ProjectivePoint as group::Curve>::AffineRepr; + +/// Projective point type for a given curve with a [`ProjectiveArithmetic`] +/// implementation. +pub type ProjectivePoint = ::ProjectivePoint; diff --git a/elliptic-curve/src/scalar.rs b/elliptic-curve/src/scalar.rs index f5466d40b..a1db5b830 100644 --- a/elliptic-curve/src/scalar.rs +++ b/elliptic-curve/src/scalar.rs @@ -3,18 +3,23 @@ use crate::{ ops::Invert, rand_core::{CryptoRng, RngCore}, - Arithmetic, Curve, FieldBytes, FromFieldBytes, + Curve, Error, FieldBytes, ProjectiveArithmetic, }; use bitvec::{array::BitArray, order::Lsb0}; -use core::ops::Deref; -use ff::Field; -use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; +use core::{convert::TryFrom, ops::Deref}; +use ff::{Field, PrimeField}; +use generic_array::{typenum::Unsigned, GenericArray}; +use group::Group; +use subtle::{Choice, ConditionallySelectable, CtOption}; #[cfg(feature = "zeroize")] use zeroize::Zeroize; +/// Scalar field element for a particular elliptic curve. +pub type Scalar = <::ProjectivePoint as Group>::Scalar; + /// Bit representation of a scalar field element of a given curve. -pub type ScalarBits = BitArray::Scalar as ff::PrimeField>::ReprBits>; +pub type ScalarBits = BitArray as PrimeField>::ReprBits>; /// Non-zero scalar type. /// @@ -25,96 +30,110 @@ pub type ScalarBits = BitArray::Scalar as ff::PrimeF /// In the context of ECC, it's useful for ensuring that scalar multiplication /// cannot result in the point at infinity. #[derive(Clone)] -pub struct NonZeroScalar { - scalar: C::Scalar, +pub struct NonZeroScalar +where + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField>, +{ + scalar: Scalar, } impl NonZeroScalar where - C: Curve + Arithmetic, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField>, { /// Generate a random `NonZeroScalar` pub fn random(mut rng: impl CryptoRng + RngCore) -> Self { // Use rejection sampling to eliminate zero values loop { - let result = Self::new(C::Scalar::random(&mut rng)); - - if result.is_some().into() { - break result.unwrap(); + if let Some(result) = Self::new(Field::random(&mut rng)) { + break result; } } } - /// Create a [`NonZeroScalar`] from a scalar, performing a constant-time - /// check that it's non-zero. - pub fn new(scalar: C::Scalar) -> CtOption { - let zero = C::Scalar::from_field_bytes(&Default::default()).unwrap(); - let is_zero = scalar.ct_eq(&zero); - CtOption::new(Self { scalar }, !is_zero) + /// Decode a [`NonZeroScalar] from a serialized field element + pub fn from_repr(repr: FieldBytes) -> Option { + Scalar::::from_repr(repr).and_then(Self::new) } - /// Serialize this [`NonZeroScalar`] as a byte array - pub fn to_bytes(&self) -> FieldBytes { - self.scalar.into() + /// Create a [`NonZeroScalar`] from a scalar. + // TODO(tarcieri): make this constant time? + pub fn new(scalar: Scalar) -> Option { + if scalar.is_zero() { + None + } else { + Some(Self { scalar }) + } } } -impl AsRef for NonZeroScalar +impl AsRef> for NonZeroScalar where - C: Curve + Arithmetic, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField>, { - fn as_ref(&self) -> &C::Scalar { + fn as_ref(&self) -> &Scalar { &self.scalar } } impl ConditionallySelectable for NonZeroScalar where - C: Curve + Arithmetic, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField>, { fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { - let scalar = C::Scalar::conditional_select(&a.scalar, &b.scalar, choice); - Self { scalar } + Self { + scalar: Scalar::::conditional_select(&a.scalar, &b.scalar, choice), + } } } -impl Copy for NonZeroScalar where C: Curve + Arithmetic {} - -impl Deref for NonZeroScalar +impl Copy for NonZeroScalar where - C: Curve + Arithmetic, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField>, { - type Target = C::Scalar; - - fn deref(&self) -> &C::Scalar { - &self.scalar - } } -impl FromFieldBytes for NonZeroScalar +impl Deref for NonZeroScalar where - C: Curve + Arithmetic, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField>, { - fn from_field_bytes(bytes: &FieldBytes) -> CtOption { - C::Scalar::from_field_bytes(bytes).and_then(Self::new) + type Target = Scalar; + + fn deref(&self) -> &Scalar { + &self.scalar } } impl From> for FieldBytes where - C: Curve + Arithmetic, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField>, { fn from(scalar: NonZeroScalar) -> FieldBytes { - scalar.to_bytes() + scalar.scalar.into() } } impl Invert for NonZeroScalar where - C: Curve + Arithmetic, - C::Scalar: Invert, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField> + Invert, { - type Output = C::Scalar; + type Output = Scalar; /// Perform a scalar inversion fn invert(&self) -> CtOption { @@ -122,11 +141,29 @@ where } } +impl TryFrom<&[u8]> for NonZeroScalar +where + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField>, +{ + type Error = Error; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() == C::FieldSize::to_usize() { + NonZeroScalar::from_repr(GenericArray::clone_from_slice(bytes)).ok_or(Error) + } else { + Err(Error) + } + } +} + #[cfg(feature = "zeroize")] impl Zeroize for NonZeroScalar where - C: Curve + Arithmetic, - C::Scalar: Zeroize, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField> + Zeroize, { fn zeroize(&mut self) { self.scalar.zeroize(); diff --git a/elliptic-curve/src/sec1.rs b/elliptic-curve/src/sec1.rs index 684509cd7..af6b8a5e8 100644 --- a/elliptic-curve/src/sec1.rs +++ b/elliptic-curve/src/sec1.rs @@ -20,13 +20,19 @@ use subtle::CtOption; use alloc::boxed::Box; #[cfg(feature = "arithmetic")] -use crate::{subtle::Choice, weierstrass::point::Decompress, Arithmetic}; +use crate::{ + ff::PrimeField, subtle::Choice, weierstrass::point::Decompress, ProjectiveArithmetic, Scalar, +}; #[cfg(all(feature = "arithmetic", feature = "zeroize"))] use crate::{ - ops::Mul, point::Generator, scalar::NonZeroScalar, secret_key::SecretKey, FromFieldBytes, + group::{Curve as _, Group}, + AffinePoint, }; +#[cfg(all(feature = "arithmetic", feature = "zeroize"))] +use crate::secret_key::SecretKey; + #[cfg(feature = "zeroize")] use zeroize::Zeroize; @@ -126,20 +132,16 @@ where #[cfg(all(feature = "arithmetic", feature = "zeroize"))] #[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))] #[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] - pub fn from_secret_key(secret_key: &SecretKey, compress: bool) -> Result + pub fn from_secret_key(secret_key: &SecretKey, compress: bool) -> Self where - C: Arithmetic, - C::AffinePoint: Mul, Output = C::AffinePoint> + ToEncodedPoint, - C::Scalar: Zeroize, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + AffinePoint: ToEncodedPoint, + Scalar: PrimeField> + Zeroize, { - if let Some(scalar) = C::Scalar::from_field_bytes(&secret_key.to_bytes()) - .and_then(NonZeroScalar::new) - .into() - { - Ok(Self::encode(C::AffinePoint::generator() * scalar, compress)) - } else { - Err(Error) - } + (C::ProjectivePoint::generator() * secret_key.secret_scalar()) + .to_affine() + .to_encoded_point(compress) } /// Get the length of the encoded point in bytes @@ -177,12 +179,13 @@ where #[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))] pub fn decompress(&self) -> CtOption where - C: Arithmetic, - C::Scalar: Decompress + ToEncodedPoint, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'r> From<&'r Scalar>, + Scalar: PrimeField> + Decompress + ToEncodedPoint, { match self.coordinates() { Coordinates::Compressed { x, y_is_odd } => { - C::Scalar::decompress(x, Choice::from(y_is_odd as u8)) + Scalar::::decompress(x, Choice::from(y_is_odd as u8)) .map(|s| s.to_encoded_point(false)) } Coordinates::Uncompressed { .. } => CtOption::new(self.clone(), Choice::from(1)), diff --git a/elliptic-curve/src/secret_key.rs b/elliptic-curve/src/secret_key.rs index 661f5bbf1..5f18959a4 100644 --- a/elliptic-curve/src/secret_key.rs +++ b/elliptic-curve/src/secret_key.rs @@ -12,11 +12,14 @@ use core::{ convert::{TryFrom, TryInto}, fmt::{self, Debug}, }; -use subtle::CtOption; use zeroize::Zeroize; #[cfg(feature = "arithmetic")] -use crate::{scalar::NonZeroScalar, Arithmetic, FromFieldBytes}; +use crate::{ + ff::PrimeField, + scalar::{NonZeroScalar, Scalar}, + ProjectiveArithmetic, +}; #[cfg(feature = "arithmetic")] use rand_core::{CryptoRng, RngCore}; @@ -26,18 +29,21 @@ pub trait SecretValue: Curve { type Secret: Into> + Zeroize; /// Parse the secret value from bytes - fn from_secret_bytes(bytes: &FieldBytes) -> CtOption; + // TODO(tarcieri): make this constant time? + fn from_secret_bytes(bytes: &FieldBytes) -> Option; } #[cfg(feature = "arithmetic")] -impl SecretValue for C +impl SecretValue for C where - C::Scalar: Zeroize, + C: Curve + ProjectiveArithmetic, + FieldBytes: From> + for<'a> From<&'a Scalar>, + Scalar: PrimeField> + Zeroize, { type Secret = NonZeroScalar; - fn from_secret_bytes(bytes: &FieldBytes) -> CtOption> { - NonZeroScalar::from_field_bytes(bytes) + fn from_secret_bytes(repr: &FieldBytes) -> Option> { + NonZeroScalar::from_repr(repr.clone()) } } @@ -63,7 +69,9 @@ where #[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))] pub fn random(rng: impl CryptoRng + RngCore) -> Self where - C: Arithmetic + SecretValue>, + C: ProjectiveArithmetic + SecretValue>, + FieldBytes: From> + for<'a> From<&'a Scalar>, + Scalar: PrimeField> + Zeroize, { Self { secret_value: NonZeroScalar::::random(rng), @@ -81,7 +89,7 @@ where .as_ref() .try_into() .ok() - .and_then(|bytes| C::from_secret_bytes(bytes).into()) + .and_then(C::from_secret_bytes) .map(|secret_value| SecretKey { secret_value }) .ok_or(Error) } @@ -93,16 +101,20 @@ where /// Borrow the inner secret scalar value. /// - /// # Notice + /// # Warning + /// + /// This value is key material. /// - /// This value is key material. Please treat it accordingly! + /// Please treat it with the care it deserves! #[cfg(feature = "arithmetic")] #[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))] - pub fn secret_scalar(&self) -> &NonZeroScalar + pub fn secret_scalar(&self) -> &Scalar where - C: Arithmetic + SecretValue>, + C: ProjectiveArithmetic + SecretValue>, + FieldBytes: From> + for<'a> From<&'a Scalar>, + Scalar: PrimeField> + Zeroize, { - &self.secret_value + self.secret_value.as_ref() } } diff --git a/elliptic-curve/src/weierstrass/point.rs b/elliptic-curve/src/weierstrass/point.rs index cd52efe07..e2612ad90 100644 --- a/elliptic-curve/src/weierstrass/point.rs +++ b/elliptic-curve/src/weierstrass/point.rs @@ -4,6 +4,12 @@ use super::Curve; use crate::FieldBytes; use subtle::{Choice, CtOption}; +/// Point compression settings +pub trait Compression { + /// Should point compression be applied by default? + const COMPRESS_POINTS: bool; +} + /// Attempt to decompress an elliptic curve point from its x-coordinate and /// a boolean flag indicating whether or not the y-coordinate is odd. pub trait Decompress: Sized {