diff --git a/Cargo.toml b/Cargo.toml index 8a53d4a07..21b01931a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ members = [ "test-utils", "crates/primitives/aead", "crates/primitives/digest", - "crates/testing/kats", + "crates/testing/kats", "crates/primitives/ecdh", ] [workspace.package] diff --git a/crates/primitives/ecdh/Cargo.toml b/crates/primitives/ecdh/Cargo.toml new file mode 100644 index 000000000..06b51b2a1 --- /dev/null +++ b/crates/primitives/ecdh/Cargo.toml @@ -0,0 +1,22 @@ +[package] +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +name = "libcrux-ecdh-new" +readme.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] +libcrux-curve25519 = { version = "0.0.3", path = "../../../curve25519", optional = true } +libcrux-p256 = { version = "0.0.3", path = "../../../p256", optional = true } +libcrux-traits = { version = "0.0.3", path = "../../../traits", optional = true } + +[features] +default = ["curve25519", "p256"] +p256 = ["dep:libcrux-p256", "dep:libcrux-traits"] +curve25519 = ["dep:libcrux-curve25519", "dep:libcrux-traits"] + +[dev-dependencies] +rand = "0.9" diff --git a/crates/primitives/ecdh/src/lib.rs b/crates/primitives/ecdh/src/lib.rs new file mode 100644 index 000000000..3e66f44f2 --- /dev/null +++ b/crates/primitives/ecdh/src/lib.rs @@ -0,0 +1,91 @@ +//! # Elliptic Curve Diffie-Hellman (ECDH) key exchange +//! +//! This crate provides a uniform API for elliptic curve Diffie-Hellman +//! key exchange over the following curves: +//! +//! - NIST P-256 +//! - Curve25519 +//! +//! *TODO*: Explain error types, different APIs. +//! +//! Usage example: +//! ```rust +//! use libcrux_ecdh_new::curve25519::{X25519Pair, RAND_LEN}; +//! +//! use rand::RngCore; +//! let mut rng = rand::rng(); +//! +//! let mut randomness_a = [0u8; RAND_LEN]; +//! let mut randomness_b = [0u8; RAND_LEN]; +//! +//! rng.fill_bytes(&mut randomness_a); +//! let x25519_pair_a = X25519Pair::generate(&randomness_a).unwrap(); +//! +//! rng.fill_bytes(&mut randomness_b); +//! let x25519_pair_b = X25519Pair::generate(&randomness_b).unwrap(); +//! +//! let derived_a = x25519_pair_a.derive_ecdh(x25519_pair_b.public()).unwrap(); +//! let derived_b = x25519_pair_b.derive_ecdh(x25519_pair_a.public()).unwrap(); +//! +//! assert_eq!(derived_a, derived_b); +//! ``` + +#[cfg(feature = "p256")] +pub mod p256 { + //! This module provides an API for ECDH over NIST-P256. + //! + //! ```rust + //! use libcrux_ecdh_new::p256::{P256Pair, RAND_LEN}; + //! use rand::RngCore; + //! + //! let mut rng = rand::rng(); + //! + //! let mut randomness_a = [0u8; RAND_LEN]; + //! let mut randomness_b = [0u8; RAND_LEN]; + //! + //! rng.fill_bytes(&mut randomness_a); + //! let p256_pair_a = P256Pair::generate(&randomness_a).unwrap(); + //! + //! rng.fill_bytes(&mut randomness_b); + //! let p256_pair_b = P256Pair::generate(&randomness_b).unwrap(); + //! + //! let derived_a = p256_pair_a.derive_ecdh(p256_pair_b.public()).unwrap(); + //! let derived_b = p256_pair_b.derive_ecdh(p256_pair_a.public()).unwrap(); + //! + //! assert_eq!(derived_a, derived_b); + //! ``` + pub use libcrux_p256::ecdh_api::*; + + /// Access low level ECDH APIs via this struct. + pub use libcrux_p256::P256; +} + +#[cfg(feature = "curve25519")] +pub mod curve25519 { + //! This module provides an API for ECDH over Curve25519. + //! + //! ```rust + //! use libcrux_ecdh_new::curve25519::{X25519Pair, RAND_LEN}; + //! + //! use rand::RngCore; + //! let mut rng = rand::rng(); + //! + //! let mut randomness_a = [0u8; RAND_LEN]; + //! let mut randomness_b = [0u8; RAND_LEN]; + //! + //! rng.fill_bytes(&mut randomness_a); + //! let x25519_pair_a = X25519Pair::generate(&randomness_a).unwrap(); + //! + //! rng.fill_bytes(&mut randomness_b); + //! let x25519_pair_b = X25519Pair::generate(&randomness_b).unwrap(); + //! + //! let derived_a = x25519_pair_a.derive_ecdh(x25519_pair_b.public()).unwrap(); + //! let derived_b = x25519_pair_b.derive_ecdh(x25519_pair_a.public()).unwrap(); + //! + //! assert_eq!(derived_a, derived_b); + //! ``` + pub use libcrux_curve25519::ecdh_api::*; + + /// Access low level ECDH APIs via this struct. + pub use libcrux_curve25519::X25519; +} diff --git a/curve25519/src/ecdh_api.rs b/curve25519/src/ecdh_api.rs index f8aa483f1..d0723c938 100644 --- a/curve25519/src/ecdh_api.rs +++ b/curve25519/src/ecdh_api.rs @@ -1,15 +1,22 @@ +use libcrux_traits::ecdh::typed_owned::Pair; pub use libcrux_traits::ecdh::{arrayref::EcdhArrayref, owned::EcdhOwned, slice::EcdhSlice}; use crate::clamp; use super::{DK_LEN, EK_LEN, X25519}; -const RAND_LEN: usize = DK_LEN; -const SECRET_LEN: usize = DK_LEN; -const PUBLIC_LEN: usize = EK_LEN; +/// Number of bytes of randomness required to generate an ECDH secret. +pub const RAND_LEN: usize = DK_LEN; +/// Length in bytes of an ECDH secret value. +pub const SECRET_LEN: usize = DK_LEN; +/// Length in bytes of an ECDH public value. +pub const PUBLIC_LEN: usize = EK_LEN; use libcrux_secrets::{Classify, Declassify, DeclassifyRef, DeclassifyRefMut, U8}; +/// A corresponding pair of ECDH public and secret values over Curve25519. +pub type X25519Pair = Pair; + impl libcrux_traits::ecdh::arrayref::EcdhArrayref for X25519 { fn generate_secret( secret: &mut [U8; SECRET_LEN], @@ -58,3 +65,4 @@ impl libcrux_traits::ecdh::arrayref::EcdhArrayref RAND_LEN, SECRET_LEN, PUBLIC_LEN); +libcrux_traits::ecdh::typed_owned::impl_ecdh_typed_owned!(X25519 => RAND_LEN, SECRET_LEN, PUBLIC_LEN); diff --git a/p256/src/ecdh_api.rs b/p256/src/ecdh_api.rs index 5bdcca2ea..ea2e73030 100644 --- a/p256/src/ecdh_api.rs +++ b/p256/src/ecdh_api.rs @@ -1,13 +1,20 @@ use super::{POINT_LEN, SCALAR_LEN}; -const RAND_LEN: usize = SCALAR_LEN; -const SECRET_LEN: usize = SCALAR_LEN; -const PUBLIC_LEN: usize = POINT_LEN; +/// Number of bytes of randomness required to generate an ECDH secret. +pub const RAND_LEN: usize = SCALAR_LEN; +/// Length in bytes of an ECDH secret value. +pub const SECRET_LEN: usize = SCALAR_LEN; +/// Length in bytes of an ECDH public value. +pub const PUBLIC_LEN: usize = POINT_LEN; +use libcrux_traits::ecdh::typed_owned::Pair; pub use libcrux_traits::ecdh::{arrayref::EcdhArrayref, owned::EcdhOwned, slice::EcdhSlice}; use libcrux_secrets::{Declassify, DeclassifyRef, DeclassifyRefMut, U8}; +/// A corresponding pair of ECDH public and secret values over P256. +pub type P256Pair = Pair; + impl libcrux_traits::ecdh::arrayref::EcdhArrayref for super::P256 { @@ -63,3 +70,4 @@ impl libcrux_traits::ecdh::arrayref::EcdhArrayref RAND_LEN, SECRET_LEN, PUBLIC_LEN); +libcrux_traits::ecdh::typed_owned::impl_ecdh_typed_owned!(super::P256 => RAND_LEN, SECRET_LEN, PUBLIC_LEN); diff --git a/traits/src/ecdh.rs b/traits/src/ecdh.rs index d8716ca77..ac681499a 100644 --- a/traits/src/ecdh.rs +++ b/traits/src/ecdh.rs @@ -4,3 +4,5 @@ pub mod arrayref; pub mod owned; pub mod slice; + +pub mod typed_owned; diff --git a/traits/src/ecdh/owned.rs b/traits/src/ecdh/owned.rs index 6970645f7..0e6001f7d 100644 --- a/traits/src/ecdh/owned.rs +++ b/traits/src/ecdh/owned.rs @@ -3,7 +3,9 @@ //! returns owned arrays. use super::arrayref; -use super::arrayref::{DeriveError, GenerateSecretError, SecretToPublicError, ValidateSecretError}; +pub use super::arrayref::{ + DeriveError, GenerateSecretError, SecretToPublicError, ValidateSecretError, +}; use libcrux_secrets::{Classify, U8}; diff --git a/traits/src/ecdh/typed_owned.rs b/traits/src/ecdh/typed_owned.rs new file mode 100644 index 000000000..fc16bc5b0 --- /dev/null +++ b/traits/src/ecdh/typed_owned.rs @@ -0,0 +1,147 @@ +//! This module provides a strongly typed ECDH API in terms of pairs +//! of corresponding ECDH secret and public values. + +use crate::ecdh::owned::{self, GenerateSecretError, SecretToPublicError}; + +/// The types associated with an ECDH implementation. +pub trait EcdhTypes { + type Secret; + type Public; + type Randomness; + type Derived; +} + +/// An pair of corresponding ECDH secret and public values, generic +/// over the underlying elliptic curve. +pub struct Pair { + secret: Algorithm::Secret, + public: Algorithm::Public, +} + +impl Pair { + /// Generate a new pair of ECDH secret and public values. + pub fn generate(rand: &Algorithm::Randomness) -> Result { + let (public, secret) = Algorithm::generate_pair(&rand)?; + Ok(Pair { secret, public }) + } + + /// Returns the secret component of the pair. + pub fn secret(&self) -> &Algorithm::Secret { + &self.secret + } + + /// Returns the public component of the pair. + pub fn public(&self) -> &Algorithm::Public { + &self.public + } + + /// Derive an ECDH shared secret value from the pairs secret value + /// and a given ECDH public value. + pub fn derive_ecdh( + &self, + public: &Algorithm::Public, + ) -> Result { + Algorithm::derive_ecdh(public, self.secret()) + } +} + +/// Trait implementing ECDH operations over a given elliptic curve. +/// +/// Mostly for `libcrux`-internal use. +pub trait Ecdh: EcdhTypes + Sized { + /// Generate a Diffie-Hellman secret value. + /// It is the responsibility of the caller to ensure that the `rand` argument is actually + /// random. + fn generate_secret(rand: &Self::Randomness) -> Result; + + /// Derive a Diffie-Hellman public value from a secret value. + fn secret_to_public(secret: &Self::Secret) -> Result; + + /// Generate a Diffie-Hellman secret value and derive the + /// corresponding public value in one step. + fn generate_pair( + rand: &Self::Randomness, + ) -> Result<(Self::Public, Self::Secret), GenerateSecretError> { + let secret = Self::generate_secret(rand)?; + let public = Self::secret_to_public(&secret).map_err(|_| GenerateSecretError::Unknown)?; + Ok((public, secret)) + } + + /// Derive a Diffie-Hellman shared secret from a public and a + /// secret value. + /// + /// This value is NOT (!) safe for use as a key and needs to be processed in a round of key + /// derivation, to ensure both that the output is uniformly random and that unkown key share + /// attacks can not happen. + fn derive_ecdh( + public: &Self::Public, + secret: &Self::Secret, + ) -> Result; + + /// Check the validity of a Diffie-Hellman secret value. + fn validate_secret(secret: &Self::Secret) -> Result<(), owned::ValidateSecretError>; +} + +#[macro_export] +/// This macro implements the traits `ecdh::typed_owned::EcdhTypes` and `ecdh::typed_owned::Ecdh` from an +/// implementation of the `ecdh::owned::EcdhOwned` trait. +/// +/// - `$ty` should provide an underlying implementation of `ecdh::owned::EcdhOwned` +/// - `$randomness_len` should be the length in bytes of randomness for ECDH secret +/// generation in the underlying implementation +/// - `$secret_len` should be the length in bytes of ECDH secret values in +/// the underlying implementation +/// - `$public_len` should be the length in bytes of ECDH public values in +/// the underlying implementation (this is also the length of ECDH +/// shared secrets) +macro_rules! impl_ecdh_typed_owned { + ($ty:ty => $randomness_len:expr, $secret_len:expr, $public_len:expr) => { + impl $crate::ecdh::typed_owned::EcdhTypes for $ty { + type Secret = [$crate::libcrux_secrets::U8; $secret_len]; + type Public = [u8; $public_len]; + type Randomness = [$crate::libcrux_secrets::U8; $randomness_len]; + type Derived = [$crate::libcrux_secrets::U8; $public_len]; + } + + impl $crate::ecdh::typed_owned::Ecdh for $ty { + fn generate_secret( + rand: &Self::Randomness, + ) -> Result { + <$ty as $crate::ecdh::owned::EcdhOwned<$randomness_len, + $secret_len, + $public_len + >>::generate_secret(rand) + } + + fn secret_to_public( + secret: &Self::Secret, + ) -> Result { + <$ty as $crate::ecdh::owned::EcdhOwned<$randomness_len, + $secret_len, + $public_len + >>::secret_to_public(secret) + } + + fn derive_ecdh( + public: &Self::Public, + secret: &Self::Secret, + ) -> Result { + <$ty as $crate::ecdh::owned::EcdhOwned<$randomness_len, + $secret_len, + $public_len + >>::derive_ecdh(public, secret) + } + + fn validate_secret( + secret: &Self::Secret, + ) -> Result<(), $crate::ecdh::owned::ValidateSecretError> { + <$ty as $crate::ecdh::owned::EcdhOwned<$randomness_len, + $secret_len, + $public_len + >>::validate_secret(secret) + } + } + }; +} + +pub use impl_ecdh_typed_owned;