diff --git a/src/lib/pubkey/dilithium/dilithium/dilithium_modern.h b/src/lib/pubkey/dilithium/dilithium/dilithium_modern.h index 56e377a15e6..956550ac9d8 100644 --- a/src/lib/pubkey/dilithium/dilithium/dilithium_modern.h +++ b/src/lib/pubkey/dilithium/dilithium/dilithium_modern.h @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -23,26 +22,30 @@ namespace Botan { class Dilithium_Common_Symmetric_Primitives : public Dilithium_Symmetric_Primitives { public: - std::unique_ptr XOF(XofType type, std::span seed, uint16_t nonce) const override { - const auto xof_type = [&] { + Dilithium_Common_Symmetric_Primitives(size_t collision_strength_in_bytes) : + Dilithium_Symmetric_Primitives(collision_strength_in_bytes) {} + + Botan::XOF& XOF(XofType type, std::span seed, uint16_t nonce) const override { + auto& xof = [&]() -> Botan::XOF& { switch(type) { case XofType::k128: - return "SHAKE-128"; + return m_xof_128; case XofType::k256: - return "SHAKE-256"; + return m_xof_256; } BOTAN_ASSERT_UNREACHABLE(); }(); - std::array nonce_buffer; - store_le(nonce, nonce_buffer.data()); - - auto xof = Botan::XOF::create_or_throw(xof_type); - xof->update(seed); - xof->update(nonce_buffer); + xof.clear(); + xof.update(seed); + xof.update(store_le(nonce)); return xof; } + + private: + mutable SHAKE_256_XOF m_xof_256; + mutable SHAKE_128_XOF m_xof_128; }; } // namespace Botan diff --git a/src/lib/pubkey/dilithium/dilithium_aes/dilithium_aes.h b/src/lib/pubkey/dilithium/dilithium_aes/dilithium_aes.h index eff8cb4e278..882252311e4 100644 --- a/src/lib/pubkey/dilithium/dilithium_aes/dilithium_aes.h +++ b/src/lib/pubkey/dilithium/dilithium_aes/dilithium_aes.h @@ -22,8 +22,11 @@ namespace Botan { class Dilithium_AES_Symmetric_Primitives : public Dilithium_Symmetric_Primitives { public: + Dilithium_AES_Symmetric_Primitives(size_t collision_strength_in_bytes) : + Dilithium_Symmetric_Primitives(collision_strength_in_bytes) {} + // AES mode always uses AES-256, regardless of the XofType - std::unique_ptr XOF(XofType /* type */, std::span seed, uint16_t nonce) const final { + Botan::XOF& XOF(XofType /* type */, std::span seed, uint16_t nonce) const final { // Algorithm Spec V. 3.1 Section 5.3 // In the AES variant, the first 32 bytes of rhoprime are used as // the key and i is extended to a 12 byte nonce for AES-256 in @@ -36,10 +39,13 @@ class Dilithium_AES_Symmetric_Primitives : public Dilithium_Symmetric_Primitives const std::array iv{get_byte<1>(nonce), get_byte<0>(nonce), 0}; const auto key = seed.first(32); - auto xof = std::make_unique(); - xof->start(iv, key); - return xof; + m_aes_xof.clear(); + m_aes_xof.start(iv, key); + return m_aes_xof; } + + private: + mutable AES_256_CTR_XOF m_aes_xof; }; } // namespace Botan diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp b/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp index 2c9387d4aa1..856c1771adf 100644 --- a/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp +++ b/src/lib/pubkey/dilithium/dilithium_common/dilithium.cpp @@ -7,6 +7,7 @@ * (C) 2021-2023 Jack Lloyd * (C) 2021-2022 Manuel Glaser - Rohde & Schwarz Cybersecurity * (C) 2021-2023 Michael Boric, René Meusel - Rohde & Schwarz Cybersecurity +* (C) 2024 René Meusel - Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -14,52 +15,18 @@ #include #include -#include #include -#include +#include +#include +#include #include -#include #include -#include #include -#include -#include -#include -#include -#include - namespace Botan { namespace { -std::pair calculate_t0_and_t1( - const DilithiumModeConstants& mode, - const std::vector& rho, - Dilithium::PolynomialVector s1, - const Dilithium::PolynomialVector& s2) { - /* Generate matrix */ - auto matrix = Dilithium::PolynomialMatrix::generate_matrix(rho, mode); - - /* Matrix-vector multiplication */ - s1.ntt(); - auto t = Dilithium::PolynomialVector::generate_polyvec_matrix_pointwise_montgomery(matrix.get_matrix(), s1, mode); - t.reduce(); - t.invntt_tomont(); - - /* Add error vector s2 */ - t.add_polyvec(s2); - - /* Extract t and write public key */ - t.cadd_q(); - - Dilithium::PolynomialVector t0(mode.k()); - Dilithium::PolynomialVector t1(mode.k()); - Dilithium::PolynomialVector::fill_polyvecs_power2round(t1, t0, t); - - return {std::move(t0), std::move(t1)}; -} - DilithiumMode::Mode dilithium_mode_from_string(std::string_view str) { if(str == "Dilithium-4x4-r3") { return DilithiumMode::Dilithium4x4; @@ -114,402 +81,271 @@ std::string DilithiumMode::to_string() const { class Dilithium_PublicKeyInternal { public: - Dilithium_PublicKeyInternal(DilithiumModeConstants mode) : m_mode(std::move(mode)) {} - - Dilithium_PublicKeyInternal(DilithiumModeConstants mode, std::span raw_pk) : - m_mode(std::move(mode)) { - BOTAN_ASSERT_NOMSG(raw_pk.size() == m_mode.public_key_bytes()); - - BufferSlicer s(raw_pk); - m_rho = s.copy_as_vector(DilithiumModeConstants::SEEDBYTES); - m_t1 = Dilithium::PolynomialVector::unpack_t1(s.take(DilithiumModeConstants::POLYT1_PACKEDBYTES * m_mode.k()), - m_mode); - - BOTAN_ASSERT_NOMSG(s.remaining() == 0); - BOTAN_STATE_CHECK(m_t1.m_vec.size() == m_mode.k()); - - m_raw_pk_shake256 = compute_raw_pk_shake256(); + static std::shared_ptr decode( + DilithiumConstants mode, StrongSpan raw_pk) { + auto [rho, t1] = Dilithium_Algos::decode_public_key(raw_pk, mode); + return std::make_shared(std::move(mode), std::move(rho), std::move(t1)); } - Dilithium_PublicKeyInternal(DilithiumModeConstants mode, - std::vector rho, - const Dilithium::PolynomialVector& s1, - const Dilithium::PolynomialVector& s2) : + Dilithium_PublicKeyInternal(DilithiumConstants mode, DilithiumSeedRho rho, DilithiumPolyVec t1) : m_mode(std::move(mode)), m_rho(std::move(rho)), - m_t1([&] { return calculate_t0_and_t1(m_mode, m_rho, s1, s2).second; }()) { - BOTAN_ASSERT_NOMSG(!m_rho.empty()); - BOTAN_ASSERT_NOMSG(!m_t1.m_vec.empty()); - m_raw_pk_shake256 = compute_raw_pk_shake256(); - } - - Dilithium_PublicKeyInternal(DilithiumModeConstants mode, - std::vector rho, - Dilithium::PolynomialVector t1) : - m_mode(std::move(mode)), m_rho(std::move(rho)), m_t1(std::move(t1)) { + m_t1(std::move(t1)), + m_tr(m_mode.symmetric_primitives().H(raw_pk())) { BOTAN_ASSERT_NOMSG(!m_rho.empty()); - BOTAN_ASSERT_NOMSG(!m_t1.m_vec.empty()); - m_raw_pk_shake256 = compute_raw_pk_shake256(); + BOTAN_ASSERT_NOMSG(m_t1.size() > 0); } - ~Dilithium_PublicKeyInternal() = default; - - Dilithium_PublicKeyInternal(const Dilithium_PublicKeyInternal&) = delete; - Dilithium_PublicKeyInternal(Dilithium_PublicKeyInternal&&) = delete; - Dilithium_PublicKeyInternal& operator=(const Dilithium_PublicKeyInternal& other) = delete; - Dilithium_PublicKeyInternal& operator=(Dilithium_PublicKeyInternal&& other) = delete; + public: + DilithiumSerializedPublicKey raw_pk() const { return Dilithium_Algos::encode_public_key(m_rho, m_t1, m_mode); } - std::vector raw_pk() const { return concat>(m_rho, m_t1.polyvec_pack_t1()); } + const DilithiumHashedPublicKey& tr() const { return m_tr; } - const std::vector& raw_pk_shake256() const { - BOTAN_STATE_CHECK(m_raw_pk_shake256.size() == DilithiumModeConstants::SEEDBYTES); - return m_raw_pk_shake256; - } + const DilithiumPolyVec& t1() const { return m_t1; } - const Dilithium::PolynomialVector& t1() const { return m_t1; } + const DilithiumSeedRho& rho() const { return m_rho; } - const std::vector& rho() const { return m_rho; } - - const DilithiumModeConstants& mode() const { return m_mode; } + const DilithiumConstants& mode() const { return m_mode; } private: - std::vector compute_raw_pk_shake256() const { - SHAKE_256 shake(DilithiumModeConstants::SEEDBYTES * 8); - shake.update(m_rho); - shake.update(m_t1.polyvec_pack_t1()); - return shake.final_stdvec(); - } - - const DilithiumModeConstants m_mode; - std::vector m_raw_pk_shake256; - std::vector m_rho; - Dilithium::PolynomialVector m_t1; + const DilithiumConstants m_mode; + DilithiumSeedRho m_rho; + DilithiumPolyVec m_t1; + DilithiumHashedPublicKey m_tr; }; class Dilithium_PrivateKeyInternal { public: - Dilithium_PrivateKeyInternal(DilithiumModeConstants mode) : m_mode(std::move(mode)) {} - - Dilithium_PrivateKeyInternal(DilithiumModeConstants mode, - std::vector rho, - secure_vector tr, - secure_vector key, - Dilithium::PolynomialVector s1, - Dilithium::PolynomialVector s2, - Dilithium::PolynomialVector t0) : + static std::shared_ptr decode(DilithiumConstants mode, + StrongSpan sk) { + auto [rho, signing_seed, tr, s1, s2, t0] = Dilithium_Algos::decode_private_key(sk, mode); + return std::make_shared(std::move(mode), + std::move(rho), + std::move(signing_seed), + std::move(tr), + std::move(s1), + std::move(s2), + std::move(t0)); + } + + Dilithium_PrivateKeyInternal(DilithiumConstants mode, + DilithiumSeedRho rho, + DilithiumSigningSeedK signing_seed, + DilithiumHashedPublicKey tr, + DilithiumPolyVec s1, + DilithiumPolyVec s2, + DilithiumPolyVec t0) : m_mode(std::move(mode)), m_rho(std::move(rho)), + m_signing_seed(std::move(signing_seed)), m_tr(std::move(tr)), - m_key(std::move(key)), m_t0(std::move(t0)), m_s1(std::move(s1)), m_s2(std::move(s2)) {} - Dilithium_PrivateKeyInternal(DilithiumModeConstants mode, std::span sk) : - Dilithium_PrivateKeyInternal(std::move(mode)) { - BOTAN_ASSERT_NOMSG(sk.size() == m_mode.private_key_bytes()); - - BufferSlicer s(sk); - m_rho = s.copy_as_vector(DilithiumModeConstants::SEEDBYTES); - m_key = s.copy_as_secure_vector(DilithiumModeConstants::SEEDBYTES); - m_tr = s.copy_as_secure_vector(DilithiumModeConstants::SEEDBYTES); - m_s1 = Dilithium::PolynomialVector::unpack_eta( - s.take(m_mode.l() * m_mode.polyeta_packedbytes()), m_mode.l(), m_mode); - m_s2 = Dilithium::PolynomialVector::unpack_eta( - s.take(m_mode.k() * m_mode.polyeta_packedbytes()), m_mode.k(), m_mode); - m_t0 = Dilithium::PolynomialVector::unpack_t0(s.take(m_mode.k() * DilithiumModeConstants::POLYT0_PACKEDBYTES), - m_mode); - } - - secure_vector raw_sk() const { - return concat>( - m_rho, m_key, m_tr, m_s1.polyvec_pack_eta(m_mode), m_s2.polyvec_pack_eta(m_mode), m_t0.polyvec_pack_t0()); + public: + DilithiumSerializedPrivateKey raw_sk() const { + return Dilithium_Algos::encode_private_key(m_rho, m_tr, m_signing_seed, m_s1, m_s2, m_t0, m_mode); } - const DilithiumModeConstants& mode() const { return m_mode; } + const DilithiumConstants& mode() const { return m_mode; } - const std::vector& rho() const { return m_rho; } + const DilithiumSeedRho& rho() const { return m_rho; } - const secure_vector& get_key() const { return m_key; } + const DilithiumSigningSeedK& signing_seed() const { return m_signing_seed; } - const secure_vector& tr() const { return m_tr; } + const DilithiumHashedPublicKey& tr() const { return m_tr; } - const Dilithium::PolynomialVector& s1() const { return m_s1; } + const DilithiumPolyVec& s1() const { return m_s1; } - const Dilithium::PolynomialVector& s2() const { return m_s2; } + const DilithiumPolyVec& s2() const { return m_s2; } - const Dilithium::PolynomialVector& t0() const { return m_t0; } + const DilithiumPolyVec& t0() const { return m_t0; } private: - const DilithiumModeConstants m_mode; - std::vector m_rho; - secure_vector m_tr, m_key; - Dilithium::PolynomialVector m_t0, m_s1, m_s2; + const DilithiumConstants m_mode; + DilithiumSeedRho m_rho; + DilithiumSigningSeedK m_signing_seed; + DilithiumHashedPublicKey m_tr; + DilithiumPolyVec m_t0; + DilithiumPolyVec m_s1; + DilithiumPolyVec m_s2; }; class Dilithium_Signature_Operation final : public PK_Ops::Signature { public: - Dilithium_Signature_Operation(const Dilithium_PrivateKey& priv_key_dilithium, bool randomized) : - m_priv_key(priv_key_dilithium), - m_matrix( - Dilithium::PolynomialMatrix::generate_matrix(m_priv_key.m_private->rho(), m_priv_key.m_private->mode())), - m_shake(DilithiumModeConstants::CRHBYTES * 8), - m_randomized(randomized) { - m_shake.update(m_priv_key.m_private->tr()); - } - - void update(const uint8_t msg[], size_t msg_len) override { m_shake.update(msg, msg_len); } - + Dilithium_Signature_Operation(std::shared_ptr sk, bool randomized) : + m_priv_key(std::move(sk)), + m_randomized(randomized), + m_h(m_priv_key->mode().symmetric_primitives().get_message_hash(m_priv_key->tr())), + m_s1(ntt(m_priv_key->s1().clone())), + m_s2(ntt(m_priv_key->s2().clone())), + m_t0(ntt(m_priv_key->t0().clone())), + m_A(Dilithium_Algos::expand_A(m_priv_key->rho(), m_priv_key->mode())) {} + + void update(const uint8_t msg[], size_t msg_len) override { m_h.update({msg, msg_len}); } + + /** + * NIST FIPS 204 IPD, Algorithm 2 (ML-DSA.Sign) + * + * Note that the private key decoding is done ahead of time. Also, the + * matrix expansion of A from 'rho' along with the NTT-transforms of s1, + * s2 and t0 are done in the constructor of this class, as a 'signature + * operation' may be used to sign multiple messages. + */ secure_vector sign(RandomNumberGenerator& rng) override { - const auto mu = m_shake.final_stdvec(); - - // Get set up for the next message (if any) - m_shake.update(m_priv_key.m_private->tr()); + const auto mu = m_h.final(); + const auto& mode = m_priv_key->mode(); + const auto& sympri = mode.symmetric_primitives(); - const auto& mode = m_priv_key.m_private->mode(); - - const auto rhoprime = (m_randomized) ? rng.random_vec(DilithiumModeConstants::CRHBYTES) - : mode.CRH(concat(m_priv_key.m_private->get_key(), mu)); - - /* Transform vectors */ - auto s1 = m_priv_key.m_private->s1(); - s1.ntt(); - - auto s2 = m_priv_key.m_private->s2(); - s2.ntt(); - - auto t0 = m_priv_key.m_private->t0(); - t0.ntt(); + // TODO: ML-DSA generates rhoprime differently, namely + // rhoprime = H(K, rnd, mu) with rnd being 32 random bytes or 32 zero bytes + const auto rhoprime = (m_randomized) + ? rng.random_vec(DilithiumConstants::SEED_RHOPRIME_BYTES) + : sympri.H(m_priv_key->signing_seed(), mu); // Note: nonce (as requested by `polyvecl_uniform_gamma1`) is actually just uint16_t // but to avoid an integer overflow, we use uint32_t as the loop variable. - for(uint32_t nonce = 0; nonce <= std::numeric_limits::max(); ++nonce) { - /* Sample intermediate vector y */ - Dilithium::PolynomialVector y(mode.l()); - - y.polyvecl_uniform_gamma1(rhoprime, static_cast(nonce), mode); - - auto z = y; - z.ntt(); - - /* Matrix-vector multiplication */ - auto w1 = Dilithium::PolynomialVector::generate_polyvec_matrix_pointwise_montgomery( - m_matrix.get_matrix(), z, mode); - - w1.reduce(); - w1.invntt_tomont(); - - /* Decompose w and call the random oracle */ - w1.cadd_q(); - - auto w1_w0 = w1.polyvec_decompose(mode); - - auto packed_w1 = std::get<0>(w1_w0).polyvec_pack_w1(mode); - - SHAKE_256 shake256_variable(DilithiumModeConstants::SEEDBYTES * 8); - shake256_variable.update(mu.data(), DilithiumModeConstants::CRHBYTES); - shake256_variable.update(packed_w1.data(), packed_w1.size()); - auto sm = shake256_variable.final(); - - auto cp = Dilithium::Polynomial::poly_challenge(sm.data(), mode); - cp.ntt(); - - /* Compute z, reject if it reveals secret */ - s1.polyvec_pointwise_poly_montgomery(z, cp); - - z.invntt_tomont(); - z.add_polyvec(y); - + for(uint32_t nonce = 0; nonce <= std::numeric_limits::max(); nonce += mode.l()) { + const auto y = Dilithium_Algos::expand_mask(rhoprime, static_cast(nonce), mode); + + auto w_ntt = m_A * ntt(y.clone()); + w_ntt.reduce(); + auto w = inverse_ntt(std::move(w_ntt)); + w.conditional_add_q(); + + auto [w1, w0] = Dilithium_Algos::decompose(w, mode); + const auto ch = sympri.H(mu, Dilithium_Algos::encode_commitment(w1, mode)); + StrongSpan c1( + std::span(ch).first(DilithiumConstants::COMMITMENT_HASH_C1_BYTES)); + const auto c = ntt(Dilithium_Algos::sample_in_ball(c1, mode)); + const auto cs1 = inverse_ntt(c * m_s1); + auto z = y + cs1; z.reduce(); - if(z.polyvec_chknorm(mode.gamma1() - mode.beta())) { + if(!Dilithium_Algos::infinity_norm_within_bound(z, to_underlying(mode.gamma1()) - mode.beta())) { continue; } - /* Check that subtracting cs2 does not change high bits of w and low bits - * do not reveal secret information */ - Dilithium::PolynomialVector h(mode.k()); - s2.polyvec_pointwise_poly_montgomery(h, cp); - h.invntt_tomont(); - std::get<1>(w1_w0) -= h; - std::get<1>(w1_w0).reduce(); - - if(std::get<1>(w1_w0).polyvec_chknorm(mode.gamma2() - mode.beta())) { + const auto cs2 = inverse_ntt(c * m_s2); + w0 -= cs2; + w0.reduce(); + if(!Dilithium_Algos::infinity_norm_within_bound(w0, to_underlying(mode.gamma2()) - mode.beta())) { continue; } - /* Compute hints for w1 */ - t0.polyvec_pointwise_poly_montgomery(h, cp); - h.invntt_tomont(); - h.reduce(); - if(h.polyvec_chknorm(mode.gamma2())) { + auto ct0 = inverse_ntt(c * m_t0); + ct0.reduce(); + if(!Dilithium_Algos::infinity_norm_within_bound(ct0, mode.gamma2())) { continue; } - std::get<1>(w1_w0).add_polyvec(h); - std::get<1>(w1_w0).cadd_q(); + w0 += ct0; + w0.conditional_add_q(); - auto n = - Dilithium::PolynomialVector::generate_hint_polyvec(h, std::get<1>(w1_w0), std::get<0>(w1_w0), mode); - if(n > mode.omega()) { + const auto hint = Dilithium_Algos::make_hint(w0, w1, mode); + if(hint.hamming_weight() > mode.omega()) { continue; } - /* Write signature */ - return pack_sig(sm, z, h); + return Dilithium_Algos::encode_signature(ch, z, hint, mode).get(); } throw Internal_Error("Dilithium signature loop did not terminate"); } - size_t signature_length() const override { - const auto& dilithium_math = m_priv_key.m_private->mode(); - return dilithium_math.crypto_bytes(); - } + size_t signature_length() const override { return m_priv_key->mode().signature_bytes(); } - AlgorithmIdentifier algorithm_identifier() const override; + AlgorithmIdentifier algorithm_identifier() const override { + return AlgorithmIdentifier(m_priv_key->mode().mode().object_identifier(), + AlgorithmIdentifier::USE_EMPTY_PARAM); + } - std::string hash_function() const override { return "SHAKE-256(512)"; } + std::string hash_function() const override { return m_h.name(); } private: - // Bit-pack signature sig = (c, z, h). - secure_vector pack_sig(const secure_vector& c, - const Dilithium::PolynomialVector& z, - const Dilithium::PolynomialVector& h) { - BOTAN_ASSERT_NOMSG(c.size() == DilithiumModeConstants::SEEDBYTES); - size_t position = 0; - const auto& mode = m_priv_key.m_private->mode(); - secure_vector sig(mode.crypto_bytes()); - - std::copy(c.begin(), c.end(), sig.begin()); - position += DilithiumModeConstants::SEEDBYTES; - - for(size_t i = 0; i < mode.l(); ++i) { - z.m_vec[i].polyz_pack(&sig[position + i * mode.polyz_packedbytes()], mode); - } - position += mode.l() * mode.polyz_packedbytes(); - - /* Encode h */ - for(size_t i = 0; i < mode.omega() + mode.k(); ++i) { - sig[i + position] = 0; - } - - size_t k = 0; - for(size_t i = 0; i < mode.k(); ++i) { - for(size_t j = 0; j < DilithiumModeConstants::N; ++j) { - if(h.m_vec[i].m_coeffs[j] != 0) { - sig[position + k] = static_cast(j); - k++; - } - } - sig[position + mode.omega() + i] = static_cast(k); - } - return sig; - } - - const Dilithium_PrivateKey m_priv_key; - const Dilithium::PolynomialMatrix m_matrix; - SHAKE_256 m_shake; + std::shared_ptr m_priv_key; bool m_randomized; -}; + DilithiumMessageHash m_h; -AlgorithmIdentifier Dilithium_Signature_Operation::algorithm_identifier() const { - return m_priv_key.algorithm_identifier(); -} + const DilithiumPolyVecNTT m_s1; + const DilithiumPolyVecNTT m_s2; + const DilithiumPolyVecNTT m_t0; + const DilithiumPolyMatNTT m_A; +}; class Dilithium_Verification_Operation final : public PK_Ops::Verification { public: - Dilithium_Verification_Operation(const Dilithium_PublicKey& pub_dilithium) : - m_pub_key(pub_dilithium.m_public), - m_matrix(Dilithium::PolynomialMatrix::generate_matrix(m_pub_key->rho(), m_pub_key->mode())), - m_pk_hash(m_pub_key->raw_pk_shake256()), - m_shake(DilithiumModeConstants::CRHBYTES * 8) { - m_shake.update(m_pk_hash); - } - - /* - * Add more data to the message currently being signed - * @param msg the message - * @param msg_len the length of msg in bytes - */ - void update(const uint8_t msg[], size_t msg_len) override { m_shake.update(msg, msg_len); } - - /* - * Perform a verification operation - * @param rng a random number generator - */ + Dilithium_Verification_Operation(std::shared_ptr pubkey) : + m_pub_key(std::move(pubkey)), + m_A(Dilithium_Algos::expand_A(m_pub_key->rho(), m_pub_key->mode())), + m_t1_ntt_shifted(ntt(m_pub_key->t1() << DilithiumConstants::D)), + m_h(m_pub_key->mode().symmetric_primitives().get_message_hash(m_pub_key->tr())) {} + + void update(const uint8_t msg[], size_t msg_len) override { m_h.update({msg, msg_len}); } + + /** + * NIST FIPS 204 IPD, Algorithm 3 (ML-DSA.Verify) + * + * Note that the public key decoding is done ahead of time. Also, the + * matrix A is expanded from 'rho' in the constructor of this class, as + * a 'verification operation' may be used to verify multiple signatures. + */ bool is_valid_signature(const uint8_t* sig, size_t sig_len) override { - /* Compute CRH(H(rho, t1), msg) */ - const auto mu = m_shake.final_stdvec(); - - // Reset the SHAKE context for the next message - m_shake.update(m_pk_hash); - const auto& mode = m_pub_key->mode(); + const auto& sympri = mode.symmetric_primitives(); + StrongSpan sig_bytes({sig, sig_len}); - if(sig_len != mode.crypto_bytes()) { + if(sig_bytes.size() != mode.signature_bytes()) { return false; } - Dilithium::PolynomialVector z(mode.l()); - Dilithium::PolynomialVector h(mode.k()); - std::vector signature(sig, sig + sig_len); - std::array c; - if(Dilithium::PolynomialVector::unpack_sig(c, z, h, signature, mode)) { + const auto mu = m_h.final(); + + auto signature = Dilithium_Algos::decode_signature(sig_bytes, mode); + if(!signature.has_value()) { return false; } + auto [ch, z, h] = std::move(signature.value()); + StrongSpan c1( + std::span(ch).first(DilithiumConstants::COMMITMENT_HASH_C1_BYTES)); - if(z.polyvec_chknorm(mode.gamma1() - mode.beta())) { + if(h.hamming_weight() > mode.omega() || + !Dilithium_Algos::infinity_norm_within_bound(z, to_underlying(mode.gamma1()) - mode.beta())) { return false; } - /* Matrix-vector multiplication; compute Az - c2^dt1 */ - auto cp = Dilithium::Polynomial::poly_challenge(c.data(), mode); - cp.ntt(); - - Dilithium::PolynomialVector t1 = m_pub_key->t1(); - t1.polyvec_shiftl(); - t1.ntt(); - t1.polyvec_pointwise_poly_montgomery(t1, cp); - - z.ntt(); - - auto w1 = - Dilithium::PolynomialVector::generate_polyvec_matrix_pointwise_montgomery(m_matrix.get_matrix(), z, mode); - w1 -= t1; - w1.reduce(); - w1.invntt_tomont(); - w1.cadd_q(); - w1.polyvec_use_hint(w1, h, mode); - auto packed_w1 = w1.polyvec_pack_w1(mode); - - /* Call random oracle and verify challenge */ - SHAKE_256 shake256_variable(DilithiumModeConstants::SEEDBYTES * 8); - shake256_variable.update(mu.data(), mu.size()); - shake256_variable.update(packed_w1.data(), packed_w1.size()); - auto c2 = shake256_variable.final(); - - BOTAN_ASSERT_NOMSG(c.size() == c2.size()); - return std::equal(c.begin(), c.end(), c2.begin()); + const auto c_hat = ntt(Dilithium_Algos::sample_in_ball(c1, mode)); + auto w_approx = m_A * ntt(std::move(z)); + w_approx -= c_hat * m_t1_ntt_shifted; + w_approx.reduce(); + auto w1 = inverse_ntt(std::move(w_approx)); + w1.conditional_add_q(); + Dilithium_Algos::use_hint(w1, h, mode); + + const auto chprime = sympri.H(mu, Dilithium_Algos::encode_commitment(w1, mode)); + + BOTAN_ASSERT_NOMSG(ch.size() == chprime.size()); + return std::equal(ch.begin(), ch.end(), chprime.begin()); } - std::string hash_function() const override { return "SHAKE-256(512)"; } + std::string hash_function() const override { return m_h.name(); } private: std::shared_ptr m_pub_key; - const Dilithium::PolynomialMatrix m_matrix; - const std::vector m_pk_hash; - SHAKE_256 m_shake; + DilithiumPolyMatNTT m_A; + DilithiumPolyVecNTT m_t1_ntt_shifted; + DilithiumMessageHash m_h; }; Dilithium_PublicKey::Dilithium_PublicKey(const AlgorithmIdentifier& alg_id, std::span pk) : Dilithium_PublicKey(pk, DilithiumMode(alg_id.oid())) {} Dilithium_PublicKey::Dilithium_PublicKey(std::span pk, DilithiumMode m) { - DilithiumModeConstants mode(m); + DilithiumConstants mode(m); BOTAN_ARG_CHECK(pk.empty() || pk.size() == mode.public_key_bytes(), "dilithium public key does not have the correct byte count"); - m_public = std::make_shared(std::move(mode), pk); + m_public = Dilithium_PublicKeyInternal::decode(std::move(mode), StrongSpan(pk)); } std::string Dilithium_PublicKey::algo_name() const { @@ -521,19 +357,19 @@ AlgorithmIdentifier Dilithium_PublicKey::algorithm_identifier() const { } OID Dilithium_PublicKey::object_identifier() const { - return m_public->mode().oid(); + return m_public->mode().mode().object_identifier(); } size_t Dilithium_PublicKey::key_length() const { - return m_public->mode().public_key_bytes(); + return m_public->mode().canonical_parameter_set_identifier(); } size_t Dilithium_PublicKey::estimated_strength() const { - return m_public->mode().nist_security_strength(); + return m_public->mode().lambda(); } std::vector Dilithium_PublicKey::raw_public_key_bits() const { - return m_public->raw_pk(); + return m_public->raw_pk().get(); } std::vector Dilithium_PublicKey::public_key_bits() const { @@ -554,7 +390,7 @@ std::unique_ptr Dilithium_PublicKey::create_verification_o std::string_view provider) const { BOTAN_ARG_CHECK(params.empty() || params == "Pure", "Unexpected parameters for verifying with Dilithium"); if(provider.empty() || provider == "base") { - return std::make_unique(*this); + return std::make_unique(m_public); } throw Provider_Not_Found(algo_name(), provider); } @@ -565,56 +401,68 @@ std::unique_ptr Dilithium_PublicKey::create_x509_verificat if(alg_id != this->algorithm_identifier()) { throw Decoding_Error("Unexpected AlgorithmIdentifier for Dilithium X.509 signature"); } - return std::make_unique(*this); + return std::make_unique(m_public); } throw Provider_Not_Found(algo_name(), provider); } -Dilithium_PrivateKey::Dilithium_PrivateKey(RandomNumberGenerator& rng, DilithiumMode m) { - DilithiumModeConstants mode(m); - - secure_vector seedbuf = rng.random_vec(DilithiumModeConstants::SEEDBYTES); +namespace Dilithium_Algos { - auto seed = mode.H(seedbuf, 2 * DilithiumModeConstants::SEEDBYTES + DilithiumModeConstants::CRHBYTES); +namespace { - // seed is a concatenation of rho || rhoprime || key - std::vector rho(seed.begin(), seed.begin() + DilithiumModeConstants::SEEDBYTES); - secure_vector rhoprime(seed.begin() + DilithiumModeConstants::SEEDBYTES, - seed.begin() + DilithiumModeConstants::SEEDBYTES + DilithiumModeConstants::CRHBYTES); - secure_vector key(seed.begin() + DilithiumModeConstants::SEEDBYTES + DilithiumModeConstants::CRHBYTES, - seed.end()); +std::pair compute_t1_and_t0(const DilithiumPolyMatNTT& A, + const DilithiumPolyVec& s1, + const DilithiumPolyVec& s2) { + auto t_hat = A * ntt(s1.clone()); + t_hat.reduce(); + auto t = inverse_ntt(std::move(t_hat)); + t += s2; + t.conditional_add_q(); - BOTAN_ASSERT_NOMSG(rho.size() == DilithiumModeConstants::SEEDBYTES); - BOTAN_ASSERT_NOMSG(rhoprime.size() == DilithiumModeConstants::CRHBYTES); - BOTAN_ASSERT_NOMSG(key.size() == DilithiumModeConstants::SEEDBYTES); + return Dilithium_Algos::power2round(t); +} - /* Sample short vectors s1 and s2 */ - Dilithium::PolynomialVector s1(mode.l()); - Dilithium::PolynomialVector::fill_polyvec_uniform_eta(s1, rhoprime, 0, mode); +} // namespace - Dilithium::PolynomialVector s2(mode.k()); - Dilithium::PolynomialVector::fill_polyvec_uniform_eta(s2, rhoprime, mode.l(), mode); +} // namespace Dilithium_Algos - auto [t0, t1] = calculate_t0_and_t1(mode, rho, s1, s2); +/** + * NIST FIPS 204 IPD, Algorithm 1 (ML-DSA.KeyGen) + */ +Dilithium_PrivateKey::Dilithium_PrivateKey(RandomNumberGenerator& rng, DilithiumMode m) { + DilithiumConstants mode(m); + const auto& sympriv = mode.symmetric_primitives(); - m_public = std::make_shared(mode, rho, std::move(t1)); + const auto xi = rng.random_vec(DilithiumConstants::SEED_RANDOMNESS_BYTES); + auto [rho, rhoprime, key] = sympriv.H(xi); - /* Compute H(rho, t1) == H(pk) and write secret key */ - auto tr = mode.H(m_public->raw_pk(), DilithiumModeConstants::SEEDBYTES); + const auto A = Dilithium_Algos::expand_A(rho, mode); + auto [s1, s2] = Dilithium_Algos::expand_s(rhoprime, mode); + auto [t1, t0] = Dilithium_Algos::compute_t1_and_t0(A, s1, s2); + m_public = std::make_shared(mode, rho, std::move(t1)); m_private = std::make_shared( - std::move(mode), std::move(rho), std::move(tr), std::move(key), std::move(s1), std::move(s2), std::move(t0)); + std::move(mode), std::move(rho), std::move(key), m_public->tr(), std::move(s1), std::move(s2), std::move(t0)); } Dilithium_PrivateKey::Dilithium_PrivateKey(const AlgorithmIdentifier& alg_id, std::span sk) : Dilithium_PrivateKey(sk, DilithiumMode(alg_id.oid())) {} Dilithium_PrivateKey::Dilithium_PrivateKey(std::span sk, DilithiumMode m) { - DilithiumModeConstants mode(m); + DilithiumConstants mode(m); BOTAN_ARG_CHECK(sk.size() == mode.private_key_bytes(), "dilithium private key does not have the correct byte count"); - m_private = std::make_shared(std::move(mode), sk); - m_public = std::make_shared( - m_private->mode(), m_private->rho(), m_private->s1(), m_private->s2()); + m_private = + Dilithium_PrivateKeyInternal::decode(std::move(mode), StrongSpan(sk)); + + // Currently, Botan's Private_Key class inherits from Public_Key, forcing us + // to derive the public key from the private key here. + const auto A = Dilithium_Algos::expand_A(m_private->rho(), m_private->mode()); + auto [t1, _] = Dilithium_Algos::compute_t1_and_t0(A, m_private->s1(), m_private->s2()); + m_public = std::make_shared(m_private->mode(), m_private->rho(), std::move(t1)); + + if(m_public->tr() != m_private->tr()) { + throw Decoding_Error("Calculated dilithium public key hash does not match the one stored in the private key"); + } } secure_vector Dilithium_PrivateKey::raw_private_key_bits() const { @@ -622,7 +470,7 @@ secure_vector Dilithium_PrivateKey::raw_private_key_bits() const { } secure_vector Dilithium_PrivateKey::private_key_bits() const { - return m_private->raw_sk(); + return std::move(m_private->raw_sk().get()); } std::unique_ptr Dilithium_PrivateKey::create_signature_op(RandomNumberGenerator& rng, @@ -633,9 +481,11 @@ std::unique_ptr Dilithium_PrivateKey::create_signature_op(Ran BOTAN_ARG_CHECK(params.empty() || params == "Deterministic" || params == "Randomized", "Unexpected parameters for signing with Dilithium"); + // TODO: ML-DSA uses the randomized (hedged) variant by default. + // We might even drop support for the deterministic variant. const bool randomized = (params == "Randomized"); if(provider.empty() || provider == "base") { - return std::make_unique(*this, randomized); + return std::make_unique(m_private, randomized); } throw Provider_Not_Found(algo_name(), provider); } diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.cpp b/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.cpp new file mode 100644 index 00000000000..8ef02df19ac --- /dev/null +++ b/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.cpp @@ -0,0 +1,779 @@ +/* + * Crystals Dilithium Internal Algorithms (aka. "Auxiliary Functions") + * + * This implements the auxiliary functions of the Crystals Dilithium signature + * scheme as specified in NIST FIPS 204 IPD, Chapter 8. + * + * Some implementations are based on the public domain reference implementation + * by the designers (https://github.com/pq-crystals/dilithium) + * + * (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 Fabian Albert and René Meusel, Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Botan::Dilithium_Algos { + +namespace { + +/** + * Returns an all-one mask if @p x is negative, otherwise an all-zero mask. + */ +template +constexpr auto is_negative_mask(T x) { + using unsigned_T = std::make_unsigned_t; + return CT::Mask::expand_top_bit(static_cast(x)); +} + +template +constexpr std::make_unsigned_t map_range(DilithiumConstants::T c) { + // NIST FIPS 204 IPD, Algorithm 11 (BitPack) + // c is in range [-a, b] and must be mapped to [0, a + b] as follows: + BOTAN_DEBUG_ASSERT(b - c >= 0); + return b - c; +} + +template +constexpr DilithiumConstants::T unmap_range(std::make_unsigned_t c) { + // NIST FIPS 204 IPD, Algorithm 13 (BitUnpack) + // c is in range [0, a + b] and must be mapped to [-a, b] as follows: + return static_cast(b - c); +} + +template +constexpr void poly_pack(const CRYSTALS::Polynomial& p, BufferStuffer& stuffer) { + if constexpr(a == 0) { + // If `a` is 0, we assume SimpleBitPack (Algorithm 10) where the + // coefficients are in the range [0, b]. + CRYSTALS::pack(p, stuffer); + } else { + // Otherwise, for BitPack (Algorithm 11), we must map the coefficients to + // positive values as they are in the range [-a, b]. + CRYSTALS::pack(p, stuffer, map_range); + } +} + +template +constexpr void poly_unpack(CRYSTALS::Polynomial& p, ByteSourceT& get_bytes, bool check_range = false) { + if constexpr(a == 0) { + // If `a` is 0, we assume SimpleBitUnpack (Algorithm 12) where the + // coefficients are in the range [0, b]. + CRYSTALS::unpack(p, get_bytes); + } else { + // Otherwise, BitUnpack (Algorithm 13) must map the unpacked coefficients + // to the range [-a, b]. + CRYSTALS::unpack(p, get_bytes, unmap_range); + } + if(check_range && !p.ct_validate_value_range(-a, b)) { + throw Decoding_Error("Decoded polynomial coefficients out of range"); + } +} + +/** + * NIST FIPS 204 IPD, Algorithm 10 (SimpleBitPack) + * (for a = 2^(bitlen(q-1)-d) - 1) + */ +void poly_pack_t1(const DilithiumPoly& p, BufferStuffer& stuffer) { + constexpr auto b = (1 << (bitlen(DilithiumConstants::Q - 1) - DilithiumConstants::D)) - 1; + poly_pack<0, b>(p, stuffer); +} + +/** + * NIST FIPS 204 IPD, Algorithm 10 (SimpleBitPack) + * (for a = (q-1)/(2*gamma2-1)) + */ +void poly_pack_w1(const DilithiumPoly& p, BufferStuffer& stuffer, const DilithiumConstants& mode) { + using Gamma2 = DilithiumConstants::DilithiumGamma2; + auto calculate_b = [](auto gamma2) { return ((DilithiumConstants::Q - 1) / (2 * gamma2)) - 1; }; + switch(mode.gamma2()) { + case Gamma2::Qminus1DevidedBy88: + return poly_pack<0, calculate_b(Gamma2::Qminus1DevidedBy88)>(p, stuffer); + case Gamma2::Qminus1DevidedBy32: + return poly_pack<0, calculate_b(Gamma2::Qminus1DevidedBy32)>(p, stuffer); + } + + BOTAN_ASSERT_UNREACHABLE(); +} + +/** + * NIST FIPS 204 IPD, Algorithm 11 (BitPack) + * (for a = -gamma1 - 1, b = gamma1) + */ +void poly_pack_gamma1(const DilithiumPoly& p, BufferStuffer& stuffer, const DilithiumConstants& mode) { + using Gamma1 = DilithiumConstants::DilithiumGamma1; + switch(mode.gamma1()) { + case Gamma1::ToThe17th: + return poly_pack(p, stuffer); + case Gamma1::ToThe19th: + return poly_pack(p, stuffer); + } + + BOTAN_ASSERT_UNREACHABLE(); +} + +/** + * NIST FIPS 204 IPD, Algorithm 11 (BitPack) + * (for a = -eta, b = eta) + */ +void poly_pack_eta(const DilithiumPoly& p, BufferStuffer& stuffer, const DilithiumConstants& mode) { + using Eta = DilithiumConstants::DilithiumEta; + switch(mode.eta()) { + case Eta::_2: + return poly_pack(p, stuffer); + case Eta::_4: + return poly_pack(p, stuffer); + } + + BOTAN_ASSERT_UNREACHABLE(); +} + +/** + * NIST FIPS 204 IPD, Algorithm 11 (BitPack) + * (for a = -2^(d-1) - 1, b = 2^(d-1)) + */ +void poly_pack_t0(const DilithiumPoly& p, BufferStuffer& stuffer) { + constexpr auto TwoToTheDminus1 = 1 << (DilithiumConstants::D - 1); + poly_pack(p, stuffer); +} + +/** + * NIST FIPS 204 IPD, Algorithm 12 (SimpleBitUnpack) + * (for a = 2^(bitlen(q-1)-d) - 1) + */ +void poly_unpack_t1(DilithiumPoly& p, BufferSlicer& slicer) { + constexpr auto b = (1 << (bitlen(DilithiumConstants::Q - 1) - DilithiumConstants::D)) - 1; + poly_unpack<0, b>(p, slicer); +} + +/** + * NIST FIPS 204 IPD, Algorithm 13 (BitUnpack) + * (for a = -gamma1 - 1, b = gamma1) + */ +template +void poly_unpack_gamma1(DilithiumPoly& p, ByteSourceT& byte_source, const DilithiumConstants& mode) { + using Gamma1 = DilithiumConstants::DilithiumGamma1; + switch(mode.gamma1()) { + case Gamma1::ToThe17th: + return poly_unpack(p, byte_source); + case Gamma1::ToThe19th: + return poly_unpack(p, byte_source); + } + + BOTAN_ASSERT_UNREACHABLE(); +} + +/** + * NIST FIPS 204 IPD, Algorithm 13 (BitUnpack) + * (for a = -eta, b = eta) + */ +void poly_unpack_eta(DilithiumPoly& p, BufferSlicer& slicer, const DilithiumConstants& mode, bool check_range = false) { + using Eta = DilithiumConstants::DilithiumEta; + switch(mode.eta()) { + case Eta::_2: + return poly_unpack(p, slicer, check_range); + case Eta::_4: + return poly_unpack(p, slicer, check_range); + } + + BOTAN_ASSERT_UNREACHABLE(); +} + +/** + * NIST FIPS 204 IPD, Algorithm 13 (BitUnpack) + * (for a = -2^(d-1) - 1, b = 2^(d-1)) + */ +void poly_unpack_t0(DilithiumPoly& p, BufferSlicer& slicer) { + constexpr auto TwoToTheDminus1 = 1 << (DilithiumConstants::D - 1); + poly_unpack(p, slicer); +} + +/** + * NIST FIPS 204 IPD, Algorithm 14 (HintBitPack) + */ +void hint_pack(const DilithiumPolyVec& h, BufferStuffer& stuffer, const DilithiumConstants& mode) { + BOTAN_ASSERT_NOMSG(h.size() == mode.k()); + BOTAN_DEBUG_ASSERT(h.ct_validate_value_range(0, 1)); + + BufferStuffer bit_positions(stuffer.next(mode.omega())); + BufferStuffer offsets(stuffer.next(mode.k())); + + uint8_t index = 0; + for(const auto& p : h) { + for(size_t i = 0; i < p.size(); ++i) { + if(p[i] == 1) { + bit_positions.append(static_cast(i)); + ++index; + } + } + offsets.append(index); + } + + // Fill the remaining bit positions with zeros + bit_positions.append(0, bit_positions.remaining_capacity()); +} + +/** + * NIST FIPS 204 IPD, Algorithm 15 (HintBitUnpack) + */ +std::optional hint_unpack(BufferSlicer& slicer, const DilithiumConstants& mode) { + BufferSlicer bit_positions(slicer.take(mode.omega())); + BufferSlicer offsets(slicer.take(mode.k())); + + DilithiumPolyVec hint(mode.k()); + uint8_t index = 0; + for(auto& p : hint) { + const auto end_index = offsets.take_byte(); + + // Check the bounds of the end index for this polynomial + if(end_index < index || end_index > mode.omega()) { + return std::nullopt; + } + + const auto set_bits = bit_positions.take(end_index - index); + + // Check that the set bit positions are ordered (strong unforgeability) + // TODO: explicitly add a test for this, Whycheproof perhaps? + for(size_t i = 1; i < set_bits.size(); ++i) { + if(set_bits[i] <= set_bits[i - 1]) { + return std::nullopt; + } + } + + // Set the specified bits in the polynomial + for(const auto i : set_bits) { + p[i] = 1; + } + + index = end_index; + } + + // Check that the remaining bit positions are all zero (strong unforgeability) + const auto remaining = bit_positions.take(bit_positions.remaining()); + if(!std::all_of(remaining.begin(), remaining.end(), [](auto b) { return b == 0; })) { + return std::nullopt; + } + + BOTAN_DEBUG_ASSERT(hint.ct_validate_value_range(0, 1)); + return hint; +} + +} // namespace + +/** + * NIST FIPS 204 IPD, Algorithm 16 (pkEncode) + */ +DilithiumSerializedPublicKey encode_public_key(StrongSpan rho, + const DilithiumPolyVec& t1, + const DilithiumConstants& mode) { + DilithiumSerializedPublicKey pk(mode.public_key_bytes()); + BufferStuffer stuffer(pk); + + stuffer.append(rho); + for(const auto& p : t1) { + poly_pack_t1(p, stuffer); + } + + BOTAN_ASSERT_NOMSG(stuffer.full()); + return pk; +} + +/** + * NIST FIPS 204 IPD, Algorithm 17 (pkDecode) + */ +std::pair decode_public_key(StrongSpan pk, + const DilithiumConstants& mode) { + if(pk.size() != mode.public_key_bytes()) { + throw Decoding_Error("Dilithium: Invalid public key length"); + } + + BufferSlicer slicer(pk); + auto rho = slicer.copy(DilithiumConstants::SEED_RHO_BYTES); + + DilithiumPolyVec t1(mode.k()); + for(auto& p : t1) { + poly_unpack_t1(p, slicer); + } + BOTAN_ASSERT_NOMSG(slicer.empty()); + + return {std::move(rho), std::move(t1)}; +} + +/** + * NIST FIPS 204 IPD, Algorithm 18 (skEncode) + */ +DilithiumSerializedPrivateKey encode_private_key(StrongSpan rho, + StrongSpan tr, + StrongSpan key, + const DilithiumPolyVec& s1, + const DilithiumPolyVec& s2, + const DilithiumPolyVec& t0, + const DilithiumConstants& mode) { + DilithiumSerializedPrivateKey sk(mode.private_key_bytes()); + BufferStuffer stuffer(sk); + + stuffer.append(rho); + stuffer.append(key); + stuffer.append(tr); + + for(const auto& p : s1) { + poly_pack_eta(p, stuffer, mode); + } + + for(const auto& p : s2) { + poly_pack_eta(p, stuffer, mode); + } + + for(const auto& p : t0) { + poly_pack_t0(p, stuffer); + } + + BOTAN_ASSERT_NOMSG(stuffer.full()); + return sk; +} + +/** + * NIST FIPS 204 IPD, Algorithm 19 (skDecode) + */ +std::tuple +decode_private_key(StrongSpan sk, const DilithiumConstants& mode) { + if(sk.size() != mode.private_key_bytes()) { + throw Decoding_Error("Dilithium: Invalid private key length"); + } + + BufferSlicer slicer(sk); + + auto rho = slicer.copy(DilithiumConstants::SEED_RHO_BYTES); + auto key = slicer.copy(DilithiumConstants::SEED_SIGNING_KEY_BYTES); + auto tr = slicer.copy(DilithiumConstants::PUBLIC_KEY_HASH_BYTES); + + DilithiumPolyVec s1(mode.l()); + for(auto& p : s1) { + poly_unpack_eta(p, slicer, mode, true /* check decoded value range */); + } + + DilithiumPolyVec s2(mode.k()); + for(auto& p : s2) { + poly_unpack_eta(p, slicer, mode, true /* check decoded value range */); + } + + DilithiumPolyVec t0(mode.k()); + for(auto& p : t0) { + poly_unpack_t0(p, slicer); + } + + BOTAN_ASSERT_NOMSG(slicer.empty()); + return {std::move(rho), std::move(key), std::move(tr), std::move(s1), std::move(s2), std::move(t0)}; +} + +/** + * NIST FIPS 204 IPD, Algorithm 20 (sigEncode) + */ +DilithiumSerializedSignature encode_signature(StrongSpan c, + const DilithiumPolyVec& response, + const DilithiumPolyVec& hint, + const DilithiumConstants& mode) { + DilithiumSerializedSignature sig(mode.signature_bytes()); + BufferStuffer stuffer(sig); + + stuffer.append(c); + for(const auto& p : response) { + poly_pack_gamma1(p, stuffer, mode); + } + hint_pack(hint, stuffer, mode); + + return sig; +} + +/** + * NIST FIPS 204 IPD, Algorithm 21 (sigDecode) + */ +std::optional> decode_signature( + StrongSpan sig, const DilithiumConstants& mode) { + BufferSlicer slicer(sig); + BOTAN_ASSERT_NOMSG(slicer.remaining() == mode.signature_bytes()); + + auto commitment_hash = slicer.copy(DilithiumConstants::COMMITMENT_HASH_C1_BYTES); + + DilithiumPolyVec response(mode.l()); + for(auto& p : response) { + poly_unpack_gamma1(p, slicer, mode); + } + BOTAN_ASSERT_NOMSG(slicer.remaining() == mode.omega() + mode.k()); + + auto hint = hint_unpack(slicer, mode); + BOTAN_ASSERT_NOMSG(slicer.empty()); + if(!hint.has_value()) { + return std::nullopt; + } + + return std::make_tuple(std::move(commitment_hash), std::move(response), std::move(hint.value())); +} + +/** + * NIST FIPS 204 IPD, Algorithm 22 (w1Encode) + */ +DilithiumSerializedCommitment encode_commitment(const DilithiumPolyVec& w1, const DilithiumConstants& mode) { + DilithiumSerializedCommitment commitment(mode.serialized_commitment_bytes()); + BufferStuffer stuffer(commitment); + + for(const auto& p : w1) { + poly_pack_w1(p, stuffer, mode); + } + + return commitment; +} + +/** + * NIST FIPS 204 IPD, Algorithm 23 (SampleInBall) + */ +DilithiumPoly sample_in_ball(StrongSpan seed, const DilithiumConstants& mode) { + auto xof = mode.symmetric_primitives().H(seed); + + // This generator resembles the while loop in the spec. + auto next_byte_lower_than = [&xof](size_t i) -> uint8_t { + while(true) { + if(const uint8_t b = xof.output_next_byte(); b <= i) { + return b; + } + } + }; + + DilithiumPoly c; + uint64_t signs = load_le(xof.output<8>()); + for(size_t i = c.size() - mode.tau(); i < c.size(); ++i) { + const auto j = next_byte_lower_than(i); + c[i] = c[j]; + c[j] = 1 - 2 * (signs & 1); + signs >>= 1; + } + + BOTAN_DEBUG_ASSERT(c.ct_validate_value_range(-1, 1)); + BOTAN_DEBUG_ASSERT(c.hamming_weight() == mode.tau()); + + return c; +} + +namespace { + +/** + * NIST FIPS 204 IPD, Algorithm 24 (RejNTTPoly) + */ +void sample_ntt_uniform(StrongSpan rho, + DilithiumPolyNTT& p, + uint16_t nonce, + const DilithiumConstants& mode) { + /** + * A generator that returns the next coefficient sampled from the XOF, + * according to: NIST FIPS 204 IPD, Algorithm 8 (CoeffFromThreeBytes). + */ + auto next_coeff = [](Botan::XOF& xof) -> uint32_t { + std::array bytes = {0}; + std::span sampling_sink_in_bytes = std::span{bytes}.first<3>(); + + while(true) { + xof.output(sampling_sink_in_bytes); + const auto z = load_le(bytes) & 0x7FFFFF; + if(z < DilithiumConstants::Q) { + return z; + } + } + }; + + auto& xof = mode.symmetric_primitives().H(rho, nonce); + for(auto& coeff : p) { + coeff = next_coeff(xof); + } + + BOTAN_DEBUG_ASSERT(p.ct_validate_value_range(0, DilithiumConstants::Q - 1)); +} + +/** + * NIST FIPS 204 IPD, Algorithm 25 (RejBoundedPoly) + */ +void sample_uniform_eta(StrongSpan rhoprime, + DilithiumPoly& p, + uint16_t nonce, + const DilithiumConstants& mode) { + using Eta = DilithiumConstants::DilithiumEta; + + /** + * NIST FIPS 204 IPD, Algorithm 9 (CoeffFromHalfByte) + */ + auto coeff_from_halfbyte = [eta = mode.eta()](uint8_t b) -> std::optional { + BOTAN_DEBUG_ASSERT(b < 16); + + if(eta == Eta::_2 && b < 15) { + b = b - (205 * b >> 10) * 5; + return 2 - b; + } + + if(eta == Eta::_4 && b < 9) { + return 4 - b; + } + + return std::nullopt; + }; + + // A generator that returns the next coefficient sampled from the XOF. As the + // sampling uses half-bytes, this keeps track of the additionally sampled + // coefficient as needed. + auto next_coeff = [&, stashed_coeff = std::optional{}](Botan::XOF& xof) mutable -> int32_t { + if(auto stashed = std::exchange(stashed_coeff, std::nullopt)) { + return *stashed; + } + + BOTAN_DEBUG_ASSERT(!stashed_coeff.has_value()); + while(true) { + const auto b = xof.output_next_byte(); + const auto z0 = coeff_from_halfbyte(b & 0x0F); + const auto z1 = coeff_from_halfbyte(b >> 4); + + if(z0.has_value()) { + stashed_coeff = z1; // keep candidate z1 for the next invocation + return *z0; + } else if(z1.has_value()) { + // z0 was invalid, z1 is valid, nothing to stash + return *z1; + } + } + }; + + auto& xof = mode.symmetric_primitives().H(rhoprime, nonce); + for(auto& coeff : p) { + coeff = next_coeff(xof); + } + + BOTAN_DEBUG_ASSERT(p.ct_validate_value_range(-static_cast(mode.eta()), mode.eta())); +} + +} // namespace + +/** + * NIST FIPS 204 IPD, Algorithm 26 (ExpandA) + */ +DilithiumPolyMatNTT expand_A(StrongSpan rho, const DilithiumConstants& mode) { + DilithiumPolyMatNTT A(mode.k(), mode.l()); + for(uint8_t r = 0; r < mode.k(); ++r) { + for(uint8_t s = 0; s < mode.l(); ++s) { + // In FIPS 204 IPD this is denoted as IntegerToBits(s,8)||IntegerToBits(r,8) + const uint16_t nonce = make_uint16(r, s); + sample_ntt_uniform(rho, A[r][s], nonce, mode); + } + } + return A; +} + +/** + * NIST FIPS 204 IPD, Algorithm 27 (ExpandS) + */ +std::pair expand_s(StrongSpan rhoprime, + const DilithiumConstants& mode) { + DilithiumPolyVec s1(mode.l()); + DilithiumPolyVec s2(mode.k()); + + uint16_t nonce = 0; + for(auto& p : s1) { + sample_uniform_eta(rhoprime, p, nonce++, mode); + } + + for(auto& p : s2) { + sample_uniform_eta(rhoprime, p, nonce++, mode); + } + + return {std::move(s1), std::move(s2)}; +} + +/** + * NIST FIPS 204 IPD, Algorithm 28 (ExpandMask) + */ +DilithiumPolyVec expand_mask(StrongSpan rhoprime, + uint16_t nonce, + const DilithiumConstants& mode) { + DilithiumPolyVec s(mode.l()); + for(auto& p : s) { + auto& xof = mode.symmetric_primitives().H(rhoprime, nonce++); + poly_unpack_gamma1(p, xof, mode); + } + return s; +} + +/** + * NIST FIPS 204 IPD, Algorithm 29 (Power2Round) + */ +std::pair power2round(const DilithiumPolyVec& vec) { + // This procedure is taken verbatim from Dilithium's reference implementation. + auto power2round = [d = DilithiumConstants::D](int32_t r) -> std::pair { + const int32_t r1 = (r + (1 << (d - 1)) - 1) >> d; + const int32_t r0 = r - (r1 << d); + return {r1, r0}; + }; + + auto result = std::make_pair(DilithiumPolyVec(vec.size()), DilithiumPolyVec(vec.size())); + + for(size_t i = 0; i < vec.size(); ++i) { + for(size_t j = 0; j < vec[i].size(); ++j) { + std::tie(result.first[i][j], result.second[i][j]) = power2round(vec[i][j]); + } + } + + return result; +} + +namespace { + +auto decompose_fn(const DilithiumConstants& mode) { + using Gamma2 = DilithiumConstants::DilithiumGamma2; + + // This procedure is taken verbatim from Dilithium's reference implementation. + return [gamma2 = mode.gamma2(), q = DilithiumConstants::Q](int32_t r) -> std::pair { + int32_t r1 = (r + 127) >> 7; + + switch(gamma2) { + case Gamma2::Qminus1DevidedBy32: + r1 = (r1 * 1025 + (1 << 21)) >> 22; + r1 &= 15; + break; + case Gamma2::Qminus1DevidedBy88: + r1 = (r1 * 11275 + (1 << 23)) >> 24; + r1 ^= is_negative_mask(43 - r1).if_set_return(r1); + break; + } + + int32_t r0 = r - r1 * 2 * gamma2; + r0 -= is_negative_mask((q - 1) / 2 - r0).if_set_return(q); + + return {r1, r0}; + }; +} + +} // namespace + +/** + * NIST FIPS 204 IPD, Algorithm 30 (Decompose) + * + * Algorithms 31 (HighBits) and 32 (LowBits) are not implemented explicitly, + * simply use the first (HighBits) and second (LowBits) element of the result. + */ +std::pair decompose(const DilithiumPolyVec& vec, const DilithiumConstants& mode) { + auto decompose = decompose_fn(mode); + auto result = std::make_pair(DilithiumPolyVec(vec.size()), DilithiumPolyVec(vec.size())); + + for(size_t i = 0; i < vec.size(); ++i) { + for(size_t j = 0; j < vec[i].size(); ++j) { + std::tie(result.first[i][j], result.second[i][j]) = decompose(vec[i][j]); + } + } + + return result; +} + +/** + * NIST FIPS 204 IPD, Algorithm 33 (MakeHint) + */ +DilithiumPolyVec make_hint(const DilithiumPolyVec& z, const DilithiumPolyVec& r, const DilithiumConstants& mode) { + BOTAN_DEBUG_ASSERT(z.size() == r.size()); + + auto make_hint = [gamma2 = int32_t(mode.gamma2()), q_gamma2 = DilithiumConstants::Q - int32_t(mode.gamma2())]( + int32_t c0, int32_t c1) -> bool { + if(c0 <= gamma2 || c0 > q_gamma2 || (c0 == q_gamma2 && c1 == 0)) { + return false; + } + return true; + }; + + DilithiumPolyVec hint(r.size()); + + for(size_t i = 0; i < r.size(); ++i) { + for(size_t j = 0; j < r[i].size(); ++j) { + hint[i][j] = make_hint(z[i][j], r[i][j]); + } + } + + BOTAN_DEBUG_ASSERT(hint.ct_validate_value_range(0, 1)); + + return hint; +} + +/** + * NIST FIPS 204 IPD, Algorithm 34 (UseHint) + */ +void use_hint(DilithiumPolyVec& vec, const DilithiumPolyVec& hints, const DilithiumConstants& mode) { + using Gamma2 = DilithiumConstants::DilithiumGamma2; + + BOTAN_DEBUG_ASSERT(hints.size() == vec.size()); + BOTAN_DEBUG_ASSERT(hints.ct_validate_value_range(0, 1)); + BOTAN_DEBUG_ASSERT(vec.ct_validate_value_range(0, DilithiumConstants::Q - 1)); + + auto use_hint = [gamma2 = mode.gamma2(), decompose = decompose_fn(mode)](bool hint, int32_t r) -> int32_t { + auto [r1, r0] = decompose(r); + + if(!hint) { + return r1; + } + + switch(gamma2) { + case Gamma2::Qminus1DevidedBy32: + BOTAN_DEBUG_ASSERT(r1 > -16 && r1 < 16); + return (r0 > 0) ? (r1 + 1) & 15 : (r1 - 1) & 15; + case Gamma2::Qminus1DevidedBy88: + BOTAN_DEBUG_ASSERT(r1 > -44 && r1 < 44); + // NOLINTNEXTLINE(*-avoid-nested-conditional-operator) + return (r0 > 0) ? ((r1 == 43) ? 0 : r1 + 1) : ((r1 == 0) ? 43 : r1 - 1); + } + + BOTAN_ASSERT_UNREACHABLE(); + }; + + for(size_t i = 0; i < vec.size(); ++i) { + for(size_t j = 0; j < vec[i].size(); ++j) { + vec[i][j] = use_hint(hints[i][j], vec[i][j]); + } + } + + BOTAN_DEBUG_ASSERT(vec.ct_validate_value_range(0, (DilithiumConstants::Q - 1) / (2 * mode.gamma2()))); +} + +bool infinity_norm_within_bound(const DilithiumPolyVec& vec, size_t bound) { + BOTAN_DEBUG_ASSERT(bound <= (DilithiumConstants::Q - 1) / 8); + + // It is ok to leak which coefficient violates the bound as the probability + // for each coefficient is independent of secret data but we must not leak + // the sign of the centralized representative. + for(const auto& p : vec) { + for(auto c : p) { + const auto abs_c = c - is_negative_mask(c).if_set_return(2 * c); + if(abs_c >= bound) { + return false; + } + } + } + + return true; +} + +} // namespace Botan::Dilithium_Algos diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.h b/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.h new file mode 100644 index 00000000000..2e891317345 --- /dev/null +++ b/src/lib/pubkey/dilithium/dilithium_common/dilithium_algos.h @@ -0,0 +1,75 @@ +/* + * Crystals Dilithium Internal Algorithms + * + * (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_DILITHIUM_ALGOS_H_ +#define BOTAN_DILITHIUM_ALGOS_H_ + +#include + +namespace Botan::Dilithium_Algos { + +DilithiumPolyMatNTT expand_A(StrongSpan rho, const DilithiumConstants& mode); + +std::pair expand_s(StrongSpan rhoprime, + const DilithiumConstants& mode); + +DilithiumPolyVec expand_mask(StrongSpan rhoprime, + uint16_t nonce, + const DilithiumConstants& mode); + +DilithiumSerializedCommitment encode_commitment(const DilithiumPolyVec& w1, const DilithiumConstants& mode); + +DilithiumPoly sample_in_ball(StrongSpan seed, const DilithiumConstants& mode); + +std::optional> decode_signature( + StrongSpan sig, const DilithiumConstants& mode); + +DilithiumSerializedSignature encode_signature(StrongSpan c, + const DilithiumPolyVec& response, + const DilithiumPolyVec& hint, + const DilithiumConstants& mode); + +DilithiumSerializedPublicKey encode_public_key(StrongSpan rho, + const DilithiumPolyVec& t1, + const DilithiumConstants& mode); + +std::pair decode_public_key(StrongSpan pk, + const DilithiumConstants& mode); + +DilithiumSerializedPrivateKey encode_private_key(StrongSpan rho, + StrongSpan tr, + StrongSpan key, + const DilithiumPolyVec& s1, + const DilithiumPolyVec& s2, + const DilithiumPolyVec& t0, + const DilithiumConstants& mode); + +std::tuple +decode_private_key(StrongSpan sk, const DilithiumConstants& mode); + +std::pair power2round(const DilithiumPolyVec& vec); + +std::pair decompose(const DilithiumPolyVec& vec, const DilithiumConstants& mode); + +DilithiumPolyVec make_hint(const DilithiumPolyVec& z, const DilithiumPolyVec& r, const DilithiumConstants& mode); + +void use_hint(DilithiumPolyVec& vec, const DilithiumPolyVec& hints, const DilithiumConstants& mode); + +bool infinity_norm_within_bound(const DilithiumPolyVec& vec, size_t bound); + +} // namespace Botan::Dilithium_Algos + +#endif diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.cpp b/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.cpp new file mode 100644 index 00000000000..636d2cc706c --- /dev/null +++ b/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.cpp @@ -0,0 +1,74 @@ +/* + * Crystals Dilithium Constants + * + * (C) 2022-2023 Jack Lloyd + * (C) 2022 Manuel Glaser - Rohde & Schwarz Cybersecurity + * (C) 2022-2023 Michael Boric, René Meusel - Rohde & Schwarz Cybersecurity + * (C) 2024 René Meusel, Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#include + +#include + +namespace Botan { + +DilithiumConstants::DilithiumConstants(DilithiumMode mode) : m_mode(mode) { + switch(m_mode.mode()) { + case Botan::DilithiumMode::Dilithium4x4: + case Botan::DilithiumMode::Dilithium4x4_AES: + m_tau = DilithiumTau::_39; + m_lambda = DilithiumLambda::_128; + m_gamma1 = DilithiumGamma1::ToThe17th; + m_gamma2 = DilithiumGamma2::Qminus1DevidedBy88; + m_k = 4; + m_l = 4; + m_eta = DilithiumEta::_2; + m_beta = DilithiumBeta::_78; + m_omega = DilithiumOmega::_80; + break; + case Botan::DilithiumMode::Dilithium6x5: + case Botan::DilithiumMode::Dilithium6x5_AES: + m_tau = DilithiumTau::_49; + m_lambda = DilithiumLambda::_192; + m_gamma1 = DilithiumGamma1::ToThe19th; + m_gamma2 = DilithiumGamma2::Qminus1DevidedBy32; + m_k = 6; + m_l = 5; + m_eta = DilithiumEta::_4; + m_beta = DilithiumBeta::_196; + m_omega = DilithiumOmega::_55; + break; + case Botan::DilithiumMode::Dilithium8x7: + case Botan::DilithiumMode::Dilithium8x7_AES: + m_tau = DilithiumTau::_60; + m_lambda = DilithiumLambda::_256; + m_gamma1 = DilithiumGamma1::ToThe19th; + m_gamma2 = DilithiumGamma2::Qminus1DevidedBy32; + m_k = 8; + m_l = 7; + m_eta = DilithiumEta::_2; + m_beta = DilithiumBeta::_120; + m_omega = DilithiumOmega::_75; + break; + } + + const auto s1_bytes = 32 * m_l * bitlen(2 * m_eta); + const auto s2_bytes = 32 * m_k * bitlen(2 * m_eta); + const auto t0_bytes = 32 * m_k * D; + const auto t1_bytes = 32 * m_k * (bitlen(static_cast(Q) - 1) - D); + const auto z_bytes = 32 * m_l * (1 + bitlen(m_gamma1 - 1)); + const auto hint_bytes = m_omega + m_k; + + m_private_key_bytes = + SEED_RHO_BYTES + SEED_SIGNING_KEY_BYTES + PUBLIC_KEY_HASH_BYTES + s1_bytes + s2_bytes + t0_bytes; + m_public_key_bytes = SEED_RHO_BYTES + t1_bytes; + m_signature_bytes = COMMITMENT_HASH_FULL_BYTES + z_bytes + hint_bytes; + m_serialized_commitment_bytes = 32 * m_k * bitlen(((Q - 1) / (2 * m_gamma2)) - 1); + + m_symmetric_primitives = Dilithium_Symmetric_Primitives::create(*this); +} + +} // namespace Botan diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.h b/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.h new file mode 100644 index 00000000000..fa7785b7cbc --- /dev/null +++ b/src/lib/pubkey/dilithium/dilithium_common/dilithium_constants.h @@ -0,0 +1,174 @@ +/* + * Crystals Dilithium Constants + * + * (C) 2022-2023 Jack Lloyd + * (C) 2022 Manuel Glaser - Rohde & Schwarz Cybersecurity + * (C) 2022-2023 Michael Boric, René Meusel - Rohde & Schwarz Cybersecurity + * (C) 2024 René Meusel, Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_DILITHIUM_CONSTANTS_H_ +#define BOTAN_DILITHIUM_CONSTANTS_H_ + +#include + +namespace Botan { + +class Dilithium_Symmetric_Primitives; + +/** + * Algorithm constants and parameter-set dependent values + */ +class DilithiumConstants final { + public: + /// base data type for most calculations + using T = int32_t; + + /// number of coefficients in a polynomial + static constexpr T N = 256; + + /// modulus + static constexpr T Q = 8380417; + + /// number of dropped bits from t (see FIPS 204 Section 5) + static constexpr T D = 13; + + /// as specified in FIPS 204 (see Algorithm 36 (NTT^-1), f = 256^-1 mod Q) + static constexpr T F = 8347681; + + /// the 512-th root of unity modulo Q (see FIPS 204 Section 8.5) + static constexpr T ROOT_OF_UNITY = 1753; + + /// degree of the NTT polynomials + static constexpr size_t NTT_Degree = 256; + + public: + /// \name Byte length's of various hash outputs and seeds + /// @{ + + static constexpr size_t SEED_RANDOMNESS_BYTES = 32; + static constexpr size_t SEED_RHO_BYTES = 32; + static constexpr size_t SEED_RHOPRIME_BYTES = 64; + static constexpr size_t SEED_SIGNING_KEY_BYTES = 32; + static constexpr size_t MESSAGE_HASH_BYTES = 64; + static constexpr size_t PUBLIC_KEY_HASH_BYTES = 32; + static constexpr size_t COMMITMENT_HASH_FULL_BYTES = 32; + static constexpr size_t COMMITMENT_HASH_C1_BYTES = 32; + + /// @} + + public: + enum DilithiumTau : uint32_t { _39 = 39, _49 = 49, _60 = 60 }; + + enum DilithiumLambda : uint32_t { _128 = 128, _192 = 192, _256 = 256 }; + + enum DilithiumGamma1 : uint32_t { ToThe17th = (1 << 17), ToThe19th = (1 << 19) }; + + enum DilithiumGamma2 : uint32_t { Qminus1DevidedBy88 = (Q - 1) / 88, Qminus1DevidedBy32 = (Q - 1) / 32 }; + + enum DilithiumEta : uint32_t { _2 = 2, _4 = 4 }; + + enum DilithiumBeta : uint32_t { _78 = 78, _196 = 196, _120 = 120 }; + + enum DilithiumOmega : uint32_t { _80 = 80, _55 = 55, _75 = 75 }; + + DilithiumConstants(DilithiumMode dimension); + ~DilithiumConstants() = default; + + DilithiumConstants(const DilithiumConstants& other) : DilithiumConstants(other.m_mode) {} + + DilithiumConstants(DilithiumConstants&& other) = default; + DilithiumConstants& operator=(const DilithiumConstants& other) = delete; + DilithiumConstants& operator=(DilithiumConstants&& other) = default; + + bool is_modern() const { return m_mode.is_modern(); } + + bool is_aes() const { return m_mode.is_aes(); } + + public: + /// \name Foundational constants + /// @{ + + /// hamming weight of the polynomial 'c' sampled from the commitment's hash + DilithiumTau tau() const { return m_tau; } + + /// collision strength of the commitment hash function + DilithiumLambda lambda() const { return m_lambda; } + + /// coefficient range of the randomly sampled mask 'y' + DilithiumGamma1 gamma1() const { return m_gamma1; } + + /// low-order rounding range for decomposing the commitment from polynomial vector 'w' + DilithiumGamma2 gamma2() const { return m_gamma2; } + + /// dimensions of the expanded matrix A + uint8_t k() const { return m_k; } + + /// dimensions of the expanded matrix A + uint8_t l() const { return m_l; } + + /// coefficient range of the private key's polynomial vectors 's1' and 's2' + DilithiumEta eta() const { return m_eta; } + + /// tau * eta + DilithiumBeta beta() const { return m_beta; } + + /// maximal hamming weight of the hint polynomial vector 'h' + DilithiumOmega omega() const { return m_omega; } + + /// length of the entire commitment hash in bytes + size_t commitment_hash_full_bytes() const { return COMMITMENT_HASH_FULL_BYTES; } + + /// @} + + /// \name Sizes of encoded data structures + /// @{ + + /// byte length of the encoded signature + size_t signature_bytes() const { return m_signature_bytes; } + + /// byte length of the encoded public key + size_t public_key_bytes() const { return m_public_key_bytes; } + + /// byte length of the encoded private key + size_t private_key_bytes() const { return m_private_key_bytes; } + + /// byte length of the packed commitment polynomial vector 'w1' + size_t serialized_commitment_bytes() const { return m_serialized_commitment_bytes; } + + /// @} + + DilithiumMode mode() const { return m_mode; } + + /// @returns one of {44, 65, 87} + size_t canonical_parameter_set_identifier() const { return k() * 10 + l(); } + + Dilithium_Symmetric_Primitives& symmetric_primitives() const { return *m_symmetric_primitives; } + + private: + DilithiumMode m_mode; + + DilithiumTau m_tau; + DilithiumLambda m_lambda; + DilithiumGamma1 m_gamma1; + DilithiumGamma2 m_gamma2; + uint8_t m_k; + uint8_t m_l; + DilithiumEta m_eta; + DilithiumBeta m_beta; + DilithiumOmega m_omega; + + uint32_t m_private_key_bytes; + uint32_t m_public_key_bytes; + uint32_t m_signature_bytes; + uint32_t m_serialized_commitment_bytes; + + // Mode dependent primitives + std::unique_ptr m_symmetric_primitives; +}; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium_polynomial.h b/src/lib/pubkey/dilithium/dilithium_common/dilithium_polynomial.h new file mode 100644 index 00000000000..398e7ca34ad --- /dev/null +++ b/src/lib/pubkey/dilithium/dilithium_common/dilithium_polynomial.h @@ -0,0 +1,119 @@ +/* + * Crystals Dilithium Polynomial Adapter + * + * (C) 2022-2023 Jack Lloyd + * (C) 2022 Manuel Glaser - Rohde & Schwarz Cybersecurity + * (C) 2022-2023 Michael Boric, René Meusel - Rohde & Schwarz Cybersecurity + * (C) 2024 René Meusel, Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_DILITHIUM_POLYNOMIAL_H_ +#define BOTAN_DILITHIUM_POLYNOMIAL_H_ + +#include +#include +#include +#include + +namespace Botan { + +class DilithiumPolyTraits final : public CRYSTALS::Trait_Base { + private: + friend class CRYSTALS::Trait_Base; + + /** + * NIST FIPS 204 IPD, Algorithm 37 (Montgomery_Reduce) + */ + static constexpr T montgomery_reduce_coefficient(T2 a) { + const T2 t = static_cast(static_cast(static_cast(a)) * Q_inverse); + return (a - static_cast(t) * Q) >> (sizeof(T) * 8); + } + + static constexpr T barrett_reduce_coefficient(T a) { + // 2**22 is roughly Q/2 and 2**23 is roughly Q + const T t = (a + (1 << 22)) >> 23; + a = a - t * Q; + return a; + } + + public: + /** + * NIST FIPS 204 IPD, Algorithm 35 (NTT) + * + * Note: ntt(), inverse_ntt() and operator* have side effects on the + * montgomery factor of the involved coefficients! + * It is assumed that EXACTLY ONE vector or matrix multiplication + * is performed between transforming in and out of NTT domain. + * + * Produces the result of the NTT transformation without any montgomery + * factors in the coefficients. + */ + static constexpr void ntt(std::span coeffs) { + size_t j; + size_t k = 0; + + for(size_t len = N / 2; len > 0; len >>= 1) { + for(size_t start = 0; start < N; start = j + len) { + const T zeta = zetas[++k]; + for(j = start; j < start + len; ++j) { + // Zetas contain the montgomery parameter 2^32 mod q + T t = fqmul(zeta, coeffs[j + len]); + coeffs[j + len] = coeffs[j] - t; + coeffs[j] = coeffs[j] + t; + } + } + } + } + + /** + * NIST FIPS 204 IPD, Algorithm 36 (NTT^-1). + * + * The output is effectively multiplied by the montgomery parameter 2^32 + * mod q so that the input factors 2^(-32) mod q are eliminated. Note + * that factors 2^(-32) mod q are introduced by multiplication and + * reduction of values not in montgomery domain. + * + * Produces the result of the inverse NTT transformation with a montgomery + * factor of (2^32 mod q) added (!). See above. + */ + static constexpr void inverse_ntt(std::span coeffs) { + size_t j; + size_t k = N; + for(size_t len = 1; len < N; len <<= 1) { + for(size_t start = 0; start < N; start = j + len) { + const T zeta = -zetas[--k]; + for(j = start; j < start + len; ++j) { + T t = coeffs[j]; + coeffs[j] = t + coeffs[j + len]; + coeffs[j + len] = t - coeffs[j + len]; + // Zetas contain the montgomery parameter 2^32 mod q + coeffs[j + len] = fqmul(zeta, coeffs[j + len]); + } + } + } + + for(auto& coeff : coeffs) { + coeff = fqmul(coeff, F_WITH_MONTY_SQUARED); + } + } + + /** + * Multiplication of two polynomials @p lhs and @p rhs in NTT domain. + * + * Produces the result of the multiplication in NTT domain, with a factor + * of (2^-32 mod q) in each element due to montgomery reduction. + */ + static constexpr void poly_pointwise_montgomery(std::span result, + std::span lhs, + std::span rhs) { + for(size_t i = 0; i < N; ++i) { + result[i] = fqmul(lhs[i], rhs[i]); + } + } +}; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium_polynomials.h b/src/lib/pubkey/dilithium/dilithium_common/dilithium_polynomials.h deleted file mode 100644 index 569c67d8d92..00000000000 --- a/src/lib/pubkey/dilithium/dilithium_common/dilithium_polynomials.h +++ /dev/null @@ -1,1437 +0,0 @@ -/* -* Crystals Dilithium Digital Signature Algorithms -* Based on the public domain reference implementation by the -* designers (https://github.com/pq-crystals/dilithium) -* -* Further changes -* (C) 2021-2023 Jack Lloyd -* (C) 2021-2022 Manuel Glaser - Rohde & Schwarz Cybersecurity -* (C) 2021-2023 Michael Boric, René Meusel - Rohde & Schwarz Cybersecurity -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_DILITHIUM_POLYNOMIAL_H_ -#define BOTAN_DILITHIUM_POLYNOMIAL_H_ - -#include - -#include -#include - -#include -#include -#include - -namespace Botan::Dilithium { - -class Polynomial { - public: - // public member is on purpose - std::array m_coeffs; - - /** - * 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->m_coeffs.size(); ++i) { - 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->m_coeffs.size(); ++i) { - this->m_coeffs[i] = this->m_coeffs[i] - other.m_coeffs[i]; - } - return *this; - } - - /*************************************************** - * Name: rej_uniform - * - * Description: Sample uniformly random coefficients in [0, Q-1] by - * performing rejection sampling on array of random bytes. - * - * Arguments: - Polynomial& a: reference to output array (allocated) - * - size_t position: starting point - * - size_t len: number of coefficients to be sampled - * - const uint8_t *buf: array of random bytes - * - size_t buflen: length of array of random bytes - * - * Returns number of sampled coefficients. Can be smaller than len if not enough - * random bytes were given. - **************************************************/ - static size_t rej_uniform(Polynomial& p, size_t position, size_t len, const uint8_t* buf, size_t buflen) { - size_t ctr = 0, pos = 0; - while(ctr < len && pos + 3 <= buflen) { - uint32_t t = buf[pos++]; - t |= static_cast(buf[pos++]) << 8; - t |= static_cast(buf[pos++]) << 16; - t &= 0x7FFFFF; - - if(t < DilithiumModeConstants::Q) { - p.m_coeffs[position + ctr++] = static_cast(t); - } - } - return ctr; - } - - /************************************************* - * Name: rej_eta - * - * Description: Sample uniformly random coefficients in [-ETA, ETA] by - * performing rejection sampling on array of random bytes. - * - * Arguments: - Polynomial &a: pointer to output array (allocated) - * - size_t offset: starting point for the output polynomial - * - size_t len: number of coefficients to be sampled - * - const secure_vector& buf: sv reference of random bytes - * - size_t buflen: length of array of random bytes - * - const DilithiumModeConstants& - * - * Returns number of sampled coefficients. Can be smaller than len if not enough - * random bytes were given. - **************************************************/ - static size_t rej_eta(Polynomial& a, - size_t offset, - size_t len, - const secure_vector& buf, - size_t buflen, - const DilithiumModeConstants& mode) { - size_t ctr = 0, pos = 0; - while(ctr < len && pos < buflen) { - uint32_t t0 = buf[pos] & 0x0F; - uint32_t t1 = buf[pos++] >> 4; - - switch(mode.eta()) { - case DilithiumEta::Eta2: { - if(t0 < 15) { - t0 = t0 - (205 * t0 >> 10) * 5; - a.m_coeffs[offset + ctr++] = 2 - t0; - } - if(t1 < 15 && ctr < len) { - t1 = t1 - (205 * t1 >> 10) * 5; - a.m_coeffs[offset + ctr++] = 2 - t1; - } - } break; - case DilithiumEta::Eta4: { - if(t0 < 9) { - a.m_coeffs[offset + ctr++] = 4 - t0; - } - if(t1 < 9 && ctr < len) { - a.m_coeffs[offset + ctr++] = 4 - t1; - } - } break; - } - } - return ctr; - } - - /************************************************* - * Name: fill_poly_uniform_eta - * - * Description: Sample polynomial with uniformly random coefficients - * in [-ETA,ETA] by performing rejection sampling on the - * output stream from SHAKE256(seed|nonce) or AES256CTR(seed,nonce). - * - * Arguments: - Polynomial& a: reference to output polynomial - * - const uint8_t seed[]: byte array with seed of length CRHBYTES - * - uint16_t nonce: 2-byte nonce - * - const DilithiumModeConstants& mode: Mode dependent values. - **************************************************/ - static void fill_poly_uniform_eta(Polynomial& a, - const secure_vector& seed, - uint16_t nonce, - const DilithiumModeConstants& mode) { - BOTAN_ASSERT_NOMSG(seed.size() == DilithiumModeConstants::CRHBYTES); - - auto xof = mode.XOF_256(seed, nonce); - - secure_vector buf(mode.poly_uniform_eta_nblocks() * mode.stream256_blockbytes()); - xof->output(buf); - size_t ctr = Polynomial::rej_eta(a, 0, DilithiumModeConstants::N, buf, buf.size(), mode); - - while(ctr < DilithiumModeConstants::N) { - xof->output(std::span(buf).first(mode.stream256_blockbytes())); - ctr += Polynomial::rej_eta(a, ctr, DilithiumModeConstants::N - ctr, buf, mode.stream256_blockbytes(), mode); - } - } - - /************************************************* - * Name: power2round - * - * Description: For finite field element a, compute a0, a1 such that - * a mod^+ Q = a1*2^D + a0 with -2^{D-1} < a0 <= 2^{D-1}. - * Assumes a to be standard representative. - * - * Arguments: - int32_t a: input element - * - int32_t *a0: pointer to output element a0 - * - * Returns a1. - **************************************************/ - static int32_t power2round(int32_t& a0, int32_t a) { - int32_t a1 = (a + (1 << (DilithiumModeConstants::D - 1)) - 1) >> DilithiumModeConstants::D; - a0 = a - (a1 << DilithiumModeConstants::D); - return a1; - } - - /************************************************* - * Name: fill_polys_power2round - * - * Description: For all coefficients c of the input polynomial, - * compute c0, c1 such that c mod Q = c1*2^D + c0 - * with -2^{D-1} < c0 <= 2^{D-1}. Assumes coefficients to be - * standard representatives. - * - * Arguments: - Polynomial& a1: pointer to output polynomial with coefficients c1 - * - Polynomial& a0: pointer to output polynomial with coefficients c0 - * - const Polynomial& a: pointer to input polynomial - **************************************************/ - static void fill_polys_power2round(Polynomial& a1, Polynomial& a0, const Polynomial& a) { - for(size_t i = 0; i < DilithiumModeConstants::N; ++i) { - a1.m_coeffs[i] = Polynomial::power2round(a0.m_coeffs[i], a.m_coeffs[i]); - } - } - - /************************************************* - * Name: challenge - * - * Description: Implementation of H. Samples polynomial with TAU nonzero - * coefficients in {-1,1} using the output stream of - * SHAKE256(seed). - * - * Arguments: - Polynomial &c: pointer to output polynomial - * - const uint8_t mu[]: byte array containing seed of length SEEDBYTES - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - **************************************************/ - static Polynomial poly_challenge(const uint8_t* seed, const DilithiumModeConstants& mode) { - Polynomial c; - - SHAKE_256 shake256_hasher(DilithiumModeConstants::SHAKE256_RATE * 8); - shake256_hasher.update(seed, DilithiumModeConstants::SEEDBYTES); - auto buf = shake256_hasher.final(); - - uint64_t signs = 0; - for(size_t i = 0; i < 8; ++i) { - signs |= static_cast(buf[i]) << 8 * i; - } - size_t pos = 8; - - for(size_t i = 0; i < DilithiumModeConstants::N; ++i) { - c.m_coeffs[i] = 0; - } - for(size_t i = DilithiumModeConstants::N - mode.tau(); i < DilithiumModeConstants::N; ++i) { - size_t b; - do { - b = buf[pos++]; - } while(b > i); - - c.m_coeffs[i] = c.m_coeffs[b]; - c.m_coeffs[b] = 1 - 2 * (signs & 1); - signs >>= 1; - } - return c; - } - - /************************************************* - * Name: poly_chknorm - * - * Description: Check infinity norm of polynomial against given bound. - * Assumes input coefficients were reduced by reduce32(). - * - * Arguments: - const Polynomial& a: pointer to polynomial - * - size_t B: norm bound - * - * Returns false if norm is strictly smaller than B <= (Q-1)/8 and true otherwise. - **************************************************/ - static bool poly_chknorm(const Polynomial& a, size_t B) { - if(B > (DilithiumModeConstants::Q - 1) / 8) { - return true; - } - - /* It is ok to leak which coefficient violates the bound since - the probability for each coefficient is independent of secret - data but we must not leak the sign of the centralized representative. */ - for(const auto& coeff : a.m_coeffs) { - /* Absolute value */ - size_t t = coeff >> 31; - t = coeff - (t & 2 * coeff); - - if(t >= B) { - return true; - } - } - return false; - } - - /************************************************* - * Name: make_hint - * - * Description: Compute hint bit indicating whether the low bits of the - * input element overflow into the high bits. Inputs assumed - * to be standard representatives. - * - * Arguments: - size_t a0: low bits of input element - * - size_t a1: high bits of input element - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - * - * Returns 1 if overflow. - **************************************************/ - static int32_t make_hint(size_t a0, size_t a1, const DilithiumModeConstants& mode) { - const auto gamma2 = mode.gamma2(); - const auto Q_gamma2 = DilithiumModeConstants::Q - gamma2; - if(a0 <= gamma2 || a0 > Q_gamma2 || (a0 == Q_gamma2 && a1 == 0)) { - return 0; - } - return 1; - } - - /************************************************* - * Name: generate_hint_polynomial - * - * Description: Compute hint polynomial. The coefficients of which indicate - * whether the low bits of the corresponding coefficient of - * the input polynomial overflow into the high bits. - * - * Arguments: - Polynomial& h: reference to output hint polynomial - * - const Polynomial& a0: reference to low part of input polynomial - * - const Polynomial& a1: reference to high part of input polynomial - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - * - * Returns number of 1 bits. - **************************************************/ - static size_t generate_hint_polynomial(Polynomial& h, - const Polynomial& a0, - const Polynomial& a1, - const DilithiumModeConstants& mode) { - size_t s = 0; - - for(size_t i = 0; i < DilithiumModeConstants::N; ++i) { - h.m_coeffs[i] = Polynomial::make_hint(a0.m_coeffs[i], a1.m_coeffs[i], mode); - s += h.m_coeffs[i]; - } - - return s; - } - - /************************************************* - * Name: decompose - * - * Description: For finite field element a, compute high and low bits a0, a1 such - * that a mod^+ Q = a1*ALPHA + a0 with -ALPHA/2 < a0 <= ALPHA/2 except - * if a1 = (Q-1)/ALPHA where we set a1 = 0 and - * -ALPHA/2 <= a0 = a mod^+ Q - Q < 0. Assumes a to be standard - * representative. - * - * Arguments: - int32_t a: input element - * - int32_t *a0: pointer to output element a0 - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - * - * Returns a1. - **************************************************/ - static int32_t decompose(int32_t* a0, int32_t a, const DilithiumModeConstants& mode) { - int32_t a1 = (a + 127) >> 7; - if(mode.gamma2() == (DilithiumModeConstants::Q - 1) / 32) { - a1 = (a1 * 1025 + (1 << 21)) >> 22; - a1 &= 15; - } else { - BOTAN_ASSERT_NOMSG(mode.gamma2() == (DilithiumModeConstants::Q - 1) / 88); - a1 = (a1 * 11275 + (1 << 23)) >> 24; - a1 ^= ((43 - a1) >> 31) & a1; - } - - *a0 = a - a1 * 2 * static_cast(mode.gamma2()); - *a0 -= (((DilithiumModeConstants::Q - 1) / 2 - *a0) >> 31) & DilithiumModeConstants::Q; - return a1; - } - - /************************************************* - * Name: use_hint - * - * Description: Correct high bits according to hint. - * - * Arguments: - int32_t a: input element - * - size_t hint: hint bit - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - * - * Returns corrected high bits. - **************************************************/ - static int32_t use_hint(int32_t a, size_t hint, const DilithiumModeConstants& mode) { - int32_t a0; - - int32_t a1 = Polynomial::decompose(&a0, a, mode); - if(hint == 0) { - return a1; - } - - if(mode.gamma2() == ((DilithiumModeConstants::Q - 1) / 32)) { - if(a0 > 0) { - return (a1 + 1) & 15; - } else { - return (a1 - 1) & 15; - } - } else { - if(a0 > 0) { - return (a1 == 43) ? 0 : a1 + 1; - } else { - return (a1 == 0) ? 43 : a1 - 1; - } - } - } - - /************************************************* - * Name: poly_use_hint - * - * Description: Use hint polynomial to correct the high bits of a polynomial. - * - * Arguments: - Polynomial& b: reference to output polynomial with corrected high bits - * - const Polynomial& a: reference to input polynomial - * - const Polynomial& h: reference to input hint polynomial - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - * - **************************************************/ - static void poly_use_hint(Polynomial& b, - const Polynomial& a, - const Polynomial& h, - const DilithiumModeConstants& mode) { - for(size_t i = 0; i < DilithiumModeConstants::N; ++i) { - b.m_coeffs[i] = Polynomial::use_hint(a.m_coeffs[i], h.m_coeffs[i], mode); - } - } - - /************************************************* - * Name: montgomery_reduce - * - * Description: For finite field element a with -2^{31}Q <= a <= Q*2^31, - * compute r \equiv a*2^{-32} (mod Q) such that -Q < r < Q. - * - * Arguments: - int64_t: finite field element a - * - * Returns r. - **************************************************/ - int32_t montgomery_reduce(int64_t a) const { - int32_t t = static_cast(static_cast(static_cast(a)) * DilithiumModeConstants::QINV); - t = (a - static_cast(t) * DilithiumModeConstants::Q) >> 32; - return t; - } - - /************************************************* - * Name: poly_pointwise_montgomery - * - * Description: Pointwise multiplication of polynomials in NTT domain - * representation and multiplication of resulting polynomial - * by 2^{-32}. - * For finite field element a with -2^{31}Q <= a <= Q*2^31, - * compute r \equiv a*2^{-32} (mod Q) such that -Q < r < Q. - * - * Arguments: - Polynomial& c: reference to output polynomial - * - const Polynomial& a: reference to first input polynomial - * - const Polynomial& b: reference to second input polynomial - **************************************************/ - void poly_pointwise_montgomery(Polynomial& output, const Polynomial& second) const { - for(size_t i = 0; i < DilithiumModeConstants::N; ++i) { - output.m_coeffs[i] = montgomery_reduce(static_cast(m_coeffs[i]) * second.m_coeffs[i]); - } - } - - /************************************************* - * Name: ntt - * - * Description: Forward NTT, in-place. No modular reduction is performed after - * additions or subtractions. Output vector is in bitreversed order. - * - * Arguments: - Polynomial& a: input/output coefficient Polynomial - **************************************************/ - void ntt() { - size_t j; - size_t k = 0; - - for(size_t len = 128; len > 0; len >>= 1) { - for(size_t start = 0; start < DilithiumModeConstants::N; start = j + len) { - int32_t zeta = DilithiumModeConstants::ZETAS[++k]; - for(j = start; j < start + len; ++j) { - int32_t t = montgomery_reduce(static_cast(zeta) * m_coeffs[j + len]); - m_coeffs[j + len] = m_coeffs[j] - t; - m_coeffs[j] = m_coeffs[j] + t; - } - } - } - } - - /************************************************* - * Name: poly_reduce - * - * Description: Inplace reduction of all coefficients of polynomial to - * representative in [-6283009,6283007]. - * For finite field element a with a <= 2^{31} - 2^{22} - 1, - * compute r \equiv a (mod Q) such that -6283009 <= r <= 6283007. - * - * Arguments: - Polynomial &a: reference to input polynomial - **************************************************/ - void poly_reduce() { - for(auto& i : m_coeffs) { - int32_t t = (i + (1 << 22)) >> 23; - t = i - t * DilithiumModeConstants::Q; - i = t; - } - } - - /************************************************* - * Name: invntt_tomont - * - * Description: Inverse NTT and multiplication by Montgomery factor 2^32. - * In-place. No modular reductions after additions or - * subtractions; input coefficients need to be smaller than - * Q in absolute value. Output coefficient are smaller than Q in - * absolute value. - **************************************************/ - void invntt_tomont() { - size_t j; - int32_t f = 41978; // mont^2/256 - size_t k = 256; - for(size_t len = 1; len < DilithiumModeConstants::N; len <<= 1) { - for(size_t start = 0; start < DilithiumModeConstants::N; start = j + len) { - int32_t zeta = -DilithiumModeConstants::ZETAS[--k]; - for(j = start; j < start + len; ++j) { - int32_t t = m_coeffs[j]; - m_coeffs[j] = t + m_coeffs[j + len]; - m_coeffs[j + len] = t - m_coeffs[j + len]; - m_coeffs[j + len] = montgomery_reduce(static_cast(zeta) * m_coeffs[j + len]); - } - } - } - - for(j = 0; j < DilithiumModeConstants::N; ++j) { - m_coeffs[j] = montgomery_reduce(static_cast(f) * m_coeffs[j]); - } - } - - /************************************************* - * Name: poly_invntt_tomont - * - * Description: Inplace inverse NTT and multiplication by 2^{32}. - * Input coefficients need to be less than Q in absolute - * value and output coefficients are again bounded by Q. - * - * Arguments: - Polynomial& a: reference to input/output polynomial - **************************************************/ - void poly_invntt_tomont() { invntt_tomont(); } - - /************************************************* - * Name: cadd_q - * - * Description: For all coefficients of in/out polynomial add Q if - * coefficient is negative. - * Add Q if input coefficient is negative. - **************************************************/ - void cadd_q() { - for(auto& i : m_coeffs) { - i += (i >> 31) & DilithiumModeConstants::Q; - } - } - - /************************************************* - * Name: poly_uniform_gamma1 - * - * Description: Sample polynomial with uniformly random coefficients - * in [-(GAMMA1 - 1), GAMMA1] by unpacking output stream - * of SHAKE256(seed|nonce) or AES256CTR(seed,nonce). - * - * Arguments: - const secure_vector& seed: vector with seed of length CRHBYTES - * - uint16_t nonce: 16-bit nonce - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - **************************************************/ - void poly_uniform_gamma1(const secure_vector& seed, uint16_t nonce, const DilithiumModeConstants& mode) { - auto buf = mode.ExpandMask(seed, nonce); - - Polynomial::polyz_unpack(*this, buf.data(), mode); - } - - /************************************************* - * Name: poly_decompose - * - * Description: For all coefficients c of the input polynomial, - * compute high and low bits c0, c1 such c mod Q = c1*ALPHA + c0 - * with -ALPHA/2 < c0 <= ALPHA/2 except c1 = (Q-1)/ALPHA where we - * set c1 = 0 and -ALPHA/2 <= c0 = c mod Q - Q < 0. - * Assumes coefficients to be standard representatives. - * - * Arguments: - Polynomial& a1: reference to output polynomial with coefficients c1 - * - Polynomial& a0: reference to output polynomial with coefficients c0 - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - **************************************************/ - void poly_decompose(Polynomial& a1, Polynomial& a0, const DilithiumModeConstants& mode) const { - for(size_t i = 0; i < DilithiumModeConstants::N; ++i) { - a1.m_coeffs[i] = Polynomial::decompose(&a0.m_coeffs[i], m_coeffs[i], mode); - } - } - - /************************************************* - * Name: poly_shiftl - * - * Description: Multiply polynomial by 2^D without modular reduction. Assumes - * input coefficients to be less than 2^{31-D} in absolute value. - * - * Arguments: - Polynomial& a: pointer to input/output polynomial - **************************************************/ - void poly_shiftl() { - for(size_t i = 0; i < m_coeffs.size(); ++i) { - m_coeffs[i] <<= DilithiumModeConstants::D; - } - } - - /************************************************* - * Name: polyw1_pack - * - * Description: Bit-pack polynomial w1 with coefficients in [0,15] or [0,43]. - * Input coefficients are assumed to be standard representatives. - * - * Arguments: - uint8_t *r: pointer to output byte array with at least - * POLYW1_PACKEDBYTES bytes - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - **************************************************/ - void polyw1_pack(uint8_t* r, const DilithiumModeConstants& mode) { - if(mode.gamma2() == (DilithiumModeConstants::Q - 1) / 88) { - for(size_t i = 0; i < DilithiumModeConstants::N / 4; ++i) { - r[3 * i + 0] = static_cast(m_coeffs[4 * i + 0]); - r[3 * i + 0] |= static_cast(m_coeffs[4 * i + 1] << 6); - r[3 * i + 1] = static_cast(m_coeffs[4 * i + 1] >> 2); - r[3 * i + 1] |= static_cast(m_coeffs[4 * i + 2] << 4); - r[3 * i + 2] = static_cast(m_coeffs[4 * i + 2] >> 4); - r[3 * i + 2] |= static_cast(m_coeffs[4 * i + 3] << 2); - } - } else { - BOTAN_ASSERT_NOMSG(mode.gamma2() == (DilithiumModeConstants::Q - 1) / 32); - for(size_t i = 0; i < DilithiumModeConstants::N / 2; ++i) { - r[i] = static_cast(m_coeffs[2 * i + 0] | (m_coeffs[2 * i + 1] << 4)); - } - } - } - - /************************************************* - * Name: polyeta_unpack - * - * Description: Unpack polynomial with coefficients in [-ETA,ETA]. - * - * Arguments: - Polynomial& r: reference to output polynomial - * - const uint8_t *a: byte array with bit-packed_t1 polynomial - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - **************************************************/ - static Polynomial polyeta_unpack(std::span a, const DilithiumModeConstants& mode) { - Polynomial r; - - switch(mode.eta()) { - case DilithiumEta::Eta2: { - for(size_t i = 0; i < DilithiumModeConstants::N / 8; ++i) { - r.m_coeffs[8 * i + 0] = (a[3 * i + 0] >> 0) & 7; - r.m_coeffs[8 * i + 1] = (a[3 * i + 0] >> 3) & 7; - r.m_coeffs[8 * i + 2] = ((a[3 * i + 0] >> 6) | (a[3 * i + 1] << 2)) & 7; - r.m_coeffs[8 * i + 3] = (a[3 * i + 1] >> 1) & 7; - r.m_coeffs[8 * i + 4] = (a[3 * i + 1] >> 4) & 7; - r.m_coeffs[8 * i + 5] = ((a[3 * i + 1] >> 7) | (a[3 * i + 2] << 1)) & 7; - r.m_coeffs[8 * i + 6] = (a[3 * i + 2] >> 2) & 7; - r.m_coeffs[8 * i + 7] = (a[3 * i + 2] >> 5) & 7; - - r.m_coeffs[8 * i + 0] = static_cast(mode.eta()) - r.m_coeffs[8 * i + 0]; - r.m_coeffs[8 * i + 1] = static_cast(mode.eta()) - r.m_coeffs[8 * i + 1]; - r.m_coeffs[8 * i + 2] = static_cast(mode.eta()) - r.m_coeffs[8 * i + 2]; - r.m_coeffs[8 * i + 3] = static_cast(mode.eta()) - r.m_coeffs[8 * i + 3]; - r.m_coeffs[8 * i + 4] = static_cast(mode.eta()) - r.m_coeffs[8 * i + 4]; - r.m_coeffs[8 * i + 5] = static_cast(mode.eta()) - r.m_coeffs[8 * i + 5]; - r.m_coeffs[8 * i + 6] = static_cast(mode.eta()) - r.m_coeffs[8 * i + 6]; - r.m_coeffs[8 * i + 7] = static_cast(mode.eta()) - r.m_coeffs[8 * i + 7]; - } - } break; - case DilithiumEta::Eta4: { - for(size_t i = 0; i < DilithiumModeConstants::N / 2; ++i) { - r.m_coeffs[2 * i + 0] = a[i] & 0x0F; - r.m_coeffs[2 * i + 1] = a[i] >> 4; - r.m_coeffs[2 * i + 0] = static_cast(mode.eta()) - r.m_coeffs[2 * i + 0]; - r.m_coeffs[2 * i + 1] = static_cast(mode.eta()) - r.m_coeffs[2 * i + 1]; - } - } break; - } - - return r; - } - - /************************************************* - * Name: polyeta_pack - * - * Description: Bit-pack polynomial with coefficients in [-ETA,ETA]. - * - * Arguments: - uint8_t *r: pointer to output byte array with at least - * POLYETA_PACKEDBYTES bytes - * - const Polynomial& a: pointer to input polynomial - * - const DilithiumModeConstants& mode: reference for dilithium mode values - **************************************************/ - void polyeta_pack(uint8_t* r, const DilithiumModeConstants& mode) const { - uint8_t t[8]; - - switch(mode.eta()) { - case DilithiumEta::Eta2: { - for(size_t i = 0; i < DilithiumModeConstants::N / 8; ++i) { - t[0] = static_cast(mode.eta() - m_coeffs[8 * i + 0]); - t[1] = static_cast(mode.eta() - m_coeffs[8 * i + 1]); - t[2] = static_cast(mode.eta() - m_coeffs[8 * i + 2]); - t[3] = static_cast(mode.eta() - m_coeffs[8 * i + 3]); - t[4] = static_cast(mode.eta() - m_coeffs[8 * i + 4]); - t[5] = static_cast(mode.eta() - m_coeffs[8 * i + 5]); - t[6] = static_cast(mode.eta() - m_coeffs[8 * i + 6]); - t[7] = static_cast(mode.eta() - m_coeffs[8 * i + 7]); - - r[3 * i + 0] = (t[0] >> 0) | (t[1] << 3) | (t[2] << 6); - r[3 * i + 1] = (t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7); - r[3 * i + 2] = (t[5] >> 1) | (t[6] << 2) | (t[7] << 5); - } - } break; - case DilithiumEta::Eta4: { - for(size_t i = 0; i < DilithiumModeConstants::N / 2; ++i) { - t[0] = static_cast(mode.eta() - m_coeffs[2 * i + 0]); - t[1] = static_cast(mode.eta() - m_coeffs[2 * i + 1]); - r[i] = static_cast(t[0] | (t[1] << 4)); - } - } break; - } - } - - /************************************************* - * Name: polyt0_unpack - * - * Description: Unpack polynomial t0 with coefficients in ]-2^{D-1}, 2^{D-1}]. - * - * Arguments: - poly *r: pointer to output polynomial - * - const uint8_t *a: byte array with bit-packed_t1 polynomial - **************************************************/ - static Polynomial polyt0_unpack(std::span a) { - Polynomial r; - - for(size_t i = 0; i < DilithiumModeConstants::N / 8; ++i) { - r.m_coeffs[8 * i + 0] = a[13 * i + 0]; - r.m_coeffs[8 * i + 0] |= static_cast(a[13 * i + 1]) << 8; - r.m_coeffs[8 * i + 0] &= 0x1FFF; - - r.m_coeffs[8 * i + 1] = a[13 * i + 1] >> 5; - r.m_coeffs[8 * i + 1] |= static_cast(a[13 * i + 2]) << 3; - r.m_coeffs[8 * i + 1] |= static_cast(a[13 * i + 3]) << 11; - r.m_coeffs[8 * i + 1] &= 0x1FFF; - - r.m_coeffs[8 * i + 2] = a[13 * i + 3] >> 2; - r.m_coeffs[8 * i + 2] |= static_cast(a[13 * i + 4]) << 6; - r.m_coeffs[8 * i + 2] &= 0x1FFF; - - r.m_coeffs[8 * i + 3] = a[13 * i + 4] >> 7; - r.m_coeffs[8 * i + 3] |= static_cast(a[13 * i + 5]) << 1; - r.m_coeffs[8 * i + 3] |= static_cast(a[13 * i + 6]) << 9; - r.m_coeffs[8 * i + 3] &= 0x1FFF; - - r.m_coeffs[8 * i + 4] = a[13 * i + 6] >> 4; - r.m_coeffs[8 * i + 4] |= static_cast(a[13 * i + 7]) << 4; - r.m_coeffs[8 * i + 4] |= static_cast(a[13 * i + 8]) << 12; - r.m_coeffs[8 * i + 4] &= 0x1FFF; - - r.m_coeffs[8 * i + 5] = a[13 * i + 8] >> 1; - r.m_coeffs[8 * i + 5] |= static_cast(a[13 * i + 9]) << 7; - r.m_coeffs[8 * i + 5] &= 0x1FFF; - - r.m_coeffs[8 * i + 6] = a[13 * i + 9] >> 6; - r.m_coeffs[8 * i + 6] |= static_cast(a[13 * i + 10]) << 2; - r.m_coeffs[8 * i + 6] |= static_cast(a[13 * i + 11]) << 10; - r.m_coeffs[8 * i + 6] &= 0x1FFF; - - r.m_coeffs[8 * i + 7] = a[13 * i + 11] >> 3; - r.m_coeffs[8 * i + 7] |= static_cast(a[13 * i + 12]) << 5; - r.m_coeffs[8 * i + 7] &= 0x1FFF; - - r.m_coeffs[8 * i + 0] = (1 << (DilithiumModeConstants::D - 1)) - r.m_coeffs[8 * i + 0]; - r.m_coeffs[8 * i + 1] = (1 << (DilithiumModeConstants::D - 1)) - r.m_coeffs[8 * i + 1]; - r.m_coeffs[8 * i + 2] = (1 << (DilithiumModeConstants::D - 1)) - r.m_coeffs[8 * i + 2]; - r.m_coeffs[8 * i + 3] = (1 << (DilithiumModeConstants::D - 1)) - r.m_coeffs[8 * i + 3]; - r.m_coeffs[8 * i + 4] = (1 << (DilithiumModeConstants::D - 1)) - r.m_coeffs[8 * i + 4]; - r.m_coeffs[8 * i + 5] = (1 << (DilithiumModeConstants::D - 1)) - r.m_coeffs[8 * i + 5]; - r.m_coeffs[8 * i + 6] = (1 << (DilithiumModeConstants::D - 1)) - r.m_coeffs[8 * i + 6]; - r.m_coeffs[8 * i + 7] = (1 << (DilithiumModeConstants::D - 1)) - r.m_coeffs[8 * i + 7]; - } - - return r; - } - - /************************************************* - * Name: polyt0_pack - * - * Description: Bit-pack polynomial t0 with coefficients in ]-2^{D-1}, 2^{D-1}]. - * - * Arguments: - uint8_t *r: pointer to output byte array with at least - * POLYT0_PACKEDBYTES bytes - * - const Polynomial& a: reference to input polynomial - **************************************************/ - void polyt0_pack(uint8_t* r) const { - uint32_t t[8]; - for(size_t i = 0; i < DilithiumModeConstants::N / 8; ++i) { - t[0] = (1 << (DilithiumModeConstants::D - 1)) - m_coeffs[8 * i + 0]; - t[1] = (1 << (DilithiumModeConstants::D - 1)) - m_coeffs[8 * i + 1]; - t[2] = (1 << (DilithiumModeConstants::D - 1)) - m_coeffs[8 * i + 2]; - t[3] = (1 << (DilithiumModeConstants::D - 1)) - m_coeffs[8 * i + 3]; - t[4] = (1 << (DilithiumModeConstants::D - 1)) - m_coeffs[8 * i + 4]; - t[5] = (1 << (DilithiumModeConstants::D - 1)) - m_coeffs[8 * i + 5]; - t[6] = (1 << (DilithiumModeConstants::D - 1)) - m_coeffs[8 * i + 6]; - t[7] = (1 << (DilithiumModeConstants::D - 1)) - m_coeffs[8 * i + 7]; - - r[13 * i + 0] = static_cast(t[0]); - r[13 * i + 1] = static_cast(t[0] >> 8); - r[13 * i + 1] |= static_cast(t[1] << 5); - r[13 * i + 2] = static_cast(t[1] >> 3); - r[13 * i + 3] = static_cast(t[1] >> 11); - r[13 * i + 3] |= static_cast(t[2] << 2); - r[13 * i + 4] = static_cast(t[2] >> 6); - r[13 * i + 4] |= static_cast(t[3] << 7); - r[13 * i + 5] = static_cast(t[3] >> 1); - r[13 * i + 6] = static_cast(t[3] >> 9); - r[13 * i + 6] |= static_cast(t[4] << 4); - r[13 * i + 7] = static_cast(t[4] >> 4); - r[13 * i + 8] = static_cast(t[4] >> 12); - r[13 * i + 8] |= static_cast(t[5] << 1); - r[13 * i + 9] = static_cast(t[5] >> 7); - r[13 * i + 9] |= static_cast(t[6] << 6); - r[13 * i + 10] = static_cast(t[6] >> 2); - r[13 * i + 11] = static_cast(t[6] >> 10); - r[13 * i + 11] |= static_cast(t[7] << 3); - r[13 * i + 12] = static_cast(t[7] >> 5); - } - } - - /************************************************* - * Name: polyz_unpack - * - * Description: Unpack polynomial z with coefficients - * in [-(GAMMA1 - 1), GAMMA1]. - * - * Arguments: - Polynomial& r: pointer to output polynomial - * - const uint8_t *a: byte array with bit-packed_t1 polynomial - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - **************************************************/ - static void polyz_unpack(Polynomial& r, const uint8_t* a, const DilithiumModeConstants& mode) { - if(mode.gamma1() == (1 << 17)) { - for(size_t i = 0; i < DilithiumModeConstants::N / 4; ++i) { - r.m_coeffs[4 * i + 0] = a[9 * i + 0]; - r.m_coeffs[4 * i + 0] |= static_cast(a[9 * i + 1]) << 8; - r.m_coeffs[4 * i + 0] |= static_cast(a[9 * i + 2]) << 16; - r.m_coeffs[4 * i + 0] &= 0x3FFFF; - - r.m_coeffs[4 * i + 1] = a[9 * i + 2] >> 2; - r.m_coeffs[4 * i + 1] |= static_cast(a[9 * i + 3]) << 6; - r.m_coeffs[4 * i + 1] |= static_cast(a[9 * i + 4]) << 14; - r.m_coeffs[4 * i + 1] &= 0x3FFFF; - - r.m_coeffs[4 * i + 2] = a[9 * i + 4] >> 4; - r.m_coeffs[4 * i + 2] |= static_cast(a[9 * i + 5]) << 4; - r.m_coeffs[4 * i + 2] |= static_cast(a[9 * i + 6]) << 12; - r.m_coeffs[4 * i + 2] &= 0x3FFFF; - - r.m_coeffs[4 * i + 3] = a[9 * i + 6] >> 6; - r.m_coeffs[4 * i + 3] |= static_cast(a[9 * i + 7]) << 2; - r.m_coeffs[4 * i + 3] |= static_cast(a[9 * i + 8]) << 10; - r.m_coeffs[4 * i + 3] &= 0x3FFFF; - - r.m_coeffs[4 * i + 0] = static_cast(mode.gamma1()) - r.m_coeffs[4 * i + 0]; - r.m_coeffs[4 * i + 1] = static_cast(mode.gamma1()) - r.m_coeffs[4 * i + 1]; - r.m_coeffs[4 * i + 2] = static_cast(mode.gamma1()) - r.m_coeffs[4 * i + 2]; - r.m_coeffs[4 * i + 3] = static_cast(mode.gamma1()) - r.m_coeffs[4 * i + 3]; - } - } else if(mode.gamma1() == (1 << 19)) { - for(size_t i = 0; i < DilithiumModeConstants::N / 2; ++i) { - r.m_coeffs[2 * i + 0] = a[5 * i + 0]; - r.m_coeffs[2 * i + 0] |= static_cast(a[5 * i + 1]) << 8; - r.m_coeffs[2 * i + 0] |= static_cast(a[5 * i + 2]) << 16; - r.m_coeffs[2 * i + 0] &= 0xFFFFF; - - r.m_coeffs[2 * i + 1] = a[5 * i + 2] >> 4; - r.m_coeffs[2 * i + 1] |= static_cast(a[5 * i + 3]) << 4; - r.m_coeffs[2 * i + 1] |= static_cast(a[5 * i + 4]) << 12; - r.m_coeffs[2 * i + 0] &= 0xFFFFF; - - r.m_coeffs[2 * i + 0] = static_cast(mode.gamma1()) - r.m_coeffs[2 * i + 0]; - r.m_coeffs[2 * i + 1] = static_cast(mode.gamma1()) - r.m_coeffs[2 * i + 1]; - } - } - } - - /************************************************* - * Name: polyz_pack - * - * Description: Bit-pack polynomial with coefficients - * in [-(GAMMA1 - 1), GAMMA1]. - * - * Arguments: - uint8_t *r: pointer to output byte array with at least - * POLYZ_PACKEDBYTES bytes - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - **************************************************/ - void polyz_pack(uint8_t* r, const DilithiumModeConstants& mode) const { - uint32_t t[4]; - if(mode.gamma1() == (1 << 17)) { - for(size_t i = 0; i < DilithiumModeConstants::N / 4; ++i) { - t[0] = static_cast(mode.gamma1()) - m_coeffs[4 * i + 0]; - t[1] = static_cast(mode.gamma1()) - m_coeffs[4 * i + 1]; - t[2] = static_cast(mode.gamma1()) - m_coeffs[4 * i + 2]; - t[3] = static_cast(mode.gamma1()) - m_coeffs[4 * i + 3]; - - r[9 * i + 0] = static_cast(t[0]); - r[9 * i + 1] = static_cast(t[0] >> 8); - r[9 * i + 2] = static_cast(t[0] >> 16); - r[9 * i + 2] |= static_cast(t[1] << 2); - r[9 * i + 3] = static_cast(t[1] >> 6); - r[9 * i + 4] = static_cast(t[1] >> 14); - r[9 * i + 4] |= static_cast(t[2] << 4); - r[9 * i + 5] = static_cast(t[2] >> 4); - r[9 * i + 6] = static_cast(t[2] >> 12); - r[9 * i + 6] |= static_cast(t[3] << 6); - r[9 * i + 7] = static_cast(t[3] >> 2); - r[9 * i + 8] = static_cast(t[3] >> 10); - } - } else if(mode.gamma1() == (1 << 19)) { - for(size_t i = 0; i < DilithiumModeConstants::N / 2; ++i) { - t[0] = static_cast(mode.gamma1()) - m_coeffs[2 * i + 0]; - t[1] = static_cast(mode.gamma1()) - m_coeffs[2 * i + 1]; - - r[5 * i + 0] = static_cast(t[0]); - r[5 * i + 1] = static_cast(t[0] >> 8); - r[5 * i + 2] = static_cast(t[0] >> 16); - r[5 * i + 2] |= static_cast(t[1] << 4); - r[5 * i + 3] = static_cast(t[1] >> 4); - r[5 * i + 4] = static_cast(t[1] >> 12); - } - } - } - - /************************************************* - * Name: polyt1_unpack - * - * Description: Unpack polynomial t1 with 10-bit coefficients. - * Output coefficients are standard representatives. - * - * Arguments: - Polynomial& r: pointer to output polynomial - * - const uint8_t *a: byte array with bit-packed_t1 polynomial - **************************************************/ - static void polyt1_unpack(Polynomial& r, const uint8_t* a) { - for(size_t i = 0; i < DilithiumModeConstants::N / 4; ++i) { - r.m_coeffs[4 * i + 0] = ((a[5 * i + 0] >> 0) | (static_cast(a[5 * i + 1]) << 8)) & 0x3FF; - r.m_coeffs[4 * i + 1] = ((a[5 * i + 1] >> 2) | (static_cast(a[5 * i + 2]) << 6)) & 0x3FF; - r.m_coeffs[4 * i + 2] = ((a[5 * i + 2] >> 4) | (static_cast(a[5 * i + 3]) << 4)) & 0x3FF; - r.m_coeffs[4 * i + 3] = ((a[5 * i + 3] >> 6) | (static_cast(a[5 * i + 4]) << 2)) & 0x3FF; - } - } - - /************************************************* - * Name: polyt1_pack - * - * Description: Bit-pack polynomial t1 with coefficients fitting in 10 bits. - * Input coefficients are assumed to be standard representatives. - * - * Arguments: - uint8_t *r: pointer to output byte array with at least - * POLYT1_PACKEDBYTES bytes - **************************************************/ - void polyt1_pack(uint8_t* r) const { - for(size_t i = 0; i < DilithiumModeConstants::N / 4; ++i) { - r[5 * i + 0] = static_cast((m_coeffs[4 * i + 0] >> 0)); - r[5 * i + 1] = static_cast((m_coeffs[4 * i + 0] >> 8) | (m_coeffs[4 * i + 1] << 2)); - r[5 * i + 2] = static_cast((m_coeffs[4 * i + 1] >> 6) | (m_coeffs[4 * i + 2] << 4)); - r[5 * i + 3] = static_cast((m_coeffs[4 * i + 2] >> 4) | (m_coeffs[4 * i + 3] << 6)); - r[5 * i + 4] = static_cast((m_coeffs[4 * i + 3] >> 2)); - } - } - - Polynomial() = default; -}; - -class PolynomialVector { - public: - // public member is on purpose - std::vector m_vec; - - public: - PolynomialVector() = default; - - PolynomialVector& operator+=(const PolynomialVector& other) { - BOTAN_ASSERT_NOMSG(m_vec.size() != other.m_vec.size()); - for(size_t i = 0; i < m_vec.size(); ++i) { - this->m_vec[i] += other.m_vec[i]; - } - return *this; - } - - PolynomialVector& operator-=(const PolynomialVector& other) { - BOTAN_ASSERT_NOMSG(m_vec.size() == other.m_vec.size()); - for(size_t i = 0; i < this->m_vec.size(); ++i) { - this->m_vec[i] -= other.m_vec[i]; - } - return *this; - } - - explicit PolynomialVector(size_t size) : m_vec(size) {} - - /************************************************* - * Name: poly_uniform - * - * Description: Sample polynomial with uniformly random coefficients - * in [0,Q-1] by performing rejection sampling on the - * output stream of SHAKE256(seed|nonce) or AES256CTR(seed,nonce). - * - * Arguments: - const uint8_t seed[]: secure vector with seed of length SEEDBYTES - * - uint16_t nonce: 2-byte nonce - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - * Return Polynomial - **************************************************/ - static Polynomial poly_uniform(const std::vector& seed, - uint16_t nonce, - const DilithiumModeConstants& mode) { - Polynomial sample_poly; - size_t buflen = mode.poly_uniform_nblocks() * mode.stream128_blockbytes(); - - std::vector buf(buflen + 2); - - auto xof = mode.XOF_128(seed, nonce); - xof->output(std::span(buf).first(buflen)); - - size_t ctr = Polynomial::rej_uniform(sample_poly, 0, DilithiumModeConstants::N, buf.data(), buflen); - size_t off; - while(ctr < DilithiumModeConstants::N) { - off = buflen % 3; - for(size_t i = 0; i < off; ++i) { - buf[i] = buf[buflen - off + i]; - } - - xof->output(std::span(buf).subspan(off, mode.stream128_blockbytes())); - buflen = mode.stream128_blockbytes() + off; - ctr += Polynomial::rej_uniform(sample_poly, ctr, DilithiumModeConstants::N - ctr, buf.data(), buflen); - } - return sample_poly; - } - - static void fill_polyvec_uniform_eta(PolynomialVector& v, - const secure_vector& seed, - uint16_t nonce, - const DilithiumModeConstants& mode) { - for(size_t i = 0; i < v.m_vec.size(); ++i) { - Polynomial::fill_poly_uniform_eta(v.m_vec[i], seed, nonce++, mode); - } - } - - /************************************************* - * Name: polyvec_pointwise_acc_montgomery - * - * Description: Pointwise multiply vectors of polynomials of length L, multiply - * resulting vector by 2^{-32} and add (accumulate) polynomials - * in it. Input/output vectors are in NTT domain representation. - * - * Arguments: - Polynomial &w: output polynomial - * - const Polynomial &u: pointer to first input vector - * - const Polynomial &v: pointer to second input vector - **************************************************/ - static void polyvec_pointwise_acc_montgomery(Polynomial& w, - const PolynomialVector& u, - const PolynomialVector& v) { - BOTAN_ASSERT_NOMSG(u.m_vec.size() == v.m_vec.size()); - BOTAN_ASSERT_NOMSG(!u.m_vec.empty() && !v.m_vec.empty()); - - u.m_vec[0].poly_pointwise_montgomery(w, v.m_vec[0]); - - for(size_t i = 1; i < v.m_vec.size(); ++i) { - Polynomial t; - u.m_vec[i].poly_pointwise_montgomery(t, v.m_vec[i]); - w += t; - } - } - - /************************************************* - * Name: fill_polyvecs_power2round - * - * Description: For all coefficients a of polynomials in vector , - * compute a0, a1 such that a mod^+ Q = a1*2^D + a0 - * with -2^{D-1} < a0 <= 2^{D-1}. Assumes coefficients to be - * standard representatives. - * - * Arguments: - PolynomialVector& v1: reference to output vector of polynomials with - * coefficients a1 - * - PolynomialVector& v0: reference to output vector of polynomials with - * coefficients a0 - * - const PolynomialVector& v: reference to input vector - **************************************************/ - static void fill_polyvecs_power2round(PolynomialVector& v1, PolynomialVector& v0, const PolynomialVector& v) { - BOTAN_ASSERT((v1.m_vec.size() == v0.m_vec.size()) && (v1.m_vec.size() == v.m_vec.size()), - "possible buffer overflow! Wrong PolynomialVector sizes."); - for(size_t i = 0; i < v1.m_vec.size(); ++i) { - Polynomial::fill_polys_power2round(v1.m_vec[i], v0.m_vec[i], v.m_vec[i]); - } - } - - static bool unpack_sig(std::array& c, - PolynomialVector& z, - PolynomialVector& h, - const std::vector& sig, - const DilithiumModeConstants& mode) { - //const auto& mode = m_pub_key.m_public->mode(); - BOTAN_ASSERT(sig.size() == mode.crypto_bytes(), "invalid signature size"); - size_t position = 0; - - std::copy(sig.begin(), sig.begin() + c.size(), c.begin()); - - position += DilithiumModeConstants::SEEDBYTES; - - for(size_t i = 0; i < mode.l(); ++i) { - Polynomial::polyz_unpack(z.m_vec[i], sig.data() + position + i * mode.polyz_packedbytes(), mode); - } - position += mode.l() * mode.polyz_packedbytes(); - - /* Decode h */ - size_t k = 0; - for(size_t i = 0; i < mode.k(); ++i) { - for(size_t j = 0; j < DilithiumModeConstants::N; ++j) { - h.m_vec[i].m_coeffs[j] = 0; - } - - if(sig[position + mode.omega() + i] < k || sig[position + mode.omega() + i] > mode.omega()) { - return true; - } - - for(size_t j = k; j < sig[position + mode.omega() + i]; ++j) { - /* Coefficients are ordered for strong unforgeability */ - if(j > k && sig[position + j] <= sig[position + j - 1]) { - return true; - } - h.m_vec[i].m_coeffs[sig[position + j]] = 1; - } - - k = sig[position + mode.omega() + i]; - } - - /* Extra indices are zero for strong unforgeability */ - for(size_t j = k; j < mode.omega(); ++j) { - if(sig[position + j]) { - return true; - } - } - - return false; - } - - /************************************************* - * Name: generate_hint_polyvec - * - * Description: Compute hint vector. - * - * Arguments: - PolynomialVector *h: reference to output vector - * - const PolynomialVector *v0: reference to low part of input vector - * - const PolynomialVector *v1: reference to high part of input vector - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - * - * Returns number of 1 bits. - **************************************************/ - static size_t generate_hint_polyvec(PolynomialVector& h, - const PolynomialVector& v0, - const PolynomialVector& v1, - const DilithiumModeConstants& mode) { - size_t s = 0; - - for(size_t i = 0; i < h.m_vec.size(); ++i) { - s += Polynomial::generate_hint_polynomial(h.m_vec[i], v0.m_vec[i], v1.m_vec[i], mode); - } - - return s; - } - - /************************************************* - * Name: ntt - * - * Description: Forward NTT of all polynomials in vector. Output - * coefficients can be up to 16*Q larger than input coefficients. - **************************************************/ - void ntt() { - for(auto& i : m_vec) { - i.ntt(); - } - } - - /************************************************* - * Name: polyveck_decompose - * - * Description: For all coefficients a of polynomials in vector, - * compute high and low bits a0, a1 such a mod^+ Q = a1*ALPHA + a0 - * with -ALPHA/2 < a0 <= ALPHA/2 except a1 = (Q-1)/ALPHA where we - * set a1 = 0 and -ALPHA/2 <= a0 = a mod Q - Q < 0. - * Assumes coefficients to be standard representatives. - * - * Arguments: - PolynomialVector& v1: reference to output vector of polynomials with - * coefficients a1 - * - PolynomialVector& v0: reference to output vector of polynomials with - * coefficients a0 - * - const PolynomialVector& v: reference to input vector - **************************************************/ - std::tuple polyvec_decompose(const DilithiumModeConstants& mode) { - PolynomialVector v1(mode.k()); - PolynomialVector v0(mode.k()); - - for(size_t i = 0; i < m_vec.size(); ++i) { - m_vec[i].poly_decompose(v1.m_vec[i], v0.m_vec[i], mode); - } - return std::make_tuple(v1, v0); - } - - /************************************************* - * Name: reduce - * - * Description: Reduce coefficients of polynomials in vector - * to representatives in [-6283009,6283007]. - **************************************************/ - void reduce() { - for(auto& i : m_vec) { - i.poly_reduce(); - } - } - - /************************************************* - * Name: invntt_tomont - * - * Description: Inverse NTT and multiplication by 2^{32} of polynomials - * in vector. Input coefficients need to be less - * than 2*Q. - **************************************************/ - void invntt_tomont() { - for(auto& i : m_vec) { - i.poly_invntt_tomont(); - } - } - - /************************************************* - * Name: add_polyvec - * - * Description: Add vectors of polynomials . - * No modular reduction is performed. - * - * Arguments: - const PolynomialVector *v: pointer to second summand - * - const PolynomialVector *u: pointer to first summand - **************************************************/ - void add_polyvec(const PolynomialVector& v) { - BOTAN_ASSERT((m_vec.size() == v.m_vec.size()), "possible buffer overflow! Wrong PolynomialVector sizes."); - for(size_t i = 0; i < m_vec.size(); ++i) { - m_vec[i] += v.m_vec[i]; - } - } - - /************************************************* - * Name: cadd_q - * - * Description: For all coefficients of polynomials in vector - * add Q if coefficient is negative. - **************************************************/ - void cadd_q() { - for(auto& i : m_vec) { - i.cadd_q(); - } - } - - void polyvecl_uniform_gamma1(const secure_vector& seed, - uint16_t nonce, - const DilithiumModeConstants& mode) { - BOTAN_ASSERT_NOMSG(m_vec.size() <= std::numeric_limits::max()); - for(uint16_t i = 0; i < static_cast(this->m_vec.size()); ++i) { - m_vec[i].poly_uniform_gamma1(seed, mode.l() * nonce + i, mode); - } - } - - void polyvec_pointwise_poly_montgomery(PolynomialVector& r, const Polynomial& a) { - for(size_t i = 0; i < m_vec.size(); ++i) { - m_vec[i].poly_pointwise_montgomery(r.m_vec[i], a); - } - } - - /************************************************* - * Name: polyvecl_chknorm - * - * Description: Check infinity norm of polynomials in vector of length L. - * Assumes input polyvecl to be reduced by polyvecl_reduce(). - * - * Arguments: - size_t B: norm bound - * - * Returns false if norm of all polynomials is strictly smaller than B <= (Q-1)/8 - * and true otherwise. - **************************************************/ - bool polyvec_chknorm(size_t bound) { - for(auto& i : m_vec) { - if(Polynomial::poly_chknorm(i, bound)) { - return true; - } - } - return false; - } - - /************************************************* - * Name: polyvec_shiftl - * - * Description: Multiply vector of polynomials by 2^D without modular - * reduction. Assumes input coefficients to be less than 2^{31-D}. - **************************************************/ - void polyvec_shiftl() { - for(auto& i : m_vec) { - i.poly_shiftl(); - } - } - - /************************************************* - * Name: polyvec_use_hint - * - * Description: Use hint vector to correct the high bits of input vector. - * - * Arguments: - PolynomialVector& w: reference to output vector of polynomials with - * corrected high bits - * - const PolynomialVector& u: reference to input vector - * - const PolynomialVector& h: reference to input hint vector - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - **************************************************/ - void polyvec_use_hint(PolynomialVector& w, const PolynomialVector& h, const DilithiumModeConstants& mode) { - for(size_t i = 0; i < w.m_vec.size(); ++i) { - Polynomial::poly_use_hint(w.m_vec[i], m_vec[i], h.m_vec[i], mode); - } - } - - secure_vector polyvec_pack_eta(const DilithiumModeConstants& mode) const { - secure_vector packed_eta(mode.polyeta_packedbytes() * m_vec.size()); - for(size_t i = 0; i < m_vec.size(); ++i) { - m_vec[i].polyeta_pack(packed_eta.data() + mode.polyeta_packedbytes() * i, mode); - } - return packed_eta; - } - - static PolynomialVector unpack_eta(std::span buffer, - size_t size, - const DilithiumModeConstants& mode) { - BOTAN_ARG_CHECK(buffer.size() == mode.polyeta_packedbytes() * size, "Invalid buffer size"); - - PolynomialVector pv(size); - for(size_t i = 0; i < pv.m_vec.size(); ++i) { - pv.m_vec[i] = Polynomial::polyeta_unpack( - buffer.subspan(i * mode.polyeta_packedbytes(), mode.polyeta_packedbytes()), mode); - } - return pv; - } - - secure_vector polyvec_pack_t0() const { - secure_vector packed_t0(m_vec.size() * DilithiumModeConstants::POLYT0_PACKEDBYTES); - for(size_t i = 0; i < m_vec.size(); ++i) { - m_vec[i].polyt0_pack(packed_t0.data() + i * DilithiumModeConstants::POLYT0_PACKEDBYTES); - } - return packed_t0; - } - - static PolynomialVector unpack_t0(std::span buffer, const DilithiumModeConstants& mode) { - BOTAN_ARG_CHECK(static_cast(buffer.size()) == DilithiumModeConstants::POLYT0_PACKEDBYTES * mode.k(), - "Invalid buffer size"); - - PolynomialVector t0(mode.k()); - for(size_t i = 0; i < t0.m_vec.size(); ++i) { - t0.m_vec[i] = Polynomial::polyt0_unpack(buffer.subspan(i * DilithiumModeConstants::POLYT0_PACKEDBYTES, - DilithiumModeConstants::POLYT0_PACKEDBYTES)); - } - return t0; - } - - std::vector polyvec_pack_t1() const { - std::vector packed_t1(m_vec.size() * DilithiumModeConstants::POLYT1_PACKEDBYTES); - for(size_t i = 0; i < m_vec.size(); ++i) { - m_vec[i].polyt1_pack(packed_t1.data() + i * DilithiumModeConstants::POLYT1_PACKEDBYTES); - } - return packed_t1; - } - - static PolynomialVector unpack_t1(std::span packed_t1, const DilithiumModeConstants& mode) { - BOTAN_ARG_CHECK( - static_cast(packed_t1.size()) == DilithiumModeConstants::POLYT1_PACKEDBYTES * mode.k(), - "Invalid buffer size"); - - PolynomialVector t1(mode.k()); - for(size_t i = 0; i < t1.m_vec.size(); ++i) { - Polynomial::polyt1_unpack(t1.m_vec[i], packed_t1.data() + i * DilithiumModeConstants::POLYT1_PACKEDBYTES); - } - return t1; - } - - std::vector polyvec_pack_w1(const DilithiumModeConstants& mode) { - std::vector packed_w1(mode.polyw1_packedbytes() * m_vec.size()); - for(size_t i = 0; i < m_vec.size(); ++i) { - m_vec[i].polyw1_pack(packed_w1.data() + i * mode.polyw1_packedbytes(), mode); - } - return packed_w1; - } - - static PolynomialVector polyvec_unpack_z(const uint8_t* packed_z, const DilithiumModeConstants& mode) { - PolynomialVector z(mode.l()); - for(size_t i = 0; i < z.m_vec.size(); ++i) { - Polynomial::polyz_unpack(z.m_vec[i], packed_z + i * mode.polyz_packedbytes(), mode); - } - return z; - } - - /************************************************* - * Name: generate_polyvec_matrix_pointwise_montgomery - * - * Description: Generates a PolynomialVector based on a matrix using pointwise montgomery acc - * - * Arguments: - const std::vector& rho[]: byte array containing seed rho - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - * Returns a PolynomialVector - **************************************************/ - static PolynomialVector generate_polyvec_matrix_pointwise_montgomery(const std::vector& mat, - const PolynomialVector& v, - const DilithiumModeConstants& mode) { - PolynomialVector t(mode.k()); - for(size_t i = 0; i < mode.k(); ++i) { - PolynomialVector::polyvec_pointwise_acc_montgomery(t.m_vec[i], mat[i], v); - } - return t; - } -}; - -class PolynomialMatrix { - private: - // Matrix of length k holding a polynomialVector of size l, which has N coeffs - std::vector m_mat; - - explicit PolynomialMatrix(const DilithiumModeConstants& mode) : m_mat(mode.k(), PolynomialVector(mode.l())) {} - - public: - PolynomialMatrix() = delete; - - /************************************************* - * Name: generate_matrix - * - * Description: Implementation of generate_matrix. Generates matrix A with uniformly - * random coefficients a_{i,j} by performing rejection - * sampling on the output stream of SHAKE128(rho|j|i) - * or AES256CTR(rho,j|i). - * - * Arguments: - const std::vector& rho[]: byte array containing seed rho - * - const DilithiumModeConstants& mode: reference to dilihtium mode values - * Returns the output matrix mat[k] - **************************************************/ - static PolynomialMatrix generate_matrix(const std::vector& rho, const DilithiumModeConstants& mode) { - BOTAN_ASSERT(rho.size() >= DilithiumModeConstants::SEEDBYTES, "wrong byte length for rho/seed"); - - PolynomialMatrix matrix(mode); - for(uint16_t i = 0; i < mode.k(); ++i) { - for(uint16_t j = 0; j < mode.l(); ++j) { - matrix.m_mat[i].m_vec[j] = PolynomialVector::poly_uniform(rho, (i << 8) + j, mode); - } - } - return matrix; - } - - const std::vector& get_matrix() const { return m_mat; } -}; -} // namespace Botan::Dilithium - -#endif diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.cpp b/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.cpp index 6d7b4aed006..698d2c0854e 100644 --- a/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.cpp +++ b/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.cpp @@ -1,8 +1,9 @@ /** - * Asymmetric primitives for dilithium -* (C) 2022-2023 Jack Lloyd -* (C) 2022-2023 Michael Boric, René Meusel - Rohde & Schwarz Cybersecurity -* (C) 2022 Manuel Glaser - Rohde & Schwarz Cybersecurity + * Symmetric primitives for dilithium + * + * (C) 2022-2023 Jack Lloyd + * (C) 2022-2023 Michael Boric, René Meusel - Rohde & Schwarz Cybersecurity + * (C) 2022 Manuel Glaser - Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -19,97 +20,20 @@ namespace Botan { -std::unique_ptr Dilithium_Symmetric_Primitives::create(DilithiumMode mode) { +std::unique_ptr Dilithium_Symmetric_Primitives::create(const DilithiumConstants& mode) { #if BOTAN_HAS_DILITHIUM if(mode.is_modern()) { - return std::make_unique(); + return std::make_unique(mode.commitment_hash_full_bytes()); } #endif #if BOTAN_HAS_DILITHIUM_AES if(mode.is_aes()) { - return std::make_unique(); + return std::make_unique(mode.commitment_hash_full_bytes()); } #endif throw Not_Implemented("requested Dilithium mode is not enabled in this build"); } -DilithiumModeConstants::DilithiumModeConstants(DilithiumMode mode) : - m_mode(mode), m_symmetric_primitives(Dilithium_Symmetric_Primitives::create(mode)) { - if(mode.is_modern()) { - m_stream128_blockbytes = DilithiumModeConstants::SHAKE128_RATE; - m_stream256_blockbytes = DilithiumModeConstants::SHAKE256_RATE; - } else { - m_stream128_blockbytes = AES256CTR_BLOCKBYTES; - m_stream256_blockbytes = AES256CTR_BLOCKBYTES; - } - - switch(m_mode.mode()) { - case Botan::DilithiumMode::Dilithium4x4: - case Botan::DilithiumMode::Dilithium4x4_AES: - m_k = 4; - m_l = 4; - m_eta = DilithiumEta::Eta2; - m_tau = 39; - m_beta = 78; - m_gamma1 = (1 << 17); - m_gamma2 = ((DilithiumModeConstants::Q - 1) / 88); - m_omega = 80; - m_nist_security_strength = 128; - m_polyz_packedbytes = 576; - m_polyw1_packedbytes = 192; - m_polyeta_packedbytes = 96; - m_poly_uniform_eta_nblocks = ((136 + m_stream128_blockbytes - 1) / m_stream128_blockbytes); - break; - case Botan::DilithiumMode::Dilithium6x5: - case Botan::DilithiumMode::Dilithium6x5_AES: - m_k = 6; - m_l = 5; - m_eta = DilithiumEta::Eta4; - m_tau = 49; - m_beta = 196; - m_gamma1 = (1 << 19); - m_gamma2 = ((DilithiumModeConstants::Q - 1) / 32); - m_omega = 55; - m_nist_security_strength = 192; - m_polyz_packedbytes = 640; - m_polyw1_packedbytes = 128; - m_polyeta_packedbytes = 128; - m_poly_uniform_eta_nblocks = ((227 + m_stream128_blockbytes - 1) / m_stream128_blockbytes); - break; - case Botan::DilithiumMode::Dilithium8x7: - case Botan::DilithiumMode::Dilithium8x7_AES: - m_k = 8; - m_l = 7; - m_eta = DilithiumEta::Eta2; - m_tau = 60; - m_beta = 120; - m_gamma1 = (1 << 19); - m_gamma2 = ((DilithiumModeConstants::Q - 1) / 32); - m_omega = 75; - m_nist_security_strength = 256; - m_polyz_packedbytes = 640; - m_polyw1_packedbytes = 128; - m_polyeta_packedbytes = 96; - m_poly_uniform_eta_nblocks = ((136 + m_stream128_blockbytes - 1) / m_stream128_blockbytes); - break; - } - - if(m_gamma1 == (1 << 17)) { - m_poly_uniform_gamma1_nblocks = (576 + m_stream256_blockbytes - 1) / m_stream256_blockbytes; - } else { - BOTAN_ASSERT_NOMSG(m_gamma1 == (1 << 19)); - m_poly_uniform_gamma1_nblocks = (640 + m_stream256_blockbytes - 1) / m_stream256_blockbytes; - } - - // For all modes the same calculation - m_polyvech_packedbytes = m_omega + m_k; - m_poly_uniform_nblocks = ((768 + m_stream128_blockbytes - 1) / m_stream128_blockbytes); - m_public_key_bytes = DilithiumModeConstants::SEEDBYTES + m_k * DilithiumModeConstants::POLYT1_PACKEDBYTES; - m_crypto_bytes = DilithiumModeConstants::SEEDBYTES + m_l * m_polyz_packedbytes + m_polyvech_packedbytes; - m_private_key_bytes = (3 * DilithiumModeConstants::SEEDBYTES + m_l * m_polyeta_packedbytes + - m_k * m_polyeta_packedbytes + m_k * DilithiumModeConstants::POLYT0_PACKEDBYTES); -} - } // namespace Botan diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.h b/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.h index c752843aa27..ce8857c879d 100644 --- a/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.h +++ b/src/lib/pubkey/dilithium/dilithium_common/dilithium_symmetric_primitives.h @@ -1,5 +1,6 @@ /* -* Asymmetric primitives for dilithium +* Symmetric primitives for dilithium +* * (C) 2022-2023 Jack Lloyd * (C) 2022-2023 Michael Boric, René Meusel - Rohde & Schwarz Cybersecurity * (C) 2022 Manuel Glaser - Rohde & Schwarz Cybersecurity @@ -12,198 +13,141 @@ #include -#include -#include - -#include -#include -#include +#include +#include +#include +#include namespace Botan { /** -* Adapter class that uses polymorphy to distinguish -* Dilithium "common" from Dilithium "AES" modes. -*/ -class Dilithium_Symmetric_Primitives { + * Wrapper type for the H() function calculating the message representative for + * the Dilithium signature scheme. This wrapper may be used multiple times. + * + * Namely: mu = H(tr || M) + */ +class DilithiumMessageHash { public: - enum class XofType { k128, k256 }; - - public: - static std::unique_ptr create(DilithiumMode mode); + DilithiumMessageHash(DilithiumHashedPublicKey tr) : m_tr(std::move(tr)) { clear(); } - virtual ~Dilithium_Symmetric_Primitives() = default; - - // H is same for all modes - secure_vector H(std::span seed, size_t out_len) const { - return SHAKE_256(out_len * 8).process(seed.data(), seed.size()); + std::string name() const { + return Botan::fmt("{}({})", m_shake.name(), DilithiumConstants::MESSAGE_HASH_BYTES * 8); } - // CRH is same for all modes - secure_vector CRH(std::span in, size_t out_len) const { - return SHAKE_256(out_len * 8).process(in.data(), in.size()); + void update(std::span data) { m_shake.update(data); } + + DilithiumMessageRepresentative final() { + scoped_cleanup clean([this]() { clear(); }); + return m_shake.output(DilithiumConstants::MESSAGE_HASH_BYTES); } - // ExpandMatrix always uses the 256 version of the XOF - secure_vector ExpandMask(std::span seed, uint16_t nonce, size_t out_len) const { - return XOF(XofType::k256, seed, nonce)->output(out_len); + private: + void clear() { + m_shake.clear(); + m_shake.update(m_tr); } - // Mode dependent function - virtual std::unique_ptr XOF(XofType type, std::span seed, uint16_t nonce) const = 0; + private: + DilithiumHashedPublicKey m_tr; + SHAKE_256_XOF m_shake; }; -enum DilithiumEta : uint32_t { Eta2 = 2, Eta4 = 4 }; - -// Constants and mode dependent values -class DilithiumModeConstants { +/** +* Adapter class that uses polymorphy to distinguish +* Dilithium "common" from Dilithium "AES" modes. +*/ +class Dilithium_Symmetric_Primitives { public: - static constexpr int32_t SEEDBYTES = 32; - static constexpr int32_t CRHBYTES = 64; - static constexpr int32_t N = 256; - static constexpr int32_t Q = 8380417; - static constexpr int32_t D = 13; - static constexpr int32_t ROOT_OF_UNITY = 1753; - static constexpr int32_t POLYT1_PACKEDBYTES = 320; - static constexpr int32_t POLYT0_PACKEDBYTES = 416; - static constexpr int32_t SHAKE128_RATE = 168; - static constexpr int32_t SHAKE256_RATE = 136; - static constexpr int32_t SHA3_256_RATE = 136; - static constexpr int32_t SHA3_512_RATE = 72; - static constexpr int32_t AES256CTR_BLOCKBYTES = 64; - static constexpr int32_t QINV = 58728449; - static constexpr int32_t ZETAS[DilithiumModeConstants::N] = { - 0, 25847, -2608894, -518909, 237124, -777960, -876248, 466468, 1826347, 2353451, -359251, - -2091905, 3119733, -2884855, 3111497, 2680103, 2725464, 1024112, -1079900, 3585928, -549488, -1119584, - 2619752, -2108549, -2118186, -3859737, -1399561, -3277672, 1757237, -19422, 4010497, 280005, 2706023, - 95776, 3077325, 3530437, -1661693, -3592148, -2537516, 3915439, -3861115, -3043716, 3574422, -2867647, - 3539968, -300467, 2348700, -539299, -1699267, -1643818, 3505694, -3821735, 3507263, -2140649, -1600420, - 3699596, 811944, 531354, 954230, 3881043, 3900724, -2556880, 2071892, -2797779, -3930395, -1528703, - -3677745, -3041255, -1452451, 3475950, 2176455, -1585221, -1257611, 1939314, -4083598, -1000202, -3190144, - -3157330, -3632928, 126922, 3412210, -983419, 2147896, 2715295, -2967645, -3693493, -411027, -2477047, - -671102, -1228525, -22981, -1308169, -381987, 1349076, 1852771, -1430430, -3343383, 264944, 508951, - 3097992, 44288, -1100098, 904516, 3958618, -3724342, -8578, 1653064, -3249728, 2389356, -210977, - 759969, -1316856, 189548, -3553272, 3159746, -1851402, -2409325, -177440, 1315589, 1341330, 1285669, - -1584928, -812732, -1439742, -3019102, -3881060, -3628969, 3839961, 2091667, 3407706, 2316500, 3817976, - -3342478, 2244091, -2446433, -3562462, 266997, 2434439, -1235728, 3513181, -3520352, -3759364, -1197226, - -3193378, 900702, 1859098, 909542, 819034, 495491, -1613174, -43260, -522500, -655327, -3122442, - 2031748, 3207046, -3556995, -525098, -768622, -3595838, 342297, 286988, -2437823, 4108315, 3437287, - -3342277, 1735879, 203044, 2842341, 2691481, -2590150, 1265009, 4055324, 1247620, 2486353, 1595974, - -3767016, 1250494, 2635921, -3548272, -2994039, 1869119, 1903435, -1050970, -1333058, 1237275, -3318210, - -1430225, -451100, 1312455, 3306115, -1962642, -1279661, 1917081, -2546312, -1374803, 1500165, 777191, - 2235880, 3406031, -542412, -2831860, -1671176, -1846953, -2584293, -3724270, 594136, -3776993, -2013608, - 2432395, 2454455, -164721, 1957272, 3369112, 185531, -1207385, -3183426, 162844, 1616392, 3014001, - 810149, 1652634, -3694233, -1799107, -3038916, 3523897, 3866901, 269760, 2213111, -975884, 1717735, - 472078, -426683, 1723600, -1803090, 1910376, -1667432, -1104333, -260646, -3833893, -2939036, -2235985, - -420899, -2286327, 183443, -976891, 1612842, -3545687, -554416, 3919660, -48306, -1362209, 3937738, - 1400424, -846154, 1976782}; - static constexpr int32_t kSerializedPolynomialByteLength = DilithiumModeConstants::N / 2 * 3; - - DilithiumModeConstants(DilithiumMode dimension); - - DilithiumModeConstants(const DilithiumModeConstants& other) : DilithiumModeConstants(other.m_mode) {} - - DilithiumModeConstants(DilithiumModeConstants&& other) = default; - DilithiumModeConstants& operator=(const DilithiumModeConstants& other) = delete; - DilithiumModeConstants& operator=(DilithiumModeConstants&& other) = default; - - // Getter - uint8_t k() const { return m_k; } - - uint8_t l() const { return m_l; } - - DilithiumEta eta() const { return m_eta; } - - size_t tau() const { return m_tau; } - - size_t poly_uniform_gamma1_nblocks() const { return m_poly_uniform_gamma1_nblocks; } - - size_t stream256_blockbytes() const { return m_stream256_blockbytes; } - - size_t stream128_blockbytes() const { return m_stream128_blockbytes; } - - size_t polyw1_packedbytes() const { return m_polyw1_packedbytes; } - - size_t omega() const { return m_omega; } - - size_t polyz_packedbytes() const { return m_polyz_packedbytes; } - - size_t gamma2() const { return m_gamma2; } - - size_t gamma1() const { return m_gamma1; } - - size_t beta() const { return m_beta; } + enum class XofType { k128, k256 }; - size_t poly_uniform_eta_nblocks() const { return m_poly_uniform_eta_nblocks; } + protected: + Dilithium_Symmetric_Primitives(size_t commitment_hash_length_bytes) : + m_commitment_hash_length_bytes(commitment_hash_length_bytes) {} - size_t poly_uniform_nblocks() const { return m_poly_uniform_nblocks; } + public: + static std::unique_ptr create(const DilithiumConstants& mode); - size_t polyeta_packedbytes() const { return m_polyeta_packedbytes; } + virtual ~Dilithium_Symmetric_Primitives() = default; + Dilithium_Symmetric_Primitives(const Dilithium_Symmetric_Primitives&) = delete; + Dilithium_Symmetric_Primitives& operator=(const Dilithium_Symmetric_Primitives&) = delete; + Dilithium_Symmetric_Primitives(Dilithium_Symmetric_Primitives&&) = delete; + Dilithium_Symmetric_Primitives& operator=(Dilithium_Symmetric_Primitives&&) = delete; - size_t public_key_bytes() const { return m_public_key_bytes; } + DilithiumMessageHash get_message_hash(DilithiumHashedPublicKey tr) const { + return DilithiumMessageHash(std::move(tr)); + } - size_t crypto_bytes() const { return m_crypto_bytes; } + DilithiumHashedPublicKey H(StrongSpan pk) const { + return H_256(DilithiumConstants::PUBLIC_KEY_HASH_BYTES, pk); + } - OID oid() const { return m_mode.object_identifier(); } + DilithiumSeedRhoPrime H(StrongSpan k, + StrongSpan mu) const { + return H_256(DilithiumConstants::SEED_RHOPRIME_BYTES, k, mu); + } - DilithiumMode mode() const { return m_mode; } + std::tuple H( + StrongSpan seed) const { + m_xof.update(seed); - size_t private_key_bytes() const { return m_private_key_bytes; } + // Note: The order of invocations in an initializer list is not + // guaranteed by the C++ standard. Hence, we have to store the + // results in variables to ensure the correct order of execution. + auto rho = m_xof.output(DilithiumConstants::SEED_RHO_BYTES); + auto rhoprime = m_xof.output(DilithiumConstants::SEED_RHOPRIME_BYTES); + auto k = m_xof.output(DilithiumConstants::SEED_SIGNING_KEY_BYTES); + m_xof.clear(); - size_t nist_security_strength() const { return m_nist_security_strength; } + return {std::move(rho), std::move(rhoprime), std::move(k)}; + } - // Wrapper - decltype(auto) H(std::span seed, size_t out_len) const { - return m_symmetric_primitives->H(seed, out_len); + DilithiumCommitmentHash H(StrongSpan mu, + StrongSpan w1) const { + return H_256(m_commitment_hash_length_bytes, mu, w1); } - secure_vector CRH(const std::span in) const { - return m_symmetric_primitives->CRH(in, DilithiumModeConstants::CRHBYTES); + SHAKE_256_XOF& H(StrongSpan seed) const { + m_xof_external.clear(); + m_xof_external.update(seed); + return m_xof_external; } - std::unique_ptr XOF_128(std::span seed, uint16_t nonce) const { - return this->m_symmetric_primitives->XOF(Dilithium_Symmetric_Primitives::XofType::k128, seed, nonce); + // Once Dilithium AES is removed, this could return a SHAKE_256_XOF and + // avoid the virtual method call. + Botan::XOF& H(StrongSpan seed, uint16_t nonce) const { + return XOF(XofType::k128, seed, nonce); } - std::unique_ptr XOF_256(std::span seed, uint16_t nonce) const { - return this->m_symmetric_primitives->XOF(Dilithium_Symmetric_Primitives::XofType::k256, seed, nonce); + // Once Dilithium AES is removed, this could return a SHAKE_128_XOF and + // avoid the virtual method call. + Botan::XOF& H(StrongSpan seed, uint16_t nonce) const { + return XOF(XofType::k256, seed, nonce); } - secure_vector ExpandMask(const secure_vector& seed, uint16_t nonce) const { - return this->m_symmetric_primitives->ExpandMask( - seed, nonce, poly_uniform_gamma1_nblocks() * stream256_blockbytes()); + protected: + /** + * Implemented by the derived classes to create the correct XOF instance. + * This is a customization point to enable support for the AES variant of + * Dilithium. This won't be standardized in the FIPS 204; ML-DSA always + * uses SHAKE. Once we decide to remove the AES variant, this virtual + * method can be removed. + */ + virtual Botan::XOF& XOF(XofType type, std::span seed, uint16_t nonce) const = 0; + + private: + template + OutT H_256(size_t outbytes, InTs&&... ins) const { + scoped_cleanup clean([this]() { m_xof.clear(); }); + (m_xof.update(ins), ...); + return m_xof.output(outbytes); } private: - DilithiumMode m_mode; - - uint16_t m_nist_security_strength; - - // generated matrix dimension is m_k x m_l - uint8_t m_k; - uint8_t m_l; - DilithiumEta m_eta; - int32_t m_tau; - int32_t m_beta; - int32_t m_gamma1; - int32_t m_gamma2; - int32_t m_omega; - int32_t m_stream128_blockbytes; - int32_t m_stream256_blockbytes; - int32_t m_poly_uniform_nblocks; - int32_t m_poly_uniform_eta_nblocks; - int32_t m_poly_uniform_gamma1_nblocks; - int32_t m_polyvech_packedbytes; - int32_t m_polyz_packedbytes; - int32_t m_polyw1_packedbytes; - int32_t m_polyeta_packedbytes; - int32_t m_private_key_bytes; - int32_t m_public_key_bytes; - int32_t m_crypto_bytes; - - // Mode dependent primitives - std::unique_ptr m_symmetric_primitives; + size_t m_commitment_hash_length_bytes; + mutable SHAKE_256_XOF m_xof; + mutable SHAKE_256_XOF m_xof_external; }; + } // namespace Botan #endif diff --git a/src/lib/pubkey/dilithium/dilithium_common/dilithium_types.h b/src/lib/pubkey/dilithium/dilithium_common/dilithium_types.h new file mode 100644 index 00000000000..941d3bd3e15 --- /dev/null +++ b/src/lib/pubkey/dilithium/dilithium_common/dilithium_types.h @@ -0,0 +1,62 @@ +/* + * Strong Type definitions used throughout the Dilithium implementation + * + * (C) 2024 Jack Lloyd + * (C) 2024 René Meusel, Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_DILITHIUM_TYPES_H_ +#define BOTAN_DILITHIUM_TYPES_H_ + +#include +#include +#include + +namespace Botan { + +using DilithiumPolyNTT = Botan::CRYSTALS::Polynomial; +using DilithiumPolyVecNTT = Botan::CRYSTALS::PolynomialVector; +using DilithiumPolyMatNTT = Botan::CRYSTALS::PolynomialMatrix; + +using DilithiumPoly = Botan::CRYSTALS::Polynomial; +using DilithiumPolyVec = Botan::CRYSTALS::PolynomialVector; + +/// Principal seed used to generate Dilithium key pairs +using DilithiumSeedRandomness = Strong, struct DilithiumSeedRandomness_>; + +/// Public seed to sample the polynomial matrix A from +using DilithiumSeedRho = Strong, struct DilithiumPublicSeed_>; + +/// Private seed to sample the polynomial vectors s1 and s2 from +using DilithiumSeedRhoPrime = Strong, struct DilithiumSeedRhoPrime_>; + +/// Private seed K used during signing +using DilithiumSigningSeedK = Strong, struct DilithiumSeedK_>; + +/// Serialized private key data +using DilithiumSerializedPrivateKey = Strong, struct DilithiumSerializedPrivateKey_>; + +/// Serialized public key data (result of pkEncode(pk)) +using DilithiumSerializedPublicKey = Strong, struct DilithiumSerializedPublicKey_>; + +/// Hash value of the serialized public key data +/// (result of H(BytesToBits(pkEncode(pk)), also referred to as 'tr') +using DilithiumHashedPublicKey = Strong, struct DilithiumHashedPublicKey_>; + +/// Representation of the message to be signed +using DilithiumMessageRepresentative = Strong, struct DilithiumMessageRepresentative_>; + +/// Serialized signature data +using DilithiumSerializedSignature = Strong, struct DilithiumSerializedSignature_>; + +/// Serialized representation of a commitment w1 +using DilithiumSerializedCommitment = Strong, struct DilithiumSerializedCommitment_>; + +/// Hash of the message representative and the signer's commitment +using DilithiumCommitmentHash = Strong, struct DilithiumCommitmentHash_>; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/dilithium/dilithium_common/info.txt b/src/lib/pubkey/dilithium/dilithium_common/info.txt index 319174bd7a6..4eb206fc4f6 100644 --- a/src/lib/pubkey/dilithium/dilithium_common/info.txt +++ b/src/lib/pubkey/dilithium/dilithium_common/info.txt @@ -13,11 +13,16 @@ dilithium.h -dilithium_polynomials.h +dilithium_algos.h +dilithium_constants.h +dilithium_polynomial.h dilithium_symmetric_primitives.h +dilithium_types.h -shake -xof +pqcrystals +pubkey +rng +shake_xof diff --git a/src/lib/pubkey/kyber/kyber_common/info.txt b/src/lib/pubkey/kyber/kyber_common/info.txt index 60c8ddbbd5f..8cb4dbef25f 100644 --- a/src/lib/pubkey/kyber/kyber_common/info.txt +++ b/src/lib/pubkey/kyber/kyber_common/info.txt @@ -9,6 +9,7 @@ type -> "Internal" +pqcrystals pubkey hash rng @@ -16,10 +17,12 @@ xof +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..a70715a8dd0 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber.cpp +++ b/src/lib/pubkey/kyber/kyber_common/kyber.cpp @@ -7,7 +7,7 @@ * (C) 2021-2022 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 + * (C) 2024 René Meusel, Fabian Albert, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -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)); @@ -187,8 +186,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().canonical_parameter_set_identifier(); } bool Kyber_PublicKey::check_key(RandomNumberGenerator&, bool) const { @@ -207,17 +205,15 @@ 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); + Kyber_Algos::PolynomialSampler 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); + auto A = Kyber_Algos::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 = montgomery(A * s); t += e; t.reduce(); @@ -225,7 +221,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 +230,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_Algos::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,10 +262,11 @@ secure_vector Kyber_PrivateKey::raw_private_key_bits() const { } secure_vector Kyber_PrivateKey::private_key_bits() const { - return concat(m_private->s().to_bytes>(), - m_public->public_key_bits_raw(), - m_public->H_public_key_bits_raw(), - m_private->z()); + return concat( + Kyber_Algos::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()); } std::unique_ptr Kyber_PublicKey::create_kem_encryption_op(std::string_view params, 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..350de1dad3b --- /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::Kyber_Algos { + +namespace { + +/** + * NIST FIPS 203 IPD, Algorithm 4 (ByteEncode) for d < 12 in combination with + * Formula 4.5 (Compress) + */ +template + requires(d < 12) +void poly_compress_and_encode(BufferStuffer& bs, const KyberPoly& p) { + CRYSTALS::pack<(1 << d) - 1>(p, bs, compress); +} + +/** + * NIST FIPS 203 IPD, Algorithm 4 (ByteEncode) for d == 12 + */ +void 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 poly_decode_and_decompress(KyberPoly& p, BufferSlicer& bs) { + CRYSTALS::unpack<(1 << d) - 1>(p, bs, decompress); +} + +/** + * NIST FIPS 203 IPD, Algorithm 5 (ByteDecode) for d == 12 + */ +void 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 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 encode_polynomial_vector(std::span out, const KyberPolyVecNTT& vec) { + BufferStuffer bs(out); + for(auto& v : vec) { + byte_encode(bs, v); + } + BOTAN_ASSERT_NOMSG(bs.full()); +} + +KyberPolyVecNTT decode_polynomial_vector(std::span a, const KyberConstants& mode) { + KyberPolyVecNTT vec(mode.k()); + + BufferSlicer bs(a); + for(auto& p : vec) { + byte_decode(p, bs); + } + BOTAN_ASSERT_NOMSG(bs.empty()); + + return vec; +} + +KyberPoly polynomial_from_message(StrongSpan msg) { + BOTAN_ASSERT(msg.size() == KyberConstants::N / 8, "message length must be N/8 bytes"); + KyberPoly r; + BufferSlicer bs(msg); + poly_decode_and_decompress<1>(r, bs); + return r; +} + +KyberMessage polynomial_to_message(const KyberPoly& p) { + KyberMessage result(p.size() / 8); + BufferStuffer bs(result); + 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) { + 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: + poly_compress_and_encode<4>(bs, p); + BOTAN_ASSERT_NOMSG(bs.full()); + return; + case KyberConstants::KyberDv::_5: + 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) { + 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 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: + poly_decode_and_decompress<4>(r, bs); + BOTAN_ASSERT_NOMSG(bs.empty()); + return r; + case KyberConstants::KyberDv::_5: + poly_decode_and_decompress<5>(r, bs); + BOTAN_ASSERT_NOMSG(bs.empty()); + return r; + } + + BOTAN_ASSERT_UNREACHABLE(); +} + +} // namespace + +void 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 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), decompress_polynomial(p, mode)}; +} + +KyberPolyMat 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); + sample_ntt_uniform(mat[i][j], mode.symmetric_primitives().XOF(seed, pos)); + } + } + + return mat; +} + +/** + * NIST FIPS 203 IPD, Algorithm 7 (SamplePolyCBD) + */ +void 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::Kyber_Algos 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..f2a885b3f57 --- /dev/null +++ b/src/lib/pubkey/kyber/kyber_common/kyber_algos.h @@ -0,0 +1,123 @@ +/* + * 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::Kyber_Algos { + +void encode_polynomial_vector(std::span out, const KyberPolyVecNTT& p); + +KyberPolyVecNTT decode_polynomial_vector(std::span a, const KyberConstants& mode); + +KyberPoly polynomial_from_message(StrongSpan msg); + +KyberMessage polynomial_to_message(const KyberPoly& p); + +void compress_ciphertext(StrongSpan out, + const KyberPolyVec& u, + const KyberPoly& v, + const KyberConstants& m_mode); + +std::pair decompress_ciphertext(StrongSpan ct, + const KyberConstants& mode); + +KyberPolyMat sample_matrix(StrongSpan seed, bool transposed, const KyberConstants& mode); + +void sample_polynomial_from_cbd(KyberPoly& poly, + KyberConstants::KyberEta eta, + const KyberSamplingRandomness& randomness); + +template > +T encode_polynomial_vector(const KyberPolyVecNTT& vec, const KyberConstants& mode) { + T r(mode.polynomial_vector_bytes()); + 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 PolynomialSampler { + public: + PolynomialSampler(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(); + }(); + + sample_polynomial_from_cbd(poly, eta, randomness); + } + + private: + StrongSpan m_seed; + const KyberConstants& m_mode; + uint8_t m_nonce; +}; + +template +PolynomialSampler(T, const KyberConstants&) -> PolynomialSampler; + +} // namespace Botan::Kyber_Algos + +#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..153dd21c05c 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,70 @@ class KyberConstants final { KyberMode mode() const { return m_mode; } - size_t estimated_strength() const { return m_nist_strength; } + /// @returns one of {512, 768, 1024} + size_t canonical_parameter_set_identifier() const { return k() * N; } + + /// \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..2e718926d96 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_Algos::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..d9d32e0e8cb --- /dev/null +++ b/src/lib/pubkey/kyber/kyber_common/kyber_helpers.h @@ -0,0 +1,74 @@ +/* + * 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::Kyber_Algos { + +/** + * 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 compress(KyberConstants::T x) { + BOTAN_DEBUG_ASSERT(x >= 0 && x < KyberConstants::Q); + 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" + 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 static_cast((n * m) >> p) & mask; +}; + +/** + * NIST FIPS 203 IPD, Formula 4.6 (Decompress) + */ +template + requires(d > 0 && d < 12) +constexpr KyberConstants::T 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::Kyber_Algos + +#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..71ea549e7a7 100644 --- a/src/lib/pubkey/kyber/kyber_common/kyber_keys.cpp +++ b/src/lib/pubkey/kyber/kyber_common/kyber_keys.cpp @@ -4,7 +4,7 @@ * (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 + * (C) 2024 René Meusel, Fabian Albert, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -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_Algos::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 { + Kyber_Algos::PolynomialSampler 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_Algos::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_Algos::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_Algos::decompress_ciphertext(ct, m_mode); + v -= inverse_ntt(m_s * ntt(std::move(u))); + v.reduce(); + return Kyber_Algos::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..a1ec1cc910b 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_Algos::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..ab267a3a2d2 --- /dev/null +++ b/src/lib/pubkey/kyber/kyber_common/kyber_polynomial.h @@ -0,0 +1,126 @@ +/* + * 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) + * + * Produces the result of the NTT transformation without any montgomery + * factors in the coefficients. + */ + 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) + * + * The output is effectively multiplied by the montgomery parameter 2^16 + * mod q so that the input factors 2^(-16) mod q are eliminated. Note + * that factors 2^(-16) mod q are introduced by multiplication and + * reduction of values not in montgomery domain. + * + * Produces the result of the inverse NTT transformation with a montgomery + * factor of (2^16 mod q) added (!). See above. + */ + 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/lib/pubkey/pqcrystals/info.txt b/src/lib/pubkey/pqcrystals/info.txt new file mode 100644 index 00000000000..5a2ab539091 --- /dev/null +++ b/src/lib/pubkey/pqcrystals/info.txt @@ -0,0 +1,18 @@ + +PQCRYSTALS -> 20240228 + + + +name -> "CRYSTALS" +brief -> "Base utilities for CRYSTALS-Kyber/ML-KEM and CRYSTALS-Dilithium/ML-DSA. CRYptographic SuiTe for Algebraic LatticeS" +type -> "Internal" + + + + + + +pqcrystals.h +pqcrystals_encoding.h +pqcrystals_helpers.h + diff --git a/src/lib/pubkey/pqcrystals/pqcrystals.h b/src/lib/pubkey/pqcrystals/pqcrystals.h new file mode 100644 index 00000000000..a94b7572b47 --- /dev/null +++ b/src/lib/pubkey/pqcrystals/pqcrystals.h @@ -0,0 +1,651 @@ +/* + * PQ CRYSTALS Common Structures + * + * 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, Fabian Albert, Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_PQ_CRYSTALS_H_ +#define BOTAN_PQ_CRYSTALS_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Botan::CRYSTALS { + +enum class Domain { Normal, NTT }; + +template +concept crystals_constants = + std::signed_integral && std::integral && std::integral && + std::integral && std::unsigned_integral && + std::integral; + +/** + * This implements basic polynomial operations for Kyber and Dilithium + * based on the given algorithm constants (@p ConstantsT) and back- + * references some of the operations to the actual implementation + * into the derived class (CRTP @p DerivedT). + * + * Polynomial parameters are passed as spans of coefficients for maximum + * flexibility. + * + * It is assumed that this is subclassed with the actual implementation + * with establishing a CRTP back-reference. + */ +template +class Trait_Base { + public: + using T = typename ConstantsT::T; + static constexpr T N = ConstantsT::N; + static constexpr T Q = ConstantsT::Q; + + protected: + using T2 = next_longer_int_t; + + /// \name Pre-computed algorithm constants + /// @{ + + static constexpr T Q_inverse = modular_inverse(Q); + static constexpr T MONTY = montgomery_R(Q); + static constexpr T MONTY_SQUARED = montgomery_R2(Q); + + // Contains the constant f from Algorithm 36 multiplied two times by + // the montgomery parameter, i.e. 2^(2*32) mod q. The first montgomery + // factor is then removed by the reduction in the loop. The second one + // is required to eliminate factors 2^(-32) mod q in coeffs introduced + // by previous montgomery multiplications in a single vector/matrix + // multiplication operation. + static constexpr T F_WITH_MONTY_SQUARED = (static_cast(ConstantsT::F) * MONTY_SQUARED) % Q; + + static constexpr auto zetas = precompute_zetas(Q, MONTY, ConstantsT::ROOT_OF_UNITY); + + /// @} + + protected: + /// @returns the number of polynomials in the polynomial vector @p polyvec. + static constexpr size_t polys_in_polyvec(std::span polyvec) { + BOTAN_DEBUG_ASSERT(polyvec.size() % N == 0); + return polyvec.size() / N; + } + + /// @returns the @p index-th polynomial in the polynomial vector @p polyvec. + template + requires(std::same_as || std::same_as) + static constexpr std::span poly_in_polyvec(std::span polyvec, size_t index) { + BOTAN_DEBUG_ASSERT(polyvec.size() % N == 0); + BOTAN_DEBUG_ASSERT(polyvec.size() / N > index); + auto polyspan = polyvec.subspan(index * N, N); + return std::span{polyspan.data(), polyspan.size()}; + } + + static constexpr T fqmul(T a, T b) { return DerivedT::montgomery_reduce_coefficient(static_cast(a) * b); } + + public: + static constexpr void poly_add(std::span result, std::span lhs, std::span rhs) { + for(size_t i = 0; i < N; ++i) { + result[i] = lhs[i] + rhs[i]; + } + } + + static constexpr void poly_sub(std::span result, std::span lhs, std::span rhs) { + for(size_t i = 0; i < N; ++i) { + result[i] = lhs[i] - rhs[i]; + } + } + + /// Adds Q if the coefficient is negative. + static constexpr void poly_cadd_q(std::span coeffs) { + for(auto& coeff : coeffs) { + using unsigned_T = std::make_unsigned_t; + const auto is_negative = CT::Mask::expand_top_bit(static_cast(coeff)); + coeff += is_negative.if_set_return(Q); + } + } + + static constexpr T to_montgomery(T a) { return fqmul(a, MONTY_SQUARED); } + + constexpr static void barrett_reduce(std::span poly) { + for(auto& coeff : poly) { + coeff = DerivedT::barrett_reduce_coefficient(coeff); + } + } + + /// Multiplication and accumulation of 2 polynomial vectors @p u and @p v. + static constexpr void polyvec_pointwise_acc_montgomery(std::span w, + std::span u, + std::span v) { + clear_mem(w); + std::array t; + for(size_t i = 0; i < polys_in_polyvec(u); ++i) { + DerivedT::poly_pointwise_montgomery(t, poly_in_polyvec(u, i), poly_in_polyvec(v, i)); + poly_add(w, w, t); + } + barrett_reduce(w); + } +}; + +template +concept crystals_trait = + std::signed_integral && sizeof(typename T::T) <= 4 && std::integral && + T::N % 2 == 0 && + requires(std::span polyspan, std::span polyvecspan, typename T::T coeff) { + { T::to_montgomery(coeff) }; + { T::barrett_reduce(polyspan) }; + { T::poly_cadd_q(polyspan) }; + { T::ntt(polyspan) }; + { T::inverse_ntt(polyspan) }; + { T::poly_pointwise_montgomery(polyspan, polyspan, polyspan) }; + { T::polyvec_pointwise_acc_montgomery(polyspan, polyvecspan, polyvecspan) }; + }; + +namespace detail { + +/** + * Converts polynomials or polynomial vectors from one domain to another. + */ +template class StructureT, crystals_trait Trait, Domain From> + requires(To != From) +StructureT domain_cast(StructureT&& p) { + // The public factory method `from_domain_cast` is just a workaround for + // Xcode and NDK not understanding the friend declaration to allow this + // to directly call the private constructor. + return StructureT::from_domain_cast(std::move(p)); +} + +/** + * Ensures that all values in the @p range are within the range [min, max] + * using constant-time operations. + * + * @returns true if all values are within the range, false otherwise. + */ +template +constexpr static bool ct_all_within_range(std::span range, T min, T max) + requires(sizeof(T) <= 4) +{ + BOTAN_DEBUG_ASSERT(min < max); + + using unsigned_T = std::make_unsigned_t; + auto map = [](T v) -> unsigned_T { + if constexpr(std::signed_integral) { + constexpr int64_t offset = -static_cast(std::numeric_limits::min()); + return static_cast(static_cast(v) + offset); + } else { + return v; + } + }; + + const auto umin = map(min); + const auto umax = map(max); + + auto mask = CT::Mask::set(); + for(const T c : range) { + mask &= CT::Mask::is_within_range(map(c), umin, umax); + } + return mask.as_bool(); +} + +} // namespace detail + +/** + * Represents a polynomial with Trait::N coefficients of type Trait::T. + * The domain of the polynomial can be either Domain::Normal or Domain::NTT and + * this information is represented in the C++ type system. + * + * Polynomials may either own their storage of piggy-back on external storage + * when they are part of a PolynomialVector. + */ +template +class Polynomial { + private: + using ThisPolynomial = Polynomial; + using T = typename Trait::T; + + private: + // TODO: perhaps secure vector + std::vector m_coeffs_storage; + std::span m_coeffs; + + private: + template + friend class Polynomial; + + template class StructureT, crystals_trait C, Domain From> + requires(To != From) + friend StructureT detail::domain_cast(StructureT&&); + + /** + * This constructor is used to convert a Polynomial from one domain to another. + * The friend declarations above facilitate this. + */ + template + requires(D != OtherD) + explicit Polynomial(Polynomial&& other) noexcept : + m_coeffs_storage(std::move(other.m_coeffs_storage)), + m_coeffs(owns_storage() ? std::span(m_coeffs_storage) : other.m_coeffs) {} + + public: + // Workaround, because Xcode and NDK don't understand the + // `detail::domain_cast` friend declaration. + // + // TODO: Try to remove this and use the c'tor directly in + // `detail::domain_cast` after updating the compilers. + template + requires(D != OtherD) + static Polynomial from_domain_cast(Polynomial&& p) { + return Polynomial(std::move(p)); + } + + public: + Polynomial() : m_coeffs_storage(Trait::N), m_coeffs(m_coeffs_storage) { BOTAN_DEBUG_ASSERT(owns_storage()); } + + explicit Polynomial(std::span coeffs) : m_coeffs(coeffs) { BOTAN_DEBUG_ASSERT(!owns_storage()); } + + Polynomial(const ThisPolynomial& other) = delete; + + Polynomial(ThisPolynomial&& other) noexcept : + m_coeffs_storage(std::move(other.m_coeffs_storage)), m_coeffs(other.m_coeffs) {} + + ThisPolynomial& operator=(const ThisPolynomial& other) = delete; + + ThisPolynomial& operator=(ThisPolynomial&& other) noexcept { + if(this != &other) { + BOTAN_ASSERT_NOMSG(owns_storage()); + m_coeffs_storage = std::move(other.m_coeffs_storage); + m_coeffs = std::span(m_coeffs_storage); + } + return *this; + } + + ~Polynomial() = default; + + constexpr size_t size() const { return m_coeffs.size(); } + + constexpr Domain domain() const noexcept { return D; } + + ThisPolynomial clone() const { + ThisPolynomial res; + copy_mem(res.m_coeffs_storage, m_coeffs); + res.m_coeffs = std::span(res.m_coeffs_storage); + BOTAN_DEBUG_ASSERT(res.owns_storage()); + return res; + } + + /// @returns true if all coefficients are within the range [min, max] + constexpr bool ct_validate_value_range(T min, T max) const noexcept { + return detail::ct_all_within_range(coefficients(), min, max); + } + + /// @returns the number of non-zero coefficients in the polynomial + constexpr size_t hamming_weight() const noexcept { + size_t weight = 0; + for(const auto c : m_coeffs) { + weight += (c != 0); + } + return weight; + } + + std::span coefficients() { return m_coeffs; } + + std::span coefficients() const { return m_coeffs; } + + T& operator[](size_t i) { return m_coeffs[i]; } + + T operator[](size_t i) const { return m_coeffs[i]; } + + decltype(auto) begin() { return m_coeffs.begin(); } + + decltype(auto) begin() const { return m_coeffs.begin(); } + + decltype(auto) end() { return m_coeffs.end(); } + + decltype(auto) end() const { return m_coeffs.end(); } + + constexpr bool owns_storage() const { return !m_coeffs_storage.empty(); } + + ThisPolynomial& reduce() { + Trait::barrett_reduce(m_coeffs); + return *this; + } + + ThisPolynomial& conditional_add_q() { + Trait::poly_cadd_q(m_coeffs); + return *this; + } + + /** + * Adds two polynomials element-wise. Does not perform a reduction after the addition. + * Therefore this operation might cause an integer overflow. + */ + decltype(auto) operator+=(const ThisPolynomial& other) { + Trait::poly_add(m_coeffs, m_coeffs, other.m_coeffs); + return *this; + } + + /** + * Subtracts two polynomials element-wise. Does not perform a reduction after the subtraction. + * Therefore this operation might cause an integer underflow. + */ + decltype(auto) operator-=(const ThisPolynomial& other) { + Trait::poly_sub(m_coeffs, m_coeffs, other.m_coeffs); + return *this; + } +}; + +template +class PolynomialVector { + private: + using ThisPolynomialVector = PolynomialVector; + using T = typename Trait::T; + + private: + std::vector m_polys_storage; + std::vector> m_vec; + + private: + template + friend class PolynomialVector; + + template class StructureT, crystals_trait C, Domain From> + requires(To != From) + friend StructureT detail::domain_cast(StructureT&&); + + /** + * This constructor is used to convert a PolynomialVector from one domain to another. + * The friend declarations above facilitate this. + */ + template + requires(D != OtherD) + explicit PolynomialVector(PolynomialVector&& other) noexcept : + m_polys_storage(std::move(other.m_polys_storage)) { + BOTAN_DEBUG_ASSERT(m_polys_storage.size() % Trait::N == 0); + const size_t vecsize = m_polys_storage.size() / Trait::N; + for(size_t i = 0; i < vecsize; ++i) { + m_vec.emplace_back( + Polynomial(std::span{m_polys_storage}.subspan(i * Trait::N).template first())); + } + } + + public: + // Workaround, because Xcode and NDK don't understand the + // `detail::domain_cast` friend declaration above. + // + // TODO: Try to remove this and use the c'tor directly in + // `detail::domain_cast` after updating the compilers. + template + requires(D != OtherD) + static PolynomialVector from_domain_cast(PolynomialVector&& other) { + return PolynomialVector(std::move(other)); + } + + public: + PolynomialVector(size_t vecsize) : m_polys_storage(vecsize * Trait::N) { + for(size_t i = 0; i < vecsize; ++i) { + m_vec.emplace_back( + Polynomial(std::span{m_polys_storage}.subspan(i * Trait::N).template first())); + } + } + + PolynomialVector(const ThisPolynomialVector& other) = delete; + PolynomialVector(ThisPolynomialVector&& other) noexcept = default; + ThisPolynomialVector& operator=(const ThisPolynomialVector& other) = delete; + ThisPolynomialVector& operator=(ThisPolynomialVector&& other) noexcept = default; + ~PolynomialVector() = default; + + size_t size() const { return m_vec.size(); } + + constexpr Domain domain() const noexcept { return D; } + + ThisPolynomialVector clone() const { + ThisPolynomialVector res(size()); + + // The default-constructed PolynomialVector has set up res.m_vec to + // point to res.m_polys_storage. Therefore we can just copy the data + // into res.m_polys_storage to fill the non-owning polynomials. + copy_mem(res.m_polys_storage, m_polys_storage); + + return res; + } + + /// @returns the number of non-zero coefficients in the polynomial vector + size_t hamming_weight() const noexcept { + size_t weight = 0; + for(const auto c : m_polys_storage) { + weight += (c != 0); + } + return weight; + } + + /// @returns true if all coefficients are within the range [min, max] + constexpr bool ct_validate_value_range(T min, T max) const noexcept { + return detail::ct_all_within_range(coefficients(), min, max); + } + + std::span coefficients() { return m_polys_storage; } + + std::span coefficients() const { return m_polys_storage; } + + ThisPolynomialVector& operator+=(const ThisPolynomialVector& 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) { + Trait::poly_add(m_vec[i].coefficients(), m_vec[i].coefficients(), other.m_vec[i].coefficients()); + } + return *this; + } + + ThisPolynomialVector& operator-=(const ThisPolynomialVector& other) { + BOTAN_ASSERT(m_vec.size() == other.m_vec.size(), "cannot subtract polynomial vectors of differing lengths"); + for(size_t i = 0; i < m_vec.size(); ++i) { + Trait::poly_sub(m_vec[i].coefficients(), m_vec[i].coefficients(), other.m_vec[i].coefficients()); + } + return *this; + } + + ThisPolynomialVector& reduce() { + for(auto& p : m_vec) { + Trait::barrett_reduce(p.coefficients()); + } + return *this; + } + + ThisPolynomialVector& conditional_add_q() { + for(auto& v : m_vec) { + Trait::poly_cadd_q(v.coefficients()); + } + return *this; + } + + Polynomial& operator[](size_t i) { return m_vec[i]; } + + const Polynomial& operator[](size_t i) const { return m_vec[i]; } + + decltype(auto) begin() { return m_vec.begin(); } + + decltype(auto) begin() const { return m_vec.begin(); } + + decltype(auto) end() { return m_vec.end(); } + + decltype(auto) end() const { return m_vec.end(); } +}; + +template +class PolynomialMatrix { + private: + using ThisPolynomialMatrix = PolynomialMatrix; + + private: + std::vector> m_mat; + + public: + PolynomialMatrix(std::vector> mat) : m_mat(std::move(mat)) {} + + PolynomialMatrix(const ThisPolynomialMatrix& other) = delete; + PolynomialMatrix(ThisPolynomialMatrix&& other) noexcept = default; + ThisPolynomialMatrix& operator=(const ThisPolynomialMatrix& other) = delete; + ThisPolynomialMatrix& operator=(ThisPolynomialMatrix&& other) noexcept = default; + ~PolynomialMatrix() = default; + + size_t size() const { return m_mat.size(); } + + PolynomialMatrix(size_t rows, size_t cols) { + m_mat.reserve(rows); + for(size_t i = 0; i < rows; ++i) { + m_mat.emplace_back(cols); + } + } + + PolynomialVector& operator[](size_t i) { return m_mat[i]; } + + const PolynomialVector& operator[](size_t i) const { return m_mat[i]; } + + decltype(auto) begin() { return m_mat.begin(); } + + decltype(auto) begin() const { return m_mat.begin(); } + + decltype(auto) end() { return m_mat.end(); } + + decltype(auto) end() const { return m_mat.end(); } +}; + +namespace detail { + +template +void montgomery(Polynomial& p) { + for(auto& c : p) { + c = Trait::to_montgomery(c); + } +} + +template +void dot_product(Polynomial& out, + const PolynomialVector& a, + const PolynomialVector& b) { + BOTAN_ASSERT(a.size() == b.size(), "Dot product requires equally sized PolynomialVectors"); + for(size_t i = 0; i < a.size(); ++i) { + out += a[i] * b[i]; + } + out.reduce(); +} + +} // namespace detail + +template +Polynomial ntt(Polynomial p) { + auto p_ntt = detail::domain_cast(std::move(p)); + Trait::ntt(p_ntt.coefficients()); + return p_ntt; +} + +template +Polynomial inverse_ntt(Polynomial p_ntt) { + auto p = detail::domain_cast(std::move(p_ntt)); + Trait::inverse_ntt(p.coefficients()); + return p; +} + +template +PolynomialVector ntt(PolynomialVector polyvec) { + auto polyvec_ntt = detail::domain_cast(std::move(polyvec)); + for(auto& poly : polyvec_ntt) { + Trait::ntt(poly.coefficients()); + } + return polyvec_ntt; +} + +template +PolynomialVector inverse_ntt(PolynomialVector polyvec_ntt) { + auto polyvec = detail::domain_cast(std::move(polyvec_ntt)); + for(auto& poly : polyvec) { + Trait::inverse_ntt(poly.coefficients()); + } + return polyvec; +} + +template +Polynomial montgomery(Polynomial p) { + detail::montgomery(p); + return p; +} + +template +PolynomialVector montgomery(PolynomialVector polyvec) { + for(auto& p : polyvec) { + detail::montgomery(p); + } + return polyvec; +} + +template +PolynomialVector operator+(const PolynomialVector& a, + const PolynomialVector& b) { + BOTAN_DEBUG_ASSERT(a.size() == b.size()); + PolynomialVector result(a.size()); + for(size_t i = 0; i < a.size(); ++i) { + Trait::poly_add(result[i].coefficients(), a[i].coefficients(), b[i].coefficients()); + } + return result; +} + +template +PolynomialVector operator*(const PolynomialMatrix& mat, + const PolynomialVector& vec) { + PolynomialVector result(mat.size()); + for(size_t i = 0; i < mat.size(); ++i) { + Trait::polyvec_pointwise_acc_montgomery(result[i].coefficients(), mat[i].coefficients(), vec.coefficients()); + } + return result; +} + +template +Polynomial operator*(const PolynomialVector& a, + const PolynomialVector& b) { + Polynomial result; + detail::dot_product(result, a, b); + return result; +} + +template +PolynomialVector operator*(const Polynomial& p, + const PolynomialVector& pv) { + PolynomialVector result(pv.size()); + for(size_t i = 0; i < pv.size(); ++i) { + Trait::poly_pointwise_montgomery(result[i].coefficients(), p.coefficients(), pv[i].coefficients()); + } + return result; +} + +template +Polynomial operator*(const Polynomial& a, + const Polynomial& b) { + Polynomial result; + Trait::poly_pointwise_montgomery(result.coefficients(), a.coefficients(), b.coefficients()); + return result; +} + +template +PolynomialVector operator<<(const PolynomialVector& pv, size_t shift) { + BOTAN_ASSERT_NOMSG(shift < sizeof(typename Trait::T) * 8); + PolynomialVector result(pv.size()); + for(size_t i = 0; i < pv.size(); ++i) { + for(size_t j = 0; j < Trait::N; ++j) { + result[i][j] = pv[i][j] << shift; + } + } + return result; +} + +} // namespace Botan::CRYSTALS + +#endif diff --git a/src/lib/pubkey/pqcrystals/pqcrystals_encoding.h b/src/lib/pubkey/pqcrystals/pqcrystals_encoding.h new file mode 100644 index 00000000000..8c99187b82b --- /dev/null +++ b/src/lib/pubkey/pqcrystals/pqcrystals_encoding.h @@ -0,0 +1,228 @@ +/* + * PQ CRYSTALS Encoding 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_PQ_CRYSTALS_ENCODING_H_ +#define BOTAN_PQ_CRYSTALS_ENCODING_H_ + +#include +#include +#include + +#include +#include +#include +#include + +#if defined(BOTAN_HAS_XOF) + #include +#endif +namespace Botan::CRYSTALS { + +namespace detail { + +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 + +template +concept byte_source = + requires(T& t) { requires std::invocable>; }; + +template +concept coeff_map_fn = std::signed_integral && requires(T fn, PolyCoeffT coeff) { + { fn(coeff) } -> std::same_as>; +}; + +template +concept coeff_unmap_fn = + std::signed_integral && requires(T fn, std::make_unsigned_t coeff_value) { + { fn(coeff_value) } -> std::same_as; + }; + +/** + * Helper for base implementations of NIST FIPS 204 IPD, Algorithms 10-13 and + * NIST FIPS Algorithms 4-5. It pre-computes generic values to bit-(un)pack + * polynomial coefficients at compile-time. + * + * The base implementations are also templated with the @p range parameter + * forcing the compiler to generate specialized code for each supported range. + */ +template +struct BitPackingTrait final { + using T = typename PolyTrait::T; + using unsigned_T = std::make_unsigned_t; + using sink_t = uint64_t; + + static_assert(range <= std::numeric_limits::max()); + + constexpr static size_t bits_in_collector = sizeof(sink_t) * 8; + constexpr static size_t bits_per_coeff = bitlen(range); + 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 = 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; + }(); + constexpr static size_t bytes_per_pack = bits_per_pack / 8; + constexpr static size_t coeffs_per_pack = bits_per_pack / bits_per_coeff; + constexpr static size_t collectors_per_pack = (bytes_per_pack + sizeof(sink_t) - 1) / sizeof(sink_t); + constexpr static size_t collector_bytes_per_pack = collectors_per_pack * sizeof(sink_t); + constexpr static sink_t value_mask = (1 << bits_per_coeff) - 1; + + using collector_array = std::array; + using collector_bytearray = std::array; + + static_assert(PolyTrait::N % coeffs_per_pack == 0); +}; + +/** + * Base implementation of NIST FIPS 203 IPD Algorithm 4 (ByteEncode) and NIST + * FIPS 204 Algorithms 10 (SimpleBitPack) and 11 (BitPack). + * + * This takes a polynomial @p p and packs its coefficients into the buffer + * represented by @p stuffer. Optionally, the coefficients can be transformed + * using the @p map function before packing them. Kyber uses @p map to compress + * the coefficients as needed, Dilithium to transform coefficients to unsigned. + * + * The implementation assumes that the values returned from the custom @p map + * transformation are in the range [0, range]. No assumption is made about the + * value range of the coefficients in the polynomial @p p. + * + * Note that this bit-packing algorithm is inefficient if the bit-length of the + * coefficients is a multiple of 8. In that case, a byte-level encoding (that + * might need to take endianess into account) would be more efficient. However, + * neither Kyber nor Dilithium instantiate bit-packings with such a value range. + * + * @tparam range the upper bound of the coefficient range. + */ +template MapFnT> +constexpr void pack(const Polynomial& p, BufferStuffer& stuffer, MapFnT map) { + using trait = BitPackingTrait; + + BOTAN_DEBUG_ASSERT(stuffer.remaining_capacity() >= p.size() * trait::bits_per_coeff / 8); + + // Bit-packing example that shows a coefficients' bit-pack that spills across + // more than one 64-bit collectors. This illustrates the algorithm below. + // + // 0 64 128 + // Collectors (64 bits): | collectors[0] | collectors[1] | + // | | | + // Coefficients (11 bits): | c[0] | c[1] | c[2] | c[3] | c[4] | c[5] | c[6] | c[7] | | | | | ... + // | | | + // | < byte-aligned coefficient pack > | < byte-aligned pad. > | + // | (one inner loop iteration) | + // 0 88 (divisible by 8) + + for(size_t i = 0; i < p.size(); i += trait::coeffs_per_pack) { + // The collectors array is filled with bit-packed coefficients to produce + // a byte-aligned pack of coefficients. When coefficients fall onto the + // boundary of two collectors, their bits must be split. + typename trait::collector_array collectors = {0}; + for(size_t j = 0, bit_offset = 0, c = 0; j < trait::coeffs_per_pack; ++j) { + // Transform p[i] via a custom map function (that may be a NOOP). + const typename trait::unsigned_T mapped_coeff = map(p[i + j]); + const auto coeff_value = static_cast(mapped_coeff); + BOTAN_DEBUG_ASSERT(coeff_value <= range); + + // Bit-pack the coefficient into the collectors array and keep track of + // the bit-offset within the current collector. Note that this might + // shift some high-bits of the coefficient out of the current collector. + collectors[c] |= coeff_value << bit_offset; + bit_offset += trait::bits_per_coeff; + + // If the bit-offset now exceeds the collector's bit-width, we fill the + // next collector with the high-bits that didn't fit into the previous. + // The bit-offset is adjusted to now point into the new collector. + if(bit_offset > trait::bits_in_collector) { + bit_offset = bit_offset - trait::bits_in_collector; + collectors[++c] = coeff_value >> (trait::bits_per_coeff - bit_offset); + } + } + + // One byte-aligned pack of bit-packed coefficients is now stored in the + // collectors and can be written to an output buffer. Note that we might + // have to remove some padding bytes of unused collector space. + const auto bytes = store_le(collectors); + stuffer.append(std::span{bytes}.template first()); + } +} + +/** + * Base implementation of NIST FIPS 203 IPD Algorithm 5 (ByteDecode) and NIST + * FIPS 204 Algorithms 12 (SimpleBitUnpack) and 13 (BitUnpack). + * + * This takes a byte sequence represented by @p byte_source and unpacks its + * coefficients into the polynomial @p p. Optionally, the coefficients can be + * transformed using the @p unmap function after unpacking them. Note that the + * provided range is assumed for the coefficients _before_ the transformation. + * + * Kyber uses @p unmap to decompress the coefficients as needed, Dilithium uses + * it to convert the coefficients back to signed integers. + * + * @tparam range the upper bound of the coefficient range. + */ +template UnmapFnT> +constexpr void unpack(Polynomial& p, ByteSourceT& byte_source, UnmapFnT unmap) { + using trait = BitPackingTrait; + + auto get_bytes = detail::as_byte_source(byte_source); + typename trait::collector_bytearray bytes = {0}; + + // This is the inverse operation of the bit-packing algorithm above. Please + // refer to the comments there for a detailed explanation of the algorithm. + for(size_t i = 0; i < p.size(); i += trait::coeffs_per_pack) { + get_bytes(std::span{bytes}.template first()); + const auto collectors = load_le(bytes); + + for(size_t j = 0, bit_offset = 0, c = 0; j < trait::coeffs_per_pack; ++j) { + typename trait::sink_t coeff_value = collectors[c] >> bit_offset; + bit_offset += trait::bits_per_coeff; + if(bit_offset > trait::bits_in_collector) { + bit_offset = bit_offset - trait::bits_in_collector; + coeff_value |= collectors[++c] << (trait::bits_per_coeff - bit_offset); + } + const auto mapped_coeff = static_cast(coeff_value & trait::value_mask); + BOTAN_DEBUG_ASSERT(mapped_coeff <= range); + p[i + j] = unmap(mapped_coeff); + } + } +} + +/// Overload for packing polynomials with a NOOP map function +template +constexpr void pack(const Polynomial& p, BufferStuffer& stuffer) { + using unsigned_T = std::make_unsigned_t; + pack(p, stuffer, [](typename PolyTrait::T x) { return static_cast(x); }); +} + +/// Overload for unpacking polynomials with a NOOP unmap function +template +constexpr void unpack(Polynomial& p, ByteSourceT& byte_source) { + using unsigned_T = std::make_unsigned_t; + unpack(p, byte_source, [](unsigned_T x) { return static_cast(x); }); +} + +} // namespace Botan::CRYSTALS + +#endif diff --git a/src/lib/pubkey/pqcrystals/pqcrystals_helpers.h b/src/lib/pubkey/pqcrystals/pqcrystals_helpers.h new file mode 100644 index 00000000000..c8d46620832 --- /dev/null +++ b/src/lib/pubkey/pqcrystals/pqcrystals_helpers.h @@ -0,0 +1,138 @@ +/* + * PQ CRYSTALS Common Helpers + * + * Further changes + * (C) 2024 Jack Lloyd + * (C) 2024 René Meusel, Fabian Albert, Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_PQ_CRYSTALS_HELPERS_H_ +#define BOTAN_PQ_CRYSTALS_HELPERS_H_ + +#include +#include +#include + +#include + +namespace Botan { + +// clang-format off + +template + requires(sizeof(T) <= 4) +using next_longer_uint_t = + std::conditional_t>>; + +template + requires(sizeof(T) <= 4) +using next_longer_int_t = + std::conditional_t>>; + +// clang-format on + +template + requires(size_t(sizeof(T)) <= 4) +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; +} + +template + requires(size_t(sizeof(T)) <= 4) +consteval T montgomery_R2(T q) { + using T2 = next_longer_int_t; + return (static_cast(montgomery_R(q)) * static_cast(montgomery_R(q))) % q; +} + +template +struct eea_result { + T gcd; + T u; + T v; +}; + +/** + * Run the extended Euclidean algorithm to find the greatest common divisor of a + * and b and the Bézout coefficients, u and v. + */ +template +consteval eea_result extended_euclidean_algorithm(T a, T b) { + if(a > b) { + std::swap(a, b); + } + + T u1 = 0, v1 = 1, u2 = 1, v2 = 0; + + if(a != b) { + while(a != 0) { + const T q = b / a; + std::tie(a, b) = std::make_tuple(b - q * a, a); + std::tie(u1, v1, u2, v2) = std::make_tuple(u2, v2, u1 - q * u2, v1 - q * v2); + } + } + + return {.gcd = b, .u = u1, .v = v1}; +} + +/** + * Calculate the modular multiplacative inverse of q modulo m. + * By default, this assumes m to be 2^bitlength of T for application in a + * Montgomery reduction. + */ +template > + requires(sizeof(T) <= 4) +consteval T modular_inverse(T q, T2 m = T2(1) << sizeof(T) * 8) { + return static_cast(extended_euclidean_algorithm(q, m).u); +} + +constexpr auto bitlen(size_t x) { + return ceil_log2(x + 1); +}; + +/** + * Precompute the zeta-values for the NTT. Note that the pre-computed values + * contain the Montgomery factor for either Kyber or Dilithium. + */ +template +consteval static auto precompute_zetas(T q, T monty, T root_of_unity) { + using T2 = next_longer_int_t; + + std::array result = {0}; + + auto bitreverse = [](size_t k) -> size_t { + size_t r = 0; + const auto l = ceil_log2(degree); + for(size_t i = 0; i < l; ++i) { + r |= ((k >> i) & 1) << (l - 1 - i); + } + return r; + }; + + auto pow = [q](T base, size_t exp) -> T2 { + T2 res = 1; + for(size_t i = 0; i < exp; ++i) { + res = (res * base) % q; + } + return res; + }; + + auto csubq = [q](T a) -> T { return a <= q / 2 ? a : a - q; }; + + for(size_t i = 0; i < result.size(); ++i) { + result[i] = csubq(pow(root_of_unity, bitreverse(i)) * monty % q); + } + + return result; +} + +} // namespace Botan + +#endif diff --git a/src/lib/rng/rng.h b/src/lib/rng/rng.h index 1ee3db9f714..1cf1a912111 100644 --- a/src/lib/rng/rng.h +++ b/src/lib/rng/rng.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include diff --git a/src/lib/utils/rounding.h b/src/lib/utils/rounding.h index 68ecb52193c..6c55632a587 100644 --- a/src/lib/utils/rounding.h +++ b/src/lib/utils/rounding.h @@ -22,7 +22,7 @@ namespace Botan { * @param align_to the alignment boundary * @return n rounded up to a multiple of align_to */ -inline size_t round_up(size_t n, size_t align_to) { +constexpr inline size_t round_up(size_t n, size_t align_to) { // Arguably returning n in this case would also be sensible BOTAN_ARG_CHECK(align_to != 0, "align_to must not be 0"); diff --git a/src/lib/utils/stl_util.h b/src/lib/utils/stl_util.h index 0b94dd647e1..9356b2db5a3 100644 --- a/src/lib/utils/stl_util.h +++ b/src/lib/utils/stl_util.h @@ -408,6 +408,13 @@ class StringLiteral { char value[N]; }; +// TODO: C++23: replace with std::to_underlying +template + requires std::is_enum_v +auto to_underlying(T e) noexcept { + return static_cast>(e); +} + } // namespace Botan #endif diff --git a/src/lib/xof/xof.h b/src/lib/xof/xof.h index b6a3bd6ccd1..f45f811f7e6 100644 --- a/src/lib/xof/xof.h +++ b/src/lib/xof/xof.h @@ -158,6 +158,16 @@ class BOTAN_PUBLIC_API(3, 2) XOF { return out; } + /** + * @return the next @p count output bytes as a std::array<>. + */ + template + std::array output() { + std::array out; + generate_bytes(out); + return out; + } + /** * Convenience overload to generate a std::vector. Same as calling * `XOF::output>()`. diff --git a/src/tests/test_crystals.cpp b/src/tests/test_crystals.cpp new file mode 100644 index 00000000000..10e81fc5a79 --- /dev/null +++ b/src/tests/test_crystals.cpp @@ -0,0 +1,508 @@ +/* + * Tests for PQ Crystals + * (C) 2024 Jack Lloyd + * (C) 2024 René Meusel, Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#include "tests.h" + +#if defined(BOTAN_HAS_PQCRYSTALS) + #include + + #include + #include + #include + #include + #include + +namespace Botan_Tests { + +namespace { + +template +consteval T gcd(T x, T y) { + return Botan::extended_euclidean_algorithm(x, y).gcd; +} + +template +consteval T v(T x, T y) { + return Botan::extended_euclidean_algorithm(x, y).v; +} + +template +consteval T u(T x, T y) { + return Botan::extended_euclidean_algorithm(x, y).u; +} + +Test::Result test_extended_euclidean_algorithm() { + Test::Result res("Extended Euclidean Algorithm"); + + // 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; +} + +// Equivalent to Kyber's constants +struct Kyberish_Constants { + using T = int16_t; + static constexpr T N = 256; + static constexpr T Q = 3329; + static constexpr T F = 3303; + static constexpr T ROOT_OF_UNITY = 17; + static constexpr size_t NTT_Degree = 128; +}; + +// Equivalent to Dilithium's constants +struct Dilithiumish_Constants { + using T = int32_t; + static constexpr T N = 256; + static constexpr T Q = 8380417; + static constexpr T F = 8347681; + static constexpr T ROOT_OF_UNITY = 1753; + static constexpr size_t NTT_Degree = 256; +}; + +template +class Mock_Trait final : public Botan::CRYSTALS::Trait_Base> { + public: + using T = typename Botan::CRYSTALS::Trait_Base>::T; + using T2 = typename Botan::CRYSTALS::Trait_Base>::T2; + constexpr static auto N = Botan::CRYSTALS::Trait_Base>::N; + + static T montgomery_reduce_coefficient(T2) { + throw Botan_Tests::Test_Error("montgomery reduction not implemented"); + } + + static T barrett_reduce_coefficient(T) { throw Botan_Tests::Test_Error("barrett reduction not implemented"); } + + static void ntt(std::span) { throw Botan_Tests::Test_Error("NTT not implemented"); } + + static void inverse_ntt(std::span) { throw Botan_Tests::Test_Error("inverse NTT not implemented"); } + + static void poly_pointwise_montgomery(std::span, std::span, std::span) { + throw Botan_Tests::Test_Error("pointwise multiplication not implemented"); + } +}; + +using Kyberish_Trait = Mock_Trait; + +using Domain = Botan::CRYSTALS::Domain; + +template +using Kyberish_Poly = Botan::CRYSTALS::Polynomial; + +template +using Kyberish_PolyVec = Botan::CRYSTALS::PolynomialVector; + +std::vector test_polynomial_basics() { + return { + 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)); + }), + + 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][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)); + }), + }; +} + +namespace { + +class DeterministicXOF : public Botan::XOF { + public: + DeterministicXOF(std::span data) : m_data(data) {} + + std::string name() const override { return "DeterministicXOF"; } + + bool accepts_input() const override { return false; } + + std::unique_ptr copy_state() const override { throw Botan_Tests::Test_Error("copy_state not implemented"); } + + std::unique_ptr new_object() const override { throw Botan_Tests::Test_Error("new_object not implemented"); } + + size_t block_size() const override { return 1; } + + void start_msg(std::span, std::span) override { + throw Botan_Tests::Test_Error("start_msg not implemented"); + } + + void add_data(std::span) override { throw Botan_Tests::Test_Error("add_data not implemented"); } + + void generate_bytes(std::span output) override { m_data.copy_into(output); } + + void reset() override {} + + private: + Botan::BufferSlicer m_data; +}; + +template +void random_encoding_roundtrips(Test::Result& res, Botan::RandomNumberGenerator& rng, size_t expected_encoding_bits) { + using Poly = Botan::CRYSTALS::Polynomial; + using T = typename Trait::T; + + auto random_poly = [&rng]() -> Poly { + Poly p; + std::array buf; + for(auto& coeff : p) { + rng.randomize(buf); + coeff = static_cast((Botan::load_be(buf) % (range + 1))); + } + return p; + }; + + const auto p = random_poly(); + std::vector buffer((p.size() * expected_encoding_bits + 7) / 8); + Botan::BufferStuffer stuffer(buffer); + Botan::CRYSTALS::pack(p, stuffer); + res.confirm("encoded polynomial fills buffer", stuffer.full()); + + Botan::BufferSlicer slicer(buffer); + Poly p_unpacked; + Botan::CRYSTALS::unpack(p_unpacked, slicer); + res.confirm("decoded polynomial reads all bytes", slicer.empty()); + + p_unpacked -= p; + res.test_eq("p = unpack(pack(p))", p_unpacked.hamming_weight(), 0); +} + +} // namespace + +std::vector test_encoding() { + const auto threebitencoding = Botan::hex_decode( + "88C61AD158231A6B44638D68AC118D35A2B14634D688C61AD158231A6B44638D68AC118D" + "35A2B14634D688C61AD158231A6B44638D68AC118D35A2B14634D688C61AD158231A6B44" + "638D68AC118D35A2B14634D688C61AD158231A6B44638D68"); + + const auto eightbitencoding = Botan::hex_decode( + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20212223" + "2425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F4041424344454647" + "48494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B" + "6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F" + "909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3" + "B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7" + "D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFB" + "FCFDFEFF"); + + const auto tenbitencoding = Botan::hex_decode( + "00084080010828C0800310484081051868C08107208840820928A8C0820B30C840830D38" + "E8C0830F40084184114828C1841350484185155868C18517608841861968A8C1861B70C8" + "41871D78E8C1871F80084288218828C2882390484289259868C28927A088428A29A8A8C2" + "8A2BB0C8428B2DB8E8C28B2FC008438C31C828C38C33D048438D35D868C38D37E088438E" + "39E8A8C38E3BF0C8438F3DF8E8C38F3F00094490410829C4904310494491451869C49147" + "208944924928A9C4924B30C944934D38E9C4934F40094594514829C59453504945955558" + "69C59557608945965968A9C5965B70C945975D78E9C5975F80094698618829C698639049" + "4699659869C69967A089469A69A8A9C69A6BB0C9469B6DB8E9C69B6FC009479C71C829C7" + "9C73D049479D75D869C79D77E089479E79E8A9C79E7BF0C9479F7DF8E9C79F7F"); + + return { + 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); + +} // namespace Botan_Tests + +#endif diff --git a/src/tests/test_dilithium.cpp b/src/tests/test_dilithium.cpp index 5d095affd98..e95d9d0992d 100644 --- a/src/tests/test_dilithium.cpp +++ b/src/tests/test_dilithium.cpp @@ -108,7 +108,8 @@ REGISTER_DILITHIUM_KAT_TEST(8x7_AES, Randomized); class DilithiumRoundtripTests final : public Test { public: - static Test::Result run_roundtrip(const char* test_name, Botan::DilithiumMode mode, bool randomized) { + static Test::Result run_roundtrip( + const char* test_name, Botan::DilithiumMode mode, bool randomized, size_t strength, size_t psid) { Test::Result result(test_name); auto rng = Test::new_rng(test_name); @@ -131,6 +132,11 @@ class DilithiumRoundtripTests final : public Test { Botan::Dilithium_PrivateKey priv_key(*rng, mode); const Botan::Dilithium_PublicKey& pub_key = priv_key; + result.test_eq("key strength", priv_key.estimated_strength(), strength); + result.test_eq("key length", priv_key.key_length(), psid); + result.test_eq("key strength", pub_key.estimated_strength(), strength); + result.test_eq("key length", pub_key.key_length(), psid); + const auto sig_before_codec = sign(priv_key, msgvec); const auto priv_key_encoded = priv_key.private_key_bits(); @@ -180,27 +186,25 @@ class DilithiumRoundtripTests final : public Test { } std::vector run() override { - std::vector results; - + return { #if defined(BOTAN_HAS_DILITHIUM) - results.push_back(run_roundtrip("Dilithium_4x4_Common", Botan::DilithiumMode::Dilithium4x4, false)); - results.push_back(run_roundtrip("Dilithium_6x5_Common", Botan::DilithiumMode::Dilithium6x5, false)); - results.push_back(run_roundtrip("Dilithium_8x7_Common", Botan::DilithiumMode::Dilithium8x7, false)); - results.push_back(run_roundtrip("Dilithium_4x4_Common_Randomized", Botan::DilithiumMode::Dilithium4x4, true)); - results.push_back(run_roundtrip("Dilithium_6x5_Common_Randomized", Botan::DilithiumMode::Dilithium6x5, true)); - results.push_back(run_roundtrip("Dilithium_8x7_Common_Randomized", Botan::DilithiumMode::Dilithium8x7, true)); + run_roundtrip("Dilithium_4x4_Common", Botan::DilithiumMode::Dilithium4x4, false, 128, 44), + run_roundtrip("Dilithium_6x5_Common", Botan::DilithiumMode::Dilithium6x5, false, 192, 65), + run_roundtrip("Dilithium_8x7_Common", Botan::DilithiumMode::Dilithium8x7, false, 256, 87), + run_roundtrip("Dilithium_4x4_Common_Randomized", Botan::DilithiumMode::Dilithium4x4, true, 128, 44), + run_roundtrip("Dilithium_6x5_Common_Randomized", Botan::DilithiumMode::Dilithium6x5, true, 192, 65), + run_roundtrip("Dilithium_8x7_Common_Randomized", Botan::DilithiumMode::Dilithium8x7, true, 256, 87), #endif #if defined(BOTAN_HAS_DILITHIUM_AES) - results.push_back(run_roundtrip("Dilithium_4x4_AES", Botan::DilithiumMode::Dilithium4x4_AES, false)); - results.push_back(run_roundtrip("Dilithium_6x5_AES", Botan::DilithiumMode::Dilithium6x5_AES, false)); - results.push_back(run_roundtrip("Dilithium_8x7_AES", Botan::DilithiumMode::Dilithium8x7_AES, false)); - results.push_back(run_roundtrip("Dilithium_4x4_AES_Randomized", Botan::DilithiumMode::Dilithium4x4_AES, true)); - results.push_back(run_roundtrip("Dilithium_6x5_AES_Randomized", Botan::DilithiumMode::Dilithium6x5_AES, true)); - results.push_back(run_roundtrip("Dilithium_8x7_AES_Randomized", Botan::DilithiumMode::Dilithium8x7_AES, true)); + run_roundtrip("Dilithium_4x4_AES", Botan::DilithiumMode::Dilithium4x4_AES, false, 128, 44), + run_roundtrip("Dilithium_6x5_AES", Botan::DilithiumMode::Dilithium6x5_AES, false, 192, 65), + run_roundtrip("Dilithium_8x7_AES", Botan::DilithiumMode::Dilithium8x7_AES, false, 256, 87), + run_roundtrip("Dilithium_4x4_AES_Randomized", Botan::DilithiumMode::Dilithium4x4_AES, true, 128, 44), + run_roundtrip("Dilithium_6x5_AES_Randomized", Botan::DilithiumMode::Dilithium6x5_AES, true, 192, 65), + run_roundtrip("Dilithium_8x7_AES_Randomized", Botan::DilithiumMode::Dilithium8x7_AES, true, 256, 87), #endif - - return results; + }; } }; diff --git a/src/tests/test_kyber.cpp b/src/tests/test_kyber.cpp index 0cb43c0cbb1..e0e28ae7e43 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 @@ -35,7 +38,7 @@ namespace Botan_Tests { class KYBER_Tests final : public Test { public: - static Test::Result run_kyber_test(const char* test_name, Botan::KyberMode mode, size_t strength) { + static Test::Result run_kyber_test(const char* test_name, Botan::KyberMode mode, size_t strength, size_t psid) { Test::Result result(test_name); auto rng = Test::new_rng(test_name); @@ -48,6 +51,8 @@ class KYBER_Tests final : public Test { result.test_eq("estimated strength private", priv_key.estimated_strength(), strength); result.test_eq("estimated strength public", pub_key->estimated_strength(), strength); + result.test_eq("canonical parameter set identifier", priv_key.key_length(), psid); + result.test_eq("canonical parameter set identifier", pub_key->key_length(), psid); // Serialize const auto priv_key_bits = priv_key.private_key_bits(); @@ -94,14 +99,14 @@ class KYBER_Tests final : public Test { std::vector results; #if defined(BOTAN_HAS_KYBER_90S) - results.push_back(run_kyber_test("Kyber512_90s API", Botan::KyberMode::Kyber512_90s, 128)); - results.push_back(run_kyber_test("Kyber768_90s API", Botan::KyberMode::Kyber768_90s, 192)); - results.push_back(run_kyber_test("Kyber1024_90s API", Botan::KyberMode::Kyber1024_90s, 256)); + results.push_back(run_kyber_test("Kyber512_90s API", Botan::KyberMode::Kyber512_90s, 128, 512)); + results.push_back(run_kyber_test("Kyber768_90s API", Botan::KyberMode::Kyber768_90s, 192, 768)); + results.push_back(run_kyber_test("Kyber1024_90s API", Botan::KyberMode::Kyber1024_90s, 256, 1024)); #endif #if defined(BOTAN_HAS_KYBER) - results.push_back(run_kyber_test("Kyber512 API", Botan::KyberMode::Kyber512_R3, 128)); - results.push_back(run_kyber_test("Kyber768 API", Botan::KyberMode::Kyber768_R3, 192)); - results.push_back(run_kyber_test("Kyber1024 API", Botan::KyberMode::Kyber1024_R3, 256)); + results.push_back(run_kyber_test("Kyber512 API", Botan::KyberMode::Kyber512_R3, 128, 512)); + results.push_back(run_kyber_test("Kyber768 API", Botan::KyberMode::Kyber768_R3, 192, 768)); + results.push_back(run_kyber_test("Kyber1024 API", Botan::KyberMode::Kyber1024_R3, 256, 1024)); #endif return results; @@ -257,6 +262,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_Algos::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_Algos::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_Algos::compress(Kyber_Algos::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 diff --git a/src/tests/test_pubkey_pqc.h b/src/tests/test_pubkey_pqc.h index 703830f2acf..e1c35417c4a 100644 --- a/src/tests/test_pubkey_pqc.h +++ b/src/tests/test_pubkey_pqc.h @@ -142,6 +142,10 @@ class PK_PQC_KEM_KAT_Test : public PK_Test { // Decapsulation auto sk2 = Botan::load_private_key(sk->algorithm_identifier(), sk->private_key_bits()); + if(!result.test_not_null("Successfully deserialized private key", sk2)) { + return result; + } + Botan::Null_RNG null_rng; auto dec = Botan::PK_KEM_Decryptor(*sk2, null_rng, "Raw"); const auto shared_key = dec.decrypt(encaped.encapsulated_shared_key(), 0 /* no KDF */);