Skip to content

Commit f2eeec2

Browse files
committed
ml-kem: replace EncodedSizeUser with ExpandedKeyEncoding
First, this commit completely migrates `EncapsulationKey` to using only the `kem`/`crypto-common` traits: `KeySizeUser`, `TryKeyInit`, `KeyExport`, and changes any remaining uses to use only the new traits. Since `DecapsulationKey` uses those same traits for handling `Seed`s, the only remaining use of the old `EncodedSizeUser` trait is handling the expanded form of `DecapsulationKey`. So this commit repurposes it into an `ExpandedKeyEncoding` trait. Like `DecapsulationKey::from_expanded`, the trait has been marked deprecated with a rationale given in the documentation for `ExpandedKeyEncoding`, namely that the expanded form has only disadvantages when compared to seeds which are significantly smaller, uniformly sized, and avoid the need to do expanded key validation. It also notes several ML-KEM libraries have dropped support entirely. In the `ml-kem` crate, for now, we still need this functionality if only for tests which have been written generically, including but not limited to the ones that run the NIST ACVP vectors.
1 parent 96aeea7 commit f2eeec2

File tree

10 files changed

+152
-144
lines changed

10 files changed

+152
-144
lines changed

ml-kem/benches/mlkem.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use ::kem::{Decapsulate, Encapsulate, Kem, KeyExport, KeyInit, TryKeyInit};
1+
use ::kem::{Decapsulate, Encapsulate, Kem, KeyExport, KeyInit};
22
use core::hint::black_box;
33
use criterion::{Criterion, criterion_group, criterion_main};
44
use getrandom::SysRng;

ml-kem/src/decapsulation_key.rs

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
use crate::{
2-
B32, EncapsulationKey, Encoded, EncodedSizeUser, ExpandedDecapsulationKey, Seed, SharedKey,
2+
B32, EncapsulationKey, Seed, SharedKey,
33
crypto::{G, J},
4-
kem::{Generate, InvalidKey, Kem, KeyInit, KeySizeUser},
5-
param::{DecapsulationKeySize, KemParams},
4+
kem::{Generate, InvalidKey, Kem, KeyExport, KeyInit, KeySizeUser},
5+
param::{DecapsulationKeySize, ExpandedDecapsulationKey, KemParams},
66
pke::{DecryptionKey, EncryptionKey},
77
};
8-
use array::sizes::{U32, U64};
8+
use array::{
9+
Array, ArraySize,
10+
sizes::{U32, U64},
11+
};
912
use kem::{Ciphertext, Decapsulate};
1013
use rand_core::{TryCryptoRng, TryRng};
1114
use subtle::{ConditionallySelectable, ConstantTimeEq};
@@ -41,7 +44,7 @@ where
4144
/// Initialize a [`DecapsulationKey`] from the serialized expanded key form.
4245
///
4346
/// Note that this form is deprecated in practice; prefer to use
44-
/// [`DecapsulationKey::from_seed`].
47+
/// [`DecapsulationKey::from_seed`]. See [`ExpandedKeyEncoding`] for more information.
4548
///
4649
/// # Errors
4750
/// - Returns [`InvalidKey`] in the event the expanded key failed validation
@@ -164,24 +167,6 @@ where
164167
}
165168
}
166169

167-
impl<P> EncodedSizeUser for DecapsulationKey<P>
168-
where
169-
P: KemParams,
170-
{
171-
type EncodedSize = DecapsulationKeySize<P>;
172-
173-
fn from_encoded_bytes(expanded: &Encoded<Self>) -> Result<Self, InvalidKey> {
174-
#[allow(deprecated)]
175-
Self::from_expanded(expanded)
176-
}
177-
178-
fn to_encoded_bytes(&self) -> Encoded<Self> {
179-
let dk_pke = self.dk_pke.to_bytes();
180-
let ek = self.ek.to_encoded_bytes();
181-
P::concat_dk(dk_pke, ek, self.ek.h(), self.z.clone())
182-
}
183-
}
184-
185170
impl<P> Generate for DecapsulationKey<P>
186171
where
187172
P: KemParams,
@@ -210,3 +195,68 @@ where
210195
Self::from_seed(*seed)
211196
}
212197
}
198+
199+
/// DEPRECATED: support for encoding and decoding [`DecapsulationKey`]s in the legacy expanded form,
200+
/// as opposed to the more widely adopted [`Seed`] form.
201+
///
202+
/// The expanded encoding format is problematic for several reasons, notably they need to validated
203+
/// whereas generation from seeds is always correct, meaning there is no performance advantage to
204+
/// using them, only additional complexity.
205+
///
206+
/// They are significantly larger than seeds (which are 64-bytes) and their sizes vary depending on
207+
/// security level whereas the size of a seed is constant:
208+
/// - ML-KEM-512: 1632 bytes
209+
/// - ML-KEM-768: 2400 bytes
210+
/// - ML-KEM-1024: 3168 bytes
211+
///
212+
/// Many ML-KEM libraries have dropped support for this format entirely.
213+
#[deprecated(since = "0.3.0", note = "use `DecapsulationKey::from_seed` instead")]
214+
pub trait ExpandedKeyEncoding: Sized {
215+
/// The size of an expanded decapsulation key.
216+
type EncodedSize: ArraySize;
217+
218+
/// Parse a [`DecapsulationKey`] from its legacy expanded form.
219+
///
220+
/// # Errors
221+
/// - If the key fails to validate successfully.
222+
fn from_expanded_bytes(enc: &Array<u8, Self::EncodedSize>) -> Result<Self, InvalidKey>;
223+
224+
/// Serialize a [`DecapsulationKey`] to its legacy expanded form.
225+
fn to_expanded_bytes(&self) -> Array<u8, Self::EncodedSize>;
226+
}
227+
228+
#[allow(deprecated)]
229+
impl<P> ExpandedKeyEncoding for DecapsulationKey<P>
230+
where
231+
P: KemParams,
232+
{
233+
type EncodedSize = DecapsulationKeySize<P>;
234+
235+
fn from_expanded_bytes(expanded: &ExpandedDecapsulationKey<P>) -> Result<Self, InvalidKey> {
236+
Self::from_expanded(expanded)
237+
}
238+
239+
fn to_expanded_bytes(&self) -> ExpandedDecapsulationKey<P> {
240+
let dk_pke = self.dk_pke.to_bytes();
241+
let ek = self.ek.to_bytes();
242+
P::concat_dk(dk_pke, ek, self.ek.h(), self.z.clone())
243+
}
244+
}
245+
246+
/// Initialize a KEM from a seed.
247+
pub trait FromSeed: Kem {
248+
/// Using the provided [`Seed`] value, create a KEM keypair.
249+
fn from_seed(seed: &Seed) -> (Self::DecapsulationKey, Self::EncapsulationKey);
250+
}
251+
252+
impl<K> FromSeed for K
253+
where
254+
K: Kem,
255+
K::DecapsulationKey: KeyInit + KeySizeUser<KeySize = U64>,
256+
{
257+
fn from_seed(seed: &Seed) -> (K::DecapsulationKey, K::EncapsulationKey) {
258+
let dk = K::DecapsulationKey::new(seed);
259+
let ek = dk.as_ref().clone();
260+
(dk, ek)
261+
}
262+
}

ml-kem/src/encapsulation_key.rs

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
B32, Encoded, EncodedSizeUser, SharedKey,
2+
B32, SharedKey,
33
crypto::{G, H},
44
kem::{InvalidKey, Kem, Key, KeyExport, KeySizeUser, TryKeyInit},
55
param::{EncapsulationKeySize, KemParams},
@@ -24,6 +24,16 @@ impl<P> EncapsulationKey<P>
2424
where
2525
P: Kem<SharedKeySize = U32> + KemParams,
2626
{
27+
/// Create a new [`EncapsulationKey`] from its serialized form.
28+
///
29+
/// # Errors
30+
/// If the key failed validation during decoding.
31+
pub fn new(encapsulation_key: &Key<Self>) -> Result<Self, InvalidKey> {
32+
EncryptionKey::from_bytes(encapsulation_key)
33+
.map(Self::from_encryption_key)
34+
.map_err(|_| InvalidKey)
35+
}
36+
2737
/// Encapsulates with the given randomness. This is useful for testing against known vectors.
2838
///
2939
/// # Warning
@@ -66,21 +76,6 @@ where
6676
}
6777
}
6878

69-
impl<P> EncodedSizeUser for EncapsulationKey<P>
70-
where
71-
P: KemParams,
72-
{
73-
type EncodedSize = EncapsulationKeySize<P>;
74-
75-
fn from_encoded_bytes(enc: &Encoded<Self>) -> Result<Self, InvalidKey> {
76-
Ok(Self::from_encryption_key(EncryptionKey::from_bytes(enc)?))
77-
}
78-
79-
fn to_encoded_bytes(&self) -> Encoded<Self> {
80-
self.ek_pke.to_bytes()
81-
}
82-
}
83-
8479
impl<P> KeyExport for EncapsulationKey<P>
8580
where
8681
P: KemParams,
@@ -102,9 +97,7 @@ where
10297
P: KemParams,
10398
{
10499
fn new(encapsulation_key: &Key<Self>) -> Result<Self, InvalidKey> {
105-
EncryptionKey::from_bytes(encapsulation_key)
106-
.map(Self::from_encryption_key)
107-
.map_err(|_| InvalidKey)
100+
Self::new(encapsulation_key)
108101
}
109102
}
110103

ml-kem/src/lib.rs

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ mod pke;
6868
/// Section 7. Parameter Sets
6969
mod param;
7070

71+
/// PKCS#8 key encoding support
7172
pub mod pkcs8;
7273

73-
/// Trait definitions
74-
mod traits;
75-
7674
pub use array;
77-
pub use decapsulation_key::DecapsulationKey;
75+
#[allow(deprecated)]
76+
pub use decapsulation_key::ExpandedKeyEncoding;
77+
pub use decapsulation_key::{DecapsulationKey, FromSeed};
7878
pub use encapsulation_key::EncapsulationKey;
7979
pub use kem::{
8080
self, Ciphertext, Decapsulate, Encapsulate, Generate, InvalidKey, Kem, Key, KeyExport, KeyInit,
@@ -85,7 +85,6 @@ pub use ml_kem_768::MlKem768;
8585
pub use ml_kem_1024::MlKem1024;
8686
pub use module_lattice::encoding::ArraySize;
8787
pub use param::{ExpandedDecapsulationKey, ParameterSet};
88-
pub use traits::*;
8988

9089
use array::{
9190
Array,
@@ -297,21 +296,41 @@ mod test {
297296
round_trip_test::<MlKem1024>();
298297
}
299298

299+
fn seed_test<P>()
300+
where
301+
P: KemParams,
302+
{
303+
let mut rng = UnwrapErr(SysRng);
304+
let mut seed = Seed::default();
305+
rng.try_fill_bytes(&mut seed).unwrap();
306+
307+
let dk = DecapsulationKey::<P>::from_seed(seed.clone());
308+
let seed_encoded = dk.to_seed().unwrap();
309+
assert_eq!(seed, seed_encoded);
310+
311+
let ek_original = dk.encapsulation_key();
312+
let ek_encoded = ek_original.to_bytes();
313+
let ek_decoded = EncapsulationKey::new(&ek_encoded).unwrap();
314+
assert_eq!(ek_original, &ek_decoded);
315+
}
316+
317+
#[test]
318+
fn seed() {
319+
seed_test::<MlKem512>();
320+
seed_test::<MlKem768>();
321+
seed_test::<MlKem1024>();
322+
}
323+
324+
#[allow(deprecated)]
300325
fn expanded_key_test<P>()
301326
where
302327
P: KemParams,
303328
{
304329
let mut rng = UnwrapErr(SysRng);
305330
let dk_original = DecapsulationKey::<P>::generate_from_rng(&mut rng);
306-
let ek_original = dk_original.encapsulation_key().clone();
307-
308-
let dk_encoded = dk_original.to_encoded_bytes();
309-
let dk_decoded = DecapsulationKey::from_encoded_bytes(&dk_encoded).unwrap();
331+
let dk_encoded = dk_original.to_expanded_bytes();
332+
let dk_decoded = DecapsulationKey::from_expanded_bytes(&dk_encoded).unwrap();
310333
assert_eq!(dk_original, dk_decoded);
311-
312-
let ek_encoded = ek_original.to_encoded_bytes();
313-
let ek_decoded = EncapsulationKey::from_encoded_bytes(&ek_encoded).unwrap();
314-
assert_eq!(ek_original, ek_decoded);
315334
}
316335

317336
#[test]
@@ -328,13 +347,17 @@ mod test {
328347
let mut rng = UnwrapErr(SysRng);
329348
let dk_original = DecapsulationKey::<P>::generate_from_rng(&mut rng);
330349

331-
let mut dk_encoded = dk_original.to_encoded_bytes();
350+
#[allow(deprecated)]
351+
let mut dk_encoded = dk_original.to_expanded_bytes();
352+
332353
// Corrupt the hash value
333354
let hash_offset = P::NttVectorSize::USIZE + P::EncryptionKeySize::USIZE;
334355
dk_encoded[hash_offset] ^= 0xFF;
335356

357+
#[allow(deprecated)]
336358
let dk_decoded: Result<DecapsulationKey<P>, InvalidKey> =
337-
DecapsulationKey::from_encoded_bytes(&dk_encoded);
359+
DecapsulationKey::from_expanded_bytes(&dk_encoded);
360+
338361
assert!(dk_decoded.is_err());
339362
}
340363

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

348-
fn seed_test<P>()
349-
where
350-
P: KemParams,
351-
{
352-
let mut rng = UnwrapErr(SysRng);
353-
let mut seed = Seed::default();
354-
rng.try_fill_bytes(&mut seed).unwrap();
355-
356-
let dk = DecapsulationKey::<P>::from_seed(seed.clone());
357-
let seed_encoded = dk.to_seed().unwrap();
358-
assert_eq!(seed, seed_encoded);
359-
}
360-
361-
#[test]
362-
fn seed() {
363-
seed_test::<MlKem512>();
364-
seed_test::<MlKem768>();
365-
seed_test::<MlKem1024>();
366-
}
367-
368371
fn key_inequality_test<P>()
369372
where
370373
P: KemParams,

ml-kem/src/pkcs8.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
//! [`EncapsulationKey`].
99
1010
#![cfg(feature = "pkcs8")]
11+
// TODO: debug why rustdoc can't find the `pkcs8::{Decode*, Encode*}` traits. They're right there!
12+
#![allow(rustdoc::broken_intra_doc_links)]
1113

1214
pub use ::pkcs8::{DecodePrivateKey, DecodePublicKey, spki::AssociatedAlgorithmIdentifier};
1315
pub use const_oid::AssociatedOid;
@@ -31,7 +33,7 @@ use array::Array;
3133

3234
#[cfg(feature = "alloc")]
3335
use {
34-
crate::EncodedSizeUser,
36+
::kem::KeyExport,
3537
::pkcs8::der::{Encode, TagMode, asn1::BitStringRef},
3638
};
3739

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

106108
::pkcs8::SubjectPublicKeyInfo {

ml-kem/src/traits.rs

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)