From 3ceac47770918704e81235b642b450db3650fde3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Thu, 20 Jun 2024 10:43:27 +0200 Subject: [PATCH] Rework Kyber using the common CRYSTALS structs --- src/lib/pubkey/kyber/kyber_common/info.txt | 5 +- src/lib/pubkey/kyber/kyber_common/kyber.cpp | 43 +- .../pubkey/kyber/kyber_common/kyber_algos.cpp | 327 ++++++++ .../pubkey/kyber/kyber_common/kyber_algos.h | 116 +++ .../kyber/kyber_common/kyber_constants.cpp | 25 +- .../kyber/kyber_common/kyber_constants.h | 121 +-- .../kyber/kyber_common/kyber_encaps_base.h | 37 +- .../pubkey/kyber/kyber_common/kyber_helpers.h | 75 ++ .../pubkey/kyber/kyber_common/kyber_keys.cpp | 46 +- .../pubkey/kyber/kyber_common/kyber_keys.h | 44 +- .../kyber/kyber_common/kyber_polynomial.h | 115 +++ .../kyber/kyber_common/kyber_structures.h | 775 ------------------ .../kyber_common/kyber_symmetric_primitives.h | 13 +- .../pubkey/kyber/kyber_common/kyber_types.h | 9 + .../kyber/kyber_round3/kyber/kyber_modern.h | 11 +- .../kyber/kyber_round3/kyber_90s/kyber_90s.h | 11 +- .../kyber/kyber_round3/kyber_encaps.cpp | 12 +- .../pubkey/kyber/kyber_round3/kyber_encaps.h | 6 +- src/tests/test_kyber.cpp | 99 +++ 19 files changed, 961 insertions(+), 929 deletions(-) create mode 100644 src/lib/pubkey/kyber/kyber_common/kyber_algos.cpp create mode 100644 src/lib/pubkey/kyber/kyber_common/kyber_algos.h create mode 100644 src/lib/pubkey/kyber/kyber_common/kyber_helpers.h create mode 100644 src/lib/pubkey/kyber/kyber_common/kyber_polynomial.h delete mode 100644 src/lib/pubkey/kyber/kyber_common/kyber_structures.h diff --git a/src/lib/pubkey/kyber/kyber_common/info.txt b/src/lib/pubkey/kyber/kyber_common/info.txt index 2977565f666..f9fa8c585d6 100644 --- a/src/lib/pubkey/kyber/kyber_common/info.txt +++ b/src/lib/pubkey/kyber/kyber_common/info.txt @@ -9,16 +9,19 @@ type -> "Internal" +pqcrystals pubkey hash rng +kyber_algos.h kyber_constants.h kyber_encaps_base.h +kyber_helpers.h kyber_keys.h -kyber_structures.h +kyber_polynomial.h kyber_symmetric_primitives.h kyber_types.h diff --git a/src/lib/pubkey/kyber/kyber_common/kyber.cpp b/src/lib/pubkey/kyber/kyber_common/kyber.cpp index 34f116a43b7..e8caf7086d7 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber.cpp +++ b/src/lib/pubkey/kyber/kyber_common/kyber.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -38,8 +39,6 @@ #include #endif -#include -#include #include #include @@ -154,14 +153,14 @@ std::shared_ptr Kyber_PublicKey::initialize_from_encodi KyberMode m) { KyberConstants mode(m); - if(pub_key.size() != mode.public_key_byte_length()) { + if(pub_key.size() != mode.public_key_bytes()) { throw Invalid_Argument("kyber public key does not have the correct byte count"); } BufferSlicer s(pub_key); - auto poly_vec = s.take(mode.polynomial_vector_byte_length()); - auto seed = s.copy(KyberConstants::kSeedLength); + auto poly_vec = s.take(mode.polynomial_vector_bytes()); + auto seed = s.copy(KyberConstants::SEED_BYTES); BOTAN_ASSERT_NOMSG(s.empty()); return std::make_shared(std::move(mode), poly_vec, std::move(seed)); @@ -188,7 +187,7 @@ std::vector Kyber_PublicKey::public_key_bits() const { size_t Kyber_PublicKey::key_length() const { // TODO: this should report 512, 768, 1024 - return m_public->mode().public_key_byte_length(); + return m_public->mode().public_key_bytes(); } bool Kyber_PublicKey::check_key(RandomNumberGenerator&, bool) const { @@ -207,17 +206,19 @@ Kyber_PrivateKey::Kyber_PrivateKey(RandomNumberGenerator& rng, KyberMode m) { // Algorithm 12 (K-PKE.KeyGen) ---------------- - const auto d = rng.random_vec(KyberConstants::kSymBytes); + const auto d = rng.random_vec(KyberConstants::SEED_BYTES); auto [rho, sigma] = mode.symmetric_primitives().G(d); + KyberPolynomialSampler ps(sigma, mode); - auto a = PolynomialMatrix::generate(rho, false /* not transposed */, mode); - auto s = PolynomialVector::getnoise_eta1(sigma, 0 /* N */, mode); - auto e = PolynomialVector::getnoise_eta1(sigma, mode.k() /* N */, 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 s = ntt(ps.sample_polynomial_vector_cbd_eta1()); + const auto e = ntt(ps.sample_polynomial_vector_cbd_eta1()); - s.ntt(); - e.ntt(); - - auto t = a.pointwise_acc_montgomery(s, true); + auto t = A * s; t += e; t.reduce(); @@ -225,7 +226,7 @@ Kyber_PrivateKey::Kyber_PrivateKey(RandomNumberGenerator& rng, KyberMode m) { m_public = std::make_shared(mode, std::move(t), std::move(rho)); m_private = std::make_shared( - std::move(mode), std::move(s), rng.random_vec(KyberConstants::kZLength)); + std::move(mode), std::move(s), rng.random_vec(KyberConstants::SEED_BYTES)); } Kyber_PrivateKey::Kyber_PrivateKey(const AlgorithmIdentifier& alg_id, std::span key_bits) : @@ -234,16 +235,16 @@ Kyber_PrivateKey::Kyber_PrivateKey(const AlgorithmIdentifier& alg_id, std::span< Kyber_PrivateKey::Kyber_PrivateKey(std::span sk, KyberMode m) { KyberConstants mode(m); - if(mode.private_key_byte_length() != sk.size()) { + if(mode.private_key_bytes() != sk.size()) { throw Invalid_Argument("kyber private key does not have the correct byte count"); } BufferSlicer s(sk); - auto skpv = PolynomialVector::from_bytes(s.take(mode.polynomial_vector_byte_length()), mode); - auto pub_key = s.take(mode.public_key_byte_length()); - auto puk_key_hash = s.take(KyberConstants::kPublicKeyHashLength); - auto z = s.copy(KyberConstants::kZLength); + auto skpv = kyber_decode_polynomial_vector(s.take(mode.polynomial_vector_bytes()), mode); + auto pub_key = s.take(mode.public_key_bytes()); + auto puk_key_hash = s.take(KyberConstants::PUBLIC_KEY_HASH_BYTES); + auto z = s.copy(KyberConstants::SEED_BYTES); BOTAN_ASSERT_NOMSG(s.empty()); @@ -266,7 +267,7 @@ secure_vector Kyber_PrivateKey::raw_private_key_bits() const { } secure_vector Kyber_PrivateKey::private_key_bits() const { - return concat(m_private->s().to_bytes>(), + return concat(kyber_encode_polynomial_vector>(m_private->s().reduce(), m_private->mode()), m_public->public_key_bits_raw(), m_public->H_public_key_bits_raw(), m_private->z()); diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_algos.cpp b/src/lib/pubkey/kyber/kyber_common/kyber_algos.cpp new file mode 100644 index 00000000000..96af78f5b42 --- /dev/null +++ b/src/lib/pubkey/kyber/kyber_common/kyber_algos.cpp @@ -0,0 +1,327 @@ +/* + * Crystals Kyber Internal Algorithms + * Based on the public domain reference implementation by the + * designers (https://github.com/pq-crystals/kyber) + * + * Further changes + * (C) 2021-2024 Jack Lloyd + * (C) 2021-2022 Manuel Glaser and Michael Boric, Rohde & Schwarz Cybersecurity + * (C) 2021-2022 René Meusel and Hannes Rantzsch, neXenio GmbH + * (C) 2024 René Meusel, Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#include + +#include +#include +#include + +namespace Botan { + +namespace { + +/** + * NIST FIPS 203 IPD, Algorithm 4 (ByteEncode) for d < 12 in combination with + * Formula 4.5 (Compress) + */ +template + requires(d < 12) +void kyber_poly_compress_and_encode(BufferStuffer& bs, const KyberPoly& p) { + CRYSTALS::pack<(1 << d) - 1>(p, bs, kyber_compress); +} + +/** + * NIST FIPS 203 IPD, Algorithm 4 (ByteEncode) for d == 12 + */ +void kyber_byte_encode(BufferStuffer& bs, const KyberPolyNTT& p) { + CRYSTALS::pack(p, bs); +} + +/** + * NIST FIPS 203 IPD, Algorithm 5 (ByteDecode) for d < 12 in combination with + * Formula 4.6 (Decompress) + */ +template + requires(d < 12) +void kyber_poly_decode_and_decompress(KyberPoly& p, BufferSlicer& bs) { + CRYSTALS::unpack<(1 << d) - 1>(p, bs, kyber_decompress); +} + +/** + * NIST FIPS 203 IPD, Algorithm 5 (ByteDecode) for d == 12 + */ +void kyber_byte_decode(KyberPolyNTT& p, BufferSlicer& bs) { + CRYSTALS::unpack(p, bs); + + if(!p.ct_validate_value_range(0, KyberConstants::Q - 1)) { + throw Decoding_Error("Decoded polynomial coefficients out of range"); + } +} + +/** + * NIST FIPS 203 IPD, Algorithm 6 (SampleNTT) + */ +void kyber_sample_ntt_uniform(KyberPolyNTT& p, XOF& xof) { + auto sample = [&xof]() -> std::pair { + const auto x = load_le3(xof.output<3>()); + return {static_cast(x) & 0x0FFF, static_cast(x >> 12)}; + }; + + for(size_t count = 0; count < p.size();) { + const auto [d1, d2] = sample(); + + if(d1 < KyberConstants::Q) { + p[count++] = d1; + } + if(count < p.size() && d2 < KyberConstants::Q) { + p[count++] = d2; + } + } +} + +/** + * NIST FIPS 203 IPD, Algorithm 7 (SamplePolyCBD) for eta = 2 + */ +void sample_poly_cbd2(KyberPoly& poly, StrongSpan randomness) { + BufferSlicer bs(randomness); + + for(size_t i = 0; i < poly.size() / 8; ++i) { + const uint32_t t = Botan::load_le(bs.take<4>()); + + // SIMD trick: calculate 16 2-bit-sums in parallel + constexpr uint32_t operand_bitmask = 0b01010101010101010101010101010101; + + // clang-format off + const uint32_t d = ((t >> 0) & operand_bitmask) + + ((t >> 1) & operand_bitmask); + // clang-format on + + for(size_t j = 0; j < 8; ++j) { + const int16_t a = (d >> (4 * j + 0)) & 0x3; + const int16_t b = (d >> (4 * j + 2)) & 0x3; + poly[8 * i + j] = a - b; + } + } + + BOTAN_ASSERT_NOMSG(bs.empty()); +} + +/** + * NIST FIPS 203 IPD, Algorithm 7 (SamplePolyCBD) for eta = 2 + */ +void sample_poly_cbd3(KyberPoly& poly, StrongSpan randomness) { + BufferSlicer bs(randomness); + + for(size_t i = 0; i < poly.size() / 4; ++i) { + const uint32_t t = load_le3(bs.take<3>()); + + // SIMD trick: calculate 8 3-bit-sums in parallel + constexpr uint32_t operand_bitmask = 0b00000000001001001001001001001001; + + // clang-format off + const uint32_t d = ((t >> 0) & operand_bitmask) + + ((t >> 1) & operand_bitmask) + + ((t >> 2) & operand_bitmask); + // clang-format on + + for(size_t j = 0; j < 4; ++j) { + const int16_t a = (d >> (6 * j + 0)) & 0x7; + const int16_t b = (d >> (6 * j + 3)) & 0x7; + poly[4 * i + j] = a - b; + } + } + + BOTAN_ASSERT_NOMSG(bs.empty()); +} + +} // namespace + +void kyber_encode_polynomial_vector(std::span out, const KyberPolyVecNTT& vec) { + BufferStuffer bs(out); + for(auto& v : vec) { + kyber_byte_encode(bs, v); + } + BOTAN_ASSERT_NOMSG(bs.full()); +} + +KyberPolyVecNTT kyber_decode_polynomial_vector(std::span a, const KyberConstants& mode) { + KyberPolyVecNTT vec(mode.k()); + + BufferSlicer bs(a); + for(auto& p : vec) { + kyber_byte_decode(p, bs); + } + BOTAN_ASSERT_NOMSG(bs.empty()); + + return vec; +} + +KyberPoly kyber_polynomial_from_message(StrongSpan msg) { + BOTAN_ASSERT(msg.size() == KyberConstants::N / 8, "message length must be Kyber_N/8 bytes"); + KyberPoly r; + BufferSlicer bs(msg); + kyber_poly_decode_and_decompress<1>(r, bs); + return r; +} + +KyberMessage kyber_polynomial_to_message(const KyberPoly& p) { + KyberMessage result(p.size() / 8); + BufferStuffer bs(result); + kyber_poly_compress_and_encode<1>(bs, p); + return result; +} + +namespace { + +template +void polyvec_compress_and_encode(BufferStuffer& sink, const KyberPolyVec& polyvec) { + for(const auto& p : polyvec) { + kyber_poly_compress_and_encode(sink, p); + } +} + +void compress_polyvec(std::span out, const KyberPolyVec& pv, const KyberConstants& mode) { + BufferStuffer bs(out); + + switch(mode.d_u()) { + case KyberConstants::KyberDu::_10: + polyvec_compress_and_encode<10>(bs, pv); + BOTAN_ASSERT_NOMSG(bs.full()); + return; + case KyberConstants::KyberDu::_11: + polyvec_compress_and_encode<11>(bs, pv); + BOTAN_ASSERT_NOMSG(bs.full()); + return; + } + + BOTAN_ASSERT_UNREACHABLE(); +} + +void compress_poly(std::span out, const KyberPoly& p, const KyberConstants& mode) { + BufferStuffer bs(out); + + switch(mode.d_v()) { + case KyberConstants::KyberDv::_4: + kyber_poly_compress_and_encode<4>(bs, p); + BOTAN_ASSERT_NOMSG(bs.full()); + return; + case KyberConstants::KyberDv::_5: + kyber_poly_compress_and_encode<5>(bs, p); + BOTAN_ASSERT_NOMSG(bs.full()); + return; + } + + BOTAN_ASSERT_UNREACHABLE(); +} + +template +void polyvec_decode_and_decompress(KyberPolyVec& polyvec, BufferSlicer& source) { + for(auto& p : polyvec) { + kyber_poly_decode_and_decompress(p, source); + } +} + +KyberPolyVec decompress_polynomial_vector(std::span buffer, const KyberConstants& mode) { + BOTAN_ASSERT(buffer.size() == mode.polynomial_vector_compressed_bytes(), + "unexpected length of compressed polynomial vector"); + + KyberPolyVec r(mode.k()); + BufferSlicer bs(buffer); + + switch(mode.d_u()) { + case KyberConstants::KyberDu::_10: + polyvec_decode_and_decompress<10>(r, bs); + BOTAN_ASSERT_NOMSG(bs.empty()); + return r; + case KyberConstants::KyberDu::_11: + polyvec_decode_and_decompress<11>(r, bs); + BOTAN_ASSERT_NOMSG(bs.empty()); + return r; + } + + BOTAN_ASSERT_UNREACHABLE(); +} + +KyberPoly kyber_decompress_polynomial(std::span buffer, const KyberConstants& mode) { + BOTAN_ASSERT(buffer.size() == mode.polynomial_compressed_bytes(), "unexpected length of compressed polynomial"); + + KyberPoly r; + BufferSlicer bs(buffer); + + switch(mode.d_v()) { + case KyberConstants::KyberDv::_4: + kyber_poly_decode_and_decompress<4>(r, bs); + BOTAN_ASSERT_NOMSG(bs.empty()); + return r; + case KyberConstants::KyberDv::_5: + kyber_poly_decode_and_decompress<5>(r, bs); + BOTAN_ASSERT_NOMSG(bs.empty()); + return r; + } + + BOTAN_ASSERT_UNREACHABLE(); +} + +} // namespace + +void kyber_compress_ciphertext(StrongSpan out, + const KyberPolyVec& u, + const KyberPoly& v, + const KyberConstants& m_mode) { + BufferStuffer bs(out); + compress_polyvec(bs.next(m_mode.polynomial_vector_compressed_bytes()), u, m_mode); + compress_poly(bs.next(m_mode.polynomial_compressed_bytes()), v, m_mode); + BOTAN_ASSERT_NOMSG(bs.full()); +} + +std::pair kyber_decompress_ciphertext(StrongSpan ct, + const KyberConstants& mode) { + const size_t pvb = mode.polynomial_vector_compressed_bytes(); + const size_t pcb = mode.polynomial_compressed_bytes(); + + if(ct.size() != pvb + pcb) { + throw Decoding_Error("Kyber: unexpected ciphertext length"); + } + + BufferSlicer bs(ct); + auto pv = bs.take(pvb); + auto p = bs.take(pcb); + BOTAN_ASSERT_NOMSG(bs.empty()); + + return {decompress_polynomial_vector(pv, mode), kyber_decompress_polynomial(p, mode)}; +} + +KyberPolyMat kyber_sample_matrix(StrongSpan seed, bool transposed, const KyberConstants& mode) { + BOTAN_ASSERT(seed.size() == KyberConstants::SEED_BYTES, "unexpected seed size"); + + KyberPolyMat mat(mode.k(), mode.k()); + + for(uint8_t i = 0; i < mode.k(); ++i) { + for(uint8_t j = 0; j < mode.k(); ++j) { + const auto pos = (transposed) ? std::tuple(i, j) : std::tuple(j, i); + kyber_sample_ntt_uniform(mat[i][j], mode.symmetric_primitives().XOF(seed, pos)); + } + } + + return mat; +} + +/** + * NIST FIPS 203 IPD, Algorithm 7 (SamplePolyCBD) + */ +void kyber_sample_polynomial_from_cbd(KyberPoly& poly, + KyberConstants::KyberEta eta, + const KyberSamplingRandomness& randomness) { + switch(eta) { + case KyberConstants::KyberEta::_2: + return sample_poly_cbd2(poly, randomness); + case KyberConstants::KyberEta::_3: + return sample_poly_cbd3(poly, randomness); + } + + BOTAN_ASSERT_UNREACHABLE(); +} + +} // namespace Botan diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_algos.h b/src/lib/pubkey/kyber/kyber_common/kyber_algos.h new file mode 100644 index 00000000000..098b4f86c9e --- /dev/null +++ b/src/lib/pubkey/kyber/kyber_common/kyber_algos.h @@ -0,0 +1,116 @@ +/* + * Crystals Kyber Internal Algorithms + * Based on the public domain reference implementation by the + * designers (https://github.com/pq-crystals/kyber) + * + * Further changes + * (C) 2021-2024 Jack Lloyd + * (C) 2021-2022 Manuel Glaser and Michael Boric, Rohde & Schwarz Cybersecurity + * (C) 2021-2022 René Meusel and Hannes Rantzsch, neXenio GmbH + * (C) 2024 René Meusel, Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_KYBER_ALGOS_H_ +#define BOTAN_KYBER_ALGOS_H_ + +#include +#include +#include +#include +#include + +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); + +template > +T kyber_encode_polynomial_vector(const KyberPolyVecNTT& vec, const KyberConstants& mode) { + T r(mode.polynomial_vector_bytes()); + kyber_encode_polynomial_vector(r, vec); + return r; +} + +/** + * Allows sampling multiple polynomials from a single seed via a XOF. + * + * Used in Algorithms 12 (K-PKE.KeyGen) and 13 (K-PKE.Encrypt), and takes care + * of the continuous nonce value internally. + */ +template + requires std::same_as || std::same_as +class KyberPolynomialSampler { + public: + KyberPolynomialSampler(StrongSpan seed, const KyberConstants& mode) : + m_seed(seed), m_mode(mode), m_nonce(0) {} + + KyberPolyVec sample_polynomial_vector_cbd_eta1() { + KyberPolyVec vec(m_mode.k()); + for(auto& poly : vec) { + sample_poly_cbd(poly, m_mode.eta1()); + } + return vec; + } + + KyberPoly sample_polynomial_cbd_eta2() + requires std::same_as + { + KyberPoly poly; + sample_poly_cbd(poly, m_mode.eta2()); + return poly; + } + + KyberPolyVec sample_polynomial_vector_cbd_eta2() + requires std::same_as + { + KyberPolyVec vec(m_mode.k()); + for(auto& poly : vec) { + sample_poly_cbd(poly, m_mode.eta2()); + } + return vec; + } + + private: + KyberSamplingRandomness prf(size_t bytes) { return m_mode.symmetric_primitives().PRF(m_seed, m_nonce++, bytes); } + + void sample_poly_cbd(KyberPoly& poly, KyberConstants::KyberEta eta) { + const auto randomness = [&] { + switch(eta) { + case KyberConstants::KyberEta::_2: + return prf(2 * poly.size() / 4); + case KyberConstants::KyberEta::_3: + return prf(3 * poly.size() / 4); + } + + BOTAN_ASSERT_UNREACHABLE(); + }(); + + kyber_sample_polynomial_from_cbd(poly, eta, randomness); + } + + private: + StrongSpan m_seed; + const KyberConstants& m_mode; + uint8_t m_nonce; +}; + +template +KyberPolynomialSampler(T, const KyberConstants&) -> KyberPolynomialSampler; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_constants.cpp b/src/lib/pubkey/kyber/kyber_common/kyber_constants.cpp index 186c9da05da..b89bd42d488 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber_constants.cpp +++ b/src/lib/pubkey/kyber/kyber_common/kyber_constants.cpp @@ -11,6 +11,8 @@ #include +#include + #if defined(BOTAN_HAS_KYBER) #include #endif @@ -25,23 +27,29 @@ KyberConstants::KyberConstants(KyberMode mode) : m_mode(mode) { switch(mode.mode()) { case KyberMode::Kyber512_R3: case KyberMode::Kyber512_90s: - m_nist_strength = 128; + m_nist_strength = KyberStrength::_128; m_k = 2; - m_eta1 = 3; + m_eta1 = KyberEta::_3; + m_du = KyberDu::_10; + m_dv = KyberDv::_4; break; case KyberMode::Kyber768_R3: case KyberMode::Kyber768_90s: - m_nist_strength = 192; + m_nist_strength = KyberStrength::_192; m_k = 3; - m_eta1 = 2; + m_eta1 = KyberEta::_2; + m_du = KyberDu::_10; + m_dv = KyberDv::_4; break; case KyberMode::Kyber1024_R3: case KyberMode::Kyber1024_90s: - m_nist_strength = 256; + m_nist_strength = KyberStrength::_256; m_k = 4; - m_eta1 = 2; + m_eta1 = KyberEta::_2; + m_du = KyberDu::_11; + m_dv = KyberDv::_5; break; default: @@ -60,6 +68,11 @@ KyberConstants::KyberConstants(KyberMode mode) : m_mode(mode) { } #endif + static_assert(N % 8 == 0); + m_polynomial_vector_bytes = (bitlen(Q) * (N / 8)) * k(); + m_polynomial_vector_compressed_bytes = d_u() * k() * (N / 8); + m_polynomial_compressed_bytes = d_v() * (N / 8); + if(!m_symmetric_primitives) { throw Not_Implemented("requested Kyber mode is not enabled in this build"); } diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_constants.h b/src/lib/pubkey/kyber/kyber_common/kyber_constants.h index eb2da0c122a..96e3e48eeaf 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber_constants.h +++ b/src/lib/pubkey/kyber/kyber_common/kyber_constants.h @@ -14,43 +14,43 @@ #include -#include - namespace Botan { class Kyber_Symmetric_Primitives; class KyberConstants final { public: - static constexpr size_t N = 256; - static constexpr size_t Q = 3329; - static constexpr size_t Q_Inv = 62209; - - static constexpr int16_t zetas[128] = { - 2285, 2571, 2970, 1812, 1493, 1422, 287, 202, 3158, 622, 1577, 182, 962, 2127, 1855, 1468, - 573, 2004, 264, 383, 2500, 1458, 1727, 3199, 2648, 1017, 732, 608, 1787, 411, 3124, 1758, - 1223, 652, 2777, 1015, 2036, 1491, 3047, 1785, 516, 3321, 3009, 2663, 1711, 2167, 126, 1469, - 2476, 3239, 3058, 830, 107, 1908, 3082, 2378, 2931, 961, 1821, 2604, 448, 2264, 677, 2054, - 2226, 430, 555, 843, 2078, 871, 1550, 105, 422, 587, 177, 3094, 3038, 2869, 1574, 1653, - 3083, 778, 1159, 3182, 2552, 1483, 2727, 1119, 1739, 644, 2457, 349, 418, 329, 3173, 3254, - 817, 1097, 603, 610, 1322, 2044, 1864, 384, 2114, 3193, 1218, 1994, 2455, 220, 2142, 1670, - 2144, 1799, 2051, 794, 1819, 2475, 2459, 478, 3221, 3021, 996, 991, 958, 1869, 1522, 1628}; - - static constexpr int16_t zetas_inv[128] = { - 1701, 1807, 1460, 2371, 2338, 2333, 308, 108, 2851, 870, 854, 1510, 2535, 1278, 1530, 1185, - 1659, 1187, 3109, 874, 1335, 2111, 136, 1215, 2945, 1465, 1285, 2007, 2719, 2726, 2232, 2512, - 75, 156, 3000, 2911, 2980, 872, 2685, 1590, 2210, 602, 1846, 777, 147, 2170, 2551, 246, - 1676, 1755, 460, 291, 235, 3152, 2742, 2907, 3224, 1779, 2458, 1251, 2486, 2774, 2899, 1103, - 1275, 2652, 1065, 2881, 725, 1508, 2368, 398, 951, 247, 1421, 3222, 2499, 271, 90, 853, - 1860, 3203, 1162, 1618, 666, 320, 8, 2813, 1544, 282, 1838, 1293, 2314, 552, 2677, 2106, - 1571, 205, 2918, 1542, 2721, 2597, 2312, 681, 130, 1602, 1871, 829, 2946, 3065, 1325, 2756, - 1861, 1474, 1202, 2367, 3147, 1752, 2707, 171, 3127, 3042, 1907, 1836, 1517, 359, 758, 1441}; - - static constexpr size_t kSymBytes = 32; - static constexpr size_t kSeedLength = kSymBytes; - static constexpr size_t kSerializedPolynomialByteLength = N / 2 * 3; - static constexpr size_t kPublicKeyHashLength = 32; - static constexpr size_t kZLength = kSymBytes; + /// base data type for most calculations + using T = int16_t; + + /// number of coefficients in a polynomial + static constexpr T N = 256; + + /// modulus + static constexpr T Q = 3329; + + /// as specified in FIPS 203 (see Algorithm 9 (NTT^-1), f = 128^-1 mod Q) + static constexpr T F = 3303; + + /// the primitive 256-th root of unity modulo Q (see FIPS 203 Section 4.3) + static constexpr T ROOT_OF_UNITY = 17; + + /// degree of the NTT polynomials + static constexpr size_t NTT_Degree = 128; + + public: + static constexpr size_t SEED_BYTES = 32; + static constexpr size_t PUBLIC_KEY_HASH_BYTES = 32; + static constexpr size_t SHARED_KEY_BYTES = 32; + + public: + enum KyberEta : uint8_t { _2 = 2, _3 = 3 }; + + enum KyberDu : uint8_t { _10 = 10, _11 = 11 }; + + enum KyberDv : uint8_t { _4 = 4, _5 = 5 }; + + enum KyberStrength : uint32_t { _128 = 128, _192 = 192, _256 = 256 }; public: KyberConstants(KyberMode mode); @@ -65,40 +65,67 @@ class KyberConstants final { KyberMode mode() const { return m_mode; } - size_t estimated_strength() const { return m_nist_strength; } + /// \name Foundational constants + /// @{ uint8_t k() const { return m_k; } - uint8_t eta1() const { return m_eta1; } + KyberEta eta1() const { return m_eta1; } - uint8_t eta2() const { return 2; } + constexpr KyberEta eta2() const { return KyberEta::_2; } - size_t polynomial_vector_byte_length() const { return kSerializedPolynomialByteLength * k(); } + KyberDu d_u() const { return m_du; } - size_t polynomial_vector_compressed_bytes() const { return (m_k == 2 || m_k == 3) ? m_k * 320 : m_k * 352; } + KyberDv d_v() const { return m_dv; } - size_t polynomial_compressed_bytes() const { return (m_k == 2 || m_k == 3) ? 128 : 160; } + KyberStrength estimated_strength() const { return m_nist_strength; } - size_t public_key_byte_length() const { return polynomial_vector_byte_length() + kSeedLength; } + /// @} - size_t encapsulated_key_length() const { - return polynomial_vector_compressed_bytes() + polynomial_compressed_bytes(); - } + /// \name Sizes of encoded data structures + /// @{ + + /// byte length of an encoded polynomial vector + size_t polynomial_vector_bytes() const { return m_polynomial_vector_bytes; } + + /// byte length of an encoded compressed polynomial vector + size_t polynomial_vector_compressed_bytes() const { return m_polynomial_vector_compressed_bytes; } + + /// byte length of an encoded compressed polynomial + size_t polynomial_compressed_bytes() const { return m_polynomial_compressed_bytes; } + + /// byte length of an encoded ciphertext + size_t ciphertext_bytes() const { return polynomial_vector_compressed_bytes() + polynomial_compressed_bytes(); } + + /// byte length of the shared key + constexpr size_t shared_key_bytes() const { return SHARED_KEY_BYTES; } - size_t shared_key_length() const { return 32; } + /// byte length of an encoded public key + size_t public_key_bytes() const { return polynomial_vector_bytes() + SEED_BYTES; } - size_t private_key_byte_length() const { - return polynomial_vector_byte_length() + public_key_byte_length() + kPublicKeyHashLength + kZLength; + /// byte length of an encoded private key + size_t private_key_bytes() const { + return polynomial_vector_bytes() + public_key_bytes() + PUBLIC_KEY_HASH_BYTES + SEED_BYTES; } + /// @} + Kyber_Symmetric_Primitives& symmetric_primitives() const { return *m_symmetric_primitives; } private: KyberMode m_mode; - std::unique_ptr m_symmetric_primitives; - size_t m_nist_strength; + + KyberStrength m_nist_strength; + KyberEta m_eta1; + KyberDu m_du; + KyberDv m_dv; uint8_t m_k; - uint8_t m_eta1; + + uint32_t m_polynomial_vector_bytes; + uint32_t m_polynomial_vector_compressed_bytes; + uint32_t m_polynomial_compressed_bytes; + + std::unique_ptr m_symmetric_primitives; }; } // namespace Botan diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_encaps_base.h b/src/lib/pubkey/kyber/kyber_common/kyber_encaps_base.h index c61bd98c0ba..57a239f7a72 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber_encaps_base.h +++ b/src/lib/pubkey/kyber/kyber_common/kyber_encaps_base.h @@ -10,17 +10,31 @@ #define BOTAN_KYBER_KEY_ENCAPSULATION_BASE_H_ #include -#include -#include +#include #include namespace Botan { -class Kyber_KEM_Encryptor_Base : public PK_Ops::KEM_Encryption_with_KDF { +class Kyber_KEM_Operation_Base { + protected: + Kyber_KEM_Operation_Base(const Kyber_PublicKeyInternal& pk) : + m_At(kyber_sample_matrix(pk.rho(), true /* transposed */, pk.mode())) {} + + const KyberPolyMat& precomputed_matrix_At() const { return m_At; } + + private: + // The public key's matrix is pre-computed to avoid redundant work when + // encapsulating multiple keys. This matrix is needed for encapsulation as + // well as for the FO transform in the decapsulation. + KyberPolyMat m_At; +}; + +class Kyber_KEM_Encryptor_Base : public PK_Ops::KEM_Encryption_with_KDF, + protected Kyber_KEM_Operation_Base { public: - size_t raw_kem_shared_key_length() const override { return mode().shared_key_length(); } + size_t raw_kem_shared_key_length() const override { return mode().shared_key_bytes(); } - size_t encapsulated_key_length() const override { return mode().encapsulated_key_length(); } + size_t encapsulated_key_length() const override { return mode().ciphertext_bytes(); } void raw_kem_encrypt(std::span out_encapsulated_key, std::span out_shared_key, @@ -31,7 +45,8 @@ class Kyber_KEM_Encryptor_Base : public PK_Ops::KEM_Encryption_with_KDF { } protected: - Kyber_KEM_Encryptor_Base(std::string_view kdf) : PK_Ops::KEM_Encryption_with_KDF(kdf) {} + Kyber_KEM_Encryptor_Base(std::string_view kdf, const Kyber_PublicKeyInternal& pk) : + PK_Ops::KEM_Encryption_with_KDF(kdf), Kyber_KEM_Operation_Base(pk) {} virtual void encapsulate(StrongSpan out_encapsulated_key, StrongSpan out_shared_key, @@ -40,11 +55,12 @@ class Kyber_KEM_Encryptor_Base : public PK_Ops::KEM_Encryption_with_KDF { virtual const KyberConstants& mode() const = 0; }; -class Kyber_KEM_Decryptor_Base : public PK_Ops::KEM_Decryption_with_KDF { +class Kyber_KEM_Decryptor_Base : public PK_Ops::KEM_Decryption_with_KDF, + protected Kyber_KEM_Operation_Base { public: - size_t raw_kem_shared_key_length() const override { return mode().shared_key_length(); } + size_t raw_kem_shared_key_length() const override { return mode().shared_key_bytes(); } - size_t encapsulated_key_length() const override { return mode().encapsulated_key_length(); } + size_t encapsulated_key_length() const override { return mode().ciphertext_bytes(); } void raw_kem_decrypt(std::span out_shared_key, std::span encapsulated_key) final { decapsulate(StrongSpan(out_shared_key), @@ -52,7 +68,8 @@ class Kyber_KEM_Decryptor_Base : public PK_Ops::KEM_Decryption_with_KDF { } protected: - Kyber_KEM_Decryptor_Base(std::string_view kdf) : PK_Ops::KEM_Decryption_with_KDF(kdf) {} + Kyber_KEM_Decryptor_Base(std::string_view kdf, const Kyber_PublicKeyInternal& pk) : + PK_Ops::KEM_Decryption_with_KDF(kdf), Kyber_KEM_Operation_Base(pk) {} virtual void decapsulate(StrongSpan out_shared_key, StrongSpan encapsulated_key) = 0; diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_helpers.h b/src/lib/pubkey/kyber/kyber_common/kyber_helpers.h new file mode 100644 index 00000000000..05e298dc7a5 --- /dev/null +++ b/src/lib/pubkey/kyber/kyber_common/kyber_helpers.h @@ -0,0 +1,75 @@ +/* + * Crystals Kyber Internal Helpers + * + * Further changes + * (C) 2024 Jack Lloyd + * (C) 2024 René Meusel, Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_KYBER_HELPERS_H_ +#define BOTAN_KYBER_HELPERS_H_ + +#include +#include +#include +#include + +namespace Botan { + +/** + * Special load_le<> that takes 3 bytes and returns a 32-bit integer. + */ +inline uint32_t load_le3(std::span in) { + return Botan::load_le(std::array{in[0], in[1], in[2], 0}); +} + +/** + * NIST FIPS 203 IPD, Formula 4.5 (Compress) + */ +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; + + // 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. + // + // 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); + }; + + constexpr unsigned_T mask = (1 << d) - 1; + return divide_by_q((static_cast(x) << d) + KyberConstants::Q / 2) & mask; +} + +/** + * NIST FIPS 203 IPD, Formula 4.6 (Decompress) + */ +template + requires(d > 0 && d < 12) +constexpr KyberConstants::T kyber_decompress(std::make_unsigned_t x) { + BOTAN_DEBUG_ASSERT(x >= 0 && x < (1 << d)); + + constexpr uint32_t offset = 1 << (d - 1); + constexpr uint32_t mask = (1 << d) - 1; + return static_cast(((static_cast(x) & mask) * KyberConstants::Q + offset) >> d); +} + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_keys.cpp b/src/lib/pubkey/kyber/kyber_common/kyber_keys.cpp index 9908edce19d..428b85bed10 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber_keys.cpp +++ b/src/lib/pubkey/kyber/kyber_common/kyber_keys.cpp @@ -16,55 +16,47 @@ namespace Botan { -Kyber_PublicKeyInternal::Kyber_PublicKeyInternal(KyberConstants mode, PolynomialVector t, KyberSeedRho rho) : +Kyber_PublicKeyInternal::Kyber_PublicKeyInternal(KyberConstants mode, KyberPolyVecNTT t, KyberSeedRho rho) : m_mode(std::move(mode)), m_t(std::move(t)), m_rho(std::move(rho)), - m_public_key_bits_raw(concat(m_t.to_bytes(), m_rho)), + m_public_key_bits_raw(concat(kyber_encode_polynomial_vector>(m_t, m_mode), m_rho)), m_H_public_key_bits_raw(m_mode.symmetric_primitives().H(m_public_key_bits_raw)) {} /** * NIST FIPS 203 IPD, Algorithm 13 (K-PKE.Encrypt) */ -Ciphertext Kyber_PublicKeyInternal::indcpa_encrypt(StrongSpan m, - StrongSpan r) const { - auto at = PolynomialMatrix::generate(m_rho, true /* transposed */, m_mode); +void Kyber_PublicKeyInternal::indcpa_encrypt(StrongSpan out_ct, + StrongSpan m, + StrongSpan r, + const KyberPolyMat& At) const { + KyberPolynomialSampler ps(r, m_mode); - auto rv = PolynomialVector::getnoise_eta1(r, 0, m_mode); - auto e1 = PolynomialVector::getnoise_eta2(r, m_mode.k(), m_mode); - auto e2 = Polynomial::getnoise_eta2(r, 2 * m_mode.k(), m_mode); + const auto rv = ntt(ps.sample_polynomial_vector_cbd_eta1()); + const auto e1 = ps.sample_polynomial_vector_cbd_eta2(); + const auto e2 = ps.sample_polynomial_cbd_eta2(); - rv.ntt(); - - auto u = at.pointwise_acc_montgomery(rv); - u.invntt_tomont(); + auto u = inverse_ntt(At * rv); u += e1; u.reduce(); - auto mu = Polynomial::from_message(m); - auto v = PolynomialVector::pointwise_acc_montgomery(m_t, rv); - v.invntt_tomont(); + const auto mu = kyber_polynomial_from_message(m); + auto v = inverse_ntt(m_t * rv); v += e2; v += mu; v.reduce(); - return Ciphertext(std::move(u), v, m_mode); + kyber_compress_ciphertext(out_ct, u, v, m_mode); } /** * NIST FIPS 203 IPD, Algorithm 14 (K-PKE.Decrypt) */ -KyberMessage Kyber_PrivateKeyInternal::indcpa_decrypt(Ciphertext ct) const { - auto& u = ct.b(); - const auto& v = ct.v(); - - u.ntt(); - auto w = PolynomialVector::pointwise_acc_montgomery(m_s, u); - w.invntt_tomont(); - - w -= v; - w.reduce(); - return w.to_message(); +KyberMessage Kyber_PrivateKeyInternal::indcpa_decrypt(StrongSpan ct) const { + auto [u, v] = kyber_decompress_ciphertext(ct, m_mode); + v -= inverse_ntt(m_s * ntt(std::move(u))); + v.reduce(); + return kyber_polynomial_to_message(v); } } // namespace Botan diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_keys.h b/src/lib/pubkey/kyber/kyber_common/kyber_keys.h index 086d4ec59cd..4c972074208 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber_keys.h +++ b/src/lib/pubkey/kyber/kyber_common/kyber_keys.h @@ -12,22 +12,40 @@ #ifndef BOTAN_KYBER_INTERNAL_KEYS_H_ #define BOTAN_KYBER_INTERNAL_KEYS_H_ +#include #include -#include #include namespace Botan { class Kyber_PublicKeyInternal { public: - Kyber_PublicKeyInternal(KyberConstants mode, PolynomialVector polynomials, KyberSeedRho seed); + Kyber_PublicKeyInternal(KyberConstants mode, KyberPolyVecNTT polynomials, KyberSeedRho seed); Kyber_PublicKeyInternal(const KyberConstants& mode, std::span polynomials, KyberSeedRho seed) : - Kyber_PublicKeyInternal(mode, PolynomialVector::from_bytes(polynomials, mode), std::move(seed)) {} - - Ciphertext indcpa_encrypt(StrongSpan m, StrongSpan r) const; - - const PolynomialVector& t() const { return m_t; } + Kyber_PublicKeyInternal(mode, kyber_decode_polynomial_vector(polynomials, mode), std::move(seed)) {} + + Kyber_PublicKeyInternal(const Kyber_PublicKeyInternal& other) : + m_mode(other.m_mode), + m_t(other.m_t.clone()), + m_rho(other.m_rho), + m_public_key_bits_raw(other.m_public_key_bits_raw), + m_H_public_key_bits_raw(other.m_H_public_key_bits_raw) {} + + void indcpa_encrypt(StrongSpan out_ct, + StrongSpan m, + StrongSpan r, + const KyberPolyMat& At) const; + + KyberCompressedCiphertext indcpa_encrypt(const KyberMessage& m, + const KyberEncryptionRandomness& r, + const KyberPolyMat& At) const { + KyberCompressedCiphertext ct(m_mode.ciphertext_bytes()); + indcpa_encrypt(ct, m, r, At); + return ct; + } + + const KyberPolyVecNTT& t() const { return m_t; } const KyberSeedRho& rho() const { return m_rho; } @@ -41,7 +59,7 @@ class Kyber_PublicKeyInternal { private: const KyberConstants m_mode; - PolynomialVector m_t; + KyberPolyVecNTT m_t; const KyberSeedRho m_rho; const KyberSerializedPublicKey m_public_key_bits_raw; const KyberHashedPublicKey m_H_public_key_bits_raw; @@ -49,14 +67,14 @@ class Kyber_PublicKeyInternal { class Kyber_PrivateKeyInternal { public: - Kyber_PrivateKeyInternal(KyberConstants mode, PolynomialVector s, KyberImplicitRejectionValue z) : + Kyber_PrivateKeyInternal(KyberConstants mode, KyberPolyVecNTT s, KyberImplicitRejectionValue z) : m_mode(std::move(mode)), m_s(std::move(s)), m_z(std::move(z)) {} - KyberMessage indcpa_decrypt(Ciphertext ct) const; + KyberMessage indcpa_decrypt(StrongSpan ct) const; - PolynomialVector& s() { return m_s; } + KyberPolyVecNTT& s() { return m_s; } - const PolynomialVector& s() const { return m_s; } + const KyberPolyVecNTT& s() const { return m_s; } const KyberImplicitRejectionValue& z() const { return m_z; } @@ -66,7 +84,7 @@ class Kyber_PrivateKeyInternal { private: KyberConstants m_mode; - PolynomialVector m_s; + KyberPolyVecNTT m_s; KyberImplicitRejectionValue m_z; }; diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_polynomial.h b/src/lib/pubkey/kyber/kyber_common/kyber_polynomial.h new file mode 100644 index 00000000000..94e1d7ed1ff --- /dev/null +++ b/src/lib/pubkey/kyber/kyber_common/kyber_polynomial.h @@ -0,0 +1,115 @@ +/* + * Crystals Kyber Polynomial Adapter + * + * (C) 2021-2024 Jack Lloyd + * (C) 2021-2022 Manuel Glaser and Michael Boric, Rohde & Schwarz Cybersecurity + * (C) 2021-2022 René Meusel and Hannes Rantzsch, neXenio GmbH + * (C) 2024 René Meusel, Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_KYBER_POLYNOMIAL_H_ +#define BOTAN_KYBER_POLYNOMIAL_H_ + +#include +#include +#include +#include +#include + +namespace Botan { + +class Kyber_Symmetric_Primitives; + +class KyberPolyTraits final : public CRYSTALS::Trait_Base { + private: + friend class CRYSTALS::Trait_Base; + + constexpr static T montgomery_reduce_coefficient(T2 a) { + const T u = static_cast(a) * Q_inverse; + auto t = static_cast(u) * Q; + t = a - t; + t >>= sizeof(T) * 8; + return static_cast(t); + } + + constexpr static T barrett_reduce_coefficient(T a) { + constexpr T2 v = ((1U << 26) + Q / 2) / Q; + const T t = (v * a >> 26) * Q; + return a - t; + } + + public: + /** + * NIST FIPS 203 IPD, Algorithm 8 (NTT) + */ + constexpr static void ntt(std::span p) { + for(size_t len = N / 2, k = 0; len >= 2; len /= 2) { + for(size_t start = 0, j = 0; start < N; start = j + len) { + const auto zeta = zetas[++k]; + for(j = start; j < start + len; ++j) { + const auto t = fqmul(zeta, p[j + len]); + p[j + len] = p[j] - t; + p[j] = p[j] + t; + } + } + } + + barrett_reduce(p); + } + + /** + * NIST FIPS 203 IPD, Algorithm 9 (NTT^-1) + */ + static constexpr void inverse_ntt(std::span p) { + for(size_t len = 2, k = 127; len <= N / 2; len *= 2) { + for(size_t start = 0, j = 0; start < N; start = j + len) { + const auto zeta = zetas[k--]; + for(j = start; j < start + len; ++j) { + const auto t = p[j]; + p[j] = barrett_reduce_coefficient(t + p[j + len]); + p[j + len] = fqmul(zeta, p[j + len] - t); + } + } + } + + for(auto& c : p) { + c = fqmul(c, F_WITH_MONTY_SQUARED); + } + } + + /** + * NIST FIPS 203 IPD, Algorithms 10 (MultiplyNTTs) and 11 (BaseCaseMultiply) + */ + static constexpr void poly_pointwise_montgomery(std::span result, + std::span lhs, + std::span rhs) { + /** + * NIST FIPS 203 IPD, Algorithm 11 (BaseCaseMultiply) + */ + auto basemul = [](const auto s, const auto t, const T zeta) -> std::tuple { + return {fqmul(fqmul(s[1], t[1]), zeta) + fqmul(s[0], t[0]), fqmul(s[0], t[1]) + fqmul(s[1], t[0])}; + }; + + auto Tq_elem_count = [](auto p) { return p.size() / 2; }; + + auto Tq_elem = [](auto p, size_t i) { + if constexpr(std::is_const_v) { + return std::array{p[2 * i], p[2 * i + 1]}; + } else { + return std::tuple{p[2 * i], p[2 * i + 1]}; + } + }; + + for(size_t i = 0; i < Tq_elem_count(result) / 2; ++i) { + const auto zeta = zetas[64 + i]; + Tq_elem(result, 2 * i) = basemul(Tq_elem(lhs, 2 * i), Tq_elem(rhs, 2 * i), zeta); + Tq_elem(result, 2 * i + 1) = basemul(Tq_elem(lhs, 2 * i + 1), Tq_elem(rhs, 2 * i + 1), -zeta); + } + } +}; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_structures.h b/src/lib/pubkey/kyber/kyber_common/kyber_structures.h deleted file mode 100644 index 6ba02401a9d..00000000000 --- a/src/lib/pubkey/kyber/kyber_common/kyber_structures.h +++ /dev/null @@ -1,775 +0,0 @@ -/* - * Crystals Kyber Structures - * Based on the public domain reference implementation by the - * designers (https://github.com/pq-crystals/kyber) - * - * Further changes - * (C) 2021-2024 Jack Lloyd - * (C) 2021-2022 Manuel Glaser and Michael Boric, Rohde & Schwarz Cybersecurity - * (C) 2021-2022 René Meusel and Hannes Rantzsch, neXenio GmbH - * (C) 2024 René Meusel, Rohde & Schwarz Cybersecurity - * - * Botan is released under the Simplified BSD License (see license.txt) - */ - -#ifndef BOTAN_KYBER_STRUCTURES_H_ -#define BOTAN_KYBER_STRUCTURES_H_ - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -namespace Botan { - -namespace detail { - -/** - * Constant time implementation for computing an unsigned integer division - * with KyberConstants::Q = 3329. - * - * It enforces the optimization of various compilers, - * replacing the division operation with multiplication and shifts. - * - * This implementation is only valid for integers <= 2**20 - * - * @returns (a / KyberConstants::Q) - */ -inline constexpr uint16_t ct_int_div_kyber_q(uint32_t a) { - BOTAN_DEBUG_ASSERT(a < (1 << 18)); - - /* - Constants based on "Hacker's Delight" (Second Edition) by Henry - S. Warren, Jr. Chapter 10-9 "Unsigned Division by Divisors >= 1" - */ - const uint64_t m = 161271; - const size_t p = 29; - return static_cast((a * m) >> p); -} - -} // namespace detail - -class Polynomial { - public: - Polynomial() : m_coeffs({0}) {} - - /** - * Applies conditional subtraction of q to each coefficient of the polynomial. - */ - void csubq() { - for(auto& coeff : m_coeffs) { - coeff -= KyberConstants::Q; - coeff += (coeff >> 15) & KyberConstants::Q; - } - } - - /** - * Applies Barrett reduction to all coefficients of the polynomial - */ - void reduce() { - for(auto& c : m_coeffs) { - c = barrett_reduce(c); - } - } - - void to_bytes(std::span out) { - this->csubq(); - - BufferStuffer bs(out); - for(size_t i = 0; i < size() / 2; ++i) { - const uint16_t t0 = m_coeffs[2 * i]; - const uint16_t t1 = m_coeffs[2 * i + 1]; - auto buf = bs.next<3>(); - buf[0] = static_cast(t0 >> 0); - buf[1] = static_cast((t0 >> 8) | (t1 << 4)); - buf[2] = static_cast(t1 >> 4); - } - BOTAN_ASSERT_NOMSG(bs.full()); - } - - /** - * Given an array of uniformly random bytes, compute polynomial with coefficients - * distributed according to a centered binomial distribution with parameter eta=2 - */ - static Polynomial cbd2(StrongSpan buf) { - Polynomial r; - - BOTAN_ASSERT(buf.size() == (2 * r.size() / 4), "wrong input buffer size for cbd2"); - - BufferSlicer bs(buf); - for(size_t i = 0; i < r.size() / 8; ++i) { - uint32_t t = load_le(bs.take<4>()); - uint32_t d = t & 0x55555555; - d += (t >> 1) & 0x55555555; - - for(size_t j = 0; j < 8; ++j) { - int16_t a = (d >> (4 * j + 0)) & 0x3; - int16_t b = (d >> (4 * j + 2)) & 0x3; - r.m_coeffs[8 * i + j] = a - b; - } - } - BOTAN_ASSERT_NOMSG(bs.empty()); - - return r; - } - - /** - * Given an array of uniformly random bytes, compute polynomial with coefficients - * distributed according to a centered binomial distribution with parameter eta=3 - * - * This function is only needed for Kyber-512 - */ - static Polynomial cbd3(StrongSpan buf) { - Polynomial r; - - BOTAN_ASSERT(buf.size() == (3 * r.size() / 4), "wrong input buffer size for cbd3"); - - // Note: load_le<> does not support loading a 3-byte value - const auto load_le = [](std::span in) { return make_uint32(0, in[2], in[1], in[0]); }; - - BufferSlicer bs(buf); - for(size_t i = 0; i < r.size() / 4; ++i) { - uint32_t t = load_le(bs.take<3>()); - uint32_t d = t & 0x00249249; - d += (t >> 1) & 0x00249249; - d += (t >> 2) & 0x00249249; - - for(size_t j = 0; j < 4; ++j) { - int16_t a = (d >> (6 * j + 0)) & 0x7; - int16_t b = (d >> (6 * j + 3)) & 0x7; - r.m_coeffs[4 * i + j] = a - b; - } - } - BOTAN_ASSERT_NOMSG(bs.empty()); - - return r; - } - - /** - * Sample a polynomial deterministically from a seed and a nonce, with output - * polynomial close to centered binomial distribution with parameter eta=2. - */ - static Polynomial getnoise_eta2(StrongSpan seed, - uint8_t nonce, - const KyberConstants& mode) { - const auto eta2 = mode.eta2(); - BOTAN_ASSERT(eta2 == 2, "Invalid eta2 value"); - - const auto outlen = eta2 * KyberConstants::N / 4; - return Polynomial::cbd2(mode.symmetric_primitives().PRF(seed, nonce, outlen)); - } - - /** - * Sample a polynomial deterministically from a seed and a nonce, with output - * polynomial close to centered binomial distribution with parameter mode.eta1() - */ - static Polynomial getnoise_eta1(KyberSigmaOrEncryptionRandomness seed, - uint8_t nonce, - const KyberConstants& mode) { - const auto eta1 = mode.eta1(); - BOTAN_ASSERT(eta1 == 2 || eta1 == 3, "Invalid eta1 value"); - - const auto outlen = eta1 * KyberConstants::N / 4; - return (eta1 == 2) ? Polynomial::cbd2(mode.symmetric_primitives().PRF(seed, nonce, outlen)) - : Polynomial::cbd3(mode.symmetric_primitives().PRF(seed, nonce, outlen)); - } - - static Polynomial from_bytes(std::span a) { - Polynomial r; - for(size_t i = 0; i < r.size() / 2; ++i) { - r.m_coeffs[2 * i] = ((a[3 * i + 0] >> 0) | (static_cast(a[3 * i + 1]) << 8)) & 0xFFF; - r.m_coeffs[2 * i + 1] = ((a[3 * i + 1] >> 4) | (static_cast(a[3 * i + 2]) << 4)) & 0xFFF; - } - return r; - } - - static Polynomial from_message(StrongSpan msg) { - BOTAN_ASSERT(msg.size() == KyberConstants::N / 8, "message length must be Kyber_N/8 bytes"); - - Polynomial r; - for(size_t i = 0; i < r.size() / 8; ++i) { - for(size_t j = 0; j < 8; ++j) { - const auto mask = CT::Mask::is_zero((msg[i] >> j) & 1); - r.m_coeffs[8 * i + j] = mask.if_not_set_return((KyberConstants::Q + 1) / 2); - } - } - return r; - } - - KyberMessage to_message() { - KyberMessage result(size() / 8); - - this->csubq(); - - for(size_t i = 0; i < size() / 8; ++i) { - result[i] = 0; - for(size_t j = 0; j < 8; ++j) { - const uint16_t t = detail::ct_int_div_kyber_q((static_cast(this->m_coeffs[8 * i + j]) << 1) + - KyberConstants::Q / 2); - result[i] |= (t & 1) << j; - } - } - - return result; - } - - /** - * Adds two polynomials element-wise. Does not perform a reduction after the addition. - * Therefore this operation might cause an integer overflow. - */ - Polynomial& operator+=(const Polynomial& other) { - for(size_t i = 0; i < this->size(); ++i) { - BOTAN_DEBUG_ASSERT(static_cast(this->m_coeffs[i]) + other.m_coeffs[i] <= - std::numeric_limits::max()); - this->m_coeffs[i] = this->m_coeffs[i] + other.m_coeffs[i]; - } - return *this; - } - - /** - * Subtracts two polynomials element-wise. Does not perform a reduction after the subtraction. - * Therefore this operation might cause an integer underflow. - */ - Polynomial& operator-=(const Polynomial& other) { - for(size_t i = 0; i < this->size(); ++i) { - BOTAN_DEBUG_ASSERT(static_cast(other.m_coeffs[i]) - this->m_coeffs[i] >= - std::numeric_limits::min()); - this->m_coeffs[i] = other.m_coeffs[i] - this->m_coeffs[i]; - } - return *this; - } - - /** - * Multiplication of two polynomials in NTT domain - */ - static Polynomial basemul_montgomery(const Polynomial& a, const Polynomial& b) { - /** - * Multiplication of polynomials in Zq[X]/(X^2-zeta) used for - * multiplication of elements in Rq in NTT domain. - */ - auto basemul = [](int16_t r[2], const int16_t s[2], const int16_t t[2], const int16_t zeta) { - r[0] = fqmul(s[1], t[1]); - r[0] = fqmul(r[0], zeta); - r[0] += fqmul(s[0], t[0]); - - r[1] = fqmul(s[0], t[1]); - r[1] += fqmul(s[1], t[0]); - }; - - Polynomial r; - - for(size_t i = 0; i < r.size() / 4; ++i) { - basemul(&r.m_coeffs[4 * i], &a.m_coeffs[4 * i], &b.m_coeffs[4 * i], KyberConstants::zetas[64 + i]); - basemul( - &r.m_coeffs[4 * i + 2], &a.m_coeffs[4 * i + 2], &b.m_coeffs[4 * i + 2], -KyberConstants::zetas[64 + i]); - } - - return r; - } - - /** - * Run rejection sampling on uniform random bytes to generate uniform - * random integers mod q. - */ - static Polynomial sample_rej_uniform(std::unique_ptr xof) { - Polynomial p; - - size_t count = 0; - while(count < p.size()) { - std::array buf; - xof->output(buf); - - const uint16_t val0 = ((buf[0] >> 0) | (static_cast(buf[1]) << 8)) & 0xFFF; - const uint16_t val1 = ((buf[1] >> 4) | (static_cast(buf[2]) << 4)) & 0xFFF; - - if(val0 < KyberConstants::Q) { - p.m_coeffs[count++] = val0; - } - if(count < p.size() && val1 < KyberConstants::Q) { - p.m_coeffs[count++] = val1; - } - } - - return p; - } - - /** - * Inplace conversion of all coefficients of a polynomial from normal - * domain to Montgomery domain. - */ - void tomont() { - constexpr int16_t f = (1ULL << 32) % KyberConstants::Q; - for(auto& c : m_coeffs) { - c = montgomery_reduce(static_cast(c) * f); - } - } - - /** - * Computes negacyclic number-theoretic transform (NTT) of a polynomial in place; - * inputs assumed to be in normal order, output in bitreversed order. - */ - void ntt() { - for(size_t len = size() / 2, k = 0; len >= 2; len /= 2) { - for(size_t start = 0, j = 0; start < size(); start = j + len) { - const auto zeta = KyberConstants::zetas[++k]; - for(j = start; j < start + len; ++j) { - const auto t = fqmul(zeta, m_coeffs[j + len]); - m_coeffs[j + len] = m_coeffs[j] - t; - m_coeffs[j] = m_coeffs[j] + t; - } - } - } - - reduce(); - } - - /** - * Computes inverse of negacyclic number-theoretic transform (NTT) of a polynomial - * in place; inputs assumed to be in bitreversed order, output in normal order. - */ - void invntt_tomont() { - for(size_t len = 2, k = 0; len <= size() / 2; len *= 2) { - for(size_t start = 0, j = 0; start < size(); start = j + len) { - const auto zeta = KyberConstants::zetas_inv[k++]; - for(j = start; j < start + len; ++j) { - const auto t = m_coeffs[j]; - m_coeffs[j] = barrett_reduce(t + m_coeffs[j + len]); - m_coeffs[j + len] = fqmul(zeta, t - m_coeffs[j + len]); - } - } - } - - for(auto& c : m_coeffs) { - c = fqmul(c, KyberConstants::zetas_inv[127]); - } - } - - size_t size() const { return m_coeffs.size(); } - - int16_t operator[](size_t idx) const { return m_coeffs[idx]; } - - int16_t& operator[](size_t idx) { return m_coeffs[idx]; } - - private: - /** - * Barrett reduction; given a 16-bit integer a, computes 16-bit integer congruent - * to a mod q in {0,...,q}. - */ - static int16_t barrett_reduce(int16_t a) { - constexpr int32_t v = ((1U << 26) + KyberConstants::Q / 2) / KyberConstants::Q; - const int16_t t = (v * a >> 26) * KyberConstants::Q; - return a - t; - } - - /** - * Multiplication followed by Montgomery reduction. - */ - static int16_t fqmul(int16_t a, int16_t b) { return montgomery_reduce(static_cast(a) * b); } - - /** - * Montgomery reduction; given a 32-bit integer a, computes 16-bit integer - * congruent to a * R^-1 mod q, where R=2^16 - */ - static int16_t montgomery_reduce(int32_t a) { - const int16_t u = static_cast(a * KyberConstants::Q_Inv); - int32_t t = static_cast(u) * KyberConstants::Q; - t = a - t; - t >>= 16; - return static_cast(t); - } - - std::array m_coeffs; -}; - -class PolynomialVector { - public: - PolynomialVector() = delete; - - explicit PolynomialVector(const size_t k) : m_vec(k) {} - - public: - static PolynomialVector from_bytes(std::span a, const KyberConstants& mode) { - BOTAN_ASSERT(a.size() == mode.polynomial_vector_byte_length(), "wrong byte length for frombytes"); - - PolynomialVector r(mode.k()); - - BufferSlicer bs(a); - for(size_t i = 0; i < mode.k(); ++i) { - r.m_vec[i] = Polynomial::from_bytes(bs.take(KyberConstants::kSerializedPolynomialByteLength)); - } - BOTAN_ASSERT_NOMSG(bs.empty()); - - return r; - } - - /** - * Pointwise multiply elements of a and b, accumulate into r, and multiply by 2^-16. - */ - static Polynomial pointwise_acc_montgomery(const PolynomialVector& a, const PolynomialVector& b) { - BOTAN_ASSERT(a.m_vec.size() == b.m_vec.size(), - "pointwise_acc_montgomery works on equally sized " - "PolynomialVectors only"); - - Polynomial r; - for(size_t i = 0; i < a.m_vec.size(); ++i) { - r += Polynomial::basemul_montgomery(a.m_vec[i], b.m_vec[i]); - } - r.reduce(); - return r; - } - - static PolynomialVector getnoise_eta2(StrongSpan seed, - uint8_t nonce, - const KyberConstants& mode) { - PolynomialVector r(mode.k()); - for(auto& p : r.m_vec) { - p = Polynomial::getnoise_eta2(seed, nonce++, mode); - } - return r; - } - - static PolynomialVector getnoise_eta1(KyberSigmaOrEncryptionRandomness seed, - uint8_t nonce, - const KyberConstants& mode) { - PolynomialVector r(mode.k()); - for(auto& p : r.m_vec) { - p = Polynomial::getnoise_eta1(seed, nonce++, mode); - } - return r; - } - - template > - T to_bytes() { - T r(m_vec.size() * KyberConstants::kSerializedPolynomialByteLength); - - BufferStuffer bs(r); - for(auto& v : m_vec) { - v.to_bytes(bs.next(KyberConstants::kSerializedPolynomialByteLength)); - } - BOTAN_ASSERT_NOMSG(bs.full()); - - return r; - } - - /** - * Applies conditional subtraction of q to each coefficient of each element - * of the vector of polynomials. - */ - void csubq() { - for(auto& p : m_vec) { - p.csubq(); - } - } - - PolynomialVector& operator+=(const PolynomialVector& other) { - BOTAN_ASSERT(m_vec.size() == other.m_vec.size(), "cannot add polynomial vectors of differing lengths"); - - for(size_t i = 0; i < m_vec.size(); ++i) { - m_vec[i] += other.m_vec[i]; - } - return *this; - } - - Polynomial& operator[](size_t idx) { return m_vec[idx]; } - - /** - * Applies Barrett reduction to each coefficient of each element of a vector of polynomials. - */ - void reduce() { - for(auto& v : m_vec) { - v.reduce(); - } - } - - /** - * Apply inverse NTT to all elements of a vector of polynomials and multiply by Montgomery factor 2^16. - */ - void invntt_tomont() { - for(auto& v : m_vec) { - v.invntt_tomont(); - } - } - - /** - * Apply forward NTT to all elements of a vector of polynomials. - */ - void ntt() { - for(auto& v : m_vec) { - v.ntt(); - } - } - - private: - std::vector m_vec; -}; - -class PolynomialMatrix { - public: - PolynomialMatrix() = delete; - - static PolynomialMatrix generate(StrongSpan seed, - const bool transposed, - const KyberConstants& mode) { - BOTAN_ASSERT(seed.size() == KyberConstants::kSymBytes, "unexpected seed size"); - - PolynomialMatrix matrix(mode); - - for(uint8_t i = 0; i < mode.k(); ++i) { - for(uint8_t j = 0; j < mode.k(); ++j) { - const auto pos = (transposed) ? std::tuple(i, j) : std::tuple(j, i); - matrix.m_mat[i][j] = Polynomial::sample_rej_uniform(mode.symmetric_primitives().XOF(seed, pos)); - } - } - - return matrix; - } - - PolynomialVector pointwise_acc_montgomery(const PolynomialVector& vec, const bool with_mont = false) const { - PolynomialVector result(m_mat.size()); - - for(size_t i = 0; i < m_mat.size(); ++i) { - result[i] = PolynomialVector::pointwise_acc_montgomery(m_mat[i], vec); - if(with_mont) { - result[i].tomont(); - } - } - - return result; - } - - private: - explicit PolynomialMatrix(const KyberConstants& mode) : m_mat(mode.k(), PolynomialVector(mode.k())) {} - - private: - std::vector m_mat; -}; - -class Ciphertext { - public: - Ciphertext() = delete; - - Ciphertext(PolynomialVector b, const Polynomial& v, KyberConstants mode) : - m_mode(std::move(mode)), m_b(std::move(b)), m_v(v) {} - - static Ciphertext from_bytes(StrongSpan buffer, const KyberConstants& mode) { - const size_t pvb = mode.polynomial_vector_compressed_bytes(); - const size_t pcb = mode.polynomial_compressed_bytes(); - - if(buffer.size() != pvb + pcb) { - throw Decoding_Error("Kyber: unexpected ciphertext length"); - } - - BufferSlicer bs(buffer); - auto pv = bs.take(pvb); - auto p = bs.take(pcb); - BOTAN_ASSERT_NOMSG(bs.empty()); - - return Ciphertext(decompress_polynomial_vector(pv, mode), decompress_polynomial(p, mode), mode); - } - - void to_bytes(StrongSpan out) { - BufferStuffer bs(out); - compress(bs.next(m_mode.polynomial_vector_compressed_bytes()), m_b, m_mode); - compress(bs.next(m_mode.polynomial_compressed_bytes()), m_v, m_mode); - BOTAN_ASSERT_NOMSG(bs.full()); - } - - KyberCompressedCiphertext to_bytes() { - KyberCompressedCiphertext r(m_mode.encapsulated_key_length()); - to_bytes(r); - return r; - } - - PolynomialVector& b() { return m_b; } - - Polynomial& v() { return m_v; } - - private: - static void compress(std::span out, PolynomialVector& pv, const KyberConstants& mode) { - pv.csubq(); - - BufferStuffer bs(out); - if(mode.k() == 2 || mode.k() == 3) { - uint16_t t[4]; - for(size_t i = 0; i < mode.k(); ++i) { - for(size_t j = 0; j < KyberConstants::N / 4; ++j) { - for(size_t k = 0; k < 4; ++k) { - t[k] = (((static_cast(pv[i][4 * j + k]) << 10) + KyberConstants::Q / 2) / - KyberConstants::Q) & - 0x3ff; - } - - auto r = bs.next<5>(); - r[0] = static_cast(t[0] >> 0); - r[1] = static_cast((t[0] >> 8) | (t[1] << 2)); - r[2] = static_cast((t[1] >> 6) | (t[2] << 4)); - r[3] = static_cast((t[2] >> 4) | (t[3] << 6)); - r[4] = static_cast(t[3] >> 2); - } - } - } else { - uint16_t t[8]; - for(size_t i = 0; i < mode.k(); ++i) { - for(size_t j = 0; j < KyberConstants::N / 8; ++j) { - for(size_t k = 0; k < 8; ++k) { - t[k] = (((static_cast(pv[i][8 * j + k]) << 11) + KyberConstants::Q / 2) / - KyberConstants::Q) & - 0x7ff; - } - - auto r = bs.next<11>(); - r[0] = static_cast(t[0] >> 0); - r[1] = static_cast((t[0] >> 8) | (t[1] << 3)); - r[2] = static_cast((t[1] >> 5) | (t[2] << 6)); - r[3] = static_cast(t[2] >> 2); - r[4] = static_cast((t[2] >> 10) | (t[3] << 1)); - r[5] = static_cast((t[3] >> 7) | (t[4] << 4)); - r[6] = static_cast((t[4] >> 4) | (t[5] << 7)); - r[7] = static_cast(t[5] >> 1); - r[8] = static_cast((t[5] >> 9) | (t[6] << 2)); - r[9] = static_cast((t[6] >> 6) | (t[7] << 5)); - r[10] = static_cast(t[7] >> 3); - } - } - } - - BOTAN_ASSERT_NOMSG(bs.full()); - } - - static void compress(std::span out, Polynomial& p, const KyberConstants& mode) { - p.csubq(); - - BufferStuffer bs(out); - uint8_t t[8]; - if(mode.k() == 2 || mode.k() == 3) { - for(size_t i = 0; i < p.size() / 8; ++i) { - for(size_t j = 0; j < 8; ++j) { - t[j] = - detail::ct_int_div_kyber_q((static_cast(p[8 * i + j]) << 4) + KyberConstants::Q / 2) & - 15; - } - - auto r = bs.next<4>(); - r[0] = t[0] | (t[1] << 4); - r[1] = t[2] | (t[3] << 4); - r[2] = t[4] | (t[5] << 4); - r[3] = t[6] | (t[7] << 4); - } - } else if(mode.k() == 4) { - for(size_t i = 0; i < p.size() / 8; ++i) { - for(size_t j = 0; j < 8; ++j) { - t[j] = - detail::ct_int_div_kyber_q((static_cast(p[8 * i + j]) << 5) + KyberConstants::Q / 2) & - 31; - } - - auto r = bs.next<5>(); - r[0] = (t[0] >> 0) | (t[1] << 5); - r[1] = (t[1] >> 3) | (t[2] << 2) | (t[3] << 7); - r[2] = (t[3] >> 1) | (t[4] << 4); - r[3] = (t[4] >> 4) | (t[5] << 1) | (t[6] << 6); - r[4] = (t[6] >> 2) | (t[7] << 3); - } - } - - BOTAN_ASSERT_NOMSG(bs.full()); - } - - static PolynomialVector decompress_polynomial_vector(std::span buffer, - const KyberConstants& mode) { - BOTAN_ASSERT(buffer.size() == mode.polynomial_vector_compressed_bytes(), - "unexpected length of compressed polynomial vector"); - - PolynomialVector r(mode.k()); - BufferSlicer bs(buffer); - if(mode.k() == 4) { - uint16_t t[8]; - for(size_t i = 0; i < mode.k(); ++i) { - for(size_t j = 0; j < KyberConstants::N / 8; ++j) { - const auto a = bs.take<11>(); - t[0] = (a[0] >> 0) | (static_cast(a[1]) << 8); - t[1] = (a[1] >> 3) | (static_cast(a[2]) << 5); - t[2] = (a[2] >> 6) | (static_cast(a[3]) << 2) | (static_cast(a[4]) << 10); - t[3] = (a[4] >> 1) | (static_cast(a[5]) << 7); - t[4] = (a[5] >> 4) | (static_cast(a[6]) << 4); - t[5] = (a[6] >> 7) | (static_cast(a[7]) << 1) | (static_cast(a[8]) << 9); - t[6] = (a[8] >> 2) | (static_cast(a[9]) << 6); - t[7] = (a[9] >> 5) | (static_cast(a[10]) << 3); - - for(size_t k = 0; k < 8; ++k) { - r[i][8 * j + k] = (static_cast(t[k] & 0x7FF) * KyberConstants::Q + 1024) >> 11; - } - } - } - } else { - uint16_t t[4]; - for(size_t i = 0; i < mode.k(); ++i) { - for(size_t j = 0; j < KyberConstants::N / 4; ++j) { - const auto a = bs.take<5>(); - t[0] = (a[0] >> 0) | (static_cast(a[1]) << 8); - t[1] = (a[1] >> 2) | (static_cast(a[2]) << 6); - t[2] = (a[2] >> 4) | (static_cast(a[3]) << 4); - t[3] = (a[3] >> 6) | (static_cast(a[4]) << 2); - - for(size_t k = 0; k < 4; ++k) { - r[i][4 * j + k] = (static_cast(t[k] & 0x3FF) * KyberConstants::Q + 512) >> 10; - } - } - } - } - BOTAN_ASSERT_NOMSG(bs.empty()); - - return r; - } - - static Polynomial decompress_polynomial(std::span buffer, const KyberConstants& mode) { - BOTAN_ASSERT(buffer.size() == mode.polynomial_compressed_bytes(), - "unexpected length of compressed polynomial"); - - Polynomial r; - BufferSlicer bs(buffer); - if(mode.k() == 4) { - uint8_t t[8]; - for(size_t i = 0; i < KyberConstants::N / 8; ++i) { - const auto a = bs.take<5>(); - t[0] = (a[0] >> 0); - t[1] = (a[0] >> 5) | (a[1] << 3); - t[2] = (a[1] >> 2); - t[3] = (a[1] >> 7) | (a[2] << 1); - t[4] = (a[2] >> 4) | (a[3] << 4); - t[5] = (a[3] >> 1); - t[6] = (a[3] >> 6) | (a[4] << 2); - t[7] = (a[4] >> 3); - - for(size_t j = 0; j < 8; ++j) { - r[8 * i + j] = (static_cast(t[j] & 31) * KyberConstants::Q + 16) >> 5; - } - } - } else { - for(size_t i = 0; i < KyberConstants::N / 2; ++i) { - const auto a = bs.take_byte(); - r[2 * i + 0] = ((static_cast(a & 15) * KyberConstants::Q) + 8) >> 4; - r[2 * i + 1] = ((static_cast(a >> 4) * KyberConstants::Q) + 8) >> 4; - } - } - BOTAN_ASSERT_NOMSG(bs.empty()); - - return r; - } - - private: - KyberConstants m_mode; - PolynomialVector m_b; - Polynomial m_v; -}; - -} // namespace Botan - -#endif diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_symmetric_primitives.h b/src/lib/pubkey/kyber/kyber_common/kyber_symmetric_primitives.h index 7d45eabc68e..55f13c95418 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber_symmetric_primitives.h +++ b/src/lib/pubkey/kyber/kyber_common/kyber_symmetric_primitives.h @@ -18,10 +18,8 @@ #include #include -#include #include #include -#include namespace Botan { @@ -71,8 +69,7 @@ class Kyber_Symmetric_Primitives { return get_PRF(bare_seed_span, nonce).output(outlen); } - std::unique_ptr XOF(StrongSpan seed, - std::tuple matrix_position) const { + Botan::XOF& XOF(StrongSpan seed, std::tuple matrix_position) const { // TODO: once we remove Kyber 90s, we should make `get_XOF()` return a // reference instead of a unique pointer (for consistency), and // call `get_XOF().copy_state()` here. The AES-CTR XOF doesn't @@ -91,8 +88,8 @@ class Kyber_Symmetric_Primitives { BufferSlicer bs(s); std::pair result; - result.first = bs.copy(KyberConstants::kSeedLength); - result.second = bs.copy(KyberConstants::kSeedLength); + result.first = bs.copy(KyberConstants::SEED_BYTES); + result.second = bs.copy(KyberConstants::SEED_BYTES); BOTAN_ASSERT_NOMSG(bs.empty()); return result; } @@ -102,8 +99,8 @@ class Kyber_Symmetric_Primitives { virtual HashFunction& get_H() const = 0; virtual HashFunction& get_KDF() const = 0; virtual Botan::XOF& get_PRF(std::span seed, uint8_t nonce) const = 0; - virtual std::unique_ptr get_XOF(std::span seed, - std::tuple matrix_position) const = 0; + virtual Botan::XOF& get_XOF(std::span seed, + std::tuple matrix_position) const = 0; }; } // namespace Botan diff --git a/src/lib/pubkey/kyber/kyber_common/kyber_types.h b/src/lib/pubkey/kyber/kyber_common/kyber_types.h index 2e130ad3d63..f4e641a73b9 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber_types.h +++ b/src/lib/pubkey/kyber/kyber_common/kyber_types.h @@ -14,12 +14,21 @@ #include #include +#include +#include #include #include namespace Botan { +using KyberPolyNTT = Botan::CRYSTALS::Polynomial; +using KyberPolyVecNTT = Botan::CRYSTALS::PolynomialVector; +using KyberPolyMat = Botan::CRYSTALS::PolynomialMatrix; + +using KyberPoly = Botan::CRYSTALS::Polynomial; +using KyberPolyVec = Botan::CRYSTALS::PolynomialVector; + /// Principal seed used to generate Kyber key pairs using KyberSeedRandomness = Strong, struct KyberSeedRandomness_>; diff --git a/src/lib/pubkey/kyber/kyber_round3/kyber/kyber_modern.h b/src/lib/pubkey/kyber/kyber_round3/kyber/kyber_modern.h index e42d310c5a7..56f0cd7dbb4 100644 --- a/src/lib/pubkey/kyber/kyber_round3/kyber/kyber_modern.h +++ b/src/lib/pubkey/kyber/kyber_round3/kyber/kyber_modern.h @@ -43,12 +43,11 @@ class Kyber_Modern_Symmetric_Primitives : public Kyber_Symmetric_Primitives { return *m_shake256; } - std::unique_ptr get_XOF(std::span seed, - std::tuple matrix_position) const override { - auto xof = m_shake128->new_object(); - xof->update(seed); - xof->update(store_be(make_uint16(std::get<0>(matrix_position), std::get<1>(matrix_position)))); - return xof; + Botan::XOF& get_XOF(std::span seed, std::tuple matrix_position) const override { + m_shake128->clear(); + m_shake128->update(seed); + m_shake128->update(store_be(make_uint16(std::get<0>(matrix_position), std::get<1>(matrix_position)))); + return *m_shake128; } private: diff --git a/src/lib/pubkey/kyber/kyber_round3/kyber_90s/kyber_90s.h b/src/lib/pubkey/kyber/kyber_round3/kyber_90s/kyber_90s.h index 4669ce81fd9..75bc1013ded 100644 --- a/src/lib/pubkey/kyber/kyber_round3/kyber_90s/kyber_90s.h +++ b/src/lib/pubkey/kyber/kyber_round3/kyber_90s/kyber_90s.h @@ -41,18 +41,17 @@ class Kyber_90s_Symmetric_Primitives : public Kyber_Symmetric_Primitives { return *m_aes256_xof; } - std::unique_ptr get_XOF(std::span seed, - std::tuple mpos) const override { - auto xof = m_aes256_xof->new_object(); + Botan::XOF& get_XOF(std::span seed, std::tuple mpos) const override { + m_aes256_xof->clear(); const std::array iv{std::get<0>(mpos), std::get<1>(mpos), 0}; - xof->start(iv, seed); - return xof; + m_aes256_xof->start(iv, seed); + return *m_aes256_xof; } private: std::unique_ptr m_sha512; std::unique_ptr m_sha256; - std::unique_ptr m_aes256_xof; + mutable std::unique_ptr m_aes256_xof; }; } // namespace Botan diff --git a/src/lib/pubkey/kyber/kyber_round3/kyber_encaps.cpp b/src/lib/pubkey/kyber/kyber_round3/kyber_encaps.cpp index 90116e90961..63fcacf3ed0 100644 --- a/src/lib/pubkey/kyber/kyber_round3/kyber_encaps.cpp +++ b/src/lib/pubkey/kyber/kyber_round3/kyber_encaps.cpp @@ -11,7 +11,6 @@ #include #include -#include #include #include @@ -25,11 +24,10 @@ void Kyber_KEM_Encryptor::encapsulate(StrongSpan out_ RandomNumberGenerator& rng) { const auto& sym = m_public_key->mode().symmetric_primitives(); - const auto m = sym.H(rng.random_vec(KyberConstants::kSymBytes)); + const auto m = sym.H(rng.random_vec(KyberConstants::SEED_BYTES)); const auto [K_bar, r] = sym.G(m, m_public_key->H_public_key_bits_raw()); - auto c = m_public_key->indcpa_encrypt(m, r); + m_public_key->indcpa_encrypt(out_encapsulated_key, m, r, precomputed_matrix_At()); - c.to_bytes(out_encapsulated_key); sym.KDF(out_shared_key, K_bar, sym.H(out_encapsulated_key)); } @@ -43,12 +41,12 @@ void Kyber_KEM_Decryptor::decapsulate(StrongSpan out_shared_k const auto& h = m_public_key->H_public_key_bits_raw(); const auto& z = m_private_key->z(); - const auto m_prime = m_private_key->indcpa_decrypt(Ciphertext::from_bytes(encapsulated_key, m_private_key->mode())); + const auto m_prime = m_private_key->indcpa_decrypt(encapsulated_key); const auto [K_bar_prime, r_prime] = sym.G(m_prime, h); - const auto c_prime = m_public_key->indcpa_encrypt(m_prime, r_prime).to_bytes(); + const auto c_prime = m_public_key->indcpa_encrypt(m_prime, r_prime, precomputed_matrix_At()); - KyberSharedSecret K(KyberConstants::kSymBytes); + KyberSharedSecret K(KyberConstants::SEED_BYTES); BOTAN_ASSERT_NOMSG(encapsulated_key.size() == c_prime.size()); BOTAN_ASSERT_NOMSG(K_bar_prime.size() == K.size()); const auto reencrypt_success = CT::is_equal(encapsulated_key.data(), c_prime.data(), encapsulated_key.size()); diff --git a/src/lib/pubkey/kyber/kyber_round3/kyber_encaps.h b/src/lib/pubkey/kyber/kyber_round3/kyber_encaps.h index afe211b911c..4088fd02e0d 100644 --- a/src/lib/pubkey/kyber/kyber_round3/kyber_encaps.h +++ b/src/lib/pubkey/kyber/kyber_round3/kyber_encaps.h @@ -20,7 +20,7 @@ namespace Botan { class Kyber_KEM_Encryptor final : public Kyber_KEM_Encryptor_Base { public: Kyber_KEM_Encryptor(std::shared_ptr key, std::string_view kdf) : - Kyber_KEM_Encryptor_Base(kdf), m_public_key(std::move(key)) {} + Kyber_KEM_Encryptor_Base(kdf, *key), m_public_key(std::move(key)) {} protected: void encapsulate(StrongSpan out_encapsulated_key, @@ -38,7 +38,9 @@ class Kyber_KEM_Decryptor final : public Kyber_KEM_Decryptor_Base { Kyber_KEM_Decryptor(std::shared_ptr private_key, std::shared_ptr public_key, std::string_view kdf) : - Kyber_KEM_Decryptor_Base(kdf), m_public_key(std::move(public_key)), m_private_key(std::move(private_key)) {} + Kyber_KEM_Decryptor_Base(kdf, *public_key), + m_public_key(std::move(public_key)), + m_private_key(std::move(private_key)) {} protected: void decapsulate(StrongSpan out_shared_key, diff --git a/src/tests/test_kyber.cpp b/src/tests/test_kyber.cpp index 0cb43c0cbb1..a450c1c3527 100644 --- a/src/tests/test_kyber.cpp +++ b/src/tests/test_kyber.cpp @@ -16,6 +16,7 @@ #include "test_rng.h" #include "tests.h" +#include #include #include @@ -26,6 +27,8 @@ #include #include #include + #include + #include #include #endif @@ -257,6 +260,102 @@ class Kyber_Keygen_Tests final : public PK_Key_Generation_Test { }; BOTAN_REGISTER_TEST("kyber", "kyber_keygen", Kyber_Keygen_Tests); + +namespace { + +template +void test_compress(Test::Result& res) { + using namespace Botan; + constexpr auto q = KyberConstants::Q; + + res.start_timer(); + + for(uint16_t x = 0; x < q; ++x) { + const uint32_t c = kyber_compress(x); + const auto twotothed = (uint32_t(1) << d); + const auto expected = (static_cast(twotothed) / q) * x; + const auto e = static_cast(std::round(expected)) % twotothed; + + if(c != e) { + res.test_failure(fmt("compress<{}>({}) = {}; expected {}", d, x, c, e)); + return; + } + } + + res.end_timer(); + res.test_success(); +} + +template +void test_decompress(Test::Result& result) { + using namespace Botan; + constexpr auto q = KyberConstants::Q; + + result.start_timer(); + + const auto twotothed = (uint32_t(1) << d); + using from_t = std::conditional_t; + + for(from_t y = 0; y < twotothed; ++y) { + const uint32_t c = kyber_decompress(y); + const auto expected = (static_cast(q) / twotothed) * y; + const auto e = static_cast(std::round(expected)) % q; + + if(c != e) { + result.test_failure(fmt("decompress<{}>({}) = {}; expected {}", d, static_cast(y), c, e)); + return; + } + } + + result.end_timer(); + result.test_success(); +} + +template +void test_compress_roundtrip(Test::Result& result) { + using namespace Botan; + constexpr auto q = KyberConstants::Q; + + result.start_timer(); + + for(uint16_t x = 0; x < q && x < (1 << d); ++x) { + const uint16_t c = kyber_compress(kyber_decompress(x)); + if(x != c) { + result.test_failure(fmt("compress<{}>(decompress<{}>({})) != {}", d, d, x, c)); + return; + } + } + + result.end_timer(); + result.test_success(); +} + +std::vector test_kyber_helpers() { + return { + Botan_Tests::CHECK("compress<1>", [](Test::Result& res) { test_compress<1>(res); }), + Botan_Tests::CHECK("compress<4>", [](Test::Result& res) { test_compress<4>(res); }), + Botan_Tests::CHECK("compress<5>", [](Test::Result& res) { test_compress<5>(res); }), + Botan_Tests::CHECK("compress<10>", [](Test::Result& res) { test_compress<10>(res); }), + Botan_Tests::CHECK("compress<11>", [](Test::Result& res) { test_compress<11>(res); }), + + Botan_Tests::CHECK("decompress<1>", [](Test::Result& res) { test_decompress<1>(res); }), + Botan_Tests::CHECK("decompress<4>", [](Test::Result& res) { test_decompress<4>(res); }), + Botan_Tests::CHECK("decompress<5>", [](Test::Result& res) { test_decompress<5>(res); }), + Botan_Tests::CHECK("decompress<10>", [](Test::Result& res) { test_decompress<10>(res); }), + Botan_Tests::CHECK("decompress<11>", [](Test::Result& res) { test_decompress<11>(res); }), + + Botan_Tests::CHECK("compress<1>(decompress())", [](Test::Result& res) { test_compress_roundtrip<1>(res); }), + Botan_Tests::CHECK("compress<4>(decompress())", [](Test::Result& res) { test_compress_roundtrip<4>(res); }), + Botan_Tests::CHECK("compress<5>(decompress())", [](Test::Result& res) { test_compress_roundtrip<5>(res); }), + Botan_Tests::CHECK("compress<10>(decompress())>", [](Test::Result& res) { test_compress_roundtrip<10>(res); }), + Botan_Tests::CHECK("compress<11>(decompress())>", [](Test::Result& res) { test_compress_roundtrip<11>(res); }), + }; +} + +} // namespace + +BOTAN_REGISTER_TEST_FN("kyber", "kyber_helpers", test_kyber_helpers); + #endif } // namespace Botan_Tests