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 })