From 3fbbec1da3badd5ed8b52cdb453e9643152687e1 Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Tue, 2 Jul 2024 09:50:42 +0200 Subject: [PATCH 1/4] Align documentation/API between assurance levels --- libcrux-ml-kem/src/kem/kyber/kyber1024.rs | 22 +++++++++++++++++--- libcrux-ml-kem/src/kem/kyber/kyber512.rs | 21 ++++++++++++++++--- libcrux-ml-kem/src/kem/kyber/kyber768.rs | 22 +++++++++++++++++--- libcrux-ml-kem/src/kem/kyber/types.rs | 25 ++++++++++++++++------- libcrux-ml-kem/src/lib.rs | 11 +++++++++- libcrux-ml-kem/src/mlkem512.rs | 4 ++-- libcrux-ml-kem/src/mlkem768.rs | 2 +- 7 files changed, 87 insertions(+), 20 deletions(-) diff --git a/libcrux-ml-kem/src/kem/kyber/kyber1024.rs b/libcrux-ml-kem/src/kem/kyber/kyber1024.rs index f67682fe5..35faf4b4a 100644 --- a/libcrux-ml-kem/src/kem/kyber/kyber1024.rs +++ b/libcrux-ml-kem/src/kem/kyber/kyber1024.rs @@ -34,8 +34,11 @@ const ETA2_RANDOMNESS_SIZE: usize = ETA2 * 64; const IMPLICIT_REJECTION_HASH_INPUT_SIZE: usize = SHARED_SECRET_SIZE + CPA_PKE_CIPHERTEXT_SIZE_1024; // Kyber 1024 types +/// An ML-KEM 1024 Ciphertext pub type MlKem1024Ciphertext = MlKemCiphertext; +/// An ML-KEM 1024 Private key pub type MlKem1024PrivateKey = MlKemPrivateKey; +/// An ML-KEM 1024 Public key pub type MlKem1024PublicKey = MlKemPublicKey; /// Validate a public key. @@ -55,6 +58,9 @@ pub fn validate_public_key(public_key: MlKem1024PublicKey) -> Option MlKemKeyPair { @@ -69,9 +75,11 @@ pub fn generate_key_pair( >(randomness) } -pub type MlKem1024State = MlKemState; +#[allow(unused)] +pub(crate) type MlKem1024State = MlKemState; -pub fn generate_key_pair_unpacked( +#[allow(unused)] +pub(crate) fn generate_key_pair_unpacked( randomness: [u8; KEY_GENERATION_SEED_SIZE], ) -> (MlKem1024State, MlKem1024PublicKey) { generate_keypair_unpacked::< @@ -86,6 +94,10 @@ pub fn generate_key_pair_unpacked( } /// Encapsulate ML-KEM 1024 +/// +/// Generates an ([`MlKem1024Ciphertext`], [`MlKemSharedSecret`]) tuple. +/// The input is a reference to an [`MlKem1024PublicKey`] and [`crate::SHARED_SECRET_SIZE`] +/// bytes of `randomness`. pub fn encapsulate( public_key: &MlKemPublicKey, randomness: [u8; SHARED_SECRET_SIZE], @@ -111,6 +123,9 @@ pub fn encapsulate( } /// Decapsulate ML-KEM 1024 +/// +/// Generates an [`MlKemSharedSecret`]. +/// The input is a reference to an [`MlKem1024PrivateKey`] and an [`MlKem1024Ciphertext`]. pub fn decapsulate( secret_key: &MlKemPrivateKey, ciphertext: &MlKemCiphertext, @@ -135,7 +150,8 @@ pub fn decapsulate( >(secret_key, ciphertext) } -pub fn decapsulate_unpacked( +#[allow(unused)] +pub(crate) fn decapsulate_unpacked( state: &MlKem1024State, ciphertext: &MlKemCiphertext, ) -> [u8; SHARED_SECRET_SIZE] { diff --git a/libcrux-ml-kem/src/kem/kyber/kyber512.rs b/libcrux-ml-kem/src/kem/kyber/kyber512.rs index 8edce7f47..0e9ece26e 100644 --- a/libcrux-ml-kem/src/kem/kyber/kyber512.rs +++ b/libcrux-ml-kem/src/kem/kyber/kyber512.rs @@ -32,8 +32,11 @@ const ETA2_RANDOMNESS_SIZE: usize = ETA2 * 64; const IMPLICIT_REJECTION_HASH_INPUT_SIZE: usize = SHARED_SECRET_SIZE + CPA_PKE_CIPHERTEXT_SIZE_512; // Kyber 512 types +/// An ML-KEM 512 Ciphertext pub type MlKem512Ciphertext = MlKemCiphertext; +/// An ML-KEM 512 Private key pub type MlKem512PrivateKey = MlKemPrivateKey; +/// An ML-KEM 512 Public key pub type MlKem512PublicKey = MlKemPublicKey; /// Validate a public key. @@ -53,6 +56,9 @@ pub fn validate_public_key(public_key: MlKem512PublicKey) -> Option MlKemKeyPair { @@ -67,9 +73,11 @@ pub fn generate_key_pair( >(randomness) } -pub type MlKem512State = MlKemState; +#[allow(unused)] +pub(crate) type MlKem512State = MlKemState; -pub fn generate_key_pair_unpacked( +#[allow(unused)] +pub(crate) fn generate_key_pair_unpacked( randomness: [u8; KEY_GENERATION_SEED_SIZE], ) -> (MlKem512State, MlKem512PublicKey) { generate_keypair_unpacked::< @@ -84,6 +92,9 @@ pub fn generate_key_pair_unpacked( } /// Encapsulate ML-KEM 512 +/// +/// Generates an ([`MlKem512Ciphertext`], [`MlKemSharedSecret`]) tuple. +/// The input is a reference to an [`MlKem512PublicKey`] and [`crate::SHARED_SECRET_SIZE`] pub fn encapsulate( public_key: &MlKemPublicKey, randomness: [u8; SHARED_SECRET_SIZE], @@ -109,6 +120,9 @@ pub fn encapsulate( } /// Decapsulate ML-KEM 512 +/// +/// Generates an [`MlKemSharedSecret`]. +/// The input is a reference to an [`MlKem512PrivateKey`] and an [`MlKem512Ciphertext`]. pub fn decapsulate( secret_key: &MlKemPrivateKey, ciphertext: &MlKemCiphertext, @@ -133,7 +147,8 @@ pub fn decapsulate( >(secret_key, ciphertext) } -pub fn decapsulate_unpacked( +#[allow(unused)] +pub(crate) fn decapsulate_unpacked( state: &MlKem512State, ciphertext: &MlKemCiphertext, ) -> [u8; SHARED_SECRET_SIZE] { diff --git a/libcrux-ml-kem/src/kem/kyber/kyber768.rs b/libcrux-ml-kem/src/kem/kyber/kyber768.rs index 7d9f5b131..a1bfb4c19 100644 --- a/libcrux-ml-kem/src/kem/kyber/kyber768.rs +++ b/libcrux-ml-kem/src/kem/kyber/kyber768.rs @@ -33,8 +33,11 @@ const ETA2_RANDOMNESS_SIZE: usize = ETA2 * 64; const IMPLICIT_REJECTION_HASH_INPUT_SIZE: usize = SHARED_SECRET_SIZE + CPA_PKE_CIPHERTEXT_SIZE_768; // Kyber 768 types +/// An ML-KEM 768 Ciphertext pub type MlKem768Ciphertext = MlKemCiphertext; +/// An ML-KEM 768 Private key pub type MlKem768PrivateKey = MlKemPrivateKey; +/// An ML-KEM 768 Public key pub type MlKem768PublicKey = MlKemPublicKey; /// Validate a public key. @@ -54,6 +57,9 @@ pub fn validate_public_key(public_key: MlKem768PublicKey) -> Option MlKemKeyPair { @@ -68,9 +74,11 @@ pub fn generate_key_pair( >(randomness) } -pub type MlKem768State = MlKemState; +#[allow(unused)] +pub(crate) type MlKem768State = MlKemState; -pub fn generate_key_pair_unpacked( +#[allow(unused)] +pub(crate) fn generate_key_pair_unpacked( randomness: [u8; KEY_GENERATION_SEED_SIZE], ) -> (MlKem768State, MlKem768PublicKey) { generate_keypair_unpacked::< @@ -85,6 +93,10 @@ pub fn generate_key_pair_unpacked( } /// Encapsulate ML-KEM 768 +/// +/// Generates an ([`MlKem768Ciphertext`], [`MlKemSharedSecret`]) tuple. +/// The input is a reference to an [`MlKem768PublicKey`] and [`crate::SHARED_SECRET_SIZE`] +/// bytes of `randomness`. pub fn encapsulate( public_key: &MlKemPublicKey, randomness: [u8; SHARED_SECRET_SIZE], @@ -110,6 +122,9 @@ pub fn encapsulate( } /// Decapsulate ML-KEM 768 +/// +/// Generates an [`MlKemSharedSecret`]. +/// The input is a reference to an [`MlKem768PrivateKey`] and an [`MlKem768Ciphertext`]. pub fn decapsulate( secret_key: &MlKemPrivateKey, ciphertext: &MlKemCiphertext, @@ -134,7 +149,8 @@ pub fn decapsulate( >(secret_key, ciphertext) } -pub fn decapsulate_unpacked( +#[allow(unused)] +pub(crate) fn decapsulate_unpacked( state: &MlKem768State, ciphertext: &MlKemCiphertext, ) -> [u8; SHARED_SECRET_SIZE] { diff --git a/libcrux-ml-kem/src/kem/kyber/types.rs b/libcrux-ml-kem/src/kem/kyber/types.rs index a3856827d..f4909fd84 100644 --- a/libcrux-ml-kem/src/kem/kyber/types.rs +++ b/libcrux-ml-kem/src/kem/kyber/types.rs @@ -1,5 +1,6 @@ macro_rules! impl_generic_struct { - ($name:ident) => { + ($name:ident, $doc:expr) => { + #[doc = $doc] pub struct $name { pub(super) value: [u8; SIZE], } @@ -42,14 +43,18 @@ macro_rules! impl_generic_struct { } impl $name { + /// A reference to the raw byte slice. pub fn as_slice(&self) -> &[u8; SIZE] { &self.value } - pub fn split_at(&self, mid: usize) -> (&[u8], &[u8]) { + // This is only used for some of the macro callers. + #[allow(dead_code)] + // /// Split this value and return the raw byte slices. + pub(crate) fn split_at(&self, mid: usize) -> (&[u8], &[u8]) { self.value.split_at(mid) } - + /// The number of bytes pub const fn len() -> usize { SIZE } @@ -92,9 +97,9 @@ macro_rules! impl_index_impls_for_generic_struct { }; } -impl_generic_struct!(MlKemCiphertext); -impl_generic_struct!(MlKemPrivateKey); -impl_generic_struct!(MlKemPublicKey); +impl_generic_struct!(MlKemCiphertext, "An ML-KEM Ciphertext"); +impl_generic_struct!(MlKemPrivateKey, "An ML-KEM Private key"); +impl_generic_struct!(MlKemPublicKey, "An ML-KEM Public key"); // These traits are used only in `ind_cpa` for kyber cipher text. mod index_impls { @@ -121,6 +126,7 @@ impl } } + /// Create a new [`MlKemKeyPair`] from the secret and public key. pub fn from( sk: MlKemPrivateKey, pk: MlKemPublicKey, @@ -128,22 +134,27 @@ impl Self { sk, pk } } + /// Get a reference to the [`MlKemPublicKey`]. pub fn public_key(&self) -> &MlKemPublicKey { &self.pk } + /// Get a reference to the [`MlKemPrivateKey`]. pub fn private_key(&self) -> &MlKemPrivateKey { &self.sk } + /// Get a reference to the raw public key bytes. pub fn pk(&self) -> &[u8; PUBLIC_KEY_SIZE] { self.pk.as_slice() } + /// Get a reference to the raw private key bytes. pub fn sk(&self) -> &[u8; PRIVATE_KEY_SIZE] { self.sk.as_slice() } - + + /// Separate this key into the public and private key. pub fn into_parts( self, ) -> ( diff --git a/libcrux-ml-kem/src/lib.rs b/libcrux-ml-kem/src/lib.rs index 7099d8953..9a9686ef1 100644 --- a/libcrux-ml-kem/src/lib.rs +++ b/libcrux-ml-kem/src/lib.rs @@ -24,7 +24,7 @@ use libcrux_ml_kem::*; - // This example use ML-KEM 768. The other variants can be used the same way. + // This example uses ML-KEM 768. The other variants can be used the same way. // Generate a key pair. let randomness = random_array(); @@ -170,24 +170,33 @@ cfg_verified! { #[cfg(feature = "mlkem512")] #[cfg_attr(docsrs, doc(cfg(feature = "mlkem512")))] pub mod mlkem512 { + //! ML-KEM 512 pub use crate::kem::kyber::kyber512::*; } #[cfg(feature = "mlkem768")] #[cfg_attr(docsrs, doc(cfg(feature = "mlkem768")))] pub mod mlkem768 { + //! ML-KEM 768 pub use crate::kem::kyber::kyber768::*; } #[cfg(feature = "mlkem1024")] #[cfg_attr(docsrs, doc(cfg(feature = "mlkem1024")))] pub mod mlkem1024 { + //! ML-KEM 1024 pub use crate::kem::kyber::kyber1024::*; } + /// The size of an ML-KEM shared secret. pub const SHARED_SECRET_SIZE: usize = kem::kyber::constants::SHARED_SECRET_SIZE; + /// An ML-KEM shared secret. + /// + /// A byte array of size [`SHARED_SECRET_SIZE`]. pub use kem::kyber::MlKemSharedSecret; + /// Seed size for encapsulation pub const ENCAPS_SEED_SIZE: usize = kem::kyber::constants::SHARED_SECRET_SIZE; + /// Seed size for key generation pub const KEY_GENERATION_SEED_SIZE: usize = kem::kyber::KEY_GENERATION_SEED_SIZE; // These types all have type aliases for the different variants. pub use kem::kyber::{MlKemCiphertext, MlKemKeyPair, MlKemPrivateKey, MlKemPublicKey}; diff --git a/libcrux-ml-kem/src/mlkem512.rs b/libcrux-ml-kem/src/mlkem512.rs index 9dadfc704..448d97c68 100644 --- a/libcrux-ml-kem/src/mlkem512.rs +++ b/libcrux-ml-kem/src/mlkem512.rs @@ -163,7 +163,7 @@ macro_rules! instantiate { >(private_key, ciphertext) } - /// Decapsulate ML-KEM 512 + /// Decapsulate Kyber 512 /// /// Generates an [`MlKemSharedSecret`]. /// The input is a reference to an [`MlKem512PrivateKey`] and an [`MlKem512Ciphertext`]. @@ -222,7 +222,7 @@ pub fn validate_public_key(public_key: MlKem512PublicKey) -> Option Date: Tue, 2 Jul 2024 10:34:30 +0200 Subject: [PATCH 2/4] Document feature detection at the top-level --- libcrux-ml-kem/src/lib.rs | 13 ++++++++++++- libcrux-ml-kem/src/mlkem768.rs | 7 ------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/libcrux-ml-kem/src/lib.rs b/libcrux-ml-kem/src/lib.rs index 9a9686ef1..191043524 100644 --- a/libcrux-ml-kem/src/lib.rs +++ b/libcrux-ml-kem/src/lib.rs @@ -4,7 +4,18 @@ //! formally verified using [hax](https://cryspen.com/hax) and //! [F*](https://fstar-lang.org). //! - +#![cfg_attr( + feature = "pre-verification", + doc = r##" +Functions in this crate use CPU feature detection to pick the most efficient version +on each platform. To use a specific version with your own feature detection +use e.g. one of the following +- `mlkem768::avx2::generate_key_pair`, +- `mlkem768::neon::generate_key_pair`, +- `mlkem768::portable::generate_key_pair`, + +analogously for encapsulation and decapsulation."## +)] #![cfg_attr( feature = "mlkem768", doc = r##" diff --git a/libcrux-ml-kem/src/mlkem768.rs b/libcrux-ml-kem/src/mlkem768.rs index 1c15d68f0..90b5c8377 100644 --- a/libcrux-ml-kem/src/mlkem768.rs +++ b/libcrux-ml-kem/src/mlkem768.rs @@ -225,13 +225,6 @@ pub fn validate_public_key(public_key: MlKem768PublicKey) -> Option MlKem768KeyPair { From 4d09f8f4274971f752be9281114607501465bc60 Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Tue, 2 Jul 2024 10:39:14 +0200 Subject: [PATCH 3/4] Add README.md to `libcrux-ml-kem` --- libcrux-ml-kem/README.md | 61 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 libcrux-ml-kem/README.md diff --git a/libcrux-ml-kem/README.md b/libcrux-ml-kem/README.md new file mode 100644 index 000000000..b28df5ed1 --- /dev/null +++ b/libcrux-ml-kem/README.md @@ -0,0 +1,61 @@ +# ML-KEM + +This crate implements all three ML-KEM variants 512, 768, and 1024. It is +formally verified using [hax](https://cryspen.com/hax) and + [F*](https://fstar-lang.org). + +Functions in this crate use CPU feature detection to pick the most efficient version +on each platform. To use a specific version with your own feature detection +use e.g. one of the following +- `mlkem768::avx2::generate_key_pair`, +- `mlkem768::neon::generate_key_pair`, +- `mlkem768::portable::generate_key_pair`, + +analogously for encapsulation and decapsulation. + +```Rust + use rand::{rngs::OsRng, RngCore}; + + // Ensure you use good randomness. + // It is not recommended to use OsRng directly! + // Instead it is highly encouraged to use RNGs like NISTs DRBG to account for + // bad system entropy. + fn random_array() -> [u8; L] { + let mut rng = OsRng; + let mut seed = [0; L]; + rng.try_fill_bytes(&mut seed).unwrap(); + seed + } + + use libcrux_ml_kem::*; + + // This example uses ML-KEM 768. The other variants can be used the same way. + + // Generate a key pair. + let randomness = random_array(); + let key_pair = mlkem768::generate_key_pair(randomness); + + // Encapsulating a shared secret to a public key. + let randomness = random_array(); + let (ciphertext, shared_secret) = mlkem768::encapsulate(key_pair.public_key(), randomness); + + // Decapsulating a shared secret with a private key. + let shared_secret_decapsulated = mlkem768::decapsulate(key_pair.private_key(), &ciphertext); +``` + + +## Features + +By default, all ML-KEM parameter sets are enabled. If required, they are +available individually under feature flags `mlkem512`, `mlkem768`, +`mlkem1024`. + +In addition to the verified implementations of the ML-KEM variants, the +feature flag `pre-verification` gives access to, as yet, unverified +implementations of ML-KEM that are optimized for SIMD instruction sets. + +### Kyber Round 3 +The `kyber` flag (in combination with `pre-verification`) also gives access +to an, as yet, unverified implementation of Kyber as submitted in Round 3 of +the NIST PQ competition. + From 7b19affbf6fb8384565485d4ccb835920564f0d4 Mon Sep 17 00:00:00 2001 From: Franziskus Kiefer Date: Tue, 2 Jul 2024 12:55:11 +0200 Subject: [PATCH 4/4] Update libcrux-ml-kem/README.md --- libcrux-ml-kem/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcrux-ml-kem/README.md b/libcrux-ml-kem/README.md index b28df5ed1..d0ff7b35d 100644 --- a/libcrux-ml-kem/README.md +++ b/libcrux-ml-kem/README.md @@ -1,6 +1,6 @@ # ML-KEM -This crate implements all three ML-KEM variants 512, 768, and 1024. It is +This crate implements all three ML-KEM ([FIPS 203](https://csrc.nist.gov/pubs/fips/203/ipd) (Initial Public Draft)) variants 512, 768, and 1024. It is formally verified using [hax](https://cryspen.com/hax) and [F*](https://fstar-lang.org).