diff --git a/tests/test_helper.hpp b/tests/test_helper.hpp index d582199..980fbf2 100644 --- a/tests/test_helper.hpp +++ b/tests/test_helper.hpp @@ -1,9 +1,11 @@ #pragma once +#include "ml_kem/internals/math/field.hpp" #include #include #include #include #include +#include #include // Given a hex encoded string of length 2*L, this routine can be used for parsing it as a byte array of length L. @@ -30,3 +32,25 @@ from_hex(std::string_view bytes) return res; } + +// Given a valid ML-KEM-{512, 768, 1024} public key, this function mutates the last coefficient +// of serialized polynomial vector s.t. it produces a malformed (i.e. non-reduced) polynomial vector. +template +static inline constexpr void +make_malformed_pubkey(std::span pubkey) +{ + constexpr auto last_coeff_ends_at = pubkey_byte_len - 32; + constexpr auto last_coeff_begins_at = last_coeff_ends_at - 2; + + // < 16 -bit word > + // (MSB) ---- | ---- | ---- | ---- (LSB) + // | 12 -bits of last coeff, to be mutated | Most significant 4 -bits of second last coeff | + const uint16_t last_coeff = (static_cast(pubkey[last_coeff_begins_at + 1]) << 8) | static_cast(pubkey[last_coeff_begins_at + 0]); + + constexpr uint16_t hi = ml_kem_field::Q << 4; // Q (=3329) is not a valid element of Zq. Any value >= Q && < 2^12, would work. + const uint16_t lo = last_coeff & 0xfu; // Don't touch most significant 4 -bits of second last coefficient + const uint16_t updated_last_coeff = hi ^ lo; // 16 -bit word s.t. last coefficient is not reduced modulo prime Q + + pubkey[last_coeff_begins_at + 0] = static_cast(updated_last_coeff >> 0); + pubkey[last_coeff_begins_at + 1] = static_cast(updated_last_coeff >> 8); +} diff --git a/tests/test_ml_kem_1024.cpp b/tests/test_ml_kem_1024.cpp index 77f5321..2458057 100644 --- a/tests/test_ml_kem_1024.cpp +++ b/tests/test_ml_kem_1024.cpp @@ -1,4 +1,5 @@ #include "ml_kem/ml_kem_1024.hpp" +#include "test_helper.hpp" #include // For ML-KEM-1024 @@ -36,3 +37,33 @@ TEST(ML_KEM, ML_KEM_1024_KeygenEncapsDecaps) EXPECT_TRUE(is_encapsulated); EXPECT_EQ(shared_secret_sender, shared_secret_receiver); } + +// For ML-KEM-1024 +// +// - Generate a valid keypair. +// - Malform public key s.t. last coefficient of polynomial vector is not properly reduced. +// - Attempt to encapsulate using malformed public key. It must fail. +TEST(ML_KEM, ML_KEM_1024_EncapsFailureDueToNonReducedPubKey) +{ + std::array seed_d{}; + std::array seed_z{}; + std::array seed_m{}; + + std::array pubkey{}; + std::array seckey{}; + std::array cipher{}; + + std::array shared_secret{}; + + ml_kem_prng::prng_t<256> prng{}; + prng.read(seed_d); + prng.read(seed_z); + prng.read(seed_m); + + ml_kem_1024::keygen(seed_d, seed_z, pubkey, seckey); + + make_malformed_pubkey(pubkey); + const auto is_encapsulated = ml_kem_1024::encapsulate(seed_m, pubkey, cipher, shared_secret); + + EXPECT_FALSE(is_encapsulated); +} diff --git a/tests/test_ml_kem_512.cpp b/tests/test_ml_kem_512.cpp index 1003ac2..71af5d8 100644 --- a/tests/test_ml_kem_512.cpp +++ b/tests/test_ml_kem_512.cpp @@ -1,4 +1,5 @@ #include "ml_kem/ml_kem_512.hpp" +#include "test_helper.hpp" #include // For ML-KEM-512 @@ -36,3 +37,33 @@ TEST(ML_KEM, ML_KEM_512_KeygenEncapsDecaps) EXPECT_TRUE(is_encapsulated); EXPECT_EQ(shared_secret_sender, shared_secret_receiver); } + +// For ML-KEM-512 +// +// - Generate a valid keypair. +// - Malform public key s.t. last coefficient of polynomial vector is not properly reduced. +// - Attempt to encapsulate using malformed public key. It must fail. +TEST(ML_KEM, ML_KEM_512_EncapsFailureDueToNonReducedPubKey) +{ + std::array seed_d{}; + std::array seed_z{}; + std::array seed_m{}; + + std::array pubkey{}; + std::array seckey{}; + std::array cipher{}; + + std::array shared_secret{}; + + ml_kem_prng::prng_t<128> prng{}; + prng.read(seed_d); + prng.read(seed_z); + prng.read(seed_m); + + ml_kem_512::keygen(seed_d, seed_z, pubkey, seckey); + + make_malformed_pubkey(pubkey); + const auto is_encapsulated = ml_kem_512::encapsulate(seed_m, pubkey, cipher, shared_secret); + + EXPECT_FALSE(is_encapsulated); +} diff --git a/tests/test_ml_kem_768.cpp b/tests/test_ml_kem_768.cpp index a51ee8a..8996688 100644 --- a/tests/test_ml_kem_768.cpp +++ b/tests/test_ml_kem_768.cpp @@ -1,4 +1,5 @@ #include "ml_kem/ml_kem_768.hpp" +#include "test_helper.hpp" #include // For ML-KEM-768 @@ -36,3 +37,33 @@ TEST(ML_KEM, ML_KEM_768_KeygenEncapsDecaps) EXPECT_TRUE(is_encapsulated); EXPECT_EQ(shared_secret_sender, shared_secret_receiver); } + +// For ML-KEM-768 +// +// - Generate a valid keypair. +// - Malform public key s.t. last coefficient of polynomial vector is not properly reduced. +// - Attempt to encapsulate using malformed public key. It must fail. +TEST(ML_KEM, ML_KEM_768_EncapsFailureDueToNonReducedPubKey) +{ + std::array seed_d{}; + std::array seed_z{}; + std::array seed_m{}; + + std::array pubkey{}; + std::array seckey{}; + std::array cipher{}; + + std::array shared_secret{}; + + ml_kem_prng::prng_t<192> prng{}; + prng.read(seed_d); + prng.read(seed_z); + prng.read(seed_m); + + ml_kem_768::keygen(seed_d, seed_z, pubkey, seckey); + + make_malformed_pubkey(pubkey); + const auto is_encapsulated = ml_kem_768::encapsulate(seed_m, pubkey, cipher, shared_secret); + + EXPECT_FALSE(is_encapsulated); +}