From 8c65974113488da1c35f9ae61746e5d1b1ae3ad9 Mon Sep 17 00:00:00 2001 From: "Jan Winkelmann (keks)" Date: Wed, 2 Jul 2025 17:26:24 +0200 Subject: [PATCH 01/12] first draft of kem traits --- curve25519/Cargo.toml | 1 + curve25519/src/lib.rs | 48 +++++++++ traits/Cargo.toml | 1 + traits/src/kem/arrayref.rs | 70 +++++++++++++ traits/src/{kem.rs => kem/mod.rs} | 4 + traits/src/kem/slice.rs | 168 ++++++++++++++++++++++++++++++ 6 files changed, 292 insertions(+) create mode 100644 traits/src/kem/arrayref.rs rename traits/src/{kem.rs => kem/mod.rs} (97%) create mode 100644 traits/src/kem/slice.rs diff --git a/curve25519/Cargo.toml b/curve25519/Cargo.toml index 6741e06b5..90c27e034 100644 --- a/curve25519/Cargo.toml +++ b/curve25519/Cargo.toml @@ -13,3 +13,4 @@ repository.workspace = true [dependencies] libcrux-hacl-rs = { version = "=0.0.3", path = "../hacl-rs/" } libcrux-macros = { version = "=0.0.3", path = "../macros" } +libcrux-traits = { version = "=0.0.3", path = "../traits" } diff --git a/curve25519/src/lib.rs b/curve25519/src/lib.rs index e61317202..d4ff7a28e 100644 --- a/curve25519/src/lib.rs +++ b/curve25519/src/lib.rs @@ -29,3 +29,51 @@ trait Curve25519 { /// error if the result is 0. fn ecdh(out: &mut [u8; SHK_LEN], pk: &[u8; PK_LEN], sk: &[u8; SK_LEN]) -> Result<(), Error>; } + +pub struct X25519; + +impl libcrux_traits::kem::arrayref::Kem + for X25519 +{ + fn keygen( + ek: &mut [u8; SK_LEN], + dk: &mut [u8; PK_LEN], + rand: &[u8; SK_LEN], + ) -> Result<(), libcrux_traits::kem::arrayref::KeyGenError> { + dk.copy_from_slice(rand); + clamp(dk); + secret_to_public(ek, dk); + Ok(()) + } + + fn encaps( + ct: &mut [u8; PK_LEN], + ss: &mut [u8; SHK_LEN], + ek: &[u8; PK_LEN], + rand: &[u8; SK_LEN], + ) -> Result<(), libcrux_traits::kem::arrayref::EncapsError> { + let mut eph_dk = *rand; + clamp(&mut eph_dk); + secret_to_public(ct, &eph_dk); + + ecdh(ss, ek, &eph_dk).map_err(|_| libcrux_traits::kem::arrayref::EncapsError::Unknown) + } + + fn decaps( + ss: &mut [u8; SHK_LEN], + ct: &[u8; SK_LEN], + dk: &[u8; PK_LEN], + ) -> Result<(), libcrux_traits::kem::arrayref::DecapsError> { + ecdh(ss, ct, dk).map_err(|_| libcrux_traits::kem::arrayref::DecapsError::Unknown) + } +} + +libcrux_traits::kem::slice::impl_trait!(X25519 => PK_LEN, SK_LEN, PK_LEN, PK_LEN, SK_LEN, SK_LEN); + +/// Clamp a scalar. +fn clamp(scalar: &mut [u8; SK_LEN]) { + // We clamp the key already to make sure it can't be misused. + scalar[0] &= 248u8; + scalar[31] &= 127u8; + scalar[31] |= 64u8; +} diff --git a/traits/Cargo.toml b/traits/Cargo.toml index 2771a2d5f..994b7988f 100644 --- a/traits/Cargo.toml +++ b/traits/Cargo.toml @@ -12,3 +12,4 @@ readme.workspace = true [dependencies] rand = { version = "0.9", default-features = false } +thiserror = { version = "2.0.12" } diff --git a/traits/src/kem/arrayref.rs b/traits/src/kem/arrayref.rs new file mode 100644 index 000000000..325bb77c1 --- /dev/null +++ b/traits/src/kem/arrayref.rs @@ -0,0 +1,70 @@ +pub trait Kem< + const EK_LEN: usize, + const DK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, + const RAND_KEYGEN_LEN: usize, + const RAND_ENCAPS_LEN: usize, +> +{ + /// Generate a pair of encapsulation and decapsulation keys. + fn keygen( + ek: &mut [u8; EK_LEN], + dk: &mut [u8; DK_LEN], + rand: &[u8; RAND_KEYGEN_LEN], + ) -> Result<(), KeyGenError>; + + /// Encapsulate a shared secret towards a given encapsulation key. + fn encaps( + ct: &mut [u8; CT_LEN], + ss: &mut [u8; SS_LEN], + ek: &[u8; EK_LEN], + rand: &[u8; RAND_ENCAPS_LEN], + ) -> Result<(), EncapsError>; + + /// Decapsulate a shared secret. + fn decaps( + ss: &mut [u8; SS_LEN], + ct: &[u8; CT_LEN], + dk: &[u8; DK_LEN], + ) -> Result<(), DecapsError>; +} + +/// Error generating key with provided randomness +#[derive(thiserror::Error, Debug)] +pub enum KeyGenError { + #[doc = "Error generating key with provided randomness"] + #[error("error generating key with provided randomness")] + InvalidRandomness, + #[doc = "An unknown error occurred"] + #[error("an unknown error occurred")] + Unknown, +} + +/// Error indicating that encapsulating failed +#[derive(thiserror::Error, Debug)] +pub enum EncapsError { + #[doc = "Encapsulation key is invalid"] + #[error("encapsulation key is invalid")] + InvalidEncapsKey, + #[doc = "Error encapsulating key with provided randomness"] + #[error("error encapsulating key with provided randomness")] + InvalidRandomness, + #[doc = "An unknown error occurred"] + #[error("an unknown error occurred")] + Unknown, +} + +/// Error indicating that decapsulating failed +#[derive(thiserror::Error, Debug)] +pub enum DecapsError { + #[doc = "Ciphertext key is invalid"] + #[error("ciphertext key is invalid")] + InvalidCipertext, + #[doc = "Decapsulation key is invalid"] + #[error("decapsulation key is invalid")] + InvalidDecapsKey, + #[doc = "An unknown error occurred"] + #[error("an unknown error occurred")] + Unknown, +} diff --git a/traits/src/kem.rs b/traits/src/kem/mod.rs similarity index 97% rename from traits/src/kem.rs rename to traits/src/kem/mod.rs index c6b2a30ce..aee421d53 100644 --- a/traits/src/kem.rs +++ b/traits/src/kem/mod.rs @@ -1,5 +1,9 @@ //! This module provides a common interface trait for key //! encapsulation mechanisms (KEMs). + +pub mod arrayref; +pub mod slice; + use rand::CryptoRng; /// A KEM keypair. diff --git a/traits/src/kem/slice.rs b/traits/src/kem/slice.rs new file mode 100644 index 000000000..fcbe1862f --- /dev/null +++ b/traits/src/kem/slice.rs @@ -0,0 +1,168 @@ +use super::arrayref; + +pub trait Kem { + /// Generate a pair of encapsulation and decapsulation keys. + fn keygen(ek: &mut [u8], dk: &mut [u8], rand: &[u8]) -> Result<(), KeyGenError>; + + /// Encapsulate a shared secret towards a given encapsulation key. + fn encaps(ct: &mut [u8], ss: &mut [u8], ek: &[u8], rand: &[u8]) -> Result<(), EncapsError>; + + /// Decapsulate a shared secret. + fn decaps(ss: &mut [u8], ct: &[u8], dk: &[u8]) -> Result<(), DecapsError>; +} + +/// Error generating key with provided randomness +#[derive(thiserror::Error, Debug)] +pub enum KeyGenError { + #[doc = "Error generating key with provided randomness"] + #[error("error generating key with provided randomness")] + InvalidRandomness, + #[doc = "The provided randomness has the wrong length"] + #[error("the provided randomness has the wrong length")] + InvalidRandomnessLength, + #[doc = "The provided encapsulation key has the wrong length"] + #[error("the provided encapsulation key has the wrong length")] + InvalidEncapsKeyLength, + #[doc = "The provided decapulation key has the wrong length"] + #[error("the provided decapulation key has the wrong length")] + InvalidDecapsKeyLength, + #[doc = "An unknown error occurred"] + #[error("an unknown error occurred")] + Unknown, +} + +/// Error indicating that encapsulating failed +#[derive(thiserror::Error, Debug)] +pub enum EncapsError { + #[doc = "Encapsulation key is invalid"] + #[error("encapsulation key is invalid")] + InvalidEncapsKey, + #[doc = "Error encapsulating key with provided randomness"] + #[error("error encapsulating key with provided randomness")] + InvalidRandomness, + #[doc = "The provided randomness has the wrong length"] + #[error("the provided randomness has the wrong length")] + InvalidRandomnessLength, + #[doc = "The provided encapsulation key has the wrong length"] + #[error("the provided encapsulation key has the wrong length")] + InvalidEncapsKeyLength, + #[doc = "The provided ciphertext has the wrong length"] + #[error("the provided ciphertext has the wrong length")] + InvalidCipertextLength, + #[doc = "The provided shared secret has the wrong length"] + #[error("the provided shared secret has the wrong length")] + InvalidSharedSecretLength, + #[doc = "An unknown error occurred"] + #[error("an unknown error occurred")] + Unknown, +} + +/// Error indicating that decapsulating failed +#[derive(thiserror::Error, Debug)] +pub enum DecapsError { + #[doc = "Ciphertext key is invalid"] + #[error("ciphertext key is invalid")] + InvalidCipertext, + #[doc = "Decapsulation key is invalid"] + #[error("decapsulation key is invalid")] + InvalidDecapsKey, + #[doc = "The provided decapulation key has the wrong length"] + #[error("the provided decapulation key has the wrong length")] + InvalidDecapsKeyLength, + #[doc = "The provided ciphertext has the wrong length"] + #[error("the provided ciphertext has the wrong length")] + InvalidCipertextLength, + #[doc = "The provided shared secret has the wrong length"] + #[error("the provided shared secret has the wrong length")] + InvalidSharedSecretLength, + #[doc = "An unknown error occurred"] + #[error("an unknown error occurred")] + Unknown, +} + +impl From for KeyGenError { + fn from(value: super::arrayref::KeyGenError) -> Self { + match value { + super::arrayref::KeyGenError::InvalidRandomness => KeyGenError::InvalidRandomness, + super::arrayref::KeyGenError::Unknown => KeyGenError::Unknown, + } + } +} + +impl From for EncapsError { + fn from(value: super::arrayref::EncapsError) -> Self { + match value { + arrayref::EncapsError::InvalidEncapsKey => EncapsError::InvalidEncapsKey, + arrayref::EncapsError::InvalidRandomness => EncapsError::InvalidRandomness, + arrayref::EncapsError::Unknown => EncapsError::Unknown, + } + } +} + +impl From for DecapsError { + fn from(value: super::arrayref::DecapsError) -> Self { + match value { + arrayref::DecapsError::InvalidCipertext => DecapsError::InvalidCipertext, + arrayref::DecapsError::InvalidDecapsKey => DecapsError::InvalidDecapsKey, + arrayref::DecapsError::Unknown => DecapsError::Unknown, + } + } +} + +#[macro_export] +macro_rules! impl_trait { + ($name:ty => $ek:expr, $dk:expr, $ct:expr, $ss:expr, $rand_kg:expr, $rand_encaps:expr) => { + impl $crate::kem::slice::Kem for $name { + fn keygen(ek: &mut [u8], dk: &mut [u8], rand: &[u8]) -> Result<(), $crate::kem::slice::KeyGenError> { + let ek : &mut [u8; $ek] = ek + .try_into() + .map_err(|_| $crate::kem::slice::KeyGenError::InvalidEncapsKeyLength)?; + let dk : &mut [u8; $dk] = dk + .try_into() + .map_err(|_| $crate::kem::slice::KeyGenError::InvalidDecapsKeyLength)?; + let rand : &[u8; $rand_kg] = rand + .try_into() + .map_err(|_| $crate::kem::slice::KeyGenError::InvalidRandomnessLength)?; + + <$name as $crate::kem::arrayref::Kem<$ek, $dk, $ct, $ss, $rand_kg, $rand_encaps>>::keygen(ek, dk, rand).map_err($crate::kem::slice::KeyGenError::from) + } + + fn encaps(ct: &mut [u8], ss: &mut [u8], ek: &[u8], rand: &[u8]) -> Result<(), $crate::kem::slice::EncapsError>{ + let ct : &mut [u8; $ct] = ct + .try_into() + .map_err(|_| $crate::kem::slice::EncapsError::InvalidCipertextLength)?; + let ss : &mut [u8; $ss] = ss + .try_into() + .map_err(|_| $crate::kem::slice::EncapsError::InvalidSharedSecretLength)?; + let ek : & [u8; $ek] = ek + .try_into() + .map_err(|_| $crate::kem::slice::EncapsError::InvalidEncapsKeyLength)?; + let rand : &[u8; $rand_encaps] = rand + .try_into() + .map_err(|_| $crate::kem::slice::EncapsError::InvalidRandomnessLength)?; + + + <$name as $crate::kem::arrayref::Kem<$ek, $dk, $ct, $ss, $rand_kg, $rand_encaps>>::encaps(ct, ss, ek,rand).map_err($crate::kem::slice::EncapsError::from) + } + + fn decaps(ss: &mut [u8], ct: &[u8], dk: &[u8]) -> Result<(), $crate::kem::slice::DecapsError> { + let ss : &mut [u8; $ss] = ss + .try_into() + .map_err(|_| $crate::kem::slice::DecapsError::InvalidSharedSecretLength)?; + let ct : &[u8; $ct] = ct + .try_into() + .map_err(|_| $crate::kem::slice::DecapsError::InvalidCipertextLength)?; + let dk : &[u8; $dk] = dk + .try_into() + .map_err(|_| $crate::kem::slice::DecapsError::InvalidDecapsKeyLength)?; + + + <$name as $crate::kem::arrayref::Kem<$ek, $dk, $ct, $ss, $rand_kg, $rand_encaps>>::decaps(ss, ct, dk).map_err($crate::kem::slice::DecapsError::from) + + } + + } + }; +} + +pub use impl_trait; From 83327c503787580d768a38afd504b3b00654a10a Mon Sep 17 00:00:00 2001 From: "Jan Winkelmann (keks)" Date: Wed, 2 Jul 2025 18:33:53 +0200 Subject: [PATCH 02/12] Drop thiserror and manually implement Display and Error --- curve25519/src/lib.rs | 8 +- traits/Cargo.toml | 4 +- traits/src/kem/arrayref.rs | 72 +++++++++++----- traits/src/kem/slice.rs | 167 ++++++++++++++++++++++++------------- 4 files changed, 165 insertions(+), 86 deletions(-) diff --git a/curve25519/src/lib.rs b/curve25519/src/lib.rs index d4ff7a28e..f806ae751 100644 --- a/curve25519/src/lib.rs +++ b/curve25519/src/lib.rs @@ -30,11 +30,9 @@ trait Curve25519 { fn ecdh(out: &mut [u8; SHK_LEN], pk: &[u8; PK_LEN], sk: &[u8; SK_LEN]) -> Result<(), Error>; } -pub struct X25519; +pub struct Kem; -impl libcrux_traits::kem::arrayref::Kem - for X25519 -{ +impl libcrux_traits::kem::arrayref::Kem for Kem { fn keygen( ek: &mut [u8; SK_LEN], dk: &mut [u8; PK_LEN], @@ -68,7 +66,7 @@ impl libcrux_traits::kem::arrayref::Kem PK_LEN, SK_LEN, PK_LEN, PK_LEN, SK_LEN, SK_LEN); +libcrux_traits::kem::slice::impl_trait!(Kem => PK_LEN, SK_LEN, PK_LEN, PK_LEN, SK_LEN, SK_LEN); /// Clamp a scalar. fn clamp(scalar: &mut [u8; SK_LEN]) { diff --git a/traits/Cargo.toml b/traits/Cargo.toml index 994b7988f..179d0828d 100644 --- a/traits/Cargo.toml +++ b/traits/Cargo.toml @@ -10,6 +10,8 @@ edition.workspace = true repository.workspace = true readme.workspace = true +[features] +error_in_core = [] + [dependencies] rand = { version = "0.9", default-features = false } -thiserror = { version = "2.0.12" } diff --git a/traits/src/kem/arrayref.rs b/traits/src/kem/arrayref.rs index 325bb77c1..7ced62c7f 100644 --- a/traits/src/kem/arrayref.rs +++ b/traits/src/kem/arrayref.rs @@ -31,40 +31,72 @@ pub trait Kem< } /// Error generating key with provided randomness -#[derive(thiserror::Error, Debug)] +#[derive(Debug)] pub enum KeyGenError { - #[doc = "Error generating key with provided randomness"] - #[error("error generating key with provided randomness")] + /// Error generating key with provided randomness InvalidRandomness, - #[doc = "An unknown error occurred"] - #[error("an unknown error occurred")] + + /// An unknown error occurred Unknown, } /// Error indicating that encapsulating failed -#[derive(thiserror::Error, Debug)] +#[derive(Debug)] pub enum EncapsError { - #[doc = "Encapsulation key is invalid"] - #[error("encapsulation key is invalid")] + /// Encapsulation key is invalid InvalidEncapsKey, - #[doc = "Error encapsulating key with provided randomness"] - #[error("error encapsulating key with provided randomness")] + + /// Error encapsulating key with provided randomness InvalidRandomness, - #[doc = "An unknown error occurred"] - #[error("an unknown error occurred")] + + /// An unknown error occurred Unknown, } /// Error indicating that decapsulating failed -#[derive(thiserror::Error, Debug)] +#[derive(Debug)] pub enum DecapsError { - #[doc = "Ciphertext key is invalid"] - #[error("ciphertext key is invalid")] - InvalidCipertext, - #[doc = "Decapsulation key is invalid"] - #[error("decapsulation key is invalid")] + /// Ciphertext key is invalid + InvalidCiphertext, + + /// Decapsulation key is invalid InvalidDecapsKey, - #[doc = "An unknown error occurred"] - #[error("an unknown error occurred")] + + /// An unknown error occurred Unknown, } + +impl core::fmt::Display for KeyGenError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let text = match self { + KeyGenError::InvalidRandomness => "error generating key with provided randomness", + KeyGenError::Unknown => "an unknown error occurred", + }; + + f.write_str(text) + } +} + +impl core::fmt::Display for EncapsError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let text = match self { + EncapsError::InvalidEncapsKey => "encapsulation key is invalid", + EncapsError::InvalidRandomness => "error generating key with provided randomness", + EncapsError::Unknown => "an unknown error occurred", + }; + + f.write_str(text) + } +} + +impl core::fmt::Display for DecapsError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let text = match self { + DecapsError::InvalidDecapsKey => "encapsulation key is invalid", + DecapsError::InvalidCiphertext => "ciphertext is invalid", + DecapsError::Unknown => "an unknown error occurred", + }; + + f.write_str(text) + } +} diff --git a/traits/src/kem/slice.rs b/traits/src/kem/slice.rs index fcbe1862f..dcb37d664 100644 --- a/traits/src/kem/slice.rs +++ b/traits/src/kem/slice.rs @@ -12,85 +12,67 @@ pub trait Kem { } /// Error generating key with provided randomness -#[derive(thiserror::Error, Debug)] +#[derive(Debug)] pub enum KeyGenError { - #[doc = "Error generating key with provided randomness"] - #[error("error generating key with provided randomness")] + /// Error generating key with provided randomness InvalidRandomness, - #[doc = "The provided randomness has the wrong length"] - #[error("the provided randomness has the wrong length")] + /// The provided randomness has the wrong length InvalidRandomnessLength, - #[doc = "The provided encapsulation key has the wrong length"] - #[error("the provided encapsulation key has the wrong length")] + /// The provided encapsulation key has the wrong length InvalidEncapsKeyLength, - #[doc = "The provided decapulation key has the wrong length"] - #[error("the provided decapulation key has the wrong length")] + /// The provided decapulation key has the wrong length InvalidDecapsKeyLength, - #[doc = "An unknown error occurred"] - #[error("an unknown error occurred")] + /// An unknown error occurred Unknown, } /// Error indicating that encapsulating failed -#[derive(thiserror::Error, Debug)] +#[derive(Debug)] pub enum EncapsError { - #[doc = "Encapsulation key is invalid"] - #[error("encapsulation key is invalid")] + /// Encapsulation key is invalid InvalidEncapsKey, - #[doc = "Error encapsulating key with provided randomness"] - #[error("error encapsulating key with provided randomness")] + /// Error encapsulating key with provided randomness InvalidRandomness, - #[doc = "The provided randomness has the wrong length"] - #[error("the provided randomness has the wrong length")] + /// The provided randomness has the wrong length InvalidRandomnessLength, - #[doc = "The provided encapsulation key has the wrong length"] - #[error("the provided encapsulation key has the wrong length")] + /// The provided encapsulation key has the wrong length InvalidEncapsKeyLength, - #[doc = "The provided ciphertext has the wrong length"] - #[error("the provided ciphertext has the wrong length")] - InvalidCipertextLength, - #[doc = "The provided shared secret has the wrong length"] - #[error("the provided shared secret has the wrong length")] + /// The provided ciphertext has the wrong length + InvalidCiphertextLength, + /// The provided shared secret has the wrong length InvalidSharedSecretLength, - #[doc = "An unknown error occurred"] - #[error("an unknown error occurred")] + /// An unknown error occurred Unknown, } /// Error indicating that decapsulating failed -#[derive(thiserror::Error, Debug)] +#[derive(Debug)] pub enum DecapsError { - #[doc = "Ciphertext key is invalid"] - #[error("ciphertext key is invalid")] - InvalidCipertext, - #[doc = "Decapsulation key is invalid"] - #[error("decapsulation key is invalid")] + /// Ciphertext key is invalid + InvalidCiphertext, + /// Decapsulation key is invalid InvalidDecapsKey, - #[doc = "The provided decapulation key has the wrong length"] - #[error("the provided decapulation key has the wrong length")] + /// The provided decapulation key has the wrong length InvalidDecapsKeyLength, - #[doc = "The provided ciphertext has the wrong length"] - #[error("the provided ciphertext has the wrong length")] - InvalidCipertextLength, - #[doc = "The provided shared secret has the wrong length"] - #[error("the provided shared secret has the wrong length")] + /// The provided ciphertext has the wrong length + InvalidCiphertextLength, + /// The provided shared secret has the wrong length InvalidSharedSecretLength, - #[doc = "An unknown error occurred"] - #[error("an unknown error occurred")] + /// An unknown error occurred Unknown, } -impl From for KeyGenError { - fn from(value: super::arrayref::KeyGenError) -> Self { +impl From for KeyGenError { + fn from(value: arrayref::KeyGenError) -> Self { match value { - super::arrayref::KeyGenError::InvalidRandomness => KeyGenError::InvalidRandomness, - super::arrayref::KeyGenError::Unknown => KeyGenError::Unknown, + arrayref::KeyGenError::InvalidRandomness => KeyGenError::InvalidRandomness, + arrayref::KeyGenError::Unknown => KeyGenError::Unknown, } } } -impl From for EncapsError { - fn from(value: super::arrayref::EncapsError) -> Self { +impl From for EncapsError { + fn from(value: arrayref::EncapsError) -> Self { match value { arrayref::EncapsError::InvalidEncapsKey => EncapsError::InvalidEncapsKey, arrayref::EncapsError::InvalidRandomness => EncapsError::InvalidRandomness, @@ -99,10 +81,10 @@ impl From for EncapsError { } } -impl From for DecapsError { - fn from(value: super::arrayref::DecapsError) -> Self { +impl From for DecapsError { + fn from(value: arrayref::DecapsError) -> Self { match value { - arrayref::DecapsError::InvalidCipertext => DecapsError::InvalidCipertext, + arrayref::DecapsError::InvalidCiphertext => DecapsError::InvalidCiphertext, arrayref::DecapsError::InvalidDecapsKey => DecapsError::InvalidDecapsKey, arrayref::DecapsError::Unknown => DecapsError::Unknown, } @@ -110,9 +92,10 @@ impl From for DecapsError { } #[macro_export] +/// Implements [`Kem`] for any [`arrayref::Kem`]. Hides the impl in a module of the given name. macro_rules! impl_trait { - ($name:ty => $ek:expr, $dk:expr, $ct:expr, $ss:expr, $rand_kg:expr, $rand_encaps:expr) => { - impl $crate::kem::slice::Kem for $name { + ($type:ty => $ek:expr, $dk:expr, $ct:expr, $ss:expr, $rand_kg:expr, $rand_encaps:expr) => { + impl $crate::kem::slice::Kem for $type { fn keygen(ek: &mut [u8], dk: &mut [u8], rand: &[u8]) -> Result<(), $crate::kem::slice::KeyGenError> { let ek : &mut [u8; $ek] = ek .try_into() @@ -124,13 +107,13 @@ macro_rules! impl_trait { .try_into() .map_err(|_| $crate::kem::slice::KeyGenError::InvalidRandomnessLength)?; - <$name as $crate::kem::arrayref::Kem<$ek, $dk, $ct, $ss, $rand_kg, $rand_encaps>>::keygen(ek, dk, rand).map_err($crate::kem::slice::KeyGenError::from) + <$type as $crate::kem::arrayref::Kem<$ek, $dk, $ct, $ss, $rand_kg, $rand_encaps>>::keygen(ek, dk, rand).map_err($crate::kem::slice::KeyGenError::from) } fn encaps(ct: &mut [u8], ss: &mut [u8], ek: &[u8], rand: &[u8]) -> Result<(), $crate::kem::slice::EncapsError>{ let ct : &mut [u8; $ct] = ct .try_into() - .map_err(|_| $crate::kem::slice::EncapsError::InvalidCipertextLength)?; + .map_err(|_| $crate::kem::slice::EncapsError::InvalidCiphertextLength)?; let ss : &mut [u8; $ss] = ss .try_into() .map_err(|_| $crate::kem::slice::EncapsError::InvalidSharedSecretLength)?; @@ -142,7 +125,7 @@ macro_rules! impl_trait { .map_err(|_| $crate::kem::slice::EncapsError::InvalidRandomnessLength)?; - <$name as $crate::kem::arrayref::Kem<$ek, $dk, $ct, $ss, $rand_kg, $rand_encaps>>::encaps(ct, ss, ek,rand).map_err($crate::kem::slice::EncapsError::from) + <$type as $crate::kem::arrayref::Kem<$ek, $dk, $ct, $ss, $rand_kg, $rand_encaps>>::encaps(ct, ss, ek,rand).map_err($crate::kem::slice::EncapsError::from) } fn decaps(ss: &mut [u8], ct: &[u8], dk: &[u8]) -> Result<(), $crate::kem::slice::DecapsError> { @@ -151,14 +134,12 @@ macro_rules! impl_trait { .map_err(|_| $crate::kem::slice::DecapsError::InvalidSharedSecretLength)?; let ct : &[u8; $ct] = ct .try_into() - .map_err(|_| $crate::kem::slice::DecapsError::InvalidCipertextLength)?; + .map_err(|_| $crate::kem::slice::DecapsError::InvalidCiphertextLength)?; let dk : &[u8; $dk] = dk .try_into() .map_err(|_| $crate::kem::slice::DecapsError::InvalidDecapsKeyLength)?; - - <$name as $crate::kem::arrayref::Kem<$ek, $dk, $ct, $ss, $rand_kg, $rand_encaps>>::decaps(ss, ct, dk).map_err($crate::kem::slice::DecapsError::from) - + <$type as $crate::kem::arrayref::Kem<$ek, $dk, $ct, $ss, $rand_kg, $rand_encaps>>::decaps(ss, ct, dk).map_err($crate::kem::slice::DecapsError::from) } } @@ -166,3 +147,69 @@ macro_rules! impl_trait { } pub use impl_trait; + +impl core::fmt::Display for KeyGenError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let text = match self { + KeyGenError::InvalidRandomness => "error generating key with provided randomness", + KeyGenError::Unknown => "an unknown error occurred", + KeyGenError::InvalidRandomnessLength => "the provided randomness has the wrong length", + KeyGenError::InvalidEncapsKeyLength => { + "the provided encapsulation key has the wrong length" + } + KeyGenError::InvalidDecapsKeyLength => { + "the provided decapsulation key has the wrong length" + } + }; + + f.write_str(text) + } +} + +impl core::fmt::Display for EncapsError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let text = match self { + EncapsError::InvalidEncapsKey => "encapsulation key is invalid", + EncapsError::InvalidRandomness => "error generating key with provided randomness", + EncapsError::Unknown => "an unknown error occurred", + EncapsError::InvalidRandomnessLength => "the provided randomness has the wrong length", + EncapsError::InvalidEncapsKeyLength => { + "the provided encapsulation key has the wrong length" + } + EncapsError::InvalidCiphertextLength => "the provided ciphertext has the wrong length", + EncapsError::InvalidSharedSecretLength => { + "the provided shared secret has the wrong length" + } + }; + + f.write_str(text) + } +} + +impl core::fmt::Display for DecapsError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let text = match self { + DecapsError::InvalidDecapsKey => "encapsulation key is invalid", + DecapsError::InvalidCiphertext => "ciphertext is invalid", + DecapsError::Unknown => "an unknown error occurred", + DecapsError::InvalidCiphertextLength => "the provided ciphertext has the wrong length", + DecapsError::InvalidSharedSecretLength => { + "the provided shared secret has the wrong length" + } + DecapsError::InvalidDecapsKeyLength => { + "the provided decapsulation key has the wrong length" + } + }; + + f.write_str(text) + } +} + +#[cfg(feature = "error_in_core")] +/// Here we implement the Error trait. This has only been added to core relatively recently, so we +/// are hiding that behind a feature. +mod error_in_core_impls { + impl core::error::Error for super::KeyGenError {} + impl core::error::Error for super::EncapsError {} + impl core::error::Error for super::DecapsError {} +} From 67a8d124098798d90de700ee86db52b8a705343e Mon Sep 17 00:00:00 2001 From: "Jan Winkelmann (keks)" Date: Wed, 2 Jul 2025 19:08:17 +0200 Subject: [PATCH 03/12] Add owned and owned-secret version --- traits/Cargo.toml | 1 + traits/src/kem/mod.rs | 2 + traits/src/kem/owned.rs | 86 ++++++++++++++++++++++++++++ traits/src/kem/secrets.rs | 114 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 203 insertions(+) create mode 100644 traits/src/kem/owned.rs create mode 100644 traits/src/kem/secrets.rs diff --git a/traits/Cargo.toml b/traits/Cargo.toml index 179d0828d..6c04847da 100644 --- a/traits/Cargo.toml +++ b/traits/Cargo.toml @@ -15,3 +15,4 @@ error_in_core = [] [dependencies] rand = { version = "0.9", default-features = false } +libcrux-secrets = { version = "=0.0.3", path = "../secrets" } diff --git a/traits/src/kem/mod.rs b/traits/src/kem/mod.rs index aee421d53..a5cce3143 100644 --- a/traits/src/kem/mod.rs +++ b/traits/src/kem/mod.rs @@ -2,6 +2,8 @@ //! encapsulation mechanisms (KEMs). pub mod arrayref; +pub mod owned; +pub mod secrets; pub mod slice; use rand::CryptoRng; diff --git a/traits/src/kem/owned.rs b/traits/src/kem/owned.rs new file mode 100644 index 000000000..691de743b --- /dev/null +++ b/traits/src/kem/owned.rs @@ -0,0 +1,86 @@ +use super::arrayref; + +pub use arrayref::{DecapsError, EncapsError, KeyGenError}; + +pub trait Kem< + const EK_LEN: usize, + const DK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, + const RAND_KEYGEN_LEN: usize, + const RAND_ENCAPS_LEN: usize, +> +{ + /// Generate a pair of encapsulation and decapsulation keys. + fn keygen(rand: &[u8; RAND_KEYGEN_LEN]) -> Result<([u8; DK_LEN], [u8; EK_LEN]), KeyGenError>; + + /// Encapsulate a shared secret towards a given encapsulation key. + fn encaps( + ek: &[u8; EK_LEN], + rand: &[u8; RAND_ENCAPS_LEN], + ) -> Result<([u8; SS_LEN], [u8; CT_LEN]), EncapsError>; + + /// Decapsulate a shared secret. + fn decaps(ct: &[u8; CT_LEN], dk: &[u8; DK_LEN]) -> Result<[u8; SS_LEN], DecapsError>; +} + +impl< + const EK_LEN: usize, + const DK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, + const RAND_KEYGEN_LEN: usize, + const RAND_ENCAPS_LEN: usize, + T: arrayref::Kem, + > Kem for T +{ + fn keygen(rand: &[u8; RAND_KEYGEN_LEN]) -> Result<([u8; DK_LEN], [u8; EK_LEN]), KeyGenError> { + let mut dk = [0u8; DK_LEN]; + let mut ek = [0u8; EK_LEN]; + + >::keygen(&mut ek, &mut dk, rand)?; + + Ok((dk, ek)) + } + + fn encaps( + ek: &[u8; EK_LEN], + rand: &[u8; RAND_ENCAPS_LEN], + ) -> Result<([u8; SS_LEN], [u8; CT_LEN]), EncapsError> { + let mut ss = [0u8; SS_LEN]; + let mut ct = [0u8; CT_LEN]; + + >::encaps(&mut ct, &mut ss, ek, rand)?; + + Ok((ss, ct)) + } + + fn decaps(ct: &[u8; CT_LEN], dk: &[u8; DK_LEN]) -> Result<[u8; SS_LEN], DecapsError> { + let mut ss = [0u8; SS_LEN]; + + >::decaps(&mut ss, ct, dk)?; + + Ok(ss) + } +} diff --git a/traits/src/kem/secrets.rs b/traits/src/kem/secrets.rs new file mode 100644 index 000000000..79f47ec5b --- /dev/null +++ b/traits/src/kem/secrets.rs @@ -0,0 +1,114 @@ +use libcrux_secrets::{Classify, Declassify}; + +use super::owned; + +pub use owned::{DecapsError, EncapsError, KeyGenError}; + +pub trait Kem< + const EK_LEN: usize, + const DK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, + const RAND_KEYGEN_LEN: usize, + const RAND_ENCAPS_LEN: usize, +> +{ + /// Generate a pair of encapsulation and decapsulation keys. + fn keygen( + rand: &[u8; RAND_KEYGEN_LEN], + ) -> Result< + ( + impl Declassify, + impl Declassify, + ), + KeyGenError, + >; + + /// Encapsulate a shared secret towards a given encapsulation key. + fn encaps( + ek: &[u8; EK_LEN], + rand: &[u8; RAND_ENCAPS_LEN], + ) -> Result< + ( + impl Declassify, + impl Declassify, + ), + EncapsError, + >; + + /// Decapsulate a shared secret. + fn decaps( + ct: &[u8; CT_LEN], + dk: &[u8; DK_LEN], + ) -> Result, DecapsError>; +} + +impl< + const EK_LEN: usize, + const DK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, + const RAND_KEYGEN_LEN: usize, + const RAND_ENCAPS_LEN: usize, + T: owned::Kem, + > Kem for T +{ + fn keygen( + rand: &[u8; RAND_KEYGEN_LEN], + ) -> Result< + ( + impl Declassify, + impl Declassify, + ), + KeyGenError, + > { + let (dk, ek) = >::keygen(rand)?; + + Ok((dk.classify(), ek.classify())) + } + + fn encaps( + ek: &[u8; EK_LEN], + rand: &[u8; RAND_ENCAPS_LEN], + ) -> Result< + ( + impl Declassify, + impl Declassify, + ), + EncapsError, + > { + let (ss, ct) = >::encaps(ek, rand)?; + + Ok((ss.classify(), ct.classify())) + } + + fn decaps( + ct: &[u8; CT_LEN], + dk: &[u8; DK_LEN], + ) -> Result, DecapsError> { + let ss = >::decaps(ct, dk)?; + + Ok(ss.classify()) + } +} From 444938ebd35136bfbb8ed4a55ad32f42e44be98e Mon Sep 17 00:00:00 2001 From: "Jan Winkelmann (keks)" Date: Mon, 7 Jul 2025 17:47:27 +0200 Subject: [PATCH 04/12] Add implementations for P-256 and ML-KEM512 --- libcrux-ml-kem/Cargo.toml | 1 + libcrux-ml-kem/src/mlkem512.rs | 57 +++++++++++++++++++++++++ p256/Cargo.toml | 1 + p256/src/impl_kem.rs | 78 ++++++++++++++++++++++++++++++++++ p256/src/lib.rs | 3 ++ 5 files changed, 140 insertions(+) create mode 100644 p256/src/impl_kem.rs diff --git a/libcrux-ml-kem/Cargo.toml b/libcrux-ml-kem/Cargo.toml index c3781b56c..85cf6d2ee 100644 --- a/libcrux-ml-kem/Cargo.toml +++ b/libcrux-ml-kem/Cargo.toml @@ -31,6 +31,7 @@ libcrux-platform = { version = "0.0.2", path = "../sys/platform" } libcrux-sha3 = { version = "0.0.3", path = "../libcrux-sha3" } libcrux-intrinsics = { version = "0.0.3", path = "../libcrux-intrinsics" } libcrux-secrets = { version = "0.0.3", path = "../secrets" } +libcrux-traits = { version = "0.0.3", path = "../traits" } hax-lib.workspace = true [features] diff --git a/libcrux-ml-kem/src/mlkem512.rs b/libcrux-ml-kem/src/mlkem512.rs index e709628ff..046dd2ab6 100644 --- a/libcrux-ml-kem/src/mlkem512.rs +++ b/libcrux-ml-kem/src/mlkem512.rs @@ -26,6 +26,8 @@ const ETA2_RANDOMNESS_SIZE: usize = ETA2 * 64; const IMPLICIT_REJECTION_HASH_INPUT_SIZE: usize = SHARED_SECRET_SIZE + CPA_PKE_CIPHERTEXT_SIZE; +pub struct MlKem512; + /// An ML-KEM 512 Ciphertext pub type MlKem512Ciphertext = MlKemCiphertext; /// An ML-KEM 512 Private key @@ -524,6 +526,61 @@ pub fn decapsulate( >(private_key, ciphertext) } +impl + libcrux_traits::kem::arrayref::Kem< + CPA_PKE_PUBLIC_KEY_SIZE, + SECRET_KEY_SIZE, + CPA_PKE_CIPHERTEXT_SIZE, + SHARED_SECRET_SIZE, + KEY_GENERATION_SEED_SIZE, + SHARED_SECRET_SIZE, + > for MlKem512 +{ + fn keygen( + ek: &mut [u8; CPA_PKE_PUBLIC_KEY_SIZE], + dk: &mut [u8; SECRET_KEY_SIZE], + rand: &[u8; KEY_GENERATION_SEED_SIZE], + ) -> Result<(), libcrux_traits::kem::owned::KeyGenError> { + let key_pair = generate_key_pair(*rand); + ek.copy_from_slice(key_pair.pk()); + dk.copy_from_slice(key_pair.sk()); + + // TODO: validate that the key is valid (how?) + + Ok(()) + } + + fn encaps( + ct: &mut [u8; CPA_PKE_CIPHERTEXT_SIZE], + ss: &mut [u8; SHARED_SECRET_SIZE], + ek: &[u8; CPA_PKE_PUBLIC_KEY_SIZE], + rand: &[u8; SHARED_SECRET_SIZE], + ) -> Result<(), libcrux_traits::kem::owned::EncapsError> { + let public_key = MlKem512PublicKey::from(ek); + + let (ct_, ss_) = encapsulate(&public_key, *rand); + ct.copy_from_slice(ct_.as_slice()); + ss.copy_from_slice(ss_.as_slice()); + + Ok(()) + } + + fn decaps( + ss: &mut [u8; SHARED_SECRET_SIZE], + ct: &[u8; CPA_PKE_CIPHERTEXT_SIZE], + dk: &[u8; SECRET_KEY_SIZE], + ) -> Result<(), libcrux_traits::kem::owned::DecapsError> { + let secret_key = MlKem512PrivateKey::from(dk); + let ciphertext = MlKem512Ciphertext::from(ct); + + let ss_ = decapsulate(&secret_key, &ciphertext); + + ss.copy_from_slice(ss_.as_slice()); + + Ok(()) + } +} + /// Randomized APIs /// /// The functions in this module are equivalent to the one in the main module, diff --git a/p256/Cargo.toml b/p256/Cargo.toml index 8d6e76625..5032b28a0 100644 --- a/p256/Cargo.toml +++ b/p256/Cargo.toml @@ -12,6 +12,7 @@ repository.workspace = true [dependencies] libcrux-macros = { version = "=0.0.3", path = "../macros" } +libcrux-traits = { version = "=0.0.3", path = "../traits" } libcrux-hacl-rs = { version = "=0.0.3", path = "../hacl-rs/" } libcrux-sha2 = { version = "=0.0.3", path = "../sha2", features = [ "expose-hacl", diff --git a/p256/src/impl_kem.rs b/p256/src/impl_kem.rs new file mode 100644 index 000000000..cc9b147af --- /dev/null +++ b/p256/src/impl_kem.rs @@ -0,0 +1,78 @@ +use libcrux_traits::kem::arrayref::*; + +// The two kinds of data that are actually there +const SCALAR_LEN: usize = 32; +const POINT_LEN: usize = 64; + +// The sorts of data that the Kem trait needs +const EK_LEN: usize = POINT_LEN; +const CT_LEN: usize = POINT_LEN; +const SS_LEN: usize = POINT_LEN; + +const DK_LEN: usize = SCALAR_LEN; +const RAND_KEYGEN_LEN: usize = SCALAR_LEN; +const RAND_ENCAPS_LEN: usize = SCALAR_LEN; + +impl Kem for super::P256 { + fn keygen( + ek: &mut [u8; EK_LEN], + dk: &mut [u8; DK_LEN], + rand: &[u8; RAND_KEYGEN_LEN], + ) -> Result<(), KeyGenError> { + if !super::p256::validate_private_key(rand.as_slice()) { + return Err(KeyGenError::InvalidRandomness); + } + + dk.copy_from_slice(rand.as_slice()); + if !super::p256::dh_initiator(ek, dk) { + return Err(KeyGenError::Unknown); + } + + Ok(()) + } + + fn encaps( + ct: &mut [u8; CT_LEN], + ss: &mut [u8; SS_LEN], + ek: &[u8; EK_LEN], + rand: &[u8; RAND_ENCAPS_LEN], + ) -> Result<(), EncapsError> { + if !super::p256::validate_public_key(ek) { + return Err(EncapsError::InvalidEncapsKey); + } + + if !super::p256::validate_private_key(rand.as_slice()) { + return Err(EncapsError::InvalidRandomness); + } + + if !super::p256::dh_responder(ss, ek, rand) { + return Err(EncapsError::Unknown); + } + + if !super::p256::dh_initiator(ct, rand) { + return Err(EncapsError::Unknown); + } + + Ok(()) + } + + fn decaps( + ss: &mut [u8; SS_LEN], + ct: &[u8; CT_LEN], + dk: &[u8; DK_LEN], + ) -> Result<(), DecapsError> { + if !super::p256::validate_public_key(ct) { + return Err(DecapsError::InvalidCiphertext); + } + + if !super::p256::validate_private_key(dk.as_slice()) { + return Err(DecapsError::InvalidDecapsKey); + } + + if !super::p256::dh_responder(ss, ct, dk) { + return Err(DecapsError::Unknown); + } + + Ok(()) + } +} diff --git a/p256/src/lib.rs b/p256/src/lib.rs index 3394e5b21..89a0895a8 100644 --- a/p256/src/lib.rs +++ b/p256/src/lib.rs @@ -5,8 +5,11 @@ #![no_std] // HACL* generated code +mod impl_kem; mod p256; mod p256_precomptable; +pub struct P256; + #[cfg(feature = "expose-hacl")] pub use p256::*; From 49b062a5623d8bd0889a33ec702cb4fa7f1edb4e Mon Sep 17 00:00:00 2001 From: "Jan Winkelmann (keks)" Date: Tue, 8 Jul 2025 12:39:52 +0200 Subject: [PATCH 05/12] More Kem Implementations: ML-KEM 768, 1024, XWing. Exported in -kem --- curve25519/src/lib.rs | 8 +- libcrux-kem/Cargo.toml | 2 + libcrux-kem/src/kem.rs | 134 ++++++++++++++++++++++++++++++++ libcrux-ml-kem/Cargo.toml | 2 +- libcrux-ml-kem/src/lib.rs | 64 +++++++++++++++ libcrux-ml-kem/src/mlkem1024.rs | 20 +++++ libcrux-ml-kem/src/mlkem512.rs | 73 +++++------------ libcrux-ml-kem/src/mlkem768.rs | 20 +++++ 8 files changed, 264 insertions(+), 59 deletions(-) diff --git a/curve25519/src/lib.rs b/curve25519/src/lib.rs index f806ae751..d4ff7a28e 100644 --- a/curve25519/src/lib.rs +++ b/curve25519/src/lib.rs @@ -30,9 +30,11 @@ trait Curve25519 { fn ecdh(out: &mut [u8; SHK_LEN], pk: &[u8; PK_LEN], sk: &[u8; SK_LEN]) -> Result<(), Error>; } -pub struct Kem; +pub struct X25519; -impl libcrux_traits::kem::arrayref::Kem for Kem { +impl libcrux_traits::kem::arrayref::Kem + for X25519 +{ fn keygen( ek: &mut [u8; SK_LEN], dk: &mut [u8; PK_LEN], @@ -66,7 +68,7 @@ impl libcrux_traits::kem::arrayref::Kem PK_LEN, SK_LEN, PK_LEN, PK_LEN, SK_LEN, SK_LEN); +libcrux_traits::kem::slice::impl_trait!(X25519 => PK_LEN, SK_LEN, PK_LEN, PK_LEN, SK_LEN, SK_LEN); /// Clamp a scalar. fn clamp(scalar: &mut [u8; SK_LEN]) { diff --git a/libcrux-kem/Cargo.toml b/libcrux-kem/Cargo.toml index 00f991390..256eeb254 100644 --- a/libcrux-kem/Cargo.toml +++ b/libcrux-kem/Cargo.toml @@ -20,6 +20,8 @@ libcrux-ml-kem = { version = "=0.0.3", path = "../libcrux-ml-kem", default-featu ] } libcrux-sha3 = { version = "=0.0.3", path = "../libcrux-sha3" } libcrux-ecdh = { version = "=0.0.3", path = "../libcrux-ecdh", default-features = false } +libcrux-curve25519 = { version = "=0.0.3", path = "../curve25519", default-features = false } +libcrux-p256 = { version = "=0.0.3", path = "../p256", default-features = false } libcrux-traits = { version = "=0.0.3", path = "../traits" } rand = { version = "0.9", default-features = false } diff --git a/libcrux-kem/src/kem.rs b/libcrux-kem/src/kem.rs index 8c65f5086..3854a9e25 100644 --- a/libcrux-kem/src/kem.rs +++ b/libcrux-kem/src/kem.rs @@ -69,6 +69,13 @@ pub mod deterministic { pub use libcrux_ml_kem::mlkem768::generate_key_pair as mlkem768_generate_keypair_derand; } +pub use libcrux_curve25519::X25519; +pub use libcrux_ml_kem::mlkem1024::MlKem1024; +pub use libcrux_ml_kem::mlkem512::MlKem512; +pub use libcrux_ml_kem::mlkem768::MlKem768; +pub use libcrux_p256::P256; +pub use xwing::XWing; + use libcrux_ml_kem::MlKemSharedSecret; pub use libcrux_ml_kem::{ mlkem1024::{MlKem1024Ciphertext, MlKem1024PrivateKey, MlKem1024PublicKey}, @@ -883,6 +890,133 @@ mod xwing { use super::*; + const MLKEM768_EK_LEN: usize = libcrux_ml_kem::mlkem768::MlKem768PublicKey::len(); + const MLKEM768_DK_LEN: usize = libcrux_ml_kem::mlkem768::MlKem768PrivateKey::len(); + const MLKEM768_CT_LEN: usize = libcrux_ml_kem::mlkem768::MlKem768Ciphertext::len(); + const MLKEM768_SS_LEN: usize = 32; + const MLKEM768_RAND_KEYGEN_LEN: usize = 64; + const MLKEM768_RAND_ENCAPS_LEN: usize = MLKEM768_SS_LEN; + + const X25519_EK_LEN: usize = 32; + const X25519_DK_LEN: usize = 32; + const X25519_CT_LEN: usize = 32; + const X25519_SS_LEN: usize = 32; + const X25519_RAND_KEYGEN_LEN: usize = X25519_DK_LEN; + const X25519_RAND_ENCAPS_LEN: usize = X25519_DK_LEN; + + const EK_LEN: usize = MLKEM768_EK_LEN + X25519_EK_LEN; + const DK_LEN: usize = MLKEM768_DK_LEN + X25519_DK_LEN; + const CT_LEN: usize = MLKEM768_CT_LEN + X25519_CT_LEN; + const SS_LEN: usize = 32; + const RAND_KEYGEN_LEN: usize = 32; // gets expanded later + const RAND_ENCAPS_LEN: usize = MLKEM768_RAND_ENCAPS_LEN + X25519_RAND_ENCAPS_LEN; + + use libcrux_curve25519::X25519; + use libcrux_ml_kem::mlkem768::MlKem768; + + pub struct XWing; + + impl + libcrux_traits::kem::arrayref::Kem< + EK_LEN, + DK_LEN, + CT_LEN, + SS_LEN, + RAND_KEYGEN_LEN, + RAND_ENCAPS_LEN, + > for XWing + { + fn keygen( + ek: &mut [u8; EK_LEN], + dk: &mut [u8; DK_LEN], + rand: &[u8; RAND_KEYGEN_LEN], + ) -> Result<(), libcrux_traits::kem::owned::KeyGenError> { + let expanded: [u8; MLKEM768_RAND_KEYGEN_LEN + X25519_RAND_KEYGEN_LEN] = + libcrux_sha3::shake256(rand); + + let (rand_m, rand_x) = expanded.split_at(MLKEM768_RAND_KEYGEN_LEN); + let rand_m: &[u8; MLKEM768_RAND_KEYGEN_LEN] = rand_m.try_into().unwrap(); + let rand_x: &[u8; X25519_RAND_KEYGEN_LEN] = rand_x.try_into().unwrap(); + + let (ek_m, ek_x) = ek.split_at_mut(MLKEM768_EK_LEN); + let ek_m: &mut [u8; MLKEM768_EK_LEN] = ek_m.try_into().unwrap(); + let ek_x: &mut [u8; X25519_EK_LEN] = ek_x.try_into().unwrap(); + + let (dk_m, dk_x) = dk.split_at_mut(MLKEM768_DK_LEN); + let dk_m: &mut [u8; MLKEM768_DK_LEN] = dk_m.try_into().unwrap(); + let dk_x: &mut [u8; X25519_DK_LEN] = dk_x.try_into().unwrap(); + + MlKem768::keygen(ek_m, dk_m, rand_m)?; + X25519::keygen(ek_x, dk_x, rand_x)?; + + Ok(()) + } + + fn encaps( + ct: &mut [u8; CT_LEN], + ss: &mut [u8; SS_LEN], + ek: &[u8; EK_LEN], + rand: &[u8; RAND_ENCAPS_LEN], + ) -> Result<(), libcrux_traits::kem::owned::EncapsError> { + let (rand_m, rand_x) = rand.split_at(MLKEM768_RAND_ENCAPS_LEN); + let rand_m: &[u8; MLKEM768_RAND_ENCAPS_LEN] = rand_m.try_into().unwrap(); + let rand_x: &[u8; X25519_RAND_ENCAPS_LEN] = rand_x.try_into().unwrap(); + + let (ek_m, ek_x) = ek.split_at(MLKEM768_EK_LEN); + let ek_m: &[u8; MLKEM768_EK_LEN] = ek_m.try_into().unwrap(); + let ek_x: &[u8; X25519_EK_LEN] = ek_x.try_into().unwrap(); + + let (ct_m, ct_x) = ct.split_at_mut(MLKEM768_CT_LEN); + let ct_m: &mut [u8; MLKEM768_CT_LEN] = ct_m.try_into().unwrap(); + let ct_x: &mut [u8; X25519_CT_LEN] = ct_x.try_into().unwrap(); + + let mut hash_buffer = [0u8; 32 + 32 + X25519_CT_LEN + X25519_EK_LEN + 6]; + hash_buffer[64..96].copy_from_slice(ct_x); + hash_buffer[96..128].copy_from_slice(ek_x); + hash_buffer[128..134].copy_from_slice(&[0x5c, 0x2e, 0x2f, 0x2f, 0x5e, 0x5c]); + + let ss_m: &mut [u8; 32] = (&mut hash_buffer[0..32]).try_into().unwrap(); + MlKem768::encaps(ct_m, ss_m, ek_m, rand_m)?; + + let ss_x: &mut [u8; 32] = (&mut hash_buffer[32..64]).try_into().unwrap(); + X25519::encaps(ct_x, ss_x, ek_x, rand_x)?; + sha3::sha256_ema(ss, &hash_buffer); + + Ok(()) + } + + fn decaps( + ss: &mut [u8; SS_LEN], + ct: &[u8; CT_LEN], + dk: &[u8; DK_LEN], + ) -> Result<(), libcrux_traits::kem::owned::DecapsError> { + let (dk_m, dk_x) = dk.split_at(MLKEM768_DK_LEN); + let dk_m: &[u8; MLKEM768_DK_LEN] = dk_m.try_into().unwrap(); + let dk_x: &[u8; X25519_DK_LEN] = dk_x.try_into().unwrap(); + + let (ct_m, ct_x) = ct.split_at(MLKEM768_CT_LEN); + let ct_m: &[u8; MLKEM768_CT_LEN] = ct_m.try_into().unwrap(); + let ct_x: &[u8; X25519_CT_LEN] = ct_x.try_into().unwrap(); + + let mut ek_x = [0u8; X25519_EK_LEN]; + libcrux_curve25519::secret_to_public(&mut ek_x, dk_x); + + let mut hash_buffer = [0u8; 32 + 32 + X25519_CT_LEN + X25519_EK_LEN + 6]; + hash_buffer[64..96].copy_from_slice(ct_x); + hash_buffer[96..128].copy_from_slice(&ek_x); + hash_buffer[128..134].copy_from_slice(&[0x5c, 0x2e, 0x2f, 0x2f, 0x5e, 0x5c]); + + let ss_m: &mut [u8; 32] = (&mut hash_buffer[0..32]).try_into().unwrap(); + MlKem768::decaps(ss_m, ct_m, dk_m)?; + + let ss_x: &mut [u8; 32] = (&mut hash_buffer[32..64]).try_into().unwrap(); + X25519::decaps(ss_x, ct_x, dk_x)?; + sha3::sha256_ema(ss, &hash_buffer); + + Ok(()) + } + } + pub struct XWingSharedSecret { pub(super) value: [u8; 32], } diff --git a/libcrux-ml-kem/Cargo.toml b/libcrux-ml-kem/Cargo.toml index 85cf6d2ee..140b4a8ee 100644 --- a/libcrux-ml-kem/Cargo.toml +++ b/libcrux-ml-kem/Cargo.toml @@ -36,7 +36,7 @@ hax-lib.workspace = true [features] # By default all variants and std are enabled. -default = ["default-no-std", "std"] +default = ["default-no-std", "std", "kyber"] default-no-std = ["mlkem512", "mlkem768", "mlkem1024", "rand"] # Hardware features can be force enabled. diff --git a/libcrux-ml-kem/src/lib.rs b/libcrux-ml-kem/src/lib.rs index bffdd6ce9..9e54ecd78 100644 --- a/libcrux-ml-kem/src/lib.rs +++ b/libcrux-ml-kem/src/lib.rs @@ -137,6 +137,7 @@ cfg_kyber! { pub mod kyber512 { //! Kyber 512 (NIST PQC Round 3) cfg_no_eurydice! { + pub use crate::mlkem512::kyber::Kyber512; pub use crate::mlkem512::kyber::generate_key_pair; pub use crate::mlkem512::kyber::decapsulate; pub use crate::mlkem512::kyber::encapsulate; @@ -150,6 +151,7 @@ cfg_kyber! { pub mod kyber768 { //! Kyber 768 (NIST PQC Round 3) cfg_no_eurydice! { + pub use crate::mlkem768::kyber::Kyber768; pub use crate::mlkem768::kyber::generate_key_pair; pub use crate::mlkem768::kyber::decapsulate; pub use crate::mlkem768::kyber::encapsulate; @@ -163,6 +165,7 @@ cfg_kyber! { pub mod kyber1024 { //! Kyber 1024 (NIST PQC Round 3) cfg_no_eurydice! { + pub use crate::mlkem1024::kyber::Kyber1024; pub use crate::mlkem1024::kyber::generate_key_pair; pub use crate::mlkem1024::kyber::decapsulate; pub use crate::mlkem1024::kyber::encapsulate; @@ -171,3 +174,64 @@ cfg_kyber! { } } } + +macro_rules! impl_kem_trait { + ($variant:ty, $pk:ty, $sk:ty, $ct:ty) => { + impl + libcrux_traits::kem::arrayref::Kem< + CPA_PKE_PUBLIC_KEY_SIZE, + SECRET_KEY_SIZE, + CPA_PKE_CIPHERTEXT_SIZE, + SHARED_SECRET_SIZE, + KEY_GENERATION_SEED_SIZE, + SHARED_SECRET_SIZE, + > for $variant + { + fn keygen( + ek: &mut [u8; CPA_PKE_PUBLIC_KEY_SIZE], + dk: &mut [u8; SECRET_KEY_SIZE], + rand: &[u8; KEY_GENERATION_SEED_SIZE], + ) -> Result<(), libcrux_traits::kem::owned::KeyGenError> { + let key_pair = generate_key_pair(*rand); + ek.copy_from_slice(key_pair.pk()); + dk.copy_from_slice(key_pair.sk()); + + // TODO: validate that the key is valid (how?) + + Ok(()) + } + + fn encaps( + ct: &mut [u8; CPA_PKE_CIPHERTEXT_SIZE], + ss: &mut [u8; SHARED_SECRET_SIZE], + ek: &[u8; CPA_PKE_PUBLIC_KEY_SIZE], + rand: &[u8; SHARED_SECRET_SIZE], + ) -> Result<(), libcrux_traits::kem::owned::EncapsError> { + let public_key: $pk = ek.into(); + + let (ct_, ss_) = encapsulate(&public_key, *rand); + ct.copy_from_slice(ct_.as_slice()); + ss.copy_from_slice(ss_.as_slice()); + + Ok(()) + } + + fn decaps( + ss: &mut [u8; SHARED_SECRET_SIZE], + ct: &[u8; CPA_PKE_CIPHERTEXT_SIZE], + dk: &[u8; SECRET_KEY_SIZE], + ) -> Result<(), libcrux_traits::kem::owned::DecapsError> { + let secret_key: $sk = dk.into(); + let ciphertext: $ct = ct.into(); + + let ss_ = decapsulate(&secret_key, &ciphertext); + + ss.copy_from_slice(ss_.as_slice()); + + Ok(()) + } + } + }; +} + +use impl_kem_trait; diff --git a/libcrux-ml-kem/src/mlkem1024.rs b/libcrux-ml-kem/src/mlkem1024.rs index 7d52a8ee5..885096b0a 100644 --- a/libcrux-ml-kem/src/mlkem1024.rs +++ b/libcrux-ml-kem/src/mlkem1024.rs @@ -25,6 +25,16 @@ const ETA2_RANDOMNESS_SIZE: usize = ETA2 * 64; const IMPLICIT_REJECTION_HASH_INPUT_SIZE: usize = SHARED_SECRET_SIZE + CPA_PKE_CIPHERTEXT_SIZE; +/// The ML-KEM 1024 algorithms +pub struct MlKem1024; + +crate::impl_kem_trait!( + MlKem1024, + MlKem1024PublicKey, + MlKem1024PrivateKey, + MlKem1024Ciphertext +); + /// An ML-KEM 1024 Ciphertext pub type MlKem1024Ciphertext = MlKemCiphertext; /// An ML-KEM 1024 Private key @@ -578,6 +588,16 @@ pub mod rand { pub(crate) mod kyber { use super::*; + /// The Kyber 1024 algorithms + pub struct Kyber1024; + + crate::impl_kem_trait!( + Kyber1024, + MlKem1024PublicKey, + MlKem1024PrivateKey, + MlKem1024Ciphertext + ); + /// Generate Kyber 1024 Key Pair /// /// Generate an ML-KEM key pair. The input is a byte array of size diff --git a/libcrux-ml-kem/src/mlkem512.rs b/libcrux-ml-kem/src/mlkem512.rs index 046dd2ab6..d383cbcc4 100644 --- a/libcrux-ml-kem/src/mlkem512.rs +++ b/libcrux-ml-kem/src/mlkem512.rs @@ -26,8 +26,16 @@ const ETA2_RANDOMNESS_SIZE: usize = ETA2 * 64; const IMPLICIT_REJECTION_HASH_INPUT_SIZE: usize = SHARED_SECRET_SIZE + CPA_PKE_CIPHERTEXT_SIZE; +/// The ML-KEM 512 algorithms pub struct MlKem512; +crate::impl_kem_trait!( + MlKem512, + MlKem512PublicKey, + MlKem512PrivateKey, + MlKem512Ciphertext +); + /// An ML-KEM 512 Ciphertext pub type MlKem512Ciphertext = MlKemCiphertext; /// An ML-KEM 512 Private key @@ -526,61 +534,6 @@ pub fn decapsulate( >(private_key, ciphertext) } -impl - libcrux_traits::kem::arrayref::Kem< - CPA_PKE_PUBLIC_KEY_SIZE, - SECRET_KEY_SIZE, - CPA_PKE_CIPHERTEXT_SIZE, - SHARED_SECRET_SIZE, - KEY_GENERATION_SEED_SIZE, - SHARED_SECRET_SIZE, - > for MlKem512 -{ - fn keygen( - ek: &mut [u8; CPA_PKE_PUBLIC_KEY_SIZE], - dk: &mut [u8; SECRET_KEY_SIZE], - rand: &[u8; KEY_GENERATION_SEED_SIZE], - ) -> Result<(), libcrux_traits::kem::owned::KeyGenError> { - let key_pair = generate_key_pair(*rand); - ek.copy_from_slice(key_pair.pk()); - dk.copy_from_slice(key_pair.sk()); - - // TODO: validate that the key is valid (how?) - - Ok(()) - } - - fn encaps( - ct: &mut [u8; CPA_PKE_CIPHERTEXT_SIZE], - ss: &mut [u8; SHARED_SECRET_SIZE], - ek: &[u8; CPA_PKE_PUBLIC_KEY_SIZE], - rand: &[u8; SHARED_SECRET_SIZE], - ) -> Result<(), libcrux_traits::kem::owned::EncapsError> { - let public_key = MlKem512PublicKey::from(ek); - - let (ct_, ss_) = encapsulate(&public_key, *rand); - ct.copy_from_slice(ct_.as_slice()); - ss.copy_from_slice(ss_.as_slice()); - - Ok(()) - } - - fn decaps( - ss: &mut [u8; SHARED_SECRET_SIZE], - ct: &[u8; CPA_PKE_CIPHERTEXT_SIZE], - dk: &[u8; SECRET_KEY_SIZE], - ) -> Result<(), libcrux_traits::kem::owned::DecapsError> { - let secret_key = MlKem512PrivateKey::from(dk); - let ciphertext = MlKem512Ciphertext::from(ct); - - let ss_ = decapsulate(&secret_key, &ciphertext); - - ss.copy_from_slice(ss_.as_slice()); - - Ok(()) - } -} - /// Randomized APIs /// /// The functions in this module are equivalent to the one in the main module, @@ -630,6 +583,16 @@ pub mod rand { pub(crate) mod kyber { use super::*; + /// The Kyber 512 algorithms + pub struct Kyber512; + + crate::impl_kem_trait!( + Kyber512, + MlKem512PublicKey, + MlKem512PrivateKey, + MlKem512Ciphertext + ); + /// Generate Kyber 512 Key Pair /// /// The input is a byte array of size diff --git a/libcrux-ml-kem/src/mlkem768.rs b/libcrux-ml-kem/src/mlkem768.rs index d54e83eae..eb810a132 100644 --- a/libcrux-ml-kem/src/mlkem768.rs +++ b/libcrux-ml-kem/src/mlkem768.rs @@ -33,6 +33,16 @@ const ETA2_RANDOMNESS_SIZE: usize = ETA2 * 64; const IMPLICIT_REJECTION_HASH_INPUT_SIZE: usize = SHARED_SECRET_SIZE + CPA_PKE_CIPHERTEXT_SIZE; +/// The ML-KEM 768 algorithms +pub struct MlKem768; + +crate::impl_kem_trait!( + MlKem768, + MlKem768PublicKey, + MlKem768PrivateKey, + MlKem768Ciphertext +); + /// An ML-KEM 768 Ciphertext pub type MlKem768Ciphertext = MlKemCiphertext; /// An ML-KEM 768 Private key @@ -553,6 +563,16 @@ pub mod rand { pub(crate) mod kyber { use super::*; + /// The Kyber 768 algorithms + pub struct Kyber768; + + crate::impl_kem_trait!( + Kyber768, + MlKem768PublicKey, + MlKem768PrivateKey, + MlKem768Ciphertext + ); + /// Generate Kyber 768 Key Pair /// /// Generate a Kyber key pair. The input is a byte array of size From 9ede2397fa7bb6d70f5fede76d6a94182ae5802f Mon Sep 17 00:00:00 2001 From: "Jan Winkelmann (keks)" Date: Tue, 8 Jul 2025 12:49:15 +0200 Subject: [PATCH 06/12] implement slice-based impl for all kems --- libcrux-kem/src/kem.rs | 2 ++ libcrux-ml-kem/src/lib.rs | 5 +++++ p256/src/impl_kem.rs | 2 ++ 3 files changed, 9 insertions(+) diff --git a/libcrux-kem/src/kem.rs b/libcrux-kem/src/kem.rs index 3854a9e25..0eb7094c7 100644 --- a/libcrux-kem/src/kem.rs +++ b/libcrux-kem/src/kem.rs @@ -1017,6 +1017,8 @@ mod xwing { } } + libcrux_traits::kem::slice::impl_trait!(XWing => EK_LEN, DK_LEN, CT_LEN, SS_LEN, RAND_KEYGEN_LEN, RAND_ENCAPS_LEN); + pub struct XWingSharedSecret { pub(super) value: [u8; 32], } diff --git a/libcrux-ml-kem/src/lib.rs b/libcrux-ml-kem/src/lib.rs index 9e54ecd78..7e9265e74 100644 --- a/libcrux-ml-kem/src/lib.rs +++ b/libcrux-ml-kem/src/lib.rs @@ -231,6 +231,11 @@ macro_rules! impl_kem_trait { Ok(()) } } + + libcrux_traits::kem::slice::impl_trait!($variant => + CPA_PKE_PUBLIC_KEY_SIZE, SECRET_KEY_SIZE, + CPA_PKE_CIPHERTEXT_SIZE, SHARED_SECRET_SIZE, + KEY_GENERATION_SEED_SIZE, SHARED_SECRET_SIZE); }; } diff --git a/p256/src/impl_kem.rs b/p256/src/impl_kem.rs index cc9b147af..f59f1e160 100644 --- a/p256/src/impl_kem.rs +++ b/p256/src/impl_kem.rs @@ -76,3 +76,5 @@ impl Kem for s Ok(()) } } + +libcrux_traits::kem::slice::impl_trait!(super::P256 => EK_LEN, DK_LEN, CT_LEN, SS_LEN, RAND_KEYGEN_LEN, RAND_ENCAPS_LEN); From b18c2bffcfe4561783d4bdaeacd0b9bc1f8263be Mon Sep 17 00:00:00 2001 From: "Jan Winkelmann (keks)" Date: Tue, 8 Jul 2025 13:06:10 +0200 Subject: [PATCH 07/12] mv kem/mod.rs kem.rs --- traits/src/{kem/mod.rs => kem.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename traits/src/{kem/mod.rs => kem.rs} (100%) diff --git a/traits/src/kem/mod.rs b/traits/src/kem.rs similarity index 100% rename from traits/src/kem/mod.rs rename to traits/src/kem.rs From 73c612d60f526cd25be443330006176cf68f52e7 Mon Sep 17 00:00:00 2001 From: "Jan Winkelmann (keks)" Date: Thu, 10 Jul 2025 12:01:31 +0200 Subject: [PATCH 08/12] Address feedback --- curve25519/Cargo.toml | 5 +++ curve25519/src/impl_hacl.rs | 8 ++--- curve25519/src/lib.rs | 51 ++++++++++++++++----------- libcrux-kem/Cargo.toml | 3 ++ libcrux-kem/src/kem.rs | 13 ++++--- libcrux-ml-kem/Cargo.toml | 5 ++- libcrux-ml-kem/src/lib.rs | 15 ++++++-- p256/Cargo.toml | 5 +++ traits/Cargo.toml | 7 ++++ traits/src/kem.rs | 3 ++ traits/src/kem/arrayref.rs | 5 +++ traits/src/kem/owned.rs | 5 +++ traits/src/kem/secrets.rs | 69 +++++++++++-------------------------- traits/src/kem/slice.rs | 13 ++++++- traits/src/kem/tests.rs | 35 +++++++++++++++++++ 15 files changed, 159 insertions(+), 83 deletions(-) create mode 100644 traits/src/kem/tests.rs diff --git a/curve25519/Cargo.toml b/curve25519/Cargo.toml index 90c27e034..22c1fe90d 100644 --- a/curve25519/Cargo.toml +++ b/curve25519/Cargo.toml @@ -14,3 +14,8 @@ repository.workspace = true libcrux-hacl-rs = { version = "=0.0.3", path = "../hacl-rs/" } libcrux-macros = { version = "=0.0.3", path = "../macros" } libcrux-traits = { version = "=0.0.3", path = "../traits" } + +[dev-dependencies] +libcrux-traits = { version = "0.0.3", path = "../traits", features = [ + "generic-tests", +] } diff --git a/curve25519/src/impl_hacl.rs b/curve25519/src/impl_hacl.rs index 8c0b469db..8e2c7ac77 100644 --- a/curve25519/src/impl_hacl.rs +++ b/curve25519/src/impl_hacl.rs @@ -7,14 +7,14 @@ impl Curve25519 for HaclCurve25519 { // The hacl::ecdh function requires all parameters to be 32 byte long, which we enforce using // types. #[inline(always)] - fn secret_to_public(pk: &mut [u8; PK_LEN], sk: &[u8; SK_LEN]) { + fn secret_to_public(pk: &mut [u8; EK_LEN], sk: &[u8; DK_LEN]) { secret_to_public(pk, sk) } // The hacl::ecdh function requires all parameters to be 32 byte long, which we enforce using // types. #[inline(always)] - fn ecdh(out: &mut [u8; SHK_LEN], pk: &[u8; PK_LEN], sk: &[u8; SK_LEN]) -> Result<(), Error> { + fn ecdh(out: &mut [u8; SS_LEN], pk: &[u8; EK_LEN], sk: &[u8; DK_LEN]) -> Result<(), Error> { ecdh(out, pk, sk) } } @@ -22,14 +22,14 @@ impl Curve25519 for HaclCurve25519 { // The hacl::ecdh function requires all parameters to be 32 byte long, which we enforce using // types. #[inline(always)] -pub fn secret_to_public(pk: &mut [u8; PK_LEN], sk: &[u8; SK_LEN]) { +pub fn secret_to_public(pk: &mut [u8; EK_LEN], sk: &[u8; DK_LEN]) { crate::hacl::secret_to_public(pk, sk) } // The hacl::ecdh function requires all parameters to be 32 byte long, which we enforce using // types. #[inline(always)] -pub fn ecdh(out: &mut [u8; SHK_LEN], pk: &[u8; PK_LEN], sk: &[u8; SK_LEN]) -> Result<(), Error> { +pub fn ecdh(out: &mut [u8; SS_LEN], pk: &[u8; EK_LEN], sk: &[u8; DK_LEN]) -> Result<(), Error> { match crate::hacl::ecdh(out, sk, pk) { true => Ok(()), false => Err(Error), diff --git a/curve25519/src/lib.rs b/curve25519/src/lib.rs index d4ff7a28e..fb8271c32 100644 --- a/curve25519/src/lib.rs +++ b/curve25519/src/lib.rs @@ -7,13 +7,13 @@ mod impl_hacl; pub use impl_hacl::{ecdh, secret_to_public}; /// The length of Curve25519 secret keys. -pub const SK_LEN: usize = 32; +pub const DK_LEN: usize = 32; /// The length of Curve25519 public keys. -pub const PK_LEN: usize = 32; +pub const EK_LEN: usize = 32; /// The length of Curve25519 shared keys. -pub const SHK_LEN: usize = 32; +pub const SS_LEN: usize = 32; /// Indicates that an error occurred pub struct Error; @@ -23,23 +23,30 @@ pub struct Error; #[allow(dead_code)] trait Curve25519 { /// Computes a public key from a secret key. - fn secret_to_public(pk: &mut [u8; PK_LEN], sk: &[u8; SK_LEN]); + fn secret_to_public(pk: &mut [u8; EK_LEN], sk: &[u8; DK_LEN]); /// Computes the scalar multiplication between the provided public and secret keys. Returns an /// error if the result is 0. - fn ecdh(out: &mut [u8; SHK_LEN], pk: &[u8; PK_LEN], sk: &[u8; SK_LEN]) -> Result<(), Error>; + fn ecdh(out: &mut [u8; SS_LEN], pk: &[u8; EK_LEN], sk: &[u8; DK_LEN]) -> Result<(), Error>; } pub struct X25519; -impl libcrux_traits::kem::arrayref::Kem - for X25519 -{ +#[inline(always)] +const fn or(a: u8, b: &u8) -> u8 { + a | *b +} + +impl libcrux_traits::kem::arrayref::Kem for X25519 { fn keygen( - ek: &mut [u8; SK_LEN], - dk: &mut [u8; PK_LEN], - rand: &[u8; SK_LEN], + ek: &mut [u8; DK_LEN], + dk: &mut [u8; EK_LEN], + rand: &[u8; DK_LEN], ) -> Result<(), libcrux_traits::kem::arrayref::KeyGenError> { + if rand.iter().fold(0, or) == 0 { + return Err(libcrux_traits::kem::arrayref::KeyGenError::InvalidRandomness); + } + dk.copy_from_slice(rand); clamp(dk); secret_to_public(ek, dk); @@ -47,11 +54,15 @@ impl libcrux_traits::kem::arrayref::Kem Result<(), libcrux_traits::kem::arrayref::EncapsError> { + if rand.iter().fold(0, or) == 0 { + return Err(libcrux_traits::kem::arrayref::EncapsError::InvalidRandomness); + } + let mut eph_dk = *rand; clamp(&mut eph_dk); secret_to_public(ct, &eph_dk); @@ -60,18 +71,18 @@ impl libcrux_traits::kem::arrayref::Kem Result<(), libcrux_traits::kem::arrayref::DecapsError> { ecdh(ss, ct, dk).map_err(|_| libcrux_traits::kem::arrayref::DecapsError::Unknown) } } -libcrux_traits::kem::slice::impl_trait!(X25519 => PK_LEN, SK_LEN, PK_LEN, PK_LEN, SK_LEN, SK_LEN); +libcrux_traits::kem::slice::impl_trait!(X25519 => EK_LEN, DK_LEN, EK_LEN, EK_LEN, DK_LEN, DK_LEN); /// Clamp a scalar. -fn clamp(scalar: &mut [u8; SK_LEN]) { +fn clamp(scalar: &mut [u8; DK_LEN]) { // We clamp the key already to make sure it can't be misused. scalar[0] &= 248u8; scalar[31] &= 127u8; diff --git a/libcrux-kem/Cargo.toml b/libcrux-kem/Cargo.toml index 256eeb254..60ba47729 100644 --- a/libcrux-kem/Cargo.toml +++ b/libcrux-kem/Cargo.toml @@ -35,3 +35,6 @@ pre-verification = [] libcrux-kem = { path = "./", features = ["tests"] } rand = { version = "0.9", features = ["os_rng"] } hex = { version = "0.4.3", features = ["serde"] } +libcrux-traits = { version = "0.0.3", path = "../traits", features = [ + "generic-tests", +] } diff --git a/libcrux-kem/src/kem.rs b/libcrux-kem/src/kem.rs index 0eb7094c7..98af1b2a9 100644 --- a/libcrux-kem/src/kem.rs +++ b/libcrux-kem/src/kem.rs @@ -893,14 +893,13 @@ mod xwing { const MLKEM768_EK_LEN: usize = libcrux_ml_kem::mlkem768::MlKem768PublicKey::len(); const MLKEM768_DK_LEN: usize = libcrux_ml_kem::mlkem768::MlKem768PrivateKey::len(); const MLKEM768_CT_LEN: usize = libcrux_ml_kem::mlkem768::MlKem768Ciphertext::len(); - const MLKEM768_SS_LEN: usize = 32; - const MLKEM768_RAND_KEYGEN_LEN: usize = 64; + const MLKEM768_SS_LEN: usize = libcrux_ml_kem::SHARED_SECRET_SIZE; + const MLKEM768_RAND_KEYGEN_LEN: usize = libcrux_ml_kem::KEY_GENERATION_SEED_SIZE; const MLKEM768_RAND_ENCAPS_LEN: usize = MLKEM768_SS_LEN; - const X25519_EK_LEN: usize = 32; - const X25519_DK_LEN: usize = 32; - const X25519_CT_LEN: usize = 32; - const X25519_SS_LEN: usize = 32; + const X25519_EK_LEN: usize = libcrux_curve25519::EK_LEN; + const X25519_DK_LEN: usize = libcrux_curve25519::DK_LEN; + const X25519_CT_LEN: usize = X25519_EK_LEN; const X25519_RAND_KEYGEN_LEN: usize = X25519_DK_LEN; const X25519_RAND_ENCAPS_LEN: usize = X25519_DK_LEN; @@ -971,7 +970,6 @@ mod xwing { let ct_x: &mut [u8; X25519_CT_LEN] = ct_x.try_into().unwrap(); let mut hash_buffer = [0u8; 32 + 32 + X25519_CT_LEN + X25519_EK_LEN + 6]; - hash_buffer[64..96].copy_from_slice(ct_x); hash_buffer[96..128].copy_from_slice(ek_x); hash_buffer[128..134].copy_from_slice(&[0x5c, 0x2e, 0x2f, 0x2f, 0x5e, 0x5c]); @@ -980,6 +978,7 @@ mod xwing { let ss_x: &mut [u8; 32] = (&mut hash_buffer[32..64]).try_into().unwrap(); X25519::encaps(ct_x, ss_x, ek_x, rand_x)?; + hash_buffer[64..96].copy_from_slice(ct_x); sha3::sha256_ema(ss, &hash_buffer); Ok(()) diff --git a/libcrux-ml-kem/Cargo.toml b/libcrux-ml-kem/Cargo.toml index 140b4a8ee..6d0eaf3b7 100644 --- a/libcrux-ml-kem/Cargo.toml +++ b/libcrux-ml-kem/Cargo.toml @@ -36,7 +36,7 @@ hax-lib.workspace = true [features] # By default all variants and std are enabled. -default = ["default-no-std", "std", "kyber"] +default = ["default-no-std", "std"] default-no-std = ["mlkem512", "mlkem768", "mlkem1024", "rand"] # Hardware features can be force enabled. @@ -72,6 +72,9 @@ serde_json = { version = "1.0" } serde = { version = "1.0", features = ["derive"] } hex = { version = "0.4.3", features = ["serde"] } criterion = "0.6" +libcrux-traits = { version = "0.0.3", path = "../traits", features = [ + "generic-tests", +] } [[bench]] name = "ml-kem" diff --git a/libcrux-ml-kem/src/lib.rs b/libcrux-ml-kem/src/lib.rs index 7e9265e74..6c02e7ef9 100644 --- a/libcrux-ml-kem/src/lib.rs +++ b/libcrux-ml-kem/src/lib.rs @@ -192,12 +192,14 @@ macro_rules! impl_kem_trait { dk: &mut [u8; SECRET_KEY_SIZE], rand: &[u8; KEY_GENERATION_SEED_SIZE], ) -> Result<(), libcrux_traits::kem::owned::KeyGenError> { + if rand.iter().fold(0, crate::or) == 0 { + return Err(libcrux_traits::kem::arrayref::KeyGenError::InvalidRandomness); + } + let key_pair = generate_key_pair(*rand); ek.copy_from_slice(key_pair.pk()); dk.copy_from_slice(key_pair.sk()); - // TODO: validate that the key is valid (how?) - Ok(()) } @@ -207,6 +209,9 @@ macro_rules! impl_kem_trait { ek: &[u8; CPA_PKE_PUBLIC_KEY_SIZE], rand: &[u8; SHARED_SECRET_SIZE], ) -> Result<(), libcrux_traits::kem::owned::EncapsError> { + if rand.iter().fold(0, crate::or) == 0 { + return Err(libcrux_traits::kem::arrayref::EncapsError::InvalidRandomness); + } let public_key: $pk = ek.into(); let (ct_, ss_) = encapsulate(&public_key, *rand); @@ -239,4 +244,10 @@ macro_rules! impl_kem_trait { }; } +/// Used in equality checks +#[inline(always)] +const fn or(a: u8, b: &u8) -> u8 { + a | *b +} + use impl_kem_trait; diff --git a/p256/Cargo.toml b/p256/Cargo.toml index 5032b28a0..0c7cc1b8b 100644 --- a/p256/Cargo.toml +++ b/p256/Cargo.toml @@ -20,3 +20,8 @@ libcrux-sha2 = { version = "=0.0.3", path = "../sha2", features = [ [features] expose-hacl = [] + +[dev-dependencies] +libcrux-traits = { version = "0.0.3", path = "../traits", features = [ + "generic-tests", +] } diff --git a/traits/Cargo.toml b/traits/Cargo.toml index 6c04847da..5d21e7967 100644 --- a/traits/Cargo.toml +++ b/traits/Cargo.toml @@ -11,7 +11,14 @@ repository.workspace = true readme.workspace = true [features] +# core::error::Error is still pretty recent, so we only implement it for our errors behind a feature error_in_core = [] +# enables checking of secret-independence +check-secret-independence = ["libcrux-secrets/check-secret-independence"] +# expose tests that are generic over the traits +generic-tests = [] + +#default = ["generic-tests"] [dependencies] rand = { version = "0.9", default-features = false } diff --git a/traits/src/kem.rs b/traits/src/kem.rs index a5cce3143..95d3b2e87 100644 --- a/traits/src/kem.rs +++ b/traits/src/kem.rs @@ -6,6 +6,9 @@ pub mod owned; pub mod secrets; pub mod slice; +#[cfg(feature = "generic-tests")] +pub mod tests; + use rand::CryptoRng; /// A KEM keypair. diff --git a/traits/src/kem/arrayref.rs b/traits/src/kem/arrayref.rs index 7ced62c7f..8cb94376f 100644 --- a/traits/src/kem/arrayref.rs +++ b/traits/src/kem/arrayref.rs @@ -1,3 +1,8 @@ +//! This module contains the trait and related errors for a KEM that takes array references as +//! arguments and writes to outputs to mutable array references. + +/// A Key Encapsulation Mechanismd (KEM). This trait is the most low-level and mostly used in the +/// implementation of other, more usabe APIs on top. pub trait Kem< const EK_LEN: usize, const DK_LEN: usize, diff --git a/traits/src/kem/owned.rs b/traits/src/kem/owned.rs index 691de743b..807dbd827 100644 --- a/traits/src/kem/owned.rs +++ b/traits/src/kem/owned.rs @@ -1,7 +1,12 @@ +//! This module contains the trait and related errors for a KEM that takes array references as +//! arguments and returns values as arrays. +//! use super::arrayref; pub use arrayref::{DecapsError, EncapsError, KeyGenError}; +/// A Key Encapsulation Mechanismd (KEM) that returns values insteaf of writing the results to +/// `&mut` arguments. pub trait Kem< const EK_LEN: usize, const DK_LEN: usize, diff --git a/traits/src/kem/secrets.rs b/traits/src/kem/secrets.rs index 79f47ec5b..406681540 100644 --- a/traits/src/kem/secrets.rs +++ b/traits/src/kem/secrets.rs @@ -1,9 +1,16 @@ -use libcrux_secrets::{Classify, Declassify}; +//! This module contains the trait and related errors for a KEM that takes array references as +//! arguments and returns results instead of writing to a mutable reference. Secret values use the +//! types from [libcrux-secrets](libcrux_secrets). + +use libcrux_secrets::{Classify as _, DeclassifyRef as _, U8}; use super::owned; +// Re-export errors use in here pub use owned::{DecapsError, EncapsError, KeyGenError}; +/// A Key Encapsulation Mechanismd (KEM). This trait makes use of types suitable for checking +/// secret independence. pub trait Kem< const EK_LEN: usize, const DK_LEN: usize, @@ -14,33 +21,16 @@ pub trait Kem< > { /// Generate a pair of encapsulation and decapsulation keys. - fn keygen( - rand: &[u8; RAND_KEYGEN_LEN], - ) -> Result< - ( - impl Declassify, - impl Declassify, - ), - KeyGenError, - >; + fn keygen(rand: &[U8; RAND_KEYGEN_LEN]) -> Result<([U8; DK_LEN], [u8; EK_LEN]), KeyGenError>; /// Encapsulate a shared secret towards a given encapsulation key. fn encaps( ek: &[u8; EK_LEN], - rand: &[u8; RAND_ENCAPS_LEN], - ) -> Result< - ( - impl Declassify, - impl Declassify, - ), - EncapsError, - >; + rand: &[U8; RAND_ENCAPS_LEN], + ) -> Result<([U8; SS_LEN], [u8; CT_LEN]), EncapsError>; /// Decapsulate a shared secret. - fn decaps( - ct: &[u8; CT_LEN], - dk: &[u8; DK_LEN], - ) -> Result, DecapsError>; + fn decaps(ct: &[u8; CT_LEN], dk: &[U8; DK_LEN]) -> Result<[U8; SS_LEN], DecapsError>; } impl< @@ -53,15 +43,7 @@ impl< T: owned::Kem, > Kem for T { - fn keygen( - rand: &[u8; RAND_KEYGEN_LEN], - ) -> Result< - ( - impl Declassify, - impl Declassify, - ), - KeyGenError, - > { + fn keygen(rand: &[U8; RAND_KEYGEN_LEN]) -> Result<([U8; DK_LEN], [u8; EK_LEN]), KeyGenError> { let (dk, ek) = >::keygen(rand)?; + >>::keygen(rand.declassify_ref())?; - Ok((dk.classify(), ek.classify())) + Ok((dk.classify(), ek)) } fn encaps( ek: &[u8; EK_LEN], - rand: &[u8; RAND_ENCAPS_LEN], - ) -> Result< - ( - impl Declassify, - impl Declassify, - ), - EncapsError, - > { + rand: &[U8; RAND_ENCAPS_LEN], + ) -> Result<([U8; SS_LEN], [u8; CT_LEN]), EncapsError> { let (ss, ct) = >::encaps(ek, rand)?; + >>::encaps(ek, rand.declassify_ref())?; - Ok((ss.classify(), ct.classify())) + Ok((ss.classify(), ct)) } - fn decaps( - ct: &[u8; CT_LEN], - dk: &[u8; DK_LEN], - ) -> Result, DecapsError> { + fn decaps(ct: &[u8; CT_LEN], dk: &[U8; DK_LEN]) -> Result<[U8; SS_LEN], DecapsError> { let ss = >::decaps(ct, dk)?; + >>::decaps(ct, dk.declassify_ref())?; Ok(ss.classify()) } diff --git a/traits/src/kem/slice.rs b/traits/src/kem/slice.rs index dcb37d664..653f4c3df 100644 --- a/traits/src/kem/slice.rs +++ b/traits/src/kem/slice.rs @@ -1,5 +1,9 @@ +//! This module contains the trait and related errors for a KEM that takes slices as +//! arguments and writes the results to mutable slices.. + use super::arrayref; +/// A Key Encapsulation Mechanismd (KEM). This trait takes slices as arguments. pub trait Kem { /// Generate a pair of encapsulation and decapsulation keys. fn keygen(ek: &mut [u8], dk: &mut [u8], rand: &[u8]) -> Result<(), KeyGenError>; @@ -92,7 +96,7 @@ impl From for DecapsError { } #[macro_export] -/// Implements [`Kem`] for any [`arrayref::Kem`]. Hides the impl in a module of the given name. +/// Implements [`Kem`] for any [`arrayref::Kem`] macro_rules! impl_trait { ($type:ty => $ek:expr, $dk:expr, $ct:expr, $ss:expr, $rand_kg:expr, $rand_encaps:expr) => { impl $crate::kem::slice::Kem for $type { @@ -143,7 +147,14 @@ macro_rules! impl_trait { } } + + #[cfg(test)] + #[test] + fn simple_kem_test(){ + $crate::kem::tests::simple::<$ek, $dk, $ct, $ss, $rand_kg, $rand_encaps, $type>(); + } }; + } pub use impl_trait; diff --git a/traits/src/kem/tests.rs b/traits/src/kem/tests.rs new file mode 100644 index 000000000..a817e92ce --- /dev/null +++ b/traits/src/kem/tests.rs @@ -0,0 +1,35 @@ +pub fn simple< + const EK_LEN: usize, + const DK_LEN: usize, + const CT_LEN: usize, + const SS_LEN: usize, + const RAND_KEYGEN_LEN: usize, + const RAND_ENCAPS_LEN: usize, + Kem: super::arrayref::Kem, +>() { + let alice_keygen_rand = [1; RAND_KEYGEN_LEN]; + let bob_keygen_rand = [2; RAND_KEYGEN_LEN]; + + let mut alice_dk = [0; DK_LEN]; + let mut alice_ek = [0; EK_LEN]; + + let mut bob_dk = [0; DK_LEN]; + let mut bob_ek = [0; EK_LEN]; + + Kem::keygen(&mut alice_ek, &mut alice_dk, &alice_keygen_rand) + .expect("error generating alice's key pair"); + Kem::keygen(&mut bob_ek, &mut bob_dk, &bob_keygen_rand) + .expect("error generating bob's key pair"); + + let mut ct = [0; CT_LEN]; + let mut ss_alice = [0; SS_LEN]; + let rand_encaps = [3; RAND_ENCAPS_LEN]; + + Kem::encaps(&mut ct, &mut ss_alice, &bob_ek, &rand_encaps).expect("error encapsulating"); + + let mut ss_bob = [0; SS_LEN]; + + Kem::decaps(&mut ss_bob, &ct, &bob_dk).expect("error decapsulating"); + + assert_eq!(ss_alice, ss_bob) +} From 4878290337e377df45176c49eddca2df2b308107 Mon Sep 17 00:00:00 2001 From: "Jan Winkelmann (keks)" Date: Thu, 10 Jul 2025 14:53:21 +0200 Subject: [PATCH 09/12] ignore slice-based kem trait in hax extraction --- libcrux-ml-kem/hax.py | 1 + 1 file changed, 1 insertion(+) diff --git a/libcrux-ml-kem/hax.py b/libcrux-ml-kem/hax.py index 3f8e536e5..9b5d40596 100755 --- a/libcrux-ml-kem/hax.py +++ b/libcrux-ml-kem/hax.py @@ -103,6 +103,7 @@ def __call__(self, parser, args, values, option_string=None) -> None: "-libcrux_ml_kem::hash_functions::avx2::*", "-libcrux_ml_kem::hash_functions::neon::*", "+:libcrux_ml_kem::hash_functions::*::*", + "-libcrux_traits::kem::slice::*", ] include_str = " ".join(includes) interface_include = "+** -libcrux_ml_kem::vector::traits -libcrux_ml_kem::types -libcrux_ml_kem::constants" From 6c7122e04cbdf1e4b84facbf81237ed98b99eab8 Mon Sep 17 00:00:00 2001 From: "Jan Winkelmann (keks)" Date: Thu, 10 Jul 2025 16:39:11 +0200 Subject: [PATCH 10/12] don't emit the kem trait impls for hax --- libcrux-ml-kem/hax.py | 1 - libcrux-ml-kem/src/mlkem1024.rs | 1 + libcrux-ml-kem/src/mlkem512.rs | 1 + libcrux-ml-kem/src/mlkem768.rs | 1 + 4 files changed, 3 insertions(+), 1 deletion(-) diff --git a/libcrux-ml-kem/hax.py b/libcrux-ml-kem/hax.py index 9b5d40596..3f8e536e5 100755 --- a/libcrux-ml-kem/hax.py +++ b/libcrux-ml-kem/hax.py @@ -103,7 +103,6 @@ def __call__(self, parser, args, values, option_string=None) -> None: "-libcrux_ml_kem::hash_functions::avx2::*", "-libcrux_ml_kem::hash_functions::neon::*", "+:libcrux_ml_kem::hash_functions::*::*", - "-libcrux_traits::kem::slice::*", ] include_str = " ".join(includes) interface_include = "+** -libcrux_ml_kem::vector::traits -libcrux_ml_kem::types -libcrux_ml_kem::constants" diff --git a/libcrux-ml-kem/src/mlkem1024.rs b/libcrux-ml-kem/src/mlkem1024.rs index 885096b0a..77b330c25 100644 --- a/libcrux-ml-kem/src/mlkem1024.rs +++ b/libcrux-ml-kem/src/mlkem1024.rs @@ -28,6 +28,7 @@ const IMPLICIT_REJECTION_HASH_INPUT_SIZE: usize = SHARED_SECRET_SIZE + CPA_PKE_C /// The ML-KEM 1024 algorithms pub struct MlKem1024; +#[cfg(not(hax))] crate::impl_kem_trait!( MlKem1024, MlKem1024PublicKey, diff --git a/libcrux-ml-kem/src/mlkem512.rs b/libcrux-ml-kem/src/mlkem512.rs index d383cbcc4..df1075e7b 100644 --- a/libcrux-ml-kem/src/mlkem512.rs +++ b/libcrux-ml-kem/src/mlkem512.rs @@ -29,6 +29,7 @@ const IMPLICIT_REJECTION_HASH_INPUT_SIZE: usize = SHARED_SECRET_SIZE + CPA_PKE_C /// The ML-KEM 512 algorithms pub struct MlKem512; +#[cfg(not(hax))] crate::impl_kem_trait!( MlKem512, MlKem512PublicKey, diff --git a/libcrux-ml-kem/src/mlkem768.rs b/libcrux-ml-kem/src/mlkem768.rs index eb810a132..f7703200d 100644 --- a/libcrux-ml-kem/src/mlkem768.rs +++ b/libcrux-ml-kem/src/mlkem768.rs @@ -36,6 +36,7 @@ const IMPLICIT_REJECTION_HASH_INPUT_SIZE: usize = SHARED_SECRET_SIZE + CPA_PKE_C /// The ML-KEM 768 algorithms pub struct MlKem768; +#[cfg(not(hax))] crate::impl_kem_trait!( MlKem768, MlKem768PublicKey, From 54c6624630e47ed599ffc1549101c2b861d51a8c Mon Sep 17 00:00:00 2001 From: "Jan Winkelmann (keks)" Date: Tue, 15 Jul 2025 18:06:31 +0200 Subject: [PATCH 11/12] the caller must ensure rand isn't 0; document and skip checks --- curve25519/src/lib.rs | 13 ------------- libcrux-ml-kem/src/lib.rs | 13 ------------- traits/src/kem/arrayref.rs | 4 ++++ traits/src/kem/owned.rs | 4 ++++ traits/src/kem/secrets.rs | 4 ++++ traits/src/kem/slice.rs | 4 ++++ 6 files changed, 16 insertions(+), 26 deletions(-) diff --git a/curve25519/src/lib.rs b/curve25519/src/lib.rs index fb8271c32..2ee1a302d 100644 --- a/curve25519/src/lib.rs +++ b/curve25519/src/lib.rs @@ -32,21 +32,12 @@ trait Curve25519 { pub struct X25519; -#[inline(always)] -const fn or(a: u8, b: &u8) -> u8 { - a | *b -} - impl libcrux_traits::kem::arrayref::Kem for X25519 { fn keygen( ek: &mut [u8; DK_LEN], dk: &mut [u8; EK_LEN], rand: &[u8; DK_LEN], ) -> Result<(), libcrux_traits::kem::arrayref::KeyGenError> { - if rand.iter().fold(0, or) == 0 { - return Err(libcrux_traits::kem::arrayref::KeyGenError::InvalidRandomness); - } - dk.copy_from_slice(rand); clamp(dk); secret_to_public(ek, dk); @@ -59,10 +50,6 @@ impl libcrux_traits::kem::arrayref::Kem Result<(), libcrux_traits::kem::arrayref::EncapsError> { - if rand.iter().fold(0, or) == 0 { - return Err(libcrux_traits::kem::arrayref::EncapsError::InvalidRandomness); - } - let mut eph_dk = *rand; clamp(&mut eph_dk); secret_to_public(ct, &eph_dk); diff --git a/libcrux-ml-kem/src/lib.rs b/libcrux-ml-kem/src/lib.rs index 6c02e7ef9..0d87aa59c 100644 --- a/libcrux-ml-kem/src/lib.rs +++ b/libcrux-ml-kem/src/lib.rs @@ -192,10 +192,6 @@ macro_rules! impl_kem_trait { dk: &mut [u8; SECRET_KEY_SIZE], rand: &[u8; KEY_GENERATION_SEED_SIZE], ) -> Result<(), libcrux_traits::kem::owned::KeyGenError> { - if rand.iter().fold(0, crate::or) == 0 { - return Err(libcrux_traits::kem::arrayref::KeyGenError::InvalidRandomness); - } - let key_pair = generate_key_pair(*rand); ek.copy_from_slice(key_pair.pk()); dk.copy_from_slice(key_pair.sk()); @@ -209,9 +205,6 @@ macro_rules! impl_kem_trait { ek: &[u8; CPA_PKE_PUBLIC_KEY_SIZE], rand: &[u8; SHARED_SECRET_SIZE], ) -> Result<(), libcrux_traits::kem::owned::EncapsError> { - if rand.iter().fold(0, crate::or) == 0 { - return Err(libcrux_traits::kem::arrayref::EncapsError::InvalidRandomness); - } let public_key: $pk = ek.into(); let (ct_, ss_) = encapsulate(&public_key, *rand); @@ -244,10 +237,4 @@ macro_rules! impl_kem_trait { }; } -/// Used in equality checks -#[inline(always)] -const fn or(a: u8, b: &u8) -> u8 { - a | *b -} - use impl_kem_trait; diff --git a/traits/src/kem/arrayref.rs b/traits/src/kem/arrayref.rs index 8cb94376f..7eec075f4 100644 --- a/traits/src/kem/arrayref.rs +++ b/traits/src/kem/arrayref.rs @@ -13,6 +13,8 @@ pub trait Kem< > { /// Generate a pair of encapsulation and decapsulation keys. + /// It is the responsibility of the caller to ensure that the `rand` argument is actually + /// random. fn keygen( ek: &mut [u8; EK_LEN], dk: &mut [u8; DK_LEN], @@ -20,6 +22,8 @@ pub trait Kem< ) -> Result<(), KeyGenError>; /// Encapsulate a shared secret towards a given encapsulation key. + /// It is the responsibility of the caller to ensure that the `rand` argument is actually + /// random. fn encaps( ct: &mut [u8; CT_LEN], ss: &mut [u8; SS_LEN], diff --git a/traits/src/kem/owned.rs b/traits/src/kem/owned.rs index 807dbd827..c702036cb 100644 --- a/traits/src/kem/owned.rs +++ b/traits/src/kem/owned.rs @@ -17,9 +17,13 @@ pub trait Kem< > { /// Generate a pair of encapsulation and decapsulation keys. + /// It is the responsibility of the caller to ensure that the `rand` argument is actually + /// random. fn keygen(rand: &[u8; RAND_KEYGEN_LEN]) -> Result<([u8; DK_LEN], [u8; EK_LEN]), KeyGenError>; /// Encapsulate a shared secret towards a given encapsulation key. + /// It is the responsibility of the caller to ensure that the `rand` argument is actually + /// random. fn encaps( ek: &[u8; EK_LEN], rand: &[u8; RAND_ENCAPS_LEN], diff --git a/traits/src/kem/secrets.rs b/traits/src/kem/secrets.rs index 406681540..97047c2e6 100644 --- a/traits/src/kem/secrets.rs +++ b/traits/src/kem/secrets.rs @@ -21,9 +21,13 @@ pub trait Kem< > { /// Generate a pair of encapsulation and decapsulation keys. + /// It is the responsibility of the caller to ensure that the `rand` argument is actually + /// random. fn keygen(rand: &[U8; RAND_KEYGEN_LEN]) -> Result<([U8; DK_LEN], [u8; EK_LEN]), KeyGenError>; /// Encapsulate a shared secret towards a given encapsulation key. + /// It is the responsibility of the caller to ensure that the `rand` argument is actually + /// random. fn encaps( ek: &[u8; EK_LEN], rand: &[U8; RAND_ENCAPS_LEN], diff --git a/traits/src/kem/slice.rs b/traits/src/kem/slice.rs index 653f4c3df..28a7b011a 100644 --- a/traits/src/kem/slice.rs +++ b/traits/src/kem/slice.rs @@ -6,9 +6,13 @@ use super::arrayref; /// A Key Encapsulation Mechanismd (KEM). This trait takes slices as arguments. pub trait Kem { /// Generate a pair of encapsulation and decapsulation keys. + /// It is the responsibility of the caller to ensure that the `rand` argument is actually + /// random. fn keygen(ek: &mut [u8], dk: &mut [u8], rand: &[u8]) -> Result<(), KeyGenError>; /// Encapsulate a shared secret towards a given encapsulation key. + /// It is the responsibility of the caller to ensure that the `rand` argument is actually + /// random. fn encaps(ct: &mut [u8], ss: &mut [u8], ek: &[u8], rand: &[u8]) -> Result<(), EncapsError>; /// Decapsulate a shared secret. From 8dd2e2a3e96687380528e56dced61721715ec38e Mon Sep 17 00:00:00 2001 From: "Jan Winkelmann (keks)" Date: Wed, 16 Jul 2025 12:06:36 +0200 Subject: [PATCH 12/12] ignore kem trait impls not just for hax but also eurydice --- libcrux-ml-kem/src/mlkem1024.rs | 2 +- libcrux-ml-kem/src/mlkem512.rs | 2 +- libcrux-ml-kem/src/mlkem768.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libcrux-ml-kem/src/mlkem1024.rs b/libcrux-ml-kem/src/mlkem1024.rs index 77b330c25..1e9c58b58 100644 --- a/libcrux-ml-kem/src/mlkem1024.rs +++ b/libcrux-ml-kem/src/mlkem1024.rs @@ -28,7 +28,7 @@ const IMPLICIT_REJECTION_HASH_INPUT_SIZE: usize = SHARED_SECRET_SIZE + CPA_PKE_C /// The ML-KEM 1024 algorithms pub struct MlKem1024; -#[cfg(not(hax))] +#[cfg(not(any(hax, eurydice)))] crate::impl_kem_trait!( MlKem1024, MlKem1024PublicKey, diff --git a/libcrux-ml-kem/src/mlkem512.rs b/libcrux-ml-kem/src/mlkem512.rs index df1075e7b..41b6e8bcd 100644 --- a/libcrux-ml-kem/src/mlkem512.rs +++ b/libcrux-ml-kem/src/mlkem512.rs @@ -29,7 +29,7 @@ const IMPLICIT_REJECTION_HASH_INPUT_SIZE: usize = SHARED_SECRET_SIZE + CPA_PKE_C /// The ML-KEM 512 algorithms pub struct MlKem512; -#[cfg(not(hax))] +#[cfg(not(any(hax, eurydice)))] crate::impl_kem_trait!( MlKem512, MlKem512PublicKey, diff --git a/libcrux-ml-kem/src/mlkem768.rs b/libcrux-ml-kem/src/mlkem768.rs index f7703200d..6f55175ac 100644 --- a/libcrux-ml-kem/src/mlkem768.rs +++ b/libcrux-ml-kem/src/mlkem768.rs @@ -36,7 +36,7 @@ const IMPLICIT_REJECTION_HASH_INPUT_SIZE: usize = SHARED_SECRET_SIZE + CPA_PKE_C /// The ML-KEM 768 algorithms pub struct MlKem768; -#[cfg(not(hax))] +#[cfg(not(any(hax, eurydice)))] crate::impl_kem_trait!( MlKem768, MlKem768PublicKey,