11//! Test against the Wycheproof test vectors.
22
3+ use array:: { Array , ArraySize } ;
34use ml_kem:: {
45 EncodedSizeUser , KemCore , MlKem512 , MlKem768 , MlKem1024 ,
5- kem:: { KeyExport , TryKeyInit } ,
6+ kem:: { Decapsulate , KeyExport , TryKeyInit } ,
67} ;
78use serde:: Deserialize ;
89use std:: fs:: File ;
@@ -43,7 +44,6 @@ struct Test {
4344 dk : Option < String > ,
4445 #[ cfg( feature = "hazmat" ) ]
4546 m : Option < String > ,
46- #[ cfg( feature = "hazmat" ) ]
4747 c : Option < String > ,
4848 #[ cfg( feature = "hazmat" ) ]
4949 #[ serde( default , rename( deserialize = "K" ) ) ]
@@ -73,13 +73,16 @@ macro_rules! load_json_file {
7373 } } ;
7474}
7575
76- fn decode_optional_hex ( opt : & Option < String > , field : & str ) -> Vec < u8 > {
77- match opt {
78- Some ( h) => {
79- hex:: decode ( h) . unwrap_or_else ( |e| panic ! ( "invalid hex for field '{field}': {e}" ) )
80- }
81- None => panic ! ( "missing field: {field}" ) ,
82- }
76+ fn decode_optional_hex < U : ArraySize > ( opt : & Option < String > , field : & str ) -> Option < Array < u8 , U > > {
77+ opt. as_ref ( ) . and_then ( |h| {
78+ let vec = hex:: decode ( h) . unwrap_or_else ( |e| panic ! ( "invalid hex for field '{field}': {e}" ) ) ;
79+ vec. as_slice ( ) . try_into ( ) . ok ( )
80+ } )
81+ }
82+
83+ fn decode_expected_hex < U : ArraySize > ( opt : & Option < String > , field : & str ) -> Array < u8 , U > {
84+ decode_optional_hex ( opt, field)
85+ . unwrap_or_else ( || panic ! ( "missing or incorrect length field: {field}" ) )
8386}
8487
8588macro_rules! mlkem_keygen_seed_test {
@@ -101,11 +104,11 @@ macro_rules! mlkem_keygen_seed_test {
101104 test. comment. as_ref( ) . unwrap( ) ,
102105 & test. result
103106 ) ;
104- let test_seed = decode_optional_hex ( & test. seed, "seed" ) ;
105- let test_dk = decode_optional_hex ( & test. dk, "dk" ) ;
107+ let test_seed = decode_expected_hex ( & test. seed, "seed" ) ;
108+ let test_dk = decode_expected_hex ( & test. dk, "dk" ) ;
106109
107- let ( dk, ek) = $kem:: from_seed( test_seed. as_slice ( ) . try_into ( ) . unwrap ( ) ) ;
108- assert_eq!( test_dk. as_slice ( ) , dk. to_encoded_bytes( ) . as_slice ( ) ) ;
110+ let ( dk, ek) = $kem:: from_seed( test_seed) ;
111+ assert_eq!( test_dk, dk. to_encoded_bytes( ) ) ;
109112 assert_eq!( test. ek. as_slice( ) , ek. to_bytes( ) . as_slice( ) ) ;
110113 }
111114 }
@@ -143,21 +146,74 @@ macro_rules! mlkem_encaps_test {
143146
144147 #[ cfg( feature = "hazmat" ) ]
145148 {
146- let test_m = decode_optional_hex( & test. m, "m" ) ;
147- let test_m = test_m. as_slice( ) . try_into( ) . unwrap( ) ;
148- let ( c, k) = ek. encapsulate_deterministic( test_m) ;
149-
150- let test_c = decode_optional_hex( & test. c, "c" ) ;
151- let test_k = decode_optional_hex( & test. k, "K" ) ;
152- assert_eq!( test_c. as_slice( ) , c. as_slice( ) ) ;
153- assert_eq!( test_k. as_slice( ) , k. as_slice( ) ) ;
149+ let test_m = decode_expected_hex( & test. m, "m" ) ;
150+ let ( c, k) = ek. encapsulate_deterministic( & test_m) ;
151+
152+ let test_c = decode_expected_hex( & test. c, "c" ) ;
153+ let test_k = decode_expected_hex( & test. k, "K" ) ;
154+ assert_eq!( test_c, c) ;
155+ assert_eq!( test_k, k) ;
154156 }
155157 }
156158 }
157159 }
158160 } ;
159161}
160162
163+ macro_rules! mlkem_decaps_test {
164+ ( $name: ident, $json_file: expr, $kem_module: ident) => {
165+ #[ test]
166+ fn $name( ) {
167+ let tests = load_json_file!( $json_file) ;
168+
169+ for group in tests. groups {
170+ println!(
171+ "Parameter set: {} ({} v{})\n " ,
172+ & group. parameter_set, & group. source. name, & group. source. version
173+ ) ;
174+
175+ for test in & group. tests {
176+ println!( "Test #{} ({:?})" , test. id, & test. result) ;
177+
178+ #[ allow( deprecated) ]
179+ use ml_kem:: $kem_module:: {
180+ DecapsulationKey , EncodedCiphertext , ExpandedDecapsulationKey ,
181+ } ;
182+
183+ #[ allow( deprecated) ]
184+ let test_dk: ExpandedDecapsulationKey =
185+ match decode_optional_hex( & test. dk, "dk" ) {
186+ Some ( dk) => dk,
187+ None => {
188+ if test. result == ExpectedResult :: Invalid {
189+ continue ;
190+ } else {
191+ panic!( "failed to decode expanded decapsulation key!" )
192+ }
193+ }
194+ } ;
195+
196+ #[ allow( deprecated) ]
197+ let dk = DecapsulationKey :: from_expanded( & test_dk) . expect( "should be valid" ) ;
198+
199+ let test_c: EncodedCiphertext = match decode_optional_hex( & test. c, "c" ) {
200+ Some ( dk) => dk,
201+ None => {
202+ if test. result == ExpectedResult :: Invalid {
203+ continue ;
204+ } else {
205+ panic!( "failed to decode ciphertext!" )
206+ }
207+ }
208+ } ;
209+
210+ let _ss = dk. decapsulate( & test_c) ;
211+ }
212+ }
213+ }
214+ } ;
215+ }
216+
161217mlkem_keygen_seed_test ! (
162218 mlkem_512_keygen_seed_test,
163219 "mlkem_512_keygen_seed_test.json" ,
@@ -174,6 +230,22 @@ mlkem_keygen_seed_test!(
174230 MlKem1024
175231) ;
176232
233+ mlkem_decaps_test ! (
234+ mlkem_512_semi_expanded_decaps_test,
235+ "mlkem_512_semi_expanded_decaps_test.json" ,
236+ ml_kem_512
237+ ) ;
238+ mlkem_decaps_test ! (
239+ mlkem_768_semi_expanded_decaps_test,
240+ "mlkem_768_semi_expanded_decaps_test.json" ,
241+ ml_kem_768
242+ ) ;
243+ mlkem_decaps_test ! (
244+ mlkem_1024_semi_expanded_decaps_test,
245+ "mlkem_1024_semi_expanded_decaps_test.json" ,
246+ ml_kem_1024
247+ ) ;
248+
177249mlkem_encaps_test ! (
178250 mlkem_512_encaps_test,
179251 "mlkem_512_encaps_test.json" ,
0 commit comments