diff --git a/ml-kem/src/decapsulation_key.rs b/ml-kem/src/decapsulation_key.rs new file mode 100644 index 0000000..7c8ce65 --- /dev/null +++ b/ml-kem/src/decapsulation_key.rs @@ -0,0 +1,212 @@ +use crate::{ + B32, EncapsulationKey, Encoded, EncodedSizeUser, ExpandedDecapsulationKey, Seed, SharedKey, + crypto::{G, J}, + kem::{Generate, InvalidKey, Kem, KeyInit, KeySizeUser}, + param::{DecapsulationKeySize, KemParams}, + pke::{DecryptionKey, EncryptionKey}, +}; +use array::sizes::{U32, U64}; +use kem::{Ciphertext, Decapsulate}; +use rand_core::{TryCryptoRng, TryRng}; +use subtle::{ConditionallySelectable, ConstantTimeEq}; + +#[cfg(feature = "zeroize")] +use zeroize::{Zeroize, ZeroizeOnDrop}; + +/// A `DecapsulationKey` provides the ability to generate a new key pair, and decapsulate an +/// encapsulated shared key. +#[derive(Clone, Debug)] +pub struct DecapsulationKey

+where + P: KemParams, +{ + dk_pke: DecryptionKey

, + ek: EncapsulationKey

, + d: Option, + z: B32, +} + +impl

DecapsulationKey

+where + P: KemParams, +{ + /// Create a [`DecapsulationKey`] instance from a 64-byte random seed value. + #[inline] + #[must_use] + pub fn from_seed(seed: Seed) -> Self { + let (d, z) = seed.split(); + Self::generate_deterministic(d, z) + } + + /// Initialize a [`DecapsulationKey`] from the serialized expanded key form. + /// + /// Note that this form is deprecated in practice; prefer to use + /// [`DecapsulationKey::from_seed`]. + /// + /// # Errors + /// - Returns [`InvalidKey`] in the event the expanded key failed validation + #[deprecated(since = "0.3.0", note = "use `DecapsulationKey::from_seed` instead")] + pub fn from_expanded(enc: &ExpandedDecapsulationKey

) -> Result { + let (dk_pke, ek_pke, h, z) = P::split_dk(enc); + let dk_pke = DecryptionKey::from_bytes(dk_pke); + let ek_pke = EncryptionKey::from_bytes(ek_pke)?; + + let ek = EncapsulationKey::from_encryption_key(ek_pke); + if ek.h() != *h { + return Err(InvalidKey); + } + + Ok(Self { + dk_pke, + ek, + d: None, + z: z.clone(), + }) + } + + /// Serialize the [`Seed`] value: 64-bytes which can be used to reconstruct the + /// [`DecapsulationKey`]. + /// + ///

+ /// Warning! + /// + /// This value is key material. Please treat it with care. + ///
+ /// + /// # Returns + /// - `Some` if the [`DecapsulationKey`] was initialized using `from_seed` or `generate`. + /// - `None` if the [`DecapsulationKey`] was initialized from the expanded form. + #[inline] + pub fn to_seed(&self) -> Option { + self.d.map(|d| d.concat(self.z)) + } + + /// Get the [`EncapsulationKey`] which corresponds to this [`DecapsulationKey`]. + pub fn encapsulation_key(&self) -> &EncapsulationKey

{ + &self.ek + } + + #[inline] + pub(crate) fn try_generate_from_rng(rng: &mut R) -> Result::Error> + where + R: TryCryptoRng + ?Sized, + { + let d = B32::try_generate_from_rng(rng)?; + let z = B32::try_generate_from_rng(rng)?; + Ok(Self::generate_deterministic(d, z)) + } + + #[inline] + #[must_use] + #[allow(clippy::similar_names)] // allow dk_pke, ek_pke, following the spec + pub(crate) fn generate_deterministic(d: B32, z: B32) -> Self { + let (dk_pke, ek_pke) = DecryptionKey::generate(&d); + let ek = EncapsulationKey::from_encryption_key(ek_pke); + let d = Some(d); + Self { dk_pke, ek, d, z } + } +} + +// Handwritten to omit `d` in the comparisons, so keys initialized from seeds compare equally to +// keys initialized from the expanded form +impl

PartialEq for DecapsulationKey

+where + P: KemParams, +{ + fn eq(&self, other: &Self) -> bool { + self.dk_pke.ct_eq(&other.dk_pke).into() && self.ek.eq(&other.ek) && self.z.eq(&other.z) + } +} + +#[cfg(feature = "zeroize")] +impl

Drop for DecapsulationKey

+where + P: KemParams, +{ + fn drop(&mut self) { + self.dk_pke.zeroize(); + self.d.zeroize(); + self.z.zeroize(); + } +} + +#[cfg(feature = "zeroize")] +impl

ZeroizeOnDrop for DecapsulationKey

where P: KemParams {} + +impl

From for DecapsulationKey

+where + P: KemParams, +{ + fn from(seed: Seed) -> Self { + Self::from_seed(seed) + } +} + +impl

Decapsulate

for DecapsulationKey

+where + P: Kem, SharedKeySize = U32> + KemParams, +{ + fn decapsulate(&self, encapsulated_key: &Ciphertext

) -> SharedKey { + let mp = self.dk_pke.decrypt(encapsulated_key); + let (Kp, rp) = G(&[&mp, &self.ek.h()]); + let Kbar = J(&[self.z.as_slice(), encapsulated_key.as_ref()]); + let cp = self.ek.ek_pke().encrypt(&mp, &rp); + B32::conditional_select(&Kbar, &Kp, cp.ct_eq(encapsulated_key)) + } +} + +impl

AsRef> for DecapsulationKey

+where + P: KemParams, +{ + fn as_ref(&self) -> &EncapsulationKey

{ + &self.ek + } +} + +impl

EncodedSizeUser for DecapsulationKey

+where + P: KemParams, +{ + type EncodedSize = DecapsulationKeySize

; + + fn from_encoded_bytes(expanded: &Encoded) -> Result { + #[allow(deprecated)] + Self::from_expanded(expanded) + } + + fn to_encoded_bytes(&self) -> Encoded { + let dk_pke = self.dk_pke.to_bytes(); + let ek = self.ek.to_encoded_bytes(); + P::concat_dk(dk_pke, ek, self.ek.h(), self.z.clone()) + } +} + +impl

Generate for DecapsulationKey

+where + P: KemParams, +{ + fn try_generate_from_rng(rng: &mut R) -> Result::Error> + where + R: TryCryptoRng + ?Sized, + { + Self::try_generate_from_rng(rng) + } +} + +impl

KeySizeUser for DecapsulationKey

+where + P: KemParams, +{ + type KeySize = U64; +} + +impl

KeyInit for DecapsulationKey

+where + P: KemParams, +{ + #[inline] + fn new(seed: &Seed) -> Self { + Self::from_seed(*seed) + } +} diff --git a/ml-kem/src/encapsulation_key.rs b/ml-kem/src/encapsulation_key.rs new file mode 100644 index 0000000..8d2d2ca --- /dev/null +++ b/ml-kem/src/encapsulation_key.rs @@ -0,0 +1,120 @@ +use crate::{ + B32, Encoded, EncodedSizeUser, SharedKey, + crypto::{G, H}, + kem::{InvalidKey, Kem, Key, KeyExport, KeySizeUser, TryKeyInit}, + param::{EncapsulationKeySize, KemParams}, + pke::EncryptionKey, +}; +use array::sizes::U32; +use kem::{Ciphertext, Encapsulate, Generate}; +use rand_core::CryptoRng; + +/// An `EncapsulationKey` provides the ability to encapsulate a shared key so that it can only be +/// decapsulated by the holder of the corresponding decapsulation key. +#[derive(Clone, Debug)] +pub struct EncapsulationKey

+where + P: KemParams, +{ + ek_pke: EncryptionKey

, + h: B32, +} + +impl

EncapsulationKey

+where + P: Kem + KemParams, +{ + /// Encapsulates with the given randomness. This is useful for testing against known vectors. + /// + /// # Warning + /// Do NOT use this function unless you know what you're doing. If you fail to use all uniform + /// random bytes even once, you can have catastrophic security failure. + #[cfg_attr(not(feature = "hazmat"), doc(hidden))] + pub fn encapsulate_deterministic(&self, m: &B32) -> (Ciphertext

, SharedKey) { + let (K, r) = G(&[m, &self.h]); + let c = self.ek_pke.encrypt(m, &r); + (c, K) + } + + /// Convert from an `EncryptionKey`. + pub(crate) fn from_encryption_key(ek_pke: EncryptionKey

) -> Self { + let h = H(ek_pke.to_bytes()); + Self { ek_pke, h } + } + + /// Borrow the encryption key. + pub(crate) fn ek_pke(&self) -> &EncryptionKey

{ + &self.ek_pke + } + + /// Retrieve the hash of the encryption key. + pub(crate) fn h(&self) -> B32 { + self.h + } +} + +impl

Encapsulate

for EncapsulationKey

+where + P: Kem + KemParams, +{ + fn encapsulate_with_rng(&self, rng: &mut R) -> (Ciphertext

, SharedKey) + where + R: CryptoRng + ?Sized, + { + let m = B32::generate_from_rng(rng); + self.encapsulate_deterministic(&m) + } +} + +impl

EncodedSizeUser for EncapsulationKey

+where + P: KemParams, +{ + type EncodedSize = EncapsulationKeySize

; + + fn from_encoded_bytes(enc: &Encoded) -> Result { + Ok(Self::from_encryption_key(EncryptionKey::from_bytes(enc)?)) + } + + fn to_encoded_bytes(&self) -> Encoded { + self.ek_pke.to_bytes() + } +} + +impl

KeyExport for EncapsulationKey

+where + P: KemParams, +{ + fn to_bytes(&self) -> Key { + self.ek_pke.to_bytes() + } +} + +impl

KeySizeUser for EncapsulationKey

+where + P: KemParams, +{ + type KeySize = EncapsulationKeySize

; +} + +impl

TryKeyInit for EncapsulationKey

+where + P: KemParams, +{ + fn new(encapsulation_key: &Key) -> Result { + EncryptionKey::from_bytes(encapsulation_key) + .map(Self::from_encryption_key) + .map_err(|_| InvalidKey) + } +} + +impl

Eq for EncapsulationKey

where P: KemParams {} +impl

PartialEq for EncapsulationKey

+where + P: KemParams, +{ + fn eq(&self, other: &Self) -> bool { + // Handwritten to avoid derive putting `Eq` bounds on `KemParams` + self.ek_pke == other.ek_pke && self.h == other.h + } +} diff --git a/ml-kem/src/kem.rs b/ml-kem/src/kem.rs deleted file mode 100644 index 2441ae8..0000000 --- a/ml-kem/src/kem.rs +++ /dev/null @@ -1,444 +0,0 @@ -//! Key encapsulation mechanism implementation. - -// Re-export traits from the `kem` crate -pub use ::kem::{ - Ciphertext, Decapsulate, Encapsulate, Generate, InvalidKey, Kem, Key, KeyExport, KeyInit, - KeySizeUser, TryKeyInit, -}; - -use crate::{ - B32, Encoded, EncodedSizeUser, Seed, SharedKey, - crypto::{G, H, J}, - param::{DecapsulationKeySize, EncapsulationKeySize, ExpandedDecapsulationKey, KemParams}, - pke::{DecryptionKey, EncryptionKey}, -}; -use array::sizes::{U32, U64}; -use rand_core::{CryptoRng, TryCryptoRng, TryRng}; -use sha3::Digest; -use subtle::{ConditionallySelectable, ConstantTimeEq}; - -#[cfg(feature = "zeroize")] -use zeroize::{Zeroize, ZeroizeOnDrop}; - -/// A `DecapsulationKey` provides the ability to generate a new key pair, and decapsulate an -/// encapsulated shared key. -#[derive(Clone, Debug)] -pub struct DecapsulationKey

-where - P: KemParams, -{ - dk_pke: DecryptionKey

, - ek: EncapsulationKey

, - d: Option, - z: B32, -} - -// Handwritten to omit `d` in the comparisons, so keys initialized from seeds compare equally to -// keys initialized from the expanded form -impl

PartialEq for DecapsulationKey

-where - P: KemParams, -{ - fn eq(&self, other: &Self) -> bool { - self.dk_pke.ct_eq(&other.dk_pke).into() && self.ek.eq(&other.ek) && self.z.eq(&other.z) - } -} - -#[cfg(feature = "zeroize")] -impl

Drop for DecapsulationKey

-where - P: KemParams, -{ - fn drop(&mut self) { - self.dk_pke.zeroize(); - self.d.zeroize(); - self.z.zeroize(); - } -} - -#[cfg(feature = "zeroize")] -impl

ZeroizeOnDrop for DecapsulationKey

where P: KemParams {} - -impl

From for DecapsulationKey

-where - P: KemParams, -{ - fn from(seed: Seed) -> Self { - Self::from_seed(seed) - } -} - -impl

Decapsulate

for DecapsulationKey

-where - P: Kem, SharedKeySize = U32> + KemParams, -{ - fn decapsulate(&self, encapsulated_key: &Ciphertext

) -> SharedKey { - let mp = self.dk_pke.decrypt(encapsulated_key); - let (Kp, rp) = G(&[&mp, &self.ek.h]); - let Kbar = J(&[self.z.as_slice(), encapsulated_key.as_ref()]); - let cp = self.ek.ek_pke.encrypt(&mp, &rp); - B32::conditional_select(&Kbar, &Kp, cp.ct_eq(encapsulated_key)) - } -} - -impl

AsRef> for DecapsulationKey

-where - P: KemParams, -{ - fn as_ref(&self) -> &EncapsulationKey

{ - &self.ek - } -} - -impl

EncodedSizeUser for DecapsulationKey

-where - P: KemParams, -{ - type EncodedSize = DecapsulationKeySize

; - - fn from_encoded_bytes(expanded: &Encoded) -> Result { - #[allow(deprecated)] - Self::from_expanded(expanded) - } - - fn to_encoded_bytes(&self) -> Encoded { - let dk_pke = self.dk_pke.to_bytes(); - let ek = self.ek.to_encoded_bytes(); - P::concat_dk(dk_pke, ek, self.ek.h.clone(), self.z.clone()) - } -} - -impl

Generate for DecapsulationKey

-where - P: KemParams, -{ - fn try_generate_from_rng(rng: &mut R) -> Result::Error> - where - R: TryCryptoRng + ?Sized, - { - Self::try_generate_from_rng(rng) - } -} - -impl

KeySizeUser for DecapsulationKey

-where - P: KemParams, -{ - type KeySize = U64; -} - -impl

KeyInit for DecapsulationKey

-where - P: KemParams, -{ - #[inline] - fn new(seed: &Seed) -> Self { - Self::from_seed(*seed) - } -} - -impl

DecapsulationKey

-where - P: KemParams, -{ - /// Create a [`DecapsulationKey`] instance from a 64-byte random seed value. - #[inline] - #[must_use] - pub fn from_seed(seed: Seed) -> Self { - let (d, z) = seed.split(); - Self::generate_deterministic(d, z) - } - - /// Initialize a [`DecapsulationKey`] from the serialized expanded key form. - /// - /// Note that this form is deprecated in practice; prefer to use - /// [`DecapsulationKey::from_seed`]. - /// - /// # Errors - /// - Returns [`InvalidKey`] in the event the expanded key failed validation - #[deprecated(since = "0.3.0", note = "use `DecapsulationKey::from_seed` instead")] - pub fn from_expanded(enc: &ExpandedDecapsulationKey

) -> Result { - let (dk_pke, ek_pke, h, z) = P::split_dk(enc); - let ek_pke = EncryptionKey::from_bytes(ek_pke)?; - - let test = sha3::Sha3_256::digest(ek_pke.to_bytes()); - if test.as_slice() != h.as_slice() { - return Err(InvalidKey); - } - - Ok(Self { - dk_pke: DecryptionKey::from_bytes(dk_pke), - ek: EncapsulationKey { - ek_pke, - h: h.clone(), - }, - d: None, - z: z.clone(), - }) - } - - /// Serialize the [`Seed`] value: 64-bytes which can be used to reconstruct the - /// [`DecapsulationKey`]. - /// - ///

- /// Warning! - /// - /// This value is key material. Please treat it with care. - ///
- /// - /// # Returns - /// - `Some` if the [`DecapsulationKey`] was initialized using `from_seed` or `generate`. - /// - `None` if the [`DecapsulationKey`] was initialized from the expanded form. - #[inline] - pub fn to_seed(&self) -> Option { - self.d.map(|d| d.concat(self.z)) - } - - /// Get the [`EncapsulationKey`] which corresponds to this [`DecapsulationKey`]. - pub fn encapsulation_key(&self) -> &EncapsulationKey

{ - &self.ek - } - - #[inline] - pub(crate) fn try_generate_from_rng(rng: &mut R) -> Result::Error> - where - R: TryCryptoRng + ?Sized, - { - let d = B32::try_generate_from_rng(rng)?; - let z = B32::try_generate_from_rng(rng)?; - Ok(Self::generate_deterministic(d, z)) - } - - #[inline] - #[must_use] - #[allow(clippy::similar_names)] // allow dk_pke, ek_pke, following the spec - pub(crate) fn generate_deterministic(d: B32, z: B32) -> Self { - let (dk_pke, ek_pke) = DecryptionKey::generate(&d); - let ek = EncapsulationKey::from_encryption_key(ek_pke); - let d = Some(d); - Self { dk_pke, ek, d, z } - } -} - -/// An `EncapsulationKey` provides the ability to encapsulate a shared key so that it can only be -/// decapsulated by the holder of the corresponding decapsulation key. -#[derive(Clone, Debug)] -pub struct EncapsulationKey

-where - P: KemParams, -{ - ek_pke: EncryptionKey

, - h: B32, -} - -impl

EncapsulationKey

-where - P: Kem + KemParams, -{ - pub(crate) fn from_encryption_key(ek_pke: EncryptionKey

) -> Self { - let h = H(ek_pke.to_bytes()); - Self { ek_pke, h } - } - - /// Encapsulates with the given randomness. This is useful for testing against known vectors. - /// - /// # Warning - /// Do NOT use this function unless you know what you're doing. If you fail to use all uniform - /// random bytes even once, you can have catastrophic security failure. - #[cfg_attr(not(feature = "hazmat"), doc(hidden))] - pub fn encapsulate_deterministic(&self, m: &B32) -> (Ciphertext

, SharedKey) { - let (K, r) = G(&[m, &self.h]); - let c = self.ek_pke.encrypt(m, &r); - (c, K) - } -} - -impl

Encapsulate

for EncapsulationKey

-where - P: Kem + KemParams, -{ - fn encapsulate_with_rng(&self, rng: &mut R) -> (Ciphertext

, SharedKey) - where - R: CryptoRng + ?Sized, - { - let m = B32::generate_from_rng(rng); - self.encapsulate_deterministic(&m) - } -} - -impl

EncodedSizeUser for EncapsulationKey

-where - P: KemParams, -{ - type EncodedSize = EncapsulationKeySize

; - - fn from_encoded_bytes(enc: &Encoded) -> Result { - Ok(Self::from_encryption_key(EncryptionKey::from_bytes(enc)?)) - } - - fn to_encoded_bytes(&self) -> Encoded { - self.ek_pke.to_bytes() - } -} - -impl

KeyExport for EncapsulationKey

-where - P: KemParams, -{ - fn to_bytes(&self) -> Key { - self.ek_pke.to_bytes() - } -} - -impl

KeySizeUser for EncapsulationKey

-where - P: KemParams, -{ - type KeySize = EncapsulationKeySize

; -} - -impl

TryKeyInit for EncapsulationKey

-where - P: KemParams, -{ - fn new(encapsulation_key: &Key) -> Result { - EncryptionKey::from_bytes(encapsulation_key) - .map(Self::from_encryption_key) - .map_err(|_| InvalidKey) - } -} - -impl

Eq for EncapsulationKey

where P: KemParams {} -impl

PartialEq for EncapsulationKey

-where - P: KemParams, -{ - fn eq(&self, other: &Self) -> bool { - // Handwritten to avoid derive putting `Eq` bounds on `KemParams` - self.ek_pke == other.ek_pke && self.h == other.h - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::{MlKem512, MlKem768, MlKem1024}; - use ::kem::{Encapsulate, Generate, TryDecapsulate}; - use array::typenum::Unsigned; - use getrandom::SysRng; - use rand_core::UnwrapErr; - - fn round_trip_test

() - where - P: Kem, - { - let mut rng = UnwrapErr(SysRng); - - let dk = P::DecapsulationKey::generate_from_rng(&mut rng); - let ek = dk.as_ref().clone(); - - let (ct, k_send) = ek.encapsulate_with_rng(&mut rng); - let k_recv = dk.try_decapsulate(&ct).unwrap(); - assert_eq!(k_send, k_recv); - } - - #[test] - fn round_trip() { - round_trip_test::(); - round_trip_test::(); - round_trip_test::(); - } - - fn expanded_key_test

() - where - P: KemParams, - { - let mut rng = UnwrapErr(SysRng); - let dk_original = DecapsulationKey::

::generate_from_rng(&mut rng); - let ek_original = dk_original.encapsulation_key().clone(); - - let dk_encoded = dk_original.to_encoded_bytes(); - let dk_decoded = DecapsulationKey::from_encoded_bytes(&dk_encoded).unwrap(); - assert_eq!(dk_original, dk_decoded); - - let ek_encoded = ek_original.to_encoded_bytes(); - let ek_decoded = EncapsulationKey::from_encoded_bytes(&ek_encoded).unwrap(); - assert_eq!(ek_original, ek_decoded); - } - - #[test] - fn expanded_key() { - expanded_key_test::(); - expanded_key_test::(); - expanded_key_test::(); - } - - fn invalid_hash_expanded_key_test

() - where - P: KemParams, - { - let mut rng = UnwrapErr(SysRng); - let dk_original = DecapsulationKey::

::generate_from_rng(&mut rng); - - let mut dk_encoded = dk_original.to_encoded_bytes(); - // Corrupt the hash value - let hash_offset = P::NttVectorSize::USIZE + P::EncryptionKeySize::USIZE; - dk_encoded[hash_offset] ^= 0xFF; - - let dk_decoded: Result, InvalidKey> = - DecapsulationKey::from_encoded_bytes(&dk_encoded); - assert!(dk_decoded.is_err()); - } - - #[test] - fn invalid_hash_expanded_key() { - invalid_hash_expanded_key_test::(); - invalid_hash_expanded_key_test::(); - invalid_hash_expanded_key_test::(); - } - - fn seed_test

() - where - P: KemParams, - { - let mut rng = UnwrapErr(SysRng); - let mut seed = Seed::default(); - rng.try_fill_bytes(&mut seed).unwrap(); - - let dk = DecapsulationKey::

::from_seed(seed.clone()); - let seed_encoded = dk.to_seed().unwrap(); - assert_eq!(seed, seed_encoded); - } - - #[test] - fn seed() { - seed_test::(); - seed_test::(); - seed_test::(); - } - - fn key_inequality_test

() - where - P: KemParams, - { - let mut rng = UnwrapErr(SysRng); - - // Generate two different keys - let dk1 = DecapsulationKey::

::generate_from_rng(&mut rng); - let dk2 = DecapsulationKey::

::generate_from_rng(&mut rng); - - let ek1 = dk1.encapsulation_key(); - let ek2 = dk2.encapsulation_key(); - - // Verify inequality (catches PartialEq mutation that returns true unconditionally) - assert_ne!(dk1, dk2); - assert_ne!(ek1, ek2); - } - - #[test] - fn key_inequality() { - key_inequality_test::(); - key_inequality_test::(); - key_inequality_test::(); - } -} diff --git a/ml-kem/src/lib.rs b/ml-kem/src/lib.rs index a91ebe3..a08bea1 100644 --- a/ml-kem/src/lib.rs +++ b/ml-kem/src/lib.rs @@ -56,12 +56,15 @@ mod crypto; /// Section 4.2.1. Conversion and Compression Algorithms, Compression and decompression mod compress; +/// Section 6. The ML-KEM Key-Encapsulation Mechanism (Decapsulation) +mod decapsulation_key; + +/// Section 6. The ML-KEM Key-Encapsulation Mechanism (Encapsulation) +mod encapsulation_key; + /// Section 5. The K-PKE Component Scheme mod pke; -/// Section 6. The ML-KEM Key-Encapsulation Mechanism -pub mod kem; - /// Section 7. Parameter Sets mod param; @@ -70,8 +73,13 @@ pub mod pkcs8; /// Trait definitions mod traits; -pub use ::kem::{Ciphertext, Kem}; pub use array; +pub use decapsulation_key::DecapsulationKey; +pub use encapsulation_key::EncapsulationKey; +pub use kem::{ + self, Ciphertext, Decapsulate, Encapsulate, Generate, InvalidKey, Kem, Key, KeyExport, KeyInit, + KeySizeUser, TryKeyInit, +}; pub use ml_kem_512::MlKem512; pub use ml_kem_768::MlKem768; pub use ml_kem_1024::MlKem1024; @@ -95,8 +103,11 @@ pub type Seed = Array; /// ML-KEM-512 is the parameter set for security category 1, corresponding to key search on a block /// cipher with a 128-bit key. pub mod ml_kem_512 { - use super::{Debug, ParameterSet, U2, U3, U4, U10, kem}; - use crate::param::{self, EncodedUSize, EncodedVSize}; + use crate::{ + Debug, ParameterSet, U2, U3, U4, U10, + kem::Kem, + param::{self, EncodedUSize, EncodedVSize}, + }; use array::{sizes::U32, typenum::Sum}; /// `MlKem512` is the parameter set for security category 1, corresponding to key search on a @@ -112,7 +123,7 @@ pub mod ml_kem_512 { type Dv = U4; } - impl kem::Kem for MlKem512 { + impl Kem for MlKem512 { type DecapsulationKey = DecapsulationKey; type EncapsulationKey = EncapsulationKey; type CiphertextSize = Sum, EncodedVSize>; @@ -121,11 +132,11 @@ pub mod ml_kem_512 { /// An ML-KEM-512 `DecapsulationKey` which provides the ability to generate a new key pair, and /// decapsulate an encapsulated shared key. - pub type DecapsulationKey = kem::DecapsulationKey; + pub type DecapsulationKey = crate::DecapsulationKey; /// An ML-KEM-512 `EncapsulationKey` provides the ability to encapsulate a shared key so that it /// can only be decapsulated by the holder of the corresponding decapsulation key. - pub type EncapsulationKey = kem::EncapsulationKey; + pub type EncapsulationKey = crate::EncapsulationKey; /// Encoded ML-KEM-512 ciphertexts. pub type Ciphertext = kem::Ciphertext; @@ -139,8 +150,11 @@ pub mod ml_kem_512 { /// ML-KEM-768 is the parameter set for security category 3, corresponding to key search on a block /// cipher with a 192-bit key. pub mod ml_kem_768 { - use super::{Debug, ParameterSet, U2, U3, U4, U10, kem}; - use crate::param::{self, EncodedUSize, EncodedVSize}; + use crate::{ + Debug, ParameterSet, U2, U3, U4, U10, + kem::Kem, + param::{self, EncodedUSize, EncodedVSize}, + }; use array::sizes::U32; use array::typenum::Sum; @@ -157,7 +171,7 @@ pub mod ml_kem_768 { type Dv = U4; } - impl kem::Kem for MlKem768 { + impl Kem for MlKem768 { type DecapsulationKey = DecapsulationKey; type EncapsulationKey = EncapsulationKey; type CiphertextSize = Sum, EncodedVSize>; @@ -166,11 +180,11 @@ pub mod ml_kem_768 { /// An ML-KEM-768 `DecapsulationKey` which provides the ability to generate a new key pair, and /// decapsulate an encapsulated shared key. - pub type DecapsulationKey = kem::DecapsulationKey; + pub type DecapsulationKey = crate::DecapsulationKey; /// An ML-KEM-768 `EncapsulationKey` provides the ability to encapsulate a shared key so that it /// can only be decapsulated by the holder of the corresponding decapsulation key. - pub type EncapsulationKey = kem::EncapsulationKey; + pub type EncapsulationKey = crate::EncapsulationKey; /// Encoded ML-KEM-512 ciphertexts. pub type Ciphertext = kem::Ciphertext; @@ -184,8 +198,11 @@ pub mod ml_kem_768 { /// ML-KEM-1024 is the parameter set for security category 5, corresponding to key search on a block /// cipher with a 256-bit key. pub mod ml_kem_1024 { - use super::{Debug, ParameterSet, U2, U4, U5, U11, kem, param}; - use crate::param::{EncodedUSize, EncodedVSize}; + use crate::{ + Debug, ParameterSet, U2, U4, U5, U11, + kem::Kem, + param::{self, EncodedUSize, EncodedVSize}, + }; use array::{sizes::U32, typenum::Sum}; /// `MlKem1024` is the parameter set for security category 5, corresponding to key search on a @@ -201,7 +218,7 @@ pub mod ml_kem_1024 { type Dv = U5; } - impl kem::Kem for MlKem1024 { + impl Kem for MlKem1024 { type DecapsulationKey = DecapsulationKey; type EncapsulationKey = EncapsulationKey; type CiphertextSize = Sum, EncodedVSize>; @@ -210,11 +227,11 @@ pub mod ml_kem_1024 { /// An ML-KEM-1024 `DecapsulationKey` which provides the ability to generate a new key pair, and /// decapsulate an encapsulated shared key. - pub type DecapsulationKey = kem::DecapsulationKey; + pub type DecapsulationKey = crate::DecapsulationKey; /// An ML-KEM-1024 `EncapsulationKey` provides the ability to encapsulate a shared key so that /// it can only be decapsulated by the holder of the corresponding decapsulation key. - pub type EncapsulationKey = kem::EncapsulationKey; + pub type EncapsulationKey = crate::EncapsulationKey; /// Encoded ML-KEM-512 ciphertexts. pub type Ciphertext = kem::Ciphertext; @@ -256,7 +273,11 @@ pub type SharedKey = Array; #[cfg(feature = "getrandom")] mod test { use super::*; - use ::kem::{Encapsulate, Generate, TryDecapsulate}; + use crate::{MlKem512, MlKem768, MlKem1024, param::KemParams}; + use ::kem::{Encapsulate, Generate, InvalidKey, TryDecapsulate}; + use array::typenum::Unsigned; + use getrandom::SysRng; + use rand_core::{TryRng, UnwrapErr}; fn round_trip_test() where @@ -275,4 +296,97 @@ mod test { round_trip_test::(); round_trip_test::(); } + + fn expanded_key_test

() + where + P: KemParams, + { + let mut rng = UnwrapErr(SysRng); + let dk_original = DecapsulationKey::

::generate_from_rng(&mut rng); + let ek_original = dk_original.encapsulation_key().clone(); + + let dk_encoded = dk_original.to_encoded_bytes(); + let dk_decoded = DecapsulationKey::from_encoded_bytes(&dk_encoded).unwrap(); + assert_eq!(dk_original, dk_decoded); + + let ek_encoded = ek_original.to_encoded_bytes(); + let ek_decoded = EncapsulationKey::from_encoded_bytes(&ek_encoded).unwrap(); + assert_eq!(ek_original, ek_decoded); + } + + #[test] + fn expanded_key() { + expanded_key_test::(); + expanded_key_test::(); + expanded_key_test::(); + } + + fn invalid_hash_expanded_key_test

() + where + P: KemParams, + { + let mut rng = UnwrapErr(SysRng); + let dk_original = DecapsulationKey::

::generate_from_rng(&mut rng); + + let mut dk_encoded = dk_original.to_encoded_bytes(); + // Corrupt the hash value + let hash_offset = P::NttVectorSize::USIZE + P::EncryptionKeySize::USIZE; + dk_encoded[hash_offset] ^= 0xFF; + + let dk_decoded: Result, InvalidKey> = + DecapsulationKey::from_encoded_bytes(&dk_encoded); + assert!(dk_decoded.is_err()); + } + + #[test] + fn invalid_hash_expanded_key() { + invalid_hash_expanded_key_test::(); + invalid_hash_expanded_key_test::(); + invalid_hash_expanded_key_test::(); + } + + fn seed_test

() + where + P: KemParams, + { + let mut rng = UnwrapErr(SysRng); + let mut seed = Seed::default(); + rng.try_fill_bytes(&mut seed).unwrap(); + + let dk = DecapsulationKey::

::from_seed(seed.clone()); + let seed_encoded = dk.to_seed().unwrap(); + assert_eq!(seed, seed_encoded); + } + + #[test] + fn seed() { + seed_test::(); + seed_test::(); + seed_test::(); + } + + fn key_inequality_test

() + where + P: KemParams, + { + let mut rng = UnwrapErr(SysRng); + + // Generate two different keys + let dk1 = DecapsulationKey::

::generate_from_rng(&mut rng); + let dk2 = DecapsulationKey::

::generate_from_rng(&mut rng); + + let ek1 = dk1.encapsulation_key(); + let ek2 = dk2.encapsulation_key(); + + // Verify inequality (catches PartialEq mutation that returns true unconditionally) + assert_ne!(dk1, dk2); + assert_ne!(ek1, ek2); + } + + #[test] + fn key_inequality() { + key_inequality_test::(); + key_inequality_test::(); + key_inequality_test::(); + } } diff --git a/ml-kem/src/pkcs8.rs b/ml-kem/src/pkcs8.rs index 2b4a787..1f3c992 100644 --- a/ml-kem/src/pkcs8.rs +++ b/ml-kem/src/pkcs8.rs @@ -16,8 +16,7 @@ pub use const_oid::AssociatedOid; pub use ::pkcs8::{EncodePrivateKey, EncodePublicKey}; use crate::{ - MlKem512, MlKem768, MlKem1024, - kem::{DecapsulationKey, EncapsulationKey}, + DecapsulationKey, EncapsulationKey, MlKem512, MlKem768, MlKem1024, param::{EncapsulationKeySize, KemParams}, pke::EncryptionKey, };