diff --git a/ml-kem/benches/mlkem.rs b/ml-kem/benches/mlkem.rs
index e7934f8..fef4e5b 100644
--- a/ml-kem/benches/mlkem.rs
+++ b/ml-kem/benches/mlkem.rs
@@ -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;
diff --git a/ml-kem/src/decapsulation_key.rs b/ml-kem/src/decapsulation_key.rs
index 7c8ce65..aa17487 100644
--- a/ml-kem/src/decapsulation_key.rs
+++ b/ml-kem/src/decapsulation_key.rs
@@ -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};
@@ -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
@@ -164,24 +167,6 @@ where
}
}
-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,
@@ -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) -> Result;
+
+ /// Serialize a [`DecapsulationKey`] to its legacy expanded form.
+ fn to_expanded_bytes(&self) -> Array;
+}
+
+#[allow(deprecated)]
+impl ExpandedKeyEncoding for DecapsulationKey
+where
+ P: KemParams,
+{
+ type EncodedSize = DecapsulationKeySize
;
+
+ fn from_expanded_bytes(expanded: &ExpandedDecapsulationKey
) -> Result {
+ Self::from_expanded(expanded)
+ }
+
+ fn to_expanded_bytes(&self) -> ExpandedDecapsulationKey {
+ 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 FromSeed for K
+where
+ K: Kem,
+ K::DecapsulationKey: KeyInit + KeySizeUser,
+{
+ fn from_seed(seed: &Seed) -> (K::DecapsulationKey, K::EncapsulationKey) {
+ let dk = K::DecapsulationKey::new(seed);
+ let ek = dk.as_ref().clone();
+ (dk, ek)
+ }
+}
diff --git a/ml-kem/src/encapsulation_key.rs b/ml-kem/src/encapsulation_key.rs
index 8d2d2ca..58ee4e9 100644
--- a/ml-kem/src/encapsulation_key.rs
+++ b/ml-kem/src/encapsulation_key.rs
@@ -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},
@@ -24,6 +24,16 @@ impl EncapsulationKey
where
P: Kem + KemParams,
{
+ /// Create a new [`EncapsulationKey`] from its serialized form.
+ ///
+ /// # Errors
+ /// If the key failed validation during decoding.
+ pub fn new(encapsulation_key: &Key) -> Result {
+ 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
@@ -66,21 +76,6 @@ where
}
}
-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,
@@ -102,9 +97,7 @@ where
P: KemParams,
{
fn new(encapsulation_key: &Key) -> Result {
- EncryptionKey::from_bytes(encapsulation_key)
- .map(Self::from_encryption_key)
- .map_err(|_| InvalidKey)
+ Self::new(encapsulation_key)
}
}
diff --git a/ml-kem/src/lib.rs b/ml-kem/src/lib.rs
index a08bea1..728a1f2 100644
--- a/ml-kem/src/lib.rs
+++ b/ml-kem/src/lib.rs
@@ -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,
@@ -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,
@@ -297,21 +296,41 @@ mod test {
round_trip_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);
+
+ 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::();
+ seed_test::();
+ seed_test::();
+ }
+
+ #[allow(deprecated)]
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();
+ 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]
@@ -328,13 +347,17 @@ mod test {
let mut rng = UnwrapErr(SysRng);
let dk_original = DecapsulationKey::
::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, InvalidKey> =
- DecapsulationKey::from_encoded_bytes(&dk_encoded);
+ DecapsulationKey::from_expanded_bytes(&dk_encoded);
+
assert!(dk_decoded.is_err());
}
@@ -345,26 +368,6 @@ mod 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,
diff --git a/ml-kem/src/pkcs8.rs b/ml-kem/src/pkcs8.rs
index 1f3c992..4fd7646 100644
--- a/ml-kem/src/pkcs8.rs
+++ b/ml-kem/src/pkcs8.rs
@@ -31,7 +31,7 @@ use array::Array;
#[cfg(feature = "alloc")]
use {
- crate::EncodedSizeUser,
+ ::kem::KeyExport,
::pkcs8::der::{Encode, TagMode, asn1::BitStringRef},
};
@@ -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 {
- let public_key = self.to_encoded_bytes();
+ let public_key = self.to_bytes();
let subject_public_key = BitStringRef::new(0, &public_key)?;
::pkcs8::SubjectPublicKeyInfo {
diff --git a/ml-kem/src/traits.rs b/ml-kem/src/traits.rs
deleted file mode 100644
index 5be5bb7..0000000
--- a/ml-kem/src/traits.rs
+++ /dev/null
@@ -1,41 +0,0 @@
-//! Trait definitions
-
-use crate::{ArraySize, Seed};
-use array::{Array, sizes::U64};
-use kem::{InvalidKey, Kem, KeyInit, KeySizeUser};
-
-/// An object that knows what size it is
-pub trait EncodedSizeUser: Sized {
- /// The size of an encoded object
- type EncodedSize: ArraySize;
-
- /// Parse an object from its encoded form
- ///
- /// # Errors
- /// - If the object failed to decode successfully
- fn from_encoded_bytes(enc: &Encoded) -> Result;
-
- /// Serialize an object to its encoded form
- fn to_encoded_bytes(&self) -> Encoded;
-}
-
-/// A byte array encoding a value the indicated size
-pub type Encoded = Array::EncodedSize>;
-
-/// 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 FromSeed for K
-where
- K: Kem,
- K::DecapsulationKey: KeyInit + KeySizeUser,
-{
- fn from_seed(seed: &Seed) -> (K::DecapsulationKey, K::EncapsulationKey) {
- let dk = K::DecapsulationKey::new(seed);
- let ek = dk.as_ref().clone();
- (dk, ek)
- }
-}
diff --git a/ml-kem/tests/encap-decap.rs b/ml-kem/tests/encap-decap.rs
index 0ec9dcb..f3f9eab 100644
--- a/ml-kem/tests/encap-decap.rs
+++ b/ml-kem/tests/encap-decap.rs
@@ -63,11 +63,10 @@ fn verify_encap_group(tg: &acvp::EncapTestGroup) {
fn verify_encap(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::::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);
@@ -85,13 +84,13 @@ fn verify_decap_group(tg: &acvp::DecapTestGroup) {
}
}
+#[allow(deprecated)]
fn verify_decap(tc: &acvp::DecapTestCase, dk_slice: &[u8])
where
K: Kem,
- K::DecapsulationKey: Decapsulate + EncodedSizeUser,
+ K::DecapsulationKey: Decapsulate + ExpandedKeyEncoding,
{
- let dk_bytes = Encoded::::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::::try_from(tc.c.as_slice()).unwrap();
let k = dk.decapsulate(&c);
diff --git a/ml-kem/tests/key-gen.rs b/ml-kem/tests/key-gen.rs
index 2ffbc91..5945975 100644
--- a/ml-kem/tests/key-gen.rs
+++ b/ml-kem/tests/key-gen.rs
@@ -25,33 +25,28 @@ fn acvp_key_gen() {
}
}
+#[allow(deprecated)]
fn verify(tc: &acvp::TestCase)
where
K: Kem + FromSeed,
- K::DecapsulationKey: EncodedSizeUser + Debug + PartialEq,
- K::EncapsulationKey: EncodedSizeUser,
+ K::DecapsulationKey: ExpandedKeyEncoding + Debug + PartialEq,
+ K::EncapsulationKey: KeySizeUser,
{
// Import test data into the relevant array structures
let d = ArrayN::::try_from(tc.d.as_slice()).unwrap();
let z = ArrayN::::try_from(tc.z.as_slice()).unwrap();
- let dk_bytes = Encoded::::try_from(tc.dk.as_slice()).unwrap();
- let ek_bytes = Encoded::::try_from(tc.ek.as_slice()).unwrap();
-
let (dk, ek) = K::from_seed(&d.concat(z));
// Verify correctness via serialization
- assert_eq!(dk.to_encoded_bytes().as_slice(), tc.dk.as_slice());
- assert_eq!(ek.to_encoded_bytes().as_slice(), tc.ek.as_slice());
+ assert_eq!(dk.to_expanded_bytes().as_slice(), tc.dk.as_slice());
+ assert_eq!(ek.to_bytes().as_slice(), tc.ek.as_slice());
// Verify correctness via deserialization
assert_eq!(
dk,
- K::DecapsulationKey::from_encoded_bytes(&dk_bytes).unwrap()
- );
- assert_eq!(
- ek,
- K::EncapsulationKey::from_encoded_bytes(&ek_bytes).unwrap()
+ K::DecapsulationKey::from_expanded_bytes(tc.dk.as_slice().try_into().unwrap()).unwrap()
);
+ assert_eq!(ek, K::EncapsulationKey::new_from_slice(&tc.ek).unwrap());
}
mod acvp {
diff --git a/ml-kem/tests/wycheproof.rs b/ml-kem/tests/wycheproof.rs
index 0e48191..8882bae 100644
--- a/ml-kem/tests/wycheproof.rs
+++ b/ml-kem/tests/wycheproof.rs
@@ -1,8 +1,10 @@
//! Test against the Wycheproof test vectors.
use array::{Array, ArraySize};
+#[allow(deprecated)]
+use ml_kem::ExpandedKeyEncoding;
use ml_kem::{
- EncodedSizeUser, FromSeed, MlKem512, MlKem768, MlKem1024,
+ FromSeed, MlKem512, MlKem768, MlKem1024,
kem::{Decapsulate, KeyExport, TryKeyInit},
};
use serde::Deserialize;
@@ -149,7 +151,12 @@ macro_rules! mlkem_keygen_seed_test {
let test_dk = decode_expected_hex(&test.dk, "dk");
let (dk, ek) = $kem::from_seed(&test_seed);
- assert_eq!(test_dk, dk.to_encoded_bytes());
+
+ #[allow(deprecated)]
+ {
+ assert_eq!(test_dk, dk.to_expanded_bytes());
+ }
+
assert_eq!(test.ek.as_slice(), ek.to_bytes().as_slice());
}
}
diff --git a/x-wing/src/lib.rs b/x-wing/src/lib.rs
index aa8bee1..98b2220 100644
--- a/x-wing/src/lib.rs
+++ b/x-wing/src/lib.rs
@@ -34,7 +34,7 @@ pub use kem::{
};
use ml_kem::{
- EncodedSizeUser, FromSeed, MlKem768,
+ FromSeed, MlKem768,
array::{
Array, ArrayN, AsArrayRef,
sizes::{U32, U1120, U1184, U1216},
@@ -154,7 +154,7 @@ impl KeyExport for EncapsulationKey {
fn to_bytes(&self) -> Key {
let mut key_bytes = Key::::default();
let (m, x) = key_bytes.split_at_mut(1184);
- m.copy_from_slice(&self.pk_m.to_encoded_bytes());
+ m.copy_from_slice(&self.pk_m.to_bytes());
x.copy_from_slice(self.pk_x.as_bytes());
key_bytes
}
@@ -164,7 +164,7 @@ impl TryKeyInit for EncapsulationKey {
fn new(key_bytes: &Key) -> Result {
let (m_bytes, x_bytes) = key_bytes.split_ref::();
- let pk_m = MlKem768EncapsulationKey::from_encoded_bytes(m_bytes).map_err(|_| InvalidKey)?;
+ let pk_m = MlKem768EncapsulationKey::new(m_bytes)?;
let pk_x = PublicKey::from(x_bytes.0);
Ok(EncapsulationKey { pk_m, pk_x })