From d2d3441f391917f88f0d0028185c6ee90ed01b95 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Tue, 6 Jan 2026 09:51:34 -0700 Subject: [PATCH] elliptic-curve: use `crypto_common::Generate` Adds a dependency on `crypto-common` (all curve implementations pretty much have a transitive one already) and replaces all of the RNG functionality with the new `Generate` trait (#2096), for the following types: - `NonZeroScalar` - `ScalarValue` - `SecretKey` - `ecdh::EphemeralSecret` Additionally `Generate` trait bounds have been added to the associated types for `CurveArithmetic`: `AffinePoint`, `ProjectivePoint`, `Scalar` --- Cargo.lock | 1 + elliptic-curve/Cargo.toml | 3 +- elliptic-curve/src/arithmetic.rs | 4 ++ elliptic-curve/src/dev.rs | 27 ++++++++++- elliptic-curve/src/ecdh.rs | 38 ++++++++------- elliptic-curve/src/lib.rs | 2 + elliptic-curve/src/scalar/nonzero.rs | 62 +++++++++--------------- elliptic-curve/src/scalar/value.rs | 51 ++++++++++++-------- elliptic-curve/src/secret_key.rs | 70 ++++++++++------------------ 9 files changed, 135 insertions(+), 123 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 065c16dac..2c2a9c565 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,6 +204,7 @@ version = "0.14.0-rc.20" dependencies = [ "base16ct", "crypto-bigint", + "crypto-common", "digest", "getrandom", "hex-literal", diff --git a/elliptic-curve/Cargo.toml b/elliptic-curve/Cargo.toml index ed21d84c3..5d65ba30d 100644 --- a/elliptic-curve/Cargo.toml +++ b/elliptic-curve/Cargo.toml @@ -25,6 +25,7 @@ features = ["hybrid-array", "rand_core", "subtle", "zeroize"] [dependencies] array = { package = "hybrid-array", version = "0.4", default-features = false, features = ["zeroize"] } base16ct = "1" +common = { package = "crypto-common", version = "0.2.0-rc.9", features = ["rand_core"], path = "../crypto-common" } rand_core = { version = "0.10.0-rc-3", default-features = false } subtle = { version = "2.6", default-features = false } zeroize = { version = "1.7", default-features = false } @@ -69,7 +70,7 @@ critical-section = ["basepoint-table", "once_cell/critical-section"] bits = ["arithmetic", "ff/bits"] dev = ["arithmetic", "dep:hex-literal", "pem", "pkcs8"] ecdh = ["arithmetic", "digest", "dep:hkdf"] -getrandom = ["dep:getrandom", "arithmetic", "bigint/getrandom"] +getrandom = ["dep:getrandom", "arithmetic", "bigint/getrandom", "common/getrandom"] group = ["dep:group", "ff"] pkcs8 = ["dep:pkcs8", "sec1"] pem = ["dep:pem-rfc7468", "alloc", "arithmetic", "pkcs8/pem", "sec1/pem"] diff --git a/elliptic-curve/src/arithmetic.rs b/elliptic-curve/src/arithmetic.rs index a1b96fef7..7a0025e41 100644 --- a/elliptic-curve/src/arithmetic.rs +++ b/elliptic-curve/src/arithmetic.rs @@ -8,6 +8,7 @@ use crate::{ scalar::{FromUintUnchecked, IsHigh}, }; use bigint::modular::Retrieve; +use common::Generate; use core::fmt::Debug; use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption}; use zeroize::DefaultIsZeroes; @@ -27,6 +28,7 @@ pub trait CurveArithmetic: Curve { + DefaultIsZeroes + Eq + From> + + Generate + PartialEq + Sized + Send @@ -52,6 +54,7 @@ pub trait CurveArithmetic: Curve { + DefaultIsZeroes + From + From> + + Generate + Into + LinearCombination<[(Self::ProjectivePoint, Self::Scalar)]> + LinearCombination<[(Self::ProjectivePoint, Self::Scalar); 2]> @@ -78,6 +81,7 @@ pub trait CurveArithmetic: Curve { + From> + From> + FromUintUnchecked + + Generate + Into> + Into> + Into diff --git a/elliptic-curve/src/dev.rs b/elliptic-curve/src/dev.rs index abac2b58b..b44e7ccbf 100644 --- a/elliptic-curve/src/dev.rs +++ b/elliptic-curve/src/dev.rs @@ -4,7 +4,7 @@ //! the traits in this crate. use crate::{ - BatchNormalize, Curve, CurveArithmetic, CurveGroup, FieldBytesEncoding, PrimeCurve, + BatchNormalize, Curve, CurveArithmetic, CurveGroup, FieldBytesEncoding, Generate, PrimeCurve, array::typenum::U32, bigint::{Limb, Odd, U256, modular::Retrieve}, ctutils, @@ -31,6 +31,7 @@ use alloc::vec::Vec; #[cfg(feature = "bits")] use ff::PrimeFieldBits; +use rand_core::TryCryptoRng; /// Pseudo-coordinate for fixed-based scalar mult output pub const PSEUDO_COORDINATE_FIXED_BASE_MUL: [u8; 32] = @@ -176,6 +177,14 @@ impl PrimeFieldBits for Scalar { } } +impl Generate for Scalar { + fn try_generate_from_rng( + rng: &mut R, + ) -> core::result::Result { + ScalarValue::try_generate_from_rng(rng).map(Self) + } +} + impl AsRef for Scalar { fn as_ref(&self) -> &Scalar { self @@ -548,6 +557,14 @@ impl From> for AffinePoint { } } +impl Generate for AffinePoint { + fn try_generate_from_rng( + _rng: &mut R, + ) -> core::result::Result { + unimplemented!() + } +} + impl FromEncodedPoint for AffinePoint { fn from_encoded_point(encoded_point: &EncodedPoint) -> ctutils::CtOption { let point = if encoded_point.is_identity() { @@ -686,6 +703,14 @@ impl From for AffinePoint { } } +impl Generate for ProjectivePoint { + fn try_generate_from_rng( + _rng: &mut R, + ) -> core::result::Result { + unimplemented!() + } +} + impl FromEncodedPoint for ProjectivePoint { fn from_encoded_point(_point: &EncodedPoint) -> ctutils::CtOption { unimplemented!(); diff --git a/elliptic-curve/src/ecdh.rs b/elliptic-curve/src/ecdh.rs index d5ee96d6d..e14cd85e9 100644 --- a/elliptic-curve/src/ecdh.rs +++ b/elliptic-curve/src/ecdh.rs @@ -32,9 +32,10 @@ use crate::{ AffinePoint, Curve, CurveArithmetic, CurveGroup, FieldBytes, NonZeroScalar, ProjectivePoint, PublicKey, point::AffineCoordinates, }; +use common::Generate; use core::{borrow::Borrow, fmt}; use hkdf::Hkdf; -use rand_core::TryCryptoRng; +use rand_core::{CryptoRng, TryCryptoRng}; use zeroize::{Zeroize, ZeroizeOnDrop}; /// Low-level Elliptic Curve Diffie-Hellman (ECDH) function. @@ -71,16 +72,15 @@ where /// Ephemeral Diffie-Hellman Secret. /// -/// These are ephemeral "secret key" values which are deliberately designed -/// to avoid being persisted. +/// These are ephemeral "secret key" values which are deliberately designed to avoid persistence. /// /// To perform an ephemeral Diffie-Hellman exchange, do the following: /// -/// - Have each participant generate an [`EphemeralSecret`] value +/// - Have each participant generate an [`EphemeralSecret`] value using the [`Generate`] trait /// - Compute the [`PublicKey`] for that value /// - Have each peer provide their [`PublicKey`] to their counterpart /// - Use [`EphemeralSecret`] and the other participant's [`PublicKey`] -/// to compute a [`SharedSecret`] value. +/// to compute a [`SharedSecret`] value using the [`EphemeralSecret::diffie_hellman`] function /// /// # ⚠️ SECURITY WARNING ⚠️ /// @@ -103,25 +103,21 @@ impl fmt::Debug for EphemeralSecret { } } -impl EphemeralSecret +impl Generate for EphemeralSecret where C: CurveArithmetic, { - /// Generate a cryptographically random [`EphemeralSecret`]. - #[cfg(feature = "getrandom")] - pub fn generate() -> Self { - Self { - scalar: NonZeroScalar::generate(), - } - } - - /// Generate a cryptographically random [`EphemeralSecret`]. - pub fn try_from_rng(rng: &mut R) -> Result { + fn try_generate_from_rng(rng: &mut R) -> Result { Ok(Self { - scalar: NonZeroScalar::try_from_rng(rng)?, + scalar: NonZeroScalar::try_generate_from_rng(rng)?, }) } +} +impl EphemeralSecret +where + C: CurveArithmetic, +{ /// Get the public key associated with this ephemeral secret. /// /// The `compress` flag enables point compression. @@ -134,6 +130,14 @@ where pub fn diffie_hellman(&self, public_key: &PublicKey) -> SharedSecret { diffie_hellman(self.scalar, public_key.as_affine()) } + + /// DEPRECATED: Generate a cryptographically random [`EphemeralSecret`]. + /// + /// Use the [`Generate`] trait instead. + #[deprecated(since = "0.14.0", note = "use the `Generate` trait instead")] + pub fn random(rng: &mut R) -> Self { + Self::generate_from_rng(rng) + } } impl From<&EphemeralSecret> for PublicKey diff --git a/elliptic-curve/src/lib.rs b/elliptic-curve/src/lib.rs index f06ca8e5d..ea04b037b 100644 --- a/elliptic-curve/src/lib.rs +++ b/elliptic-curve/src/lib.rs @@ -115,6 +115,8 @@ pub use array; pub use array::typenum::consts; pub use bigint; pub use bigint::ctutils; +pub use common; +pub use common::Generate; pub use rand_core; pub use subtle; pub use zeroize; diff --git a/elliptic-curve/src/scalar/nonzero.rs b/elliptic-curve/src/scalar/nonzero.rs index 510565608..318bab624 100644 --- a/elliptic-curve/src/scalar/nonzero.rs +++ b/elliptic-curve/src/scalar/nonzero.rs @@ -7,6 +7,7 @@ use crate::{ scalar::IsHigh, }; use base16ct::HexDisplay; +use common::Generate; use core::{ fmt, ops::{Deref, Mul, MulAssign, Neg}, @@ -50,45 +51,6 @@ impl NonZeroScalar where C: CurveArithmetic, { - /// Generate a random [`NonZeroScalar`]. - /// - /// # Panics - /// - /// If the system's cryptographically secure RNG has an internal error. - #[cfg(feature = "getrandom")] - pub fn generate() -> Self { - // Use rejection sampling to eliminate invalid values - // While this method isn't constant-time, the attacker shouldn't learn - // anything about unrelated outputs so long as `rng` is a secure `CryptoRng`. - loop { - let mut repr = FieldBytes::::default(); - getrandom::fill(&mut repr).expect("RNG failure"); - if let Some(result) = Self::from_repr(repr).into() { - break result; - } - } - } - - /// Generate a random [`NonZeroScalar`]. - pub fn try_from_rng(rng: &mut R) -> Result { - // Use rejection sampling to eliminate zero values. - // While this method isn't constant-time, the attacker shouldn't learn - // anything about unrelated outputs so long as `rng` is a secure `CryptoRng`. - loop { - if let Some(result) = Self::new(Scalar::::try_from_rng(rng)?).into() { - break Ok(result); - } - } - } - - /// Deprecated: Generate a random [`NonZeroScalar`]. - #[cfg(feature = "arithmetic")] - #[deprecated(since = "0.14.0", note = "use `generate` or `try_from_rng` instead")] - pub fn random(rng: &mut R) -> Self { - let Ok(ret) = Self::try_from_rng(rng); - ret - } - /// Create a [`NonZeroScalar`] from a scalar. pub fn new(scalar: Scalar) -> CtOption { CtOption::new(Self { scalar }, !scalar.is_zero()) @@ -124,6 +86,12 @@ where &*(scalars as *const [NonZeroScalar] as *const [Scalar]) } } + + /// Deprecated: Generate a random [`NonZeroScalar`]. + #[deprecated(since = "0.14.0", note = "use the `Generate` trait instead")] + pub fn random(rng: &mut R) -> Self { + Self::generate_from_rng(rng) + } } impl AsRef> for NonZeroScalar @@ -268,6 +236,22 @@ where } } +impl Generate for NonZeroScalar +where + C: CurveArithmetic, +{ + fn try_generate_from_rng(rng: &mut R) -> Result { + // Use rejection sampling to eliminate zero values. + // While this method isn't constant-time, the attacker shouldn't learn + // anything about unrelated outputs so long as `rng` is a secure `CryptoRng`. + loop { + if let Some(result) = Self::new(Scalar::::try_generate_from_rng(rng)?).into() { + break Ok(result); + } + } + } +} + impl Invert for NonZeroScalar where C: CurveArithmetic, diff --git a/elliptic-curve/src/scalar/value.rs b/elliptic-curve/src/scalar/value.rs index 974020d9d..4c431b66e 100644 --- a/elliptic-curve/src/scalar/value.rs +++ b/elliptic-curve/src/scalar/value.rs @@ -8,13 +8,14 @@ use crate::{ scalar::{FromUintUnchecked, IsHigh}, }; use base16ct::HexDisplay; +use common::Generate; use core::{ cmp::Ordering, fmt, ops::{Add, AddAssign, Neg, ShrAssign, Sub, SubAssign}, str, }; -use rand_core::CryptoRng; +use rand_core::{CryptoRng, TryCryptoRng}; use subtle::{ Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess, CtOption, @@ -63,13 +64,6 @@ where /// Scalar modulus. pub const MODULUS: Odd = C::ORDER; - /// Generate a random [`ScalarValue`]. - pub fn random(rng: &mut R) -> Self { - Self { - inner: C::Uint::random_mod_vartime(rng, Self::MODULUS.as_nz_ref()), - } - } - /// Create a new scalar from [`Curve::Uint`]. pub fn new(uint: C::Uint) -> CtOption { CtOption::new( @@ -123,6 +117,23 @@ where pub fn to_uint(&self) -> C::Uint { self.inner } + + /// Deprecated: Generate a random [`ScalarValue`]. + #[deprecated(since = "0.14.0", note = "use the `Generate` trait instead")] + pub fn random(rng: &mut R) -> Self { + Self::generate_from_rng(rng) + } +} + +impl From for ScalarValue +where + C: Curve, +{ + fn from(n: u64) -> Self { + Self { + inner: C::Uint::from(n), + } + } } impl FromUintUnchecked for ScalarValue @@ -136,6 +147,19 @@ where } } +impl Generate for ScalarValue +where + C: Curve, +{ + fn try_generate_from_rng( + rng: &mut R, + ) -> core::result::Result { + Ok(Self { + inner: C::Uint::try_random_mod_vartime(rng, Self::MODULUS.as_nz_ref())?, + }) + } +} + #[cfg(feature = "arithmetic")] impl ScalarValue where @@ -264,17 +288,6 @@ where } } -impl From for ScalarValue -where - C: Curve, -{ - fn from(n: u64) -> Self { - Self { - inner: C::Uint::from(n), - } - } -} - impl Add> for ScalarValue where C: Curve, diff --git a/elliptic-curve/src/secret_key.rs b/elliptic-curve/src/secret_key.rs index 47c9ed917..fe9a26a78 100644 --- a/elliptic-curve/src/secret_key.rs +++ b/elliptic-curve/src/secret_key.rs @@ -10,15 +10,14 @@ mod pkcs8; use crate::{Curve, Error, FieldBytes, Result, ScalarValue}; use array::typenum::Unsigned; +use common::Generate; use core::fmt::{self, Debug}; +use rand_core::{CryptoRng, TryCryptoRng}; use subtle::{Choice, ConstantTimeEq, CtOption}; use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; #[cfg(feature = "arithmetic")] -use crate::{ - CurveArithmetic, NonZeroScalar, PublicKey, - rand_core::{CryptoRng, TryCryptoRng}, -}; +use crate::{CurveArithmetic, NonZeroScalar, PublicKey}; #[cfg(feature = "pem")] use pem_rfc7468::{self as pem, PemLabel}; @@ -50,11 +49,13 @@ use {crate::pkcs8::DecodePrivateKey, core::str::FromStr}; /// Elliptic curve secret keys. /// -/// This type wraps a secret scalar value, helping to prevent accidental -/// exposure and securely erasing the value from memory when dropped. +/// This type wraps a secret scalar value, helping to prevent accidental exposure and securely +/// erasing the value from memory when dropped. /// -/// # Parsing PKCS#8 Keys +/// # Generating secret keys +/// [`SecretKey`]s can be generated using the [`Generate`] trait. /// +/// # Parsing PKCS#8 Keys /// PKCS#8 is a commonly used format for encoding secret keys (especially ones /// generated by OpenSSL). /// @@ -86,45 +87,6 @@ where /// This provides the equivalent of 96-bits of symmetric security. const MIN_SIZE: usize = 24; - /// Generate a random [`SecretKey`]. - /// - /// # Panics - /// - /// If the system's cryptographically secure RNG has an internal error. - #[cfg(feature = "getrandom")] - pub fn generate() -> Self - where - C: CurveArithmetic, - { - Self { - inner: NonZeroScalar::::generate().into(), - } - } - - /// Generate a random [`SecretKey`]. - #[cfg(feature = "arithmetic")] - pub fn try_from_rng( - rng: &mut R, - ) -> core::result::Result - where - C: CurveArithmetic, - { - Ok(Self { - inner: NonZeroScalar::::try_from_rng(rng)?.into(), - }) - } - - /// Deprecated: Generate a random [`SecretKey`]. - #[cfg(feature = "arithmetic")] - #[deprecated(since = "0.14.0", note = "use `generate` or `try_from_rng` instead")] - pub fn random(rng: &mut R) -> Self - where - C: CurveArithmetic, - { - let Ok(ret) = Self::try_from_rng(rng); - ret - } - /// Create a new secret key from a scalar value. /// /// # Returns @@ -287,6 +249,12 @@ where .map(Zeroizing::new) .ok_or(Error) } + + /// Deprecated: Generate a random [`SecretKey`]. + #[deprecated(since = "0.14.0", note = "use the `Generate` trait instead")] + pub fn random(rng: &mut R) -> Self { + Self::generate_from_rng(rng) + } } impl ConstantTimeEq for SecretKey @@ -330,6 +298,16 @@ where } } +impl Generate for SecretKey { + fn try_generate_from_rng( + rng: &mut R, + ) -> core::result::Result { + Ok(Self { + inner: ScalarValue::::try_generate_from_rng(rng)?, + }) + } +} + #[cfg(feature = "sec1")] impl sec1::DecodeEcPrivateKey for SecretKey where