From e9863c18d9f5f4528f45bd06bb73681fa1e2c04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Thu, 27 Jun 2024 13:38:11 +0200 Subject: [PATCH] Review comments Co-Authored-By: Fabian Albert Co-Authored-By: Jack Lloyd --- .../dilithium_common/dilithium_algos.h | 2 + src/lib/pubkey/kyber/kyber_common/kyber.cpp | 8 +- .../pubkey/kyber/kyber_common/kyber_algos.h | 7 + .../pubkey/kyber/kyber_common/kyber_helpers.h | 25 +- .../kyber/kyber_common/kyber_polynomial.h | 11 + src/lib/pubkey/pqcrystals/pqcrystals.h | 10 - .../pubkey/pqcrystals/pqcrystals_encoding.h | 10 +- .../pubkey/pqcrystals/pqcrystals_helpers.h | 13 +- src/lib/rng/rng.h | 1 - src/tests/test_crystals.cpp | 609 +++++++++--------- 10 files changed, 362 insertions(+), 334 deletions(-) diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.h b/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.h index 692b20839ea..7b0f87d429e 100644 --- a/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.h +++ b/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.h @@ -17,8 +17,10 @@ namespace Botan { DilithiumPolyMatNTT dilithium_expand_A(StrongSpan rho, const DilithiumConstants& mode); + std::pair dilithium_expand_s(StrongSpan rhoprime, const DilithiumConstants& mode); + DilithiumPolyVec dilithium_expand_mask(StrongSpan rhoprime, uint16_t nonce, const DilithiumConstants& mode); diff --git a/src/lib/pubkey/kyber/kyber_common/kyber.cpp b/src/lib/pubkey/kyber/kyber_common/kyber.cpp index e6ec79ed905..abd77f66e22 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber.cpp +++ b/src/lib/pubkey/kyber/kyber_common/kyber.cpp @@ -210,15 +210,11 @@ Kyber_PrivateKey::Kyber_PrivateKey(RandomNumberGenerator& rng, KyberMode m) { auto [rho, sigma] = mode.symmetric_primitives().G(d); KyberPolynomialSampler ps(sigma, mode); - // TODO: Remove the need for the montgomery transformation - // - // -> When calculating A*s below, A is not in montgomery form, but s is. The - // operation uses fqmul internally, which performs a montgomery reduction. - auto A = montgomery(kyber_sample_matrix(rho, false /* not transposed */, mode)); + auto A = kyber_sample_matrix(rho, false /* not transposed */, mode); auto s = ntt(ps.sample_polynomial_vector_cbd_eta1()); const auto e = ntt(ps.sample_polynomial_vector_cbd_eta1()); - auto t = A * s; + auto t = montgomery(A * s); t += e; t.reduce(); diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_algos.h b/src/lib/pubkey/kyber/kyber_common/kyber_algos.h index 098b4f86c9e..cc7f70c2b06 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber_algos.h +++ b/src/lib/pubkey/kyber/kyber_common/kyber_algos.h @@ -24,16 +24,23 @@ namespace Botan { void kyber_encode_polynomial_vector(std::span out, const KyberPolyVecNTT& p); + KyberPolyVecNTT kyber_decode_polynomial_vector(std::span a, const KyberConstants& mode); + KyberPoly kyber_polynomial_from_message(StrongSpan msg); + KyberMessage kyber_polynomial_to_message(const KyberPoly& p); + void kyber_compress_ciphertext(StrongSpan out, const KyberPolyVec& u, const KyberPoly& v, const KyberConstants& m_mode); + std::pair kyber_decompress_ciphertext(StrongSpan ct, const KyberConstants& mode); + KyberPolyMat kyber_sample_matrix(StrongSpan seed, bool transposed, const KyberConstants& mode); + void kyber_sample_polynomial_from_cbd(KyberPoly& poly, KyberConstants::KyberEta eta, const KyberSamplingRandomness& randomness); diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_helpers.h b/src/lib/pubkey/kyber/kyber_common/kyber_helpers.h index 05e298dc7a5..4434726d332 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber_helpers.h +++ b/src/lib/pubkey/kyber/kyber_common/kyber_helpers.h @@ -32,30 +32,29 @@ template requires(d > 0 && d < 12) constexpr std::make_unsigned_t kyber_compress(KyberConstants::T x) { BOTAN_DEBUG_ASSERT(x >= 0 && x < KyberConstants::Q); - using unsigned_T = std::make_unsigned_t; + const uint32_t n = (static_cast(x) << d) + KyberConstants::Q / 2; // This is a mitigation for a potential side channel called "KyberSlash". + // // It implements the division by Q using a multiplication and a shift. Most // compilers would generate similar code for such a division by a constant. // Though, in some cases, compilers might use a variable-time int division, // resulting in a potential side channel. // + // The constants below work for all values that appear in Kyber with the + // greatest being 3328 * 2^11 + Q // 2 = 6,817,408 < 2**23 = 8,388,608. + // // See "Hacker's Delight" (Second Edition) by Henry S. Warren, Jr. // Chapter 10-9 "Unsigned Division by Divisors >= 1" - auto divide_by_q = [](uint32_t n) -> unsigned_T { - static_assert(KyberConstants::Q == 3329); - BOTAN_DEBUG_ASSERT(n < (1 << 23)); - - // These constants work for all values that appear in Kyber with the - // greatest being 3328 * 2^11 + Q // 2 = 6,817,408 < 2**23 = 8,388,608. - constexpr uint64_t m = 2580335; - constexpr size_t p = 33; - return static_cast((n * m) >> p); - }; + BOTAN_DEBUG_ASSERT(n < (1 << 23)); + static_assert(KyberConstants::Q == 3329); + using unsigned_T = std::make_unsigned_t; + constexpr uint64_t m = 2580335; + constexpr size_t p = 33; constexpr unsigned_T mask = (1 << d) - 1; - return divide_by_q((static_cast(x) << d) + KyberConstants::Q / 2) & mask; -} + return static_cast((n * m) >> p) & mask; +}; /** * NIST FIPS 203 IPD, Formula 4.6 (Decompress) diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_polynomial.h b/src/lib/pubkey/kyber/kyber_common/kyber_polynomial.h index 94e1d7ed1ff..ab267a3a2d2 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber_polynomial.h +++ b/src/lib/pubkey/kyber/kyber_common/kyber_polynomial.h @@ -43,6 +43,9 @@ class KyberPolyTraits final : public CRYSTALS::Trait_Base p) { for(size_t len = N / 2, k = 0; len >= 2; len /= 2) { @@ -61,6 +64,14 @@ class KyberPolyTraits final : public CRYSTALS::Trait_Base p) { for(size_t len = 2, k = 127; len <= N / 2; len *= 2) { diff --git a/src/lib/pubkey/pqcrystals/pqcrystals.h b/src/lib/pubkey/pqcrystals/pqcrystals.h index 5f6d57aeef8..a94b7572b47 100644 --- a/src/lib/pubkey/pqcrystals/pqcrystals.h +++ b/src/lib/pubkey/pqcrystals/pqcrystals.h @@ -587,16 +587,6 @@ PolynomialVector montgomery(PolynomialVector polyvec) { return polyvec; } -template -PolynomialMatrix montgomery(PolynomialMatrix m) { - for(auto& polyvec : m) { - for(auto& poly : polyvec) { - detail::montgomery(poly); - } - } - return m; -} - template PolynomialVector operator+(const PolynomialVector& a, const PolynomialVector& b) { diff --git a/src/lib/pubkey/pqcrystals/pqcrystals_encoding.h b/src/lib/pubkey/pqcrystals/pqcrystals_encoding.h index e7963ecc6f1..8c99187b82b 100644 --- a/src/lib/pubkey/pqcrystals/pqcrystals_encoding.h +++ b/src/lib/pubkey/pqcrystals/pqcrystals_encoding.h @@ -12,15 +12,17 @@ #define BOTAN_PQ_CRYSTALS_ENCODING_H_ #include +#include #include -#include - #include #include #include #include +#if defined(BOTAN_HAS_XOF) + #include +#endif namespace Botan::CRYSTALS { namespace detail { @@ -29,9 +31,11 @@ constexpr auto as_byte_source(BufferSlicer& slicer) { return [&](std::span out) { slicer.copy_into(out); }; } +#if defined(BOTAN_HAS_XOF) constexpr auto as_byte_source(Botan::XOF& xof) { return [&](std::span out) { xof.output(out); }; } +#endif } // namespace detail @@ -71,7 +75,7 @@ struct BitPackingTrait final { constexpr static size_t bits_per_pack = [] { // Ensure that the bit-packing is byte-aligned and scale it // to utilize the collector's bit-width as much as possible. - size_t smallest_aligned_pack = lcm(bits_per_coeff, size_t(8)); + size_t smallest_aligned_pack = std::lcm(bits_per_coeff, size_t(8)); return (smallest_aligned_pack < bits_in_collector) ? (bits_in_collector / smallest_aligned_pack) * smallest_aligned_pack : smallest_aligned_pack; diff --git a/src/lib/pubkey/pqcrystals/pqcrystals_helpers.h b/src/lib/pubkey/pqcrystals/pqcrystals_helpers.h index e71f9c8bf21..c8d46620832 100644 --- a/src/lib/pubkey/pqcrystals/pqcrystals_helpers.h +++ b/src/lib/pubkey/pqcrystals/pqcrystals_helpers.h @@ -39,7 +39,7 @@ using next_longer_int_t = template requires(size_t(sizeof(T)) <= 4) -constexpr T montgomery_R(T q) { +consteval T montgomery_R(T q) { using T_unsigned = std::make_unsigned_t; using T2 = next_longer_uint_t; return (T2(1) << (sizeof(T) * 8)) % q; @@ -47,7 +47,7 @@ constexpr T montgomery_R(T q) { template requires(size_t(sizeof(T)) <= 4) -constexpr T montgomery_R2(T q) { +consteval T montgomery_R2(T q) { using T2 = next_longer_int_t; return (static_cast(montgomery_R(q)) * static_cast(montgomery_R(q))) % q; } @@ -64,7 +64,7 @@ struct eea_result { * and b and the Bézout coefficients, u and v. */ template -constexpr eea_result extended_euclidean_algorithm(T a, T b) { +consteval eea_result extended_euclidean_algorithm(T a, T b) { if(a > b) { std::swap(a, b); } @@ -89,15 +89,10 @@ constexpr eea_result extended_euclidean_algorithm(T a, T b) { */ template > requires(sizeof(T) <= 4) -constexpr T modular_inverse(T q, T2 m = T2(1) << sizeof(T) * 8) { +consteval T modular_inverse(T q, T2 m = T2(1) << sizeof(T) * 8) { return static_cast(extended_euclidean_algorithm(q, m).u); } -template -constexpr T lcm(T a, T b) { - return a / extended_euclidean_algorithm(a, b).gcd * b; -} - constexpr auto bitlen(size_t x) { return ceil_log2(x + 1); }; diff --git a/src/lib/rng/rng.h b/src/lib/rng/rng.h index 1cf1a912111..1ee3db9f714 100644 --- a/src/lib/rng/rng.h +++ b/src/lib/rng/rng.h @@ -14,7 +14,6 @@ #include #include -#include #include #include #include diff --git a/src/tests/test_crystals.cpp b/src/tests/test_crystals.cpp index 075c97d4662..10e81fc5a79 100644 --- a/src/tests/test_crystals.cpp +++ b/src/tests/test_crystals.cpp @@ -21,23 +21,48 @@ namespace Botan_Tests { namespace { -Test::Result test_extended_euclidean_algorithm() { - Test::Result res("Extended Euclidean Algorithm"); +template +consteval T gcd(T x, T y) { + return Botan::extended_euclidean_algorithm(x, y).gcd; +} - res.test_is_eq("gcd(1337, 1337)", Botan::extended_euclidean_algorithm(1337, 1337).gcd, 1337); - res.test_is_eq("gcd(350, 294)", Botan::extended_euclidean_algorithm(350, 294).gcd, 14); - res.test_is_eq("gcd(294, 350)", Botan::extended_euclidean_algorithm(294, 350).gcd, 14); +template +consteval T v(T x, T y) { + return Botan::extended_euclidean_algorithm(x, y).v; +} - res.test_is_eq("gcd(1337, 1337)", Botan::extended_euclidean_algorithm(1337, 1337).gcd, 1337); - res.test_is_eq("gcd(350, 294)", Botan::extended_euclidean_algorithm(350, 294).gcd, 14); - res.test_is_eq("gcd(294, 350)", Botan::extended_euclidean_algorithm(294, 350).gcd, 14); +template +consteval T u(T x, T y) { + return Botan::extended_euclidean_algorithm(x, y).u; +} - res.test_is_eq("u(1337, 1337)", Botan::extended_euclidean_algorithm(1337, 1337).u, 0); - res.test_is_eq("v(1337, 1337)", Botan::extended_euclidean_algorithm(1337, 1337).v, 1); - res.test_is_eq("u(294, 350)", Botan::extended_euclidean_algorithm(294, 350).u, 6); +Test::Result test_extended_euclidean_algorithm() { + Test::Result res("Extended Euclidean Algorithm"); - res.test_is_eq("q^-1(3329) - Kyber::Q", Botan::modular_inverse(3329), 62209); - res.test_is_eq("q^-1(8380417) - Dilithium::Q", Botan::modular_inverse(8380417), 58728449); + // The wrapper template functions gcd<>(), v<>() and u<>() are workarounds + // for an assumed bug in MSVC 19.38.33134 that does not accept the invocation + // of the consteval function `extended_euclidean_algorithm` as a parameter to + // `test_is_eq()`. + // + // The resulting error is: + // error C7595: 'Botan::extended_euclidean_algorithm': call to immediate function is not a constant expression + // + // What we'd actually want to write here: + // res.test_is_eq("gcd(350, 294)", Botan::extended_euclidean_algorithm(350, 294).gcd, 14); + res.test_is_eq("gcd(1337, 1337)", gcd(1337, 1337), 1337); + res.test_is_eq("gcd(350, 294)", gcd(350, 294), 14); + res.test_is_eq("gcd(294, 350)", gcd(294, 350), 14); + + res.test_is_eq("gcd(1337, 1337)", gcd(1337, 1337), 1337); + res.test_is_eq("gcd(350, 294)", gcd(350, 294), 14); + res.test_is_eq("gcd(294, 350)", gcd(294, 350), 14); + + res.test_is_eq("u(1337, 1337)", u(1337, 1337), 0); + res.test_is_eq("v(1337, 1337)", v(1337, 1337), 1); + res.test_is_eq("u(294, 350)", u(294, 350), 6); + + res.test_is_eq("q^-1(3329) - Kyber::Q", Botan::modular_inverse(3329), -3327); + res.test_is_eq("q^-1(8380417) - Dilithium::Q", Botan::modular_inverse(8380417), 58728449); return res; } @@ -96,160 +121,160 @@ using Kyberish_PolyVec = Botan::CRYSTALS::PolynomialVector; std::vector test_polynomial_basics() { return { - Botan_Tests::CHECK("polynomial owning storage", - [](Test::Result& res) { - Kyberish_Poly p; - res.confirm("default constructed poly owns memory", p.owns_storage()); - for(auto coeff : p) { - res.test_is_eq("default constructed poly has 0 coefficients", coeff, 0); - } - - Kyberish_Poly p_ntt; - res.confirm("default constructed poly owns memory (NTT)", p_ntt.owns_storage()); - for(auto coeff : p) { - res.test_is_eq("default constructed poly (NTT) has 0 coefficients", coeff, 0); - } - }), - - Botan_Tests::CHECK("polynomial vector managing storage", - [](Test::Result& res) { - Kyberish_PolyVec polys(4); - res.test_is_eq("requested size", polys.size(), 4); - - for(const auto& poly : polys) { - res.confirm("poly embedded in vector does not own memory", !poly.owns_storage()); - } - - Kyberish_PolyVec polys_ntt(4); - res.test_is_eq("requested size (NTT)", polys.size(), 4); - - for(const auto& poly : polys_ntt) { - res.confirm("poly (NTT) embedded in vector does not own memory", !poly.owns_storage()); - } - }), - - Botan_Tests::CHECK("cloned polynomials always manage their storge", - [](Test::Result& res) { - Kyberish_Poly p; - auto p2 = p.clone(); - res.confirm("cloned poly owns memory", p2.owns_storage()); - - Kyberish_PolyVec pv(3); - for(auto& poly : pv) { - res.require("poly in vector does not own memory", !poly.owns_storage()); - auto pv2 = poly.clone(); - res.confirm("cloned poly in vector owns memory", pv2.owns_storage()); - } - - auto pv2 = pv.clone(); - for(const auto& poly : pv2) { - res.confirm("cloned vector polynomial don't own memory", !poly.owns_storage()); - } - - Kyberish_Poly p_ntt; - auto p2_ntt = p_ntt.clone(); - res.confirm("cloned poly (NTT) owns memory", p2_ntt.owns_storage()); - - Kyberish_PolyVec pv_ntt(3); - for(auto& poly : pv_ntt) { - res.require("poly (NTT) in vector does not own memory", !poly.owns_storage()); - auto pv2_ntt = poly.clone(); - res.confirm("cloned poly (NTT) in vector owns memory", pv2_ntt.owns_storage()); - } - - auto pv2_ntt = pv_ntt.clone(); - for(const auto& poly : pv2_ntt) { - res.confirm("cloned vector polynomial (NTT) don't own memory", !poly.owns_storage()); - } - }), - - Botan_Tests::CHECK("hamming weight of polynomials", - [](Test::Result& res) { - Kyberish_Poly p; - res.test_is_eq("hamming weight of 0", p.hamming_weight(), 0); - - p[0] = 1337; - res.test_is_eq("hamming weight of 1", p.hamming_weight(), 1); - - p[1] = 42; - res.test_is_eq("hamming weight of 2", p.hamming_weight(), 2); - - p[2] = 11; - res.test_is_eq("hamming weight of 3", p.hamming_weight(), 3); - - p[3] = 4; - res.test_is_eq("hamming weight of 4", p.hamming_weight(), 4); - - p[3] = 0; - res.test_is_eq("hamming weight of 3", p.hamming_weight(), 3); - - p[2] = 0; - res.test_is_eq("hamming weight of 2", p.hamming_weight(), 2); - - p[1] = 0; - res.test_is_eq("hamming weight of 1", p.hamming_weight(), 1); - - p[0] = 0; - res.test_is_eq("hamming weight of 0", p.hamming_weight(), 0); - }), - - Botan_Tests::CHECK("hamming weight of polynomial vectors", - [](Test::Result& res) { - Kyberish_PolyVec pv(3); - res.test_is_eq("hamming weight of 0", pv.hamming_weight(), 0); - - pv[0][0] = 1337; - res.test_is_eq("hamming weight of 1", pv.hamming_weight(), 1); - - pv[1][1] = 42; - res.test_is_eq("hamming weight of 2", pv.hamming_weight(), 2); - - pv[2][2] = 11; - res.test_is_eq("hamming weight of 3", pv.hamming_weight(), 3); - - pv[2][2] = 0; - res.test_is_eq("hamming weight of 2", pv.hamming_weight(), 2); - - pv[1][1] = 0; - res.test_is_eq("hamming weight of 1", pv.hamming_weight(), 1); - - pv[0][0] = 0; - res.test_is_eq("hamming weight of 0", pv.hamming_weight(), 0); - }), - - Botan_Tests::CHECK("value range validation", - [](Test::Result& res) { - Kyberish_Poly p; - res.confirm("value range validation (all zero)", p.ct_validate_value_range(0, 1)); - - p[0] = 1; - p[32] = 1; - p[172] = 1; - res.confirm("value range validation", p.ct_validate_value_range(0, 1)); - - p[11] = 2; - res.confirm("value range validation", !p.ct_validate_value_range(0, 1)); - - p[11] = -1; - res.confirm("value range validation", !p.ct_validate_value_range(0, 1)); - }), + CHECK("polynomial owning storage", + [](Test::Result& res) { + Kyberish_Poly p; + res.confirm("default constructed poly owns memory", p.owns_storage()); + for(auto coeff : p) { + res.test_is_eq("default constructed poly has 0 coefficients", coeff, 0); + } + + Kyberish_Poly p_ntt; + res.confirm("default constructed poly owns memory (NTT)", p_ntt.owns_storage()); + for(auto coeff : p) { + res.test_is_eq("default constructed poly (NTT) has 0 coefficients", coeff, 0); + } + }), + + CHECK("polynomial vector managing storage", + [](Test::Result& res) { + Kyberish_PolyVec polys(4); + res.test_is_eq("requested size", polys.size(), 4); + + for(const auto& poly : polys) { + res.confirm("poly embedded in vector does not own memory", !poly.owns_storage()); + } + + Kyberish_PolyVec polys_ntt(4); + res.test_is_eq("requested size (NTT)", polys.size(), 4); + + for(const auto& poly : polys_ntt) { + res.confirm("poly (NTT) embedded in vector does not own memory", !poly.owns_storage()); + } + }), + + CHECK("cloned polynomials always manage their storge", + [](Test::Result& res) { + Kyberish_Poly p; + auto p2 = p.clone(); + res.confirm("cloned poly owns memory", p2.owns_storage()); + + Kyberish_PolyVec pv(3); + for(auto& poly : pv) { + res.require("poly in vector does not own memory", !poly.owns_storage()); + auto pv2 = poly.clone(); + res.confirm("cloned poly in vector owns memory", pv2.owns_storage()); + } + + auto pv2 = pv.clone(); + for(const auto& poly : pv2) { + res.confirm("cloned vector polynomial don't own memory", !poly.owns_storage()); + } + + Kyberish_Poly p_ntt; + auto p2_ntt = p_ntt.clone(); + res.confirm("cloned poly (NTT) owns memory", p2_ntt.owns_storage()); + + Kyberish_PolyVec pv_ntt(3); + for(auto& poly : pv_ntt) { + res.require("poly (NTT) in vector does not own memory", !poly.owns_storage()); + auto pv2_ntt = poly.clone(); + res.confirm("cloned poly (NTT) in vector owns memory", pv2_ntt.owns_storage()); + } + + auto pv2_ntt = pv_ntt.clone(); + for(const auto& poly : pv2_ntt) { + res.confirm("cloned vector polynomial (NTT) don't own memory", !poly.owns_storage()); + } + }), + + CHECK("hamming weight of polynomials", + [](Test::Result& res) { + Kyberish_Poly p; + res.test_is_eq("hamming weight of 0", p.hamming_weight(), 0); + + p[0] = 1337; + res.test_is_eq("hamming weight of 1", p.hamming_weight(), 1); + + p[1] = 42; + res.test_is_eq("hamming weight of 2", p.hamming_weight(), 2); + + p[2] = 11; + res.test_is_eq("hamming weight of 3", p.hamming_weight(), 3); + + p[3] = 4; + res.test_is_eq("hamming weight of 4", p.hamming_weight(), 4); + + p[3] = 0; + res.test_is_eq("hamming weight of 3", p.hamming_weight(), 3); + + p[2] = 0; + res.test_is_eq("hamming weight of 2", p.hamming_weight(), 2); + + p[1] = 0; + res.test_is_eq("hamming weight of 1", p.hamming_weight(), 1); + + p[0] = 0; + res.test_is_eq("hamming weight of 0", p.hamming_weight(), 0); + }), + + CHECK("hamming weight of polynomial vectors", + [](Test::Result& res) { + Kyberish_PolyVec pv(3); + res.test_is_eq("hamming weight of 0", pv.hamming_weight(), 0); + + pv[0][0] = 1337; + res.test_is_eq("hamming weight of 1", pv.hamming_weight(), 1); + + pv[1][1] = 42; + res.test_is_eq("hamming weight of 2", pv.hamming_weight(), 2); + + pv[2][2] = 11; + res.test_is_eq("hamming weight of 3", pv.hamming_weight(), 3); + + pv[2][2] = 0; + res.test_is_eq("hamming weight of 2", pv.hamming_weight(), 2); + + pv[1][1] = 0; + res.test_is_eq("hamming weight of 1", pv.hamming_weight(), 1); + + pv[0][0] = 0; + res.test_is_eq("hamming weight of 0", pv.hamming_weight(), 0); + }), + + CHECK("value range validation", + [](Test::Result& res) { + Kyberish_Poly p; + res.confirm("value range validation (all zero)", p.ct_validate_value_range(0, 1)); + + p[0] = 1; + p[32] = 1; + p[172] = 1; + res.confirm("value range validation", p.ct_validate_value_range(0, 1)); + + p[11] = 2; + res.confirm("value range validation", !p.ct_validate_value_range(0, 1)); + + p[11] = -1; + res.confirm("value range validation", !p.ct_validate_value_range(0, 1)); + }), - Botan_Tests::CHECK("value range validation for polynomial vectors", - [](Test::Result& res) { - Kyberish_PolyVec pv(3); - res.confirm("value range validation (all zero)", pv.ct_validate_value_range(0, 1)); + CHECK("value range validation for polynomial vectors", + [](Test::Result& res) { + Kyberish_PolyVec pv(3); + res.confirm("value range validation (all zero)", pv.ct_validate_value_range(0, 1)); - pv[0][0] = 1; - pv[1][32] = 1; - pv[2][172] = 1; - res.confirm("value range validation", pv.ct_validate_value_range(0, 1)); + pv[0][0] = 1; + pv[1][32] = 1; + pv[2][172] = 1; + res.confirm("value range validation", pv.ct_validate_value_range(0, 1)); - pv[0][11] = 2; - res.confirm("value range validation", !pv.ct_validate_value_range(0, 1)); + pv[0][11] = 2; + res.confirm("value range validation", !pv.ct_validate_value_range(0, 1)); - pv[0][11] = -1; - res.confirm("value range validation", !pv.ct_validate_value_range(0, 1)); - }), + pv[0][11] = -1; + res.confirm("value range validation", !pv.ct_validate_value_range(0, 1)); + }), }; } @@ -343,140 +368,140 @@ std::vector test_encoding() { "9C73D049479D75D869C79D77E089479E79E8A9C79E7BF0C9479F7DF8E9C79F7F"); return { - Botan_Tests::CHECK("encode polynomial coefficients into buffer", - [&](Test::Result& res) { - // value range is about 3 bits - Kyberish_Poly p1; - for(size_t i = 0; i < p1.size(); ++i) { - p1[i] = static_cast(i % 7); - } - - std::vector buffer1(96); - Botan::BufferStuffer stuffer1(buffer1); - Botan::CRYSTALS::pack<6>(p1, stuffer1); - res.test_eq("3 bit encoding", buffer1, threebitencoding); - - // value range is exactly one byte - Kyberish_Poly p2; - for(size_t i = 0; i < p2.size(); ++i) { - p2[i] = static_cast(i); - } - - std::vector buffer2(256); - Botan::BufferStuffer stuffer2(buffer2); - Botan::CRYSTALS::pack<255>(p2, stuffer2); - res.test_eq("8 bit encoding", buffer2, eightbitencoding); - - // value range for 10 bits, with mapping function - std::vector buffer3(p2.size() / 8 * 10 /* bits */); - Botan::BufferStuffer stuffer3(buffer3); - Botan::CRYSTALS::pack<512>(p2, stuffer3, [](int16_t x) -> uint16_t { return x * 2; }); - res.test_eq("10 bit encoding", buffer3, tenbitencoding); - }), - - Botan_Tests::CHECK("decode polynomial coefficients from buffer", - [&](Test::Result& res) { - Kyberish_Poly p1; - Botan::BufferSlicer slicer1(threebitencoding); - Botan::CRYSTALS::unpack<6>(p1, slicer1); - res.require("read all bytes from 3-bit encoding", slicer1.empty()); - for(size_t i = 0; i < p1.size(); ++i) { - res.test_is_eq("decoded 3-bit coefficient", p1[i], i % 7); - } - - Kyberish_Poly p2; - Botan::BufferSlicer slicer2(eightbitencoding); - Botan::CRYSTALS::unpack<255>(p2, slicer2); - res.require("read all bytes from 8-bit encoding", slicer2.empty()); - for(size_t i = 0; i < p2.size(); ++i) { - res.test_is_eq("decoded 8-bit coefficient", p2[i], i); - } - - Kyberish_Poly p3; - Botan::BufferSlicer slicer3(tenbitencoding); - Botan::CRYSTALS::unpack<512>(p3, slicer3, [](uint16_t x) -> int16_t { return x / 2; }); - res.require("read all bytes from 10-bit encoding", slicer3.empty()); - for(size_t i = 0; i < p3.size(); ++i) { - res.test_is_eq("decoded 10-bit coefficient with mapping", p3[i], i); - } - }), - - Botan_Tests::CHECK("decode polynomial coefficients from XOF", - [&](Test::Result& res) { - Kyberish_Poly p1; - DeterministicXOF xof1(threebitencoding); - Botan::CRYSTALS::unpack<6>(p1, xof1); - for(size_t i = 0; i < p1.size(); ++i) { - res.test_is_eq("decoded 3-bit coefficient", p1[i], i % 7); - } - - Kyberish_Poly p2; - DeterministicXOF xof2(eightbitencoding); - Botan::CRYSTALS::unpack<255>(p2, xof2); - for(size_t i = 0; i < p2.size(); ++i) { - res.test_is_eq("decoded 8-bit coefficient", p2[i], i); - } - - Kyberish_Poly p3; - DeterministicXOF xof3(tenbitencoding); - Botan::CRYSTALS::unpack<512>(p3, xof3, [](int16_t x) -> int16_t { return x / 2; }); - for(size_t i = 0; i < p3.size(); ++i) { - res.test_is_eq("decoded 10-bit coefficient with mapping", p3[i], i); - } - }), - - Botan_Tests::CHECK("random encoding roundtrips (0 to x)", - [](Test::Result& res) { - auto rng = Test::new_rng("CRYSTALS encoding roundtrips"); - random_encoding_roundtrips(res, *rng, 2); - random_encoding_roundtrips(res, *rng, 3); - random_encoding_roundtrips(res, *rng, 4); - random_encoding_roundtrips(res, *rng, 4); - random_encoding_roundtrips(res, *rng, 5); - random_encoding_roundtrips(res, *rng, 6); - random_encoding_roundtrips(res, *rng, 8); - random_encoding_roundtrips(res, *rng, 11); - }), - - Botan_Tests::CHECK("random encoding roundtrips (Kyber ranges)", - [](Test::Result& res) { - auto rng = Test::new_rng("CRYSTALS encoding roundtrips as used in kyber"); - random_encoding_roundtrips(res, *rng, 1); - random_encoding_roundtrips(res, *rng, 4); - random_encoding_roundtrips(res, *rng, 5); - random_encoding_roundtrips(res, *rng, 10); - random_encoding_roundtrips(res, *rng, 11); - random_encoding_roundtrips(res, *rng, 12); - }), - - Botan_Tests::CHECK("random encoding roundtrips (Dilithium ranges)", - [](Test::Result& res) { - using Dilithiumish_Trait = Mock_Trait; - - auto rng = Test::new_rng("CRYSTALS encoding roundtrips as used in kyber"); - constexpr auto t1 = 1023; - constexpr auto gamma2_32 = 15; - constexpr auto gamma2_88 = 43; - constexpr auto gamma1_17 = 131072; - constexpr auto gamma1_19 = 524288; - constexpr auto eta2 = 2; - constexpr auto eta4 = 4; - constexpr auto twotothed = 4096; - random_encoding_roundtrips(res, *rng, 10); - random_encoding_roundtrips(res, *rng, 4); - random_encoding_roundtrips(res, *rng, 6); - random_encoding_roundtrips(res, *rng, 18); - random_encoding_roundtrips(res, *rng, 20); - random_encoding_roundtrips(res, *rng, 3); - random_encoding_roundtrips(res, *rng, 4); - random_encoding_roundtrips(res, *rng, 13); - }), + CHECK("encode polynomial coefficients into buffer", + [&](Test::Result& res) { + // value range is about 3 bits + Kyberish_Poly p1; + for(size_t i = 0; i < p1.size(); ++i) { + p1[i] = static_cast(i % 7); + } + + std::vector buffer1(96); + Botan::BufferStuffer stuffer1(buffer1); + Botan::CRYSTALS::pack<6>(p1, stuffer1); + res.test_eq("3 bit encoding", buffer1, threebitencoding); + + // value range is exactly one byte + Kyberish_Poly p2; + for(size_t i = 0; i < p2.size(); ++i) { + p2[i] = static_cast(i); + } + + std::vector buffer2(256); + Botan::BufferStuffer stuffer2(buffer2); + Botan::CRYSTALS::pack<255>(p2, stuffer2); + res.test_eq("8 bit encoding", buffer2, eightbitencoding); + + // value range for 10 bits, with mapping function + std::vector buffer3(p2.size() / 8 * 10 /* bits */); + Botan::BufferStuffer stuffer3(buffer3); + Botan::CRYSTALS::pack<512>(p2, stuffer3, [](int16_t x) -> uint16_t { return x * 2; }); + res.test_eq("10 bit encoding", buffer3, tenbitencoding); + }), + + CHECK("decode polynomial coefficients from buffer", + [&](Test::Result& res) { + Kyberish_Poly p1; + Botan::BufferSlicer slicer1(threebitencoding); + Botan::CRYSTALS::unpack<6>(p1, slicer1); + res.require("read all bytes from 3-bit encoding", slicer1.empty()); + for(size_t i = 0; i < p1.size(); ++i) { + res.test_is_eq("decoded 3-bit coefficient", p1[i], i % 7); + } + + Kyberish_Poly p2; + Botan::BufferSlicer slicer2(eightbitencoding); + Botan::CRYSTALS::unpack<255>(p2, slicer2); + res.require("read all bytes from 8-bit encoding", slicer2.empty()); + for(size_t i = 0; i < p2.size(); ++i) { + res.test_is_eq("decoded 8-bit coefficient", p2[i], i); + } + + Kyberish_Poly p3; + Botan::BufferSlicer slicer3(tenbitencoding); + Botan::CRYSTALS::unpack<512>(p3, slicer3, [](uint16_t x) -> int16_t { return x / 2; }); + res.require("read all bytes from 10-bit encoding", slicer3.empty()); + for(size_t i = 0; i < p3.size(); ++i) { + res.test_is_eq("decoded 10-bit coefficient with mapping", p3[i], i); + } + }), + + CHECK("decode polynomial coefficients from XOF", + [&](Test::Result& res) { + Kyberish_Poly p1; + DeterministicXOF xof1(threebitencoding); + Botan::CRYSTALS::unpack<6>(p1, xof1); + for(size_t i = 0; i < p1.size(); ++i) { + res.test_is_eq("decoded 3-bit coefficient", p1[i], i % 7); + } + + Kyberish_Poly p2; + DeterministicXOF xof2(eightbitencoding); + Botan::CRYSTALS::unpack<255>(p2, xof2); + for(size_t i = 0; i < p2.size(); ++i) { + res.test_is_eq("decoded 8-bit coefficient", p2[i], i); + } + + Kyberish_Poly p3; + DeterministicXOF xof3(tenbitencoding); + Botan::CRYSTALS::unpack<512>(p3, xof3, [](int16_t x) -> int16_t { return x / 2; }); + for(size_t i = 0; i < p3.size(); ++i) { + res.test_is_eq("decoded 10-bit coefficient with mapping", p3[i], i); + } + }), + + CHECK("random encoding roundtrips (0 to x)", + [](Test::Result& res) { + auto rng = Test::new_rng("CRYSTALS encoding roundtrips"); + random_encoding_roundtrips(res, *rng, 2); + random_encoding_roundtrips(res, *rng, 3); + random_encoding_roundtrips(res, *rng, 4); + random_encoding_roundtrips(res, *rng, 4); + random_encoding_roundtrips(res, *rng, 5); + random_encoding_roundtrips(res, *rng, 6); + random_encoding_roundtrips(res, *rng, 8); + random_encoding_roundtrips(res, *rng, 11); + }), + + CHECK("random encoding roundtrips (Kyber ranges)", + [](Test::Result& res) { + auto rng = Test::new_rng("CRYSTALS encoding roundtrips as used in kyber"); + random_encoding_roundtrips(res, *rng, 1); + random_encoding_roundtrips(res, *rng, 4); + random_encoding_roundtrips(res, *rng, 5); + random_encoding_roundtrips(res, *rng, 10); + random_encoding_roundtrips(res, *rng, 11); + random_encoding_roundtrips(res, *rng, 12); + }), + + CHECK("random encoding roundtrips (Dilithium ranges)", + [](Test::Result& res) { + using Dilithiumish_Trait = Mock_Trait; + + auto rng = Test::new_rng("CRYSTALS encoding roundtrips as used in kyber"); + constexpr auto t1 = 1023; + constexpr auto gamma2_32 = 15; + constexpr auto gamma2_88 = 43; + constexpr auto gamma1_17 = 131072; + constexpr auto gamma1_19 = 524288; + constexpr auto eta2 = 2; + constexpr auto eta4 = 4; + constexpr auto twotothed = 4096; + random_encoding_roundtrips(res, *rng, 10); + random_encoding_roundtrips(res, *rng, 4); + random_encoding_roundtrips(res, *rng, 6); + random_encoding_roundtrips(res, *rng, 18); + random_encoding_roundtrips(res, *rng, 20); + random_encoding_roundtrips(res, *rng, 3); + random_encoding_roundtrips(res, *rng, 4); + random_encoding_roundtrips(res, *rng, 13); + }), }; } } // namespace -BOTAN_REGISTER_TEST_FN("pubkey", "CRYSTALS", test_extended_euclidean_algorithm, test_polynomial_basics, test_encoding); +BOTAN_REGISTER_TEST_FN("pubkey", "crystals", test_extended_euclidean_algorithm, test_polynomial_basics, test_encoding); } // namespace Botan_Tests