Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ml-kem/benches/mlkem.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use ::kem::{Decapsulate, Encapsulate, Kem, KeyExport, KeyInit, TryKeyInit};
use ::kem::{Decapsulate, Encapsulate, Kem, KeyExport, KeyInit};
use core::hint::black_box;
use criterion::{Criterion, criterion_group, criterion_main};
use getrandom::SysRng;
Expand Down
96 changes: 73 additions & 23 deletions ml-kem/src/decapsulation_key.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use crate::{
B32, EncapsulationKey, Encoded, EncodedSizeUser, ExpandedDecapsulationKey, Seed, SharedKey,
B32, EncapsulationKey, Seed, SharedKey,
crypto::{G, J},
kem::{Generate, InvalidKey, Kem, KeyInit, KeySizeUser},
param::{DecapsulationKeySize, KemParams},
kem::{Generate, InvalidKey, Kem, KeyExport, KeyInit, KeySizeUser},
param::{DecapsulationKeySize, ExpandedDecapsulationKey, KemParams},
pke::{DecryptionKey, EncryptionKey},
};
use array::sizes::{U32, U64};
use array::{
Array, ArraySize,
sizes::{U32, U64},
};
use kem::{Ciphertext, Decapsulate};
use rand_core::{TryCryptoRng, TryRng};
use subtle::{ConditionallySelectable, ConstantTimeEq};
Expand Down Expand Up @@ -41,7 +44,7 @@ where
/// Initialize a [`DecapsulationKey`] from the serialized expanded key form.
///
/// Note that this form is deprecated in practice; prefer to use
/// [`DecapsulationKey::from_seed`].
/// [`DecapsulationKey::from_seed`]. See [`ExpandedKeyEncoding`] for more information.
///
/// # Errors
/// - Returns [`InvalidKey`] in the event the expanded key failed validation
Expand Down Expand Up @@ -164,24 +167,6 @@ where
}
}

impl<P> EncodedSizeUser for DecapsulationKey<P>
where
P: KemParams,
{
type EncodedSize = DecapsulationKeySize<P>;

fn from_encoded_bytes(expanded: &Encoded<Self>) -> Result<Self, InvalidKey> {
#[allow(deprecated)]
Self::from_expanded(expanded)
}

fn to_encoded_bytes(&self) -> Encoded<Self> {
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<P> Generate for DecapsulationKey<P>
where
P: KemParams,
Expand Down Expand Up @@ -210,3 +195,68 @@ where
Self::from_seed(*seed)
}
}

/// DEPRECATED: support for encoding and decoding [`DecapsulationKey`]s in the legacy expanded form,
/// as opposed to the more widely adopted [`Seed`] form.
///
/// The expanded encoding format is problematic for several reasons, notably they need to validated
/// whereas generation from seeds is always correct, meaning there is no performance advantage to
/// using them, only additional complexity.
///
/// They are significantly larger than seeds (which are 64-bytes) and their sizes vary depending on
/// security level whereas the size of a seed is constant:
/// - ML-KEM-512: 1632 bytes
/// - ML-KEM-768: 2400 bytes
/// - ML-KEM-1024: 3168 bytes
///
/// Many ML-KEM libraries have dropped support for this format entirely.
#[deprecated(since = "0.3.0", note = "use `DecapsulationKey::from_seed` instead")]
pub trait ExpandedKeyEncoding: Sized {
/// The size of an expanded decapsulation key.
type EncodedSize: ArraySize;

/// Parse a [`DecapsulationKey`] from its legacy expanded form.
///
/// # Errors
/// - If the key fails to validate successfully.
fn from_expanded_bytes(enc: &Array<u8, Self::EncodedSize>) -> Result<Self, InvalidKey>;

/// Serialize a [`DecapsulationKey`] to its legacy expanded form.
fn to_expanded_bytes(&self) -> Array<u8, Self::EncodedSize>;
}

#[allow(deprecated)]
impl<P> ExpandedKeyEncoding for DecapsulationKey<P>
where
P: KemParams,
{
type EncodedSize = DecapsulationKeySize<P>;

fn from_expanded_bytes(expanded: &ExpandedDecapsulationKey<P>) -> Result<Self, InvalidKey> {
Self::from_expanded(expanded)
}

fn to_expanded_bytes(&self) -> ExpandedDecapsulationKey<P> {
let dk_pke = self.dk_pke.to_bytes();
let ek = self.ek.to_bytes();
P::concat_dk(dk_pke, ek, self.ek.h(), self.z.clone())
}
}

/// Initialize a KEM from a seed.
pub trait FromSeed: Kem {
/// Using the provided [`Seed`] value, create a KEM keypair.
fn from_seed(seed: &Seed) -> (Self::DecapsulationKey, Self::EncapsulationKey);
}

impl<K> FromSeed for K
where
K: Kem,
K::DecapsulationKey: KeyInit + KeySizeUser<KeySize = U64>,
{
fn from_seed(seed: &Seed) -> (K::DecapsulationKey, K::EncapsulationKey) {
let dk = K::DecapsulationKey::new(seed);
let ek = dk.as_ref().clone();
(dk, ek)
}
}
31 changes: 12 additions & 19 deletions ml-kem/src/encapsulation_key.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
B32, Encoded, EncodedSizeUser, SharedKey,
B32, SharedKey,
crypto::{G, H},
kem::{InvalidKey, Kem, Key, KeyExport, KeySizeUser, TryKeyInit},
param::{EncapsulationKeySize, KemParams},
Expand All @@ -24,6 +24,16 @@ impl<P> EncapsulationKey<P>
where
P: Kem<SharedKeySize = U32> + KemParams,
{
/// Create a new [`EncapsulationKey`] from its serialized form.
///
/// # Errors
/// If the key failed validation during decoding.
pub fn new(encapsulation_key: &Key<Self>) -> Result<Self, InvalidKey> {
EncryptionKey::from_bytes(encapsulation_key)
.map(Self::from_encryption_key)
.map_err(|_| InvalidKey)
}

/// Encapsulates with the given randomness. This is useful for testing against known vectors.
///
/// # Warning
Expand Down Expand Up @@ -66,21 +76,6 @@ where
}
}

impl<P> EncodedSizeUser for EncapsulationKey<P>
where
P: KemParams,
{
type EncodedSize = EncapsulationKeySize<P>;

fn from_encoded_bytes(enc: &Encoded<Self>) -> Result<Self, InvalidKey> {
Ok(Self::from_encryption_key(EncryptionKey::from_bytes(enc)?))
}

fn to_encoded_bytes(&self) -> Encoded<Self> {
self.ek_pke.to_bytes()
}
}

impl<P> KeyExport for EncapsulationKey<P>
where
P: KemParams,
Expand All @@ -102,9 +97,7 @@ where
P: KemParams,
{
fn new(encapsulation_key: &Key<Self>) -> Result<Self, InvalidKey> {
EncryptionKey::from_bytes(encapsulation_key)
.map(Self::from_encryption_key)
.map_err(|_| InvalidKey)
Self::new(encapsulation_key)
}
}

Expand Down
73 changes: 38 additions & 35 deletions ml-kem/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,13 @@ mod pke;
/// Section 7. Parameter Sets
mod param;

// PKCS#8 key encoding support (doc comments in module)
pub mod pkcs8;

/// Trait definitions
mod traits;

pub use array;
pub use decapsulation_key::DecapsulationKey;
#[allow(deprecated)]
pub use decapsulation_key::ExpandedKeyEncoding;
pub use decapsulation_key::{DecapsulationKey, FromSeed};
pub use encapsulation_key::EncapsulationKey;
pub use kem::{
self, Ciphertext, Decapsulate, Encapsulate, Generate, InvalidKey, Kem, Key, KeyExport, KeyInit,
Expand All @@ -85,7 +85,6 @@ pub use ml_kem_768::MlKem768;
pub use ml_kem_1024::MlKem1024;
pub use module_lattice::encoding::ArraySize;
pub use param::{ExpandedDecapsulationKey, ParameterSet};
pub use traits::*;

use array::{
Array,
Expand Down Expand Up @@ -297,21 +296,41 @@ mod test {
round_trip_test::<MlKem1024>();
}

fn seed_test<P>()
where
P: KemParams,
{
let mut rng = UnwrapErr(SysRng);
let mut seed = Seed::default();
rng.try_fill_bytes(&mut seed).unwrap();

let dk = DecapsulationKey::<P>::from_seed(seed.clone());
let seed_encoded = dk.to_seed().unwrap();
assert_eq!(seed, seed_encoded);

let ek_original = dk.encapsulation_key();
let ek_encoded = ek_original.to_bytes();
let ek_decoded = EncapsulationKey::new(&ek_encoded).unwrap();
assert_eq!(ek_original, &ek_decoded);
}

#[test]
fn seed() {
seed_test::<MlKem512>();
seed_test::<MlKem768>();
seed_test::<MlKem1024>();
}

#[allow(deprecated)]
fn expanded_key_test<P>()
where
P: KemParams,
{
let mut rng = UnwrapErr(SysRng);
let dk_original = DecapsulationKey::<P>::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();
let dk_encoded = dk_original.to_expanded_bytes();
let dk_decoded = DecapsulationKey::from_expanded_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]
Expand All @@ -328,13 +347,17 @@ mod test {
let mut rng = UnwrapErr(SysRng);
let dk_original = DecapsulationKey::<P>::generate_from_rng(&mut rng);

let mut dk_encoded = dk_original.to_encoded_bytes();
#[allow(deprecated)]
let mut dk_encoded = dk_original.to_expanded_bytes();

// Corrupt the hash value
let hash_offset = P::NttVectorSize::USIZE + P::EncryptionKeySize::USIZE;
dk_encoded[hash_offset] ^= 0xFF;

#[allow(deprecated)]
let dk_decoded: Result<DecapsulationKey<P>, InvalidKey> =
DecapsulationKey::from_encoded_bytes(&dk_encoded);
DecapsulationKey::from_expanded_bytes(&dk_encoded);

assert!(dk_decoded.is_err());
}

Expand All @@ -345,26 +368,6 @@ mod test {
invalid_hash_expanded_key_test::<MlKem1024>();
}

fn seed_test<P>()
where
P: KemParams,
{
let mut rng = UnwrapErr(SysRng);
let mut seed = Seed::default();
rng.try_fill_bytes(&mut seed).unwrap();

let dk = DecapsulationKey::<P>::from_seed(seed.clone());
let seed_encoded = dk.to_seed().unwrap();
assert_eq!(seed, seed_encoded);
}

#[test]
fn seed() {
seed_test::<MlKem512>();
seed_test::<MlKem768>();
seed_test::<MlKem1024>();
}

fn key_inequality_test<P>()
where
P: KemParams,
Expand Down
4 changes: 2 additions & 2 deletions ml-kem/src/pkcs8.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use array::Array;

#[cfg(feature = "alloc")]
use {
crate::EncodedSizeUser,
::kem::KeyExport,
::pkcs8::der::{Encode, TagMode, asn1::BitStringRef},
};

Expand Down Expand Up @@ -100,7 +100,7 @@ where
/// Serialize the given `EncapsulationKey` into DER format.
/// Returns a `Document` which wraps the DER document in case of success.
fn to_public_key_der(&self) -> spki::Result<pkcs8::Document> {
let public_key = self.to_encoded_bytes();
let public_key = self.to_bytes();
let subject_public_key = BitStringRef::new(0, &public_key)?;

::pkcs8::SubjectPublicKeyInfo {
Expand Down
41 changes: 0 additions & 41 deletions ml-kem/src/traits.rs

This file was deleted.

11 changes: 5 additions & 6 deletions ml-kem/tests/encap-decap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,10 @@ fn verify_encap_group(tg: &acvp::EncapTestGroup) {
fn verify_encap<K>(tc: &acvp::EncapTestCase)
where
K: Kem,
K::EncapsulationKey: EncapsulateDeterministic + EncodedSizeUser,
K::EncapsulationKey: EncapsulateDeterministic + TryKeyInit,
{
let m = Array::try_from(tc.m.as_slice()).unwrap();
let ek_bytes = Encoded::<K::EncapsulationKey>::try_from(tc.ek.as_slice()).unwrap();
let ek = K::EncapsulationKey::from_encoded_bytes(&ek_bytes).unwrap();
let ek = K::EncapsulationKey::new_from_slice(&tc.ek).unwrap();

let (c, k) = ek.encapsulate_deterministic(&m);

Expand All @@ -85,13 +84,13 @@ fn verify_decap_group(tg: &acvp::DecapTestGroup) {
}
}

#[allow(deprecated)]
fn verify_decap<K>(tc: &acvp::DecapTestCase, dk_slice: &[u8])
where
K: Kem,
K::DecapsulationKey: Decapsulate<K> + EncodedSizeUser,
K::DecapsulationKey: Decapsulate<K> + ExpandedKeyEncoding,
{
let dk_bytes = Encoded::<K::DecapsulationKey>::try_from(dk_slice).unwrap();
let dk = K::DecapsulationKey::from_encoded_bytes(&dk_bytes).unwrap();
let dk = K::DecapsulationKey::from_expanded_bytes(dk_slice.try_into().unwrap()).unwrap();

let c = ::kem::Ciphertext::<K>::try_from(tc.c.as_slice()).unwrap();
let k = dk.decapsulate(&c);
Expand Down
Loading