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 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 {
+ &self.ek
+ }
+
+ #[inline]
+ pub(crate) fn try_generate_from_rng 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
+where
+ P: KemParams,
+{
+ fn from(seed: Seed) -> Self {
+ Self::from_seed(seed)
+ }
+}
+
+impl Decapsulate for DecapsulationKey
+where
+ P: Kem ) -> 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
+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 Generate for DecapsulationKey
+where
+ P: KemParams,
+{
+ fn try_generate_from_rng 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 , 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 , 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 KeyExport for EncapsulationKey
+where
+ P: KemParams,
+{
+ fn to_bytes(&self) -> Key KeySizeUser for EncapsulationKey
+where
+ P: KemParams,
+{
+ type KeySize = EncapsulationKeySize ;
+}
+
+impl TryKeyInit for EncapsulationKey
+where
+ P: KemParams,
+{
+ fn new(encapsulation_key: &Key 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 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
-where
- P: KemParams,
-{
- fn from(seed: Seed) -> Self {
- Self::from_seed(seed)
- }
-}
-
-impl Decapsulate for DecapsulationKey
-where
- P: Kem ) -> 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
-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 Generate for DecapsulationKey
-where
- P: KemParams,
-{
- fn try_generate_from_rng 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 {
- &self.ek
- }
-
- #[inline]
- pub(crate) fn try_generate_from_rng
-where
- P: KemParams,
-{
- ek_pke: EncryptionKey ,
- h: B32,
-}
-
-impl EncapsulationKey
-where
- P: Kem ) -> 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 , 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 KeyExport for EncapsulationKey
-where
- P: KemParams,
-{
- fn to_bytes(&self) -> Key KeySizeUser for EncapsulationKey
-where
- P: KemParams,
-{
- type KeySize = EncapsulationKeySize ;
-}
-
-impl TryKeyInit for EncapsulationKey
-where
- P: KemParams,
-{
- fn new(encapsulation_key: &Key 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:: ()
- 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:: ()
- 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 ()
- 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:: ()
- 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:: ()
+ 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:: ()
+ 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 ()
+ 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:: ()
+ 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::