Skip to content

Commit 3fe8d03

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 3fe8d03

File tree

9 files changed

+154
-146
lines changed

9 files changed

+154
-146
lines changed

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: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ pub use ::pkcs8::{DecodePrivateKey, DecodePublicKey, spki::AssociatedAlgorithmId
1313
pub use const_oid::AssociatedOid;
1414

1515
#[cfg(feature = "alloc")]
16-
pub use ::pkcs8::{EncodePrivateKey, EncodePublicKey};
16+
pub use {
17+
::kem::KeyExport,
18+
::pkcs8::{EncodePrivateKey, EncodePublicKey},
19+
};
1720

1821
use crate::{
1922
DecapsulationKey, EncapsulationKey, MlKem512, MlKem768, MlKem1024,
@@ -30,10 +33,7 @@ use ::pkcs8::{
3033
use array::Array;
3134

3235
#[cfg(feature = "alloc")]
33-
use {
34-
crate::EncodedSizeUser,
35-
::pkcs8::der::{Encode, TagMode, asn1::BitStringRef},
36-
};
36+
use ::pkcs8::der::{Encode, TagMode, asn1::BitStringRef};
3737

3838
/// Tag number for the seed value.
3939
const SEED_TAG_NUMBER: TagNumber = TagNumber(0);
@@ -100,7 +100,7 @@ where
100100
/// Serialize the given `EncapsulationKey` into DER format.
101101
/// Returns a `Document` which wraps the DER document in case of success.
102102
fn to_public_key_der(&self) -> spki::Result<pkcs8::Document> {
103-
let public_key = self.to_encoded_bytes();
103+
let public_key = self.to_bytes();
104104
let subject_public_key = BitStringRef::new(0, &public_key)?;
105105

106106
::pkcs8::SubjectPublicKeyInfo {

ml-kem/src/traits.rs

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

0 commit comments

Comments
 (0)