diff --git a/doc/api_ref/pubkey.rst b/doc/api_ref/pubkey.rst index 88f31c4c9d5..21f1045b6e8 100644 --- a/doc/api_ref/pubkey.rst +++ b/doc/api_ref/pubkey.rst @@ -142,6 +142,11 @@ McEliece Post-quantum secure key encapsulation scheme based on the hardness of certain decoding problems. +Classic McEliece +~~~~~~~~~~~~~~~~ + +Post-quantum secure, code-based key encapsulation scheme. + ElGamal ~~~~~~~~ @@ -1128,6 +1133,7 @@ Botan implements the following KEM schemes: #. Kyber #. FrodoKEM #. McEliece +#. Classic McEliece .. _kyber_example: @@ -1193,6 +1199,48 @@ parameters n and t, and have the corresponding key sizes listed: You can check the speed of McEliece with the suggested parameters above using ``botan speed McEliece`` +Classic McEliece KEM +-------------------- + +`Classic McEliece `_ is an IND-CCA2 secure key +encapsulation algorithm based on the McEliece cryptosystem introduced in 1978. +It is a code-based scheme that relies on conservative security assumptions and +is considered secure against quantum computers. It is an alternative to +lattice-based schemes. + +Other advantages of Classic McEliece are the small ciphertext size and the fast +encapsulation. Key generation and decapsulation are slower than in lattice-based +schemes. The main disadvantage of Classic McEliece is the large public key size, +ranging from 0.26 MB to 1.36 MB, depending on the instance. Due to its large key +size, Classic McEliece is recommended for applications where the public key is +stored for a long time, and memory is not a critical resource. Usage with +ephemeral keys is not recommended. + +Botan's implementation covers the parameter sets of the `NIST round 4 +specification `_ +and the `Classic McEliece ISO draft specification +`_. +These are the following: + ++------------------+-------------------+-------------------+--------------------+-------------------+ +| Set without f/pc | Set with f | Set with pc | Set with pcf | Public Key Size | ++==================+===================+===================+====================+===================+ +| mceliece348864 | mceliece348864f | | | 0.26 MB | ++------------------+-------------------+-------------------+--------------------+-------------------+ +| mceliece460896 | mceliece460896f | | | 0.52 MB | ++------------------+-------------------+-------------------+--------------------+-------------------+ +| mceliece6688128 | mceliece6688128f | mceliece6688128pc | mceliece6688128pcf | 1.04 MB | ++------------------+-------------------+-------------------+--------------------+-------------------+ +| mceliece6960119 | mceliece6960119f | mceliece6960119pc | mceliece6960119pcf | 1.05 MB | ++------------------+-------------------+-------------------+--------------------+-------------------+ +| mceliece8192128 | mceliece8192128f | mceliece8192128pc | mceliece8192128pcf | 1.36 MB | ++------------------+-------------------+-------------------+--------------------+-------------------+ + +The instances with the suffix 'f' use a faster key generation algorithm that is more consistent in +runtime. The instances with the suffix 'pc' use plaintext confirmation, which is only specified in +the ISO document. The instances mceliece348864(f) and mceliece460896(f) are only defined in the +NIST round 4 submission. + eXtended Merkle Signature Scheme (XMSS) ---------------------------------------- diff --git a/doc/credits.rst b/doc/credits.rst index 5592a41bca9..4a1f7f40580 100644 --- a/doc/credits.rst +++ b/doc/credits.rst @@ -175,5 +175,11 @@ snail-mail address (S), and Bitcoin address (B). N: Fabian Albert E: fabian.albert@rohde-schwarz.com W: https://www.rohde-schwarz.com/cybersecurity - D: SPHINCS+ + D: SPHINCS+, HSS/LMS, Classic McEliece S: Bochum, Germany + + N: Amos Treiber + E: amos.treiber@rohde-schwarz.com + W: https://www.rohde-schwarz.com/cybersecurity + D: SPHINCS+, FrodoKEM, Classic McEliece + S: Cologne, Germany diff --git a/doc/dev_ref/oids.rst b/doc/dev_ref/oids.rst index 47683e9fc60..2397b7dafb2 100644 --- a/doc/dev_ref/oids.rst +++ b/doc/dev_ref/oids.rst @@ -84,6 +84,15 @@ Values currently assigned are:: SphincsPlus-haraka-256s-r3.1 OBJECT IDENTIFIER ::= { SphincsPlus-haraka 5 } SphincsPlus-haraka-256f-r3.1 OBJECT IDENTIFIER ::= { SphincsPlus-haraka 6 } + mceliece OBJECT IDENTIFIER ::= { publicKey 18 } + + mceliece6688128pc OBJECT IDENTIFIER ::= { mceliece 1 } + mceliece6688128pcf OBJECT IDENTIFIER ::= { mceliece 2 } + mceliece6960119pc OBJECT IDENTIFIER ::= { mceliece 3 } + mceliece6960119pcf OBJECT IDENTIFIER ::= { mceliece 4 } + mceliece8192128pc OBJECT IDENTIFIER ::= { mceliece 5 } + mceliece8192128pcf OBJECT IDENTIFIER ::= { mceliece 6 } + symmetricKey OBJECT IDENTIFIER ::= { randombit 3 } ocbModes OBJECT IDENTIFIER ::= { symmetricKey 2 } diff --git a/src/build-data/oids.txt b/src/build-data/oids.txt index b3146f4d5fb..a018f2b9e05 100644 --- a/src/build-data/oids.txt +++ b/src/build-data/oids.txt @@ -1,6 +1,6 @@ -# Regenerate with ./src/scripts/dev_tools/oids.py oids > src/lib/asn1/oid_maps.cpp -# AND ./src/scripts/dev_tools/oids.py dn_ub > src/lib/x509/x509_dn_ub.cpp -# (if you modified something under [dn] +# Regenerate with ./src/scripts/dev_tools/gen_oids.py oids > src/lib/asn1/oid_maps.cpp +# AND ./src/scripts/dev_tools/gen_oids.py dn_ub > src/lib/x509/x509_dn_ub.cpp +# (if you modified something under [dn]) # Public key types [pubkey] @@ -63,6 +63,26 @@ 1.3.6.1.4.1.25258.1.12.3.5 = SphincsPlus-haraka-256s-r3.1 1.3.6.1.4.1.25258.1.12.3.6 = SphincsPlus-haraka-256f-r3.1 +# Classic McEliece OID selection from IETF Hackathon/BouncyCastle for non PC instances +1.3.6.1.4.1.22554.5.1.1 = mceliece348864 +1.3.6.1.4.1.22554.5.1.2 = mceliece348864f +1.3.6.1.4.1.22554.5.1.3 = mceliece460896 +1.3.6.1.4.1.22554.5.1.4 = mceliece460896f +1.3.6.1.4.1.22554.5.1.5 = mceliece6688128 +1.3.6.1.4.1.22554.5.1.6 = mceliece6688128f +1.3.6.1.4.1.22554.5.1.7 = mceliece6960119 +1.3.6.1.4.1.22554.5.1.8 = mceliece6960119f +1.3.6.1.4.1.22554.5.1.9 = mceliece8192128 +1.3.6.1.4.1.22554.5.1.10 = mceliece8192128f + +# Classic McEliece PC OIDs are currently in Botan's private arc +1.3.6.1.4.1.25258.1.18.1 = mceliece6688128pc +1.3.6.1.4.1.25258.1.18.2 = mceliece6688128pcf +1.3.6.1.4.1.25258.1.18.3 = mceliece6960119pc +1.3.6.1.4.1.25258.1.18.4 = mceliece6960119pcf +1.3.6.1.4.1.25258.1.18.5 = mceliece8192128pc +1.3.6.1.4.1.25258.1.18.6 = mceliece8192128pcf + # XMSS 1.3.6.1.4.1.25258.1.5 = XMSS-draft6 1.3.6.1.4.1.25258.1.8 = XMSS-draft12 diff --git a/src/cli/speed.cpp b/src/cli/speed.cpp index 746adfada0c..aa903e19094 100644 --- a/src/cli/speed.cpp +++ b/src/cli/speed.cpp @@ -132,6 +132,10 @@ #include #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) + #include +#endif + #if defined(BOTAN_HAS_ECDSA) #include #endif @@ -624,6 +628,11 @@ class Speed final : public Command { bench_frodokem(provider, msec); } #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) + else if(algo == "ClassicMcEliece") { + bench_classic_mceliece(provider, msec); + } +#endif #if defined(BOTAN_HAS_SCRYPT) else if(algo == "scrypt") { bench_scrypt(provider, msec); @@ -2082,6 +2091,41 @@ class Speed final : public Command { } #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) + void bench_classic_mceliece(const std::string& provider, std::chrono::milliseconds msec) { + std::vector cmce_param_sets{ + Botan::Classic_McEliece_Parameter_Set::mceliece348864, + Botan::Classic_McEliece_Parameter_Set::mceliece348864f, + Botan::Classic_McEliece_Parameter_Set::mceliece460896, + Botan::Classic_McEliece_Parameter_Set::mceliece460896f, + Botan::Classic_McEliece_Parameter_Set::mceliece6688128, + Botan::Classic_McEliece_Parameter_Set::mceliece6688128f, + Botan::Classic_McEliece_Parameter_Set::mceliece6688128pc, + Botan::Classic_McEliece_Parameter_Set::mceliece6688128pcf, + Botan::Classic_McEliece_Parameter_Set::mceliece6960119, + Botan::Classic_McEliece_Parameter_Set::mceliece6960119f, + Botan::Classic_McEliece_Parameter_Set::mceliece6960119pc, + Botan::Classic_McEliece_Parameter_Set::mceliece6960119pcf, + Botan::Classic_McEliece_Parameter_Set::mceliece8192128, + Botan::Classic_McEliece_Parameter_Set::mceliece8192128f, + Botan::Classic_McEliece_Parameter_Set::mceliece8192128pc, + Botan::Classic_McEliece_Parameter_Set::mceliece8192128pcf, + }; + + for(auto cmce_set : cmce_param_sets) { + auto cmce_set_str = Botan::cmce_str_from_param_set(cmce_set); + + auto keygen_timer = make_timer(cmce_set_str, provider, "keygen"); + + auto key = keygen_timer->run([&] { return Botan::Classic_McEliece_PrivateKey(rng(), cmce_set); }); + + record_result(keygen_timer); + + bench_pk_kem(key, cmce_set_str, provider, "KDF2(SHA-256)", msec); + } + } +#endif + #if defined(BOTAN_HAS_XMSS_RFC8391) void bench_xmss(const std::string& provider, std::chrono::milliseconds msec) { /* diff --git a/src/lib/asn1/oid_maps.cpp b/src/lib/asn1/oid_maps.cpp index d879c2fe6f3..96289444e84 100644 --- a/src/lib/asn1/oid_maps.cpp +++ b/src/lib/asn1/oid_maps.cpp @@ -1,7 +1,7 @@ /* * OID maps * -* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2023-11-02 +* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2024-02-01 * * All manual edits to this file will be lost. Edit the script * then regenerate this source file. @@ -138,6 +138,16 @@ std::unordered_map OID_Map::load_oid2str_map() { {"1.3.36.3.3.2.8.1.1.9", "brainpool320r1"}, {"1.3.6.1.4.1.11591.15.1", "OpenPGP.Ed25519"}, {"1.3.6.1.4.1.11591.4.11", "Scrypt"}, + {"1.3.6.1.4.1.22554.5.1.1", "mceliece348864"}, + {"1.3.6.1.4.1.22554.5.1.10", "mceliece8192128f"}, + {"1.3.6.1.4.1.22554.5.1.2", "mceliece348864f"}, + {"1.3.6.1.4.1.22554.5.1.3", "mceliece460896"}, + {"1.3.6.1.4.1.22554.5.1.4", "mceliece460896f"}, + {"1.3.6.1.4.1.22554.5.1.5", "mceliece6688128"}, + {"1.3.6.1.4.1.22554.5.1.6", "mceliece6688128f"}, + {"1.3.6.1.4.1.22554.5.1.7", "mceliece6960119"}, + {"1.3.6.1.4.1.22554.5.1.8", "mceliece6960119f"}, + {"1.3.6.1.4.1.22554.5.1.9", "mceliece8192128"}, {"1.3.6.1.4.1.25258.1.10.1", "Dilithium-4x4-AES-r3"}, {"1.3.6.1.4.1.25258.1.10.2", "Dilithium-6x5-AES-r3"}, {"1.3.6.1.4.1.25258.1.10.3", "Dilithium-8x7-AES-r3"}, @@ -174,6 +184,12 @@ std::unordered_map OID_Map::load_oid2str_map() { {"1.3.6.1.4.1.25258.1.17.1", "eFrodoKEM-640-AES"}, {"1.3.6.1.4.1.25258.1.17.2", "eFrodoKEM-976-AES"}, {"1.3.6.1.4.1.25258.1.17.3", "eFrodoKEM-1344-AES"}, + {"1.3.6.1.4.1.25258.1.18.1", "mceliece6688128pc"}, + {"1.3.6.1.4.1.25258.1.18.2", "mceliece6688128pcf"}, + {"1.3.6.1.4.1.25258.1.18.3", "mceliece6960119pc"}, + {"1.3.6.1.4.1.25258.1.18.4", "mceliece6960119pcf"}, + {"1.3.6.1.4.1.25258.1.18.5", "mceliece8192128pc"}, + {"1.3.6.1.4.1.25258.1.18.6", "mceliece8192128pcf"}, {"1.3.6.1.4.1.25258.1.3", "McEliece"}, {"1.3.6.1.4.1.25258.1.5", "XMSS-draft6"}, {"1.3.6.1.4.1.25258.1.6.1", "GOST-34.10-2012-256/SHA-256"}, @@ -569,6 +585,22 @@ std::unordered_map OID_Map::load_str2oid_map() { {"gost_256B", OID({1, 2, 643, 7, 1, 2, 1, 1, 2})}, {"gost_512A", OID({1, 2, 643, 7, 1, 2, 1, 2, 1})}, {"gost_512B", OID({1, 2, 643, 7, 1, 2, 1, 2, 2})}, + {"mceliece348864", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 1})}, + {"mceliece348864f", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 2})}, + {"mceliece460896", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 3})}, + {"mceliece460896f", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 4})}, + {"mceliece6688128", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 5})}, + {"mceliece6688128f", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 6})}, + {"mceliece6688128pc", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 1})}, + {"mceliece6688128pcf", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 2})}, + {"mceliece6960119", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 7})}, + {"mceliece6960119f", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 8})}, + {"mceliece6960119pc", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 3})}, + {"mceliece6960119pcf", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 4})}, + {"mceliece8192128", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 9})}, + {"mceliece8192128f", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 10})}, + {"mceliece8192128pc", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 5})}, + {"mceliece8192128pcf", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 6})}, {"secp160k1", OID({1, 3, 132, 0, 9})}, {"secp160r1", OID({1, 3, 132, 0, 8})}, {"secp160r2", OID({1, 3, 132, 0, 30})}, diff --git a/src/lib/pubkey/classic_mceliece/cmce.cpp b/src/lib/pubkey/classic_mceliece/cmce.cpp new file mode 100644 index 00000000000..e67028161a5 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce.cpp @@ -0,0 +1,162 @@ +/* + * Classic McEliece Key Generation + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Botan { + +Classic_McEliece_PublicKey::Classic_McEliece_PublicKey(const AlgorithmIdentifier& alg_id, + std::span key_bits) : + Classic_McEliece_PublicKey(key_bits, cmce_param_set_from_oid(alg_id.oid())) {} + +Classic_McEliece_PublicKey::Classic_McEliece_PublicKey(std::span key_bits, + Classic_McEliece_Parameter_Set param_set) { + auto params = Classic_McEliece_Parameters::create(param_set); + BOTAN_ARG_CHECK(key_bits.size() == params.pk_size_bytes(), "Wrong public key length"); + m_public = std::make_shared( + params, Classic_McEliece_Matrix(params, {key_bits.begin(), key_bits.end()})); +} + +Classic_McEliece_PublicKey::Classic_McEliece_PublicKey(const Classic_McEliece_PublicKey& other) { + m_public = std::make_shared(*other.m_public); +} + +Classic_McEliece_PublicKey& Classic_McEliece_PublicKey::operator=(const Classic_McEliece_PublicKey& other) { + if(this != &other) { + m_public = std::make_shared(*other.m_public); + } + return *this; +} + +AlgorithmIdentifier Classic_McEliece_PublicKey::algorithm_identifier() const { + return AlgorithmIdentifier(object_identifier(), AlgorithmIdentifier::USE_EMPTY_PARAM); +} + +OID Classic_McEliece_PublicKey::object_identifier() const { + return m_public->params().object_identifier(); +} + +size_t Classic_McEliece_PublicKey::key_length() const { + return m_public->matrix().bytes().size(); +} + +size_t Classic_McEliece_PublicKey::estimated_strength() const { + return m_public->params().estimated_strength(); +} + +std::vector Classic_McEliece_PublicKey::public_key_bits() const { + return m_public->matrix().bytes(); +} + +bool Classic_McEliece_PublicKey::check_key(RandomNumberGenerator&, bool) const { + return true; +} + +std::unique_ptr Classic_McEliece_PublicKey::generate_another(RandomNumberGenerator& rng) const { + return std::make_unique(rng, m_public->params().parameter_set()); +} + +std::unique_ptr Classic_McEliece_PublicKey::create_kem_encryption_op( + std::string_view params, std::string_view provider) const { + if(provider.empty() || provider == "base") { + return std::make_unique(this->m_public, params); + } + throw Provider_Not_Found(algo_name(), provider); +} + +Classic_McEliece_PrivateKey::Classic_McEliece_PrivateKey(RandomNumberGenerator& rng, + Classic_McEliece_Parameter_Set param_set) { + auto params = Classic_McEliece_Parameters::create(param_set); + auto seed = rng.random_vec(params.seed_len()); + std::tie(m_private, m_public) = Classic_McEliece_KeyPair_Internal::generate(params, seed).decompose_to_pair(); +} + +Classic_McEliece_PrivateKey::Classic_McEliece_PrivateKey(std::span sk, + Classic_McEliece_Parameter_Set param_set) { + auto params = Classic_McEliece_Parameters::create(param_set); + auto sk_internal = Classic_McEliece_PrivateKeyInternal::from_bytes(params, sk); + m_private = std::make_shared(std::move(sk_internal)); + // This creates and loads the public key, which is very large. Potentially, we could only load + // it on demand (since one may use the private key only for decapsulation without needing the public key). + // TODO: consider building a load-on-demand mechanism for the public key + m_public = Classic_McEliece_PublicKeyInternal::create_from_private_key(*m_private); +} + +Classic_McEliece_PrivateKey::Classic_McEliece_PrivateKey(const AlgorithmIdentifier& alg_id, + std::span key_bits) : + Classic_McEliece_PrivateKey(key_bits, cmce_param_set_from_oid(alg_id.oid())) {} + +std::unique_ptr Classic_McEliece_PrivateKey::public_key() const { + return std::make_unique(*this); +} + +secure_vector Classic_McEliece_PrivateKey::private_key_bits() const { + return raw_private_key_bits(); +} + +secure_vector Classic_McEliece_PrivateKey::raw_private_key_bits() const { + return m_private->serialize(); +} + +bool Classic_McEliece_PrivateKey::check_key(RandomNumberGenerator&, bool) const { + auto prg = m_private->params().prg(m_private->delta()); + + const auto s = prg->output(m_private->params().n() / 8); + const auto ordering_bits = + prg->output((m_private->params().sigma2() * m_private->params().q()) / 8); + const auto irreducible_bits = + prg->output((m_private->params().sigma1() * m_private->params().t()) / 8); + + // Recomputing s as hash of delta + auto ret = + CT::Mask::expand(CT::is_equal(s.data(), m_private->s().data(), m_private->params().n() / 8)); + + // Checking weight of c + ret &= CT::Mask::is_equal(m_private->c().ct_hamming_weight(), 32); + + if(auto g = m_private->params().poly_ring().compute_minimal_polynomial(irreducible_bits)) { + for(size_t i = 0; i < g->degree() - 1; ++i) { + ret &= CT::Mask::expand(GF_Mask::is_equal(g->coef_at(i), m_private->g().coef_at(i)).elem_mask()); + } + } else { + ret = CT::Mask::cleared(); + } + + // Check alpha control bits + if(auto field_ord_from_seed = + Classic_McEliece_Field_Ordering::create_field_ordering(m_private->params(), ordering_bits)) { + field_ord_from_seed->permute_with_pivots(m_private->params(), m_private->c()); + ret &= CT::Mask::expand(field_ord_from_seed->ct_is_equal(m_private->field_ordering())); + } else { + ret = CT::Mask::cleared(); + } + + return ret.as_bool(); +} + +std::unique_ptr Classic_McEliece_PrivateKey::create_kem_decryption_op( + RandomNumberGenerator& rng, std::string_view params, std::string_view provider) const { + BOTAN_UNUSED(rng); + if(provider.empty() || provider == "base") { + return std::make_unique(this->m_private, params); + } + throw Provider_Not_Found(algo_name(), provider); +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce.h b/src/lib/pubkey/classic_mceliece/cmce.h new file mode 100644 index 00000000000..8fd160f55ee --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce.h @@ -0,0 +1,151 @@ +/* + * Classic McEliece Key Generation + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_H_ +#define BOTAN_CMCE_H_ + +#include + +#include + +namespace Botan { + +class Classic_McEliece_PublicKeyInternal; +class Classic_McEliece_PrivateKeyInternal; + +/** + * Classic McEliece is a Code-Based KEM. It is a round 4 candidate in NIST's PQC competition. + * It is endorsed by the German Federal Office for Information Security (BSI) for its conservative security + * assumptions and a corresponding draft for an ISO standard has been prepared. Both NIST and ISO parameter + * sets are implemented here. See https://classic.mceliece.org/ for the specifications and other details. + * + * Advantages of Classic McEliece: + * - Conservative post-quantum security assumptions + * - Very fast encapsulation + * - Fast decapsulation + * + * Disadvantages of Classic McEliece: + * - Very large public keys (0.26 MB - 1.36 MB) + * - Relatively slow key generation + * - Algorithm is complex and hard to implement side-channel resistant + */ +class BOTAN_PUBLIC_API(3, 4) Classic_McEliece_PublicKey : public virtual Public_Key { + public: + /** + * @brief Load a Classic McEliece public key from bytes. + * + * @param alg_id The algorithm identifier + * @param key_bits The public key bytes + */ + Classic_McEliece_PublicKey(const AlgorithmIdentifier& alg_id, std::span key_bits); + + /** + * @brief Load a Classic McEliece public key from bytes. + * + * @param key_bits The public key bytes + * @param param_set The parameter set + */ + Classic_McEliece_PublicKey(std::span key_bits, Classic_McEliece_Parameter_Set param_set); + + Classic_McEliece_PublicKey(const Classic_McEliece_PublicKey& other); + Classic_McEliece_PublicKey& operator=(const Classic_McEliece_PublicKey& other); + Classic_McEliece_PublicKey(Classic_McEliece_PublicKey&&) = default; + Classic_McEliece_PublicKey& operator=(Classic_McEliece_PublicKey&&) = default; + + ~Classic_McEliece_PublicKey() override = default; + + std::string algo_name() const override { return "ClassicMcEliece"; } + + AlgorithmIdentifier algorithm_identifier() const override; + + OID object_identifier() const override; + + size_t key_length() const override; + + size_t estimated_strength() const override; + + std::vector public_key_bits() const override; + + bool check_key(RandomNumberGenerator&, bool) const override; + + bool supports_operation(PublicKeyOperation op) const override { + return (op == PublicKeyOperation::KeyEncapsulation); + } + + std::unique_ptr generate_another(RandomNumberGenerator& rng) const final; + + std::unique_ptr create_kem_encryption_op(std::string_view params, + std::string_view provider) const override; + + protected: + Classic_McEliece_PublicKey() = default; + + protected: + std::shared_ptr + m_public; // NOLINT(misc-non-private-member-variables-in-classes) +}; + +BOTAN_DIAGNOSTIC_PUSH +BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE + +class BOTAN_PUBLIC_API(3, 4) Classic_McEliece_PrivateKey final : public virtual Classic_McEliece_PublicKey, + public virtual Private_Key { + public: + /** + * @brief Create a new Classic McEliece private key for a specified parameter set. + * + * @param rng A random number generator + * @param param_set The parameter set to use + */ + Classic_McEliece_PrivateKey(RandomNumberGenerator& rng, Classic_McEliece_Parameter_Set param_set); + + /** + * @brief Load a Classic McEliece private key from bytes. + * + * @param sk The private key bytes + * @param param_set The parameter set to use + */ + Classic_McEliece_PrivateKey(std::span sk, Classic_McEliece_Parameter_Set param_set); + + /** + * @brief Load a Classic McEliece private key from bytes. + * + * @param alg_id The algorithm identifier + * @param key_bits The private key bytes + */ + Classic_McEliece_PrivateKey(const AlgorithmIdentifier& alg_id, std::span key_bits); + + std::unique_ptr public_key() const override; + + secure_vector private_key_bits() const override; + + secure_vector raw_private_key_bits() const override; + + /** + * @brief Checks the private key for consistency with the first component delta, i.e., + * recomputes s as a hash of delta and checks equivalence with sk.s, checks the weight of c, + * and checks the control bits. It also recomputes beta based on delta and recomputes g based on beta, + * checking that g is equal to the value sk.s + * + * See NIST Impl. guide 6.3 Double-Checks on Private Keys. + */ + bool check_key(RandomNumberGenerator&, bool) const override; + + std::unique_ptr create_kem_decryption_op(RandomNumberGenerator& rng, + std::string_view params, + std::string_view provider) const override; + + private: + std::shared_ptr m_private; +}; + +BOTAN_DIAGNOSTIC_POP + +} // namespace Botan + +#endif // BOTAN_CMCE_GF_H_ diff --git a/src/lib/pubkey/classic_mceliece/cmce_decaps.cpp b/src/lib/pubkey/classic_mceliece/cmce_decaps.cpp new file mode 100644 index 00000000000..9133ee8f749 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_decaps.cpp @@ -0,0 +1,165 @@ +/* + * Classic McEliece Decapsulation + * Based on the public domain reference implementation by the designers + * (https://classic.mceliece.org/impl.html - released in Oct 2022 for NISTPQC-R4) + * + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#include + +namespace Botan { + +Classic_McEliece_Polynomial Classic_McEliece_Decryptor::compute_goppa_syndrome( + const Classic_McEliece_Parameters& params, + const Classic_McEliece_Minimal_Polynomial& goppa_poly, + const Classic_McEliece_Field_Ordering& ordering, + const secure_bitvector& code_word) const { + BOTAN_ASSERT(params.n() == code_word.size(), "Correct code word size"); + std::vector syndrome(2 * params.t(), params.gf(CmceGfElem(0))); + + auto alphas = ordering.alphas(params.n()); + + for(size_t i = 0; i < params.n(); ++i) { + auto g_alpha = goppa_poly(alphas[i]); + auto r = (g_alpha * g_alpha).inv(); + + auto c_mask = GF_Mask(CT::Mask::expand(code_word.at(i).as())); + + for(size_t j = 0; j < 2 * params.t(); ++j) { + syndrome[j] += c_mask.if_set_return(r); + r = r * alphas[i]; + } + } + + return Classic_McEliece_Polynomial(syndrome); +} + +Classic_McEliece_Polynomial Classic_McEliece_Decryptor::berlekamp_massey( + const Classic_McEliece_Parameters& params, const Classic_McEliece_Polynomial& syndrome) const { + // Represents coefficients of corresponding polynomials + std::vector big_c(params.t() + 1, params.gf(CmceGfElem(0))); + std::vector big_b(params.t() + 1, params.gf(CmceGfElem(0))); + + auto b = params.gf(CmceGfElem(1)); + + // Start with x^m for m=1, see pseudocode of https://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm + big_b.at(1) = CmceGfElem(1); + big_c.at(0) = CmceGfElem(1); + + for(size_t big_n = 0, big_l = 0; big_n < 2 * params.t(); ++big_n) { + auto d = params.gf(CmceGfElem(0)); + for(size_t i = 0; i <= std::min(big_n, params.t()); ++i) { + d += big_c.at(i) * syndrome.coef_at(big_n - i); + } + + // Pseudocode branch if (d == 0) + auto d_not_zero = GF_Mask::expand(d); + + // Pseudocode branch else if (2* L <= N) + auto adjust_big_c = GF_Mask(CT::Mask::is_lte(uint16_t(2 * big_l), uint16_t(big_n))); + adjust_big_c &= d_not_zero; + + auto big_t = big_c; // Copy + auto f = d / b; + + for(size_t i = 0; i <= params.t(); ++i) { + // Occurs for all other d!=0 branches in the pseudocode + big_c.at(i) += d_not_zero.if_set_return((f * big_b.at(i))); + } + + big_l = adjust_big_c.select(uint16_t((big_n + 1) - big_l), uint16_t(big_l)); + + for(size_t i = 0; i <= params.t(); ++i) { + big_b.at(i) = adjust_big_c.select(big_t.at(i), big_b.at(i)); + } + + b = adjust_big_c.select(d, b); + + // Rotate big_b one to the right (multiplies with x), replaces increments of m in pseudocode + std::rotate(big_b.rbegin(), big_b.rbegin() + 1, big_b.rend()); + } + + std::reverse(big_c.begin(), big_c.end()); + + return Classic_McEliece_Polynomial(big_c); +} + +std::pair, CmceErrorVector> Classic_McEliece_Decryptor::decode(CmceCodeWord big_c) const { + BOTAN_ASSERT(big_c.size() == m_key->params().m() * m_key->params().t(), "Correct ciphertext input size"); + big_c.resize(m_key->params().n()); + + const auto syndrome = + compute_goppa_syndrome(m_key->params(), m_key->g(), m_key->field_ordering(), big_c.as()); + const auto locator = berlekamp_massey(m_key->params(), syndrome); + + std::vector images; + const auto alphas = m_key->field_ordering().alphas(m_key->params().n()); + std::transform( + alphas.begin(), alphas.end(), std::back_inserter(images), [&](const auto& alpha) { return locator(alpha); }); + + // Obtain e and check whether wt(e) = t. locator(alpha_i) = 0 <=> error at position i + CmceErrorVector e; + e.get().reserve(m_key->params().n()); + auto decode_success = CT::Mask::set(); // Avoid bool to avoid possible compiler optimizations + for(const auto& image : images) { + e.push_back(GF_Mask::is_zero(image).as_bool()); + } + decode_success &= CT::Mask(CT::Mask::is_equal(e.ct_hamming_weight(), m_key->params().t())); + + // Check the error vector by checking H'C = H'e <=> H'(C + e) = 0; see guide for implementors Sec. 6.3 + const auto syndrome_from_e = compute_goppa_syndrome(m_key->params(), m_key->g(), m_key->field_ordering(), e.get()); + auto syndromes_are_eq = GF_Mask::set(); + for(size_t i = 0; i < syndrome.degree() - 1; ++i) { + syndromes_are_eq &= GF_Mask::is_equal(syndrome.coef_at(i), syndrome_from_e.coef_at(i)); + } + + decode_success &= syndromes_are_eq.elem_mask(); + + return {decode_success, std::move(e)}; +} + +void Classic_McEliece_Decryptor::raw_kem_decrypt(std::span out_shared_key, + std::span encapsulated_key) { + BOTAN_ARG_CHECK(out_shared_key.size() == m_key->params().hash_out_bytes(), "Invalid shared key output size"); + BOTAN_ARG_CHECK(encapsulated_key.size() == m_key->params().ciphertext_size(), "Invalid ciphertext size"); + + auto [ct, c1] = [&]() -> std::pair> { + if(m_key->params().is_pc()) { + BufferSlicer encaps_key_slicer(encapsulated_key); + auto c0_ret = encaps_key_slicer.take(m_key->params().encode_out_size()); + auto c1_ret = encaps_key_slicer.take(m_key->params().hash_out_bytes()); + BOTAN_ASSERT_NOMSG(encaps_key_slicer.empty()); + return {CmceCodeWord(secure_bitvector(c0_ret, m_key->params().m() * m_key->params().t())), c1_ret}; + } else { + return {CmceCodeWord(secure_bitvector(encapsulated_key, m_key->params().m() * m_key->params().t())), {}}; + } + }(); + + auto [decode_success_mask, maybe_e] = decode(ct); + + secure_vector e_bytes(m_key->s().size()); + decode_success_mask.select_n(e_bytes.data(), maybe_e.get().to_bytes().data(), m_key->s().data(), m_key->s().size()); + uint8_t b = decode_success_mask.select(1, 0); + + auto hash_func = m_key->params().hash_func(); + + if(m_key->params().is_pc()) { + hash_func->update(0x02); + hash_func->update(e_bytes); + const auto c1_p = hash_func->final_stdvec(); + const CT::Mask eq_mask = CT::is_equal(c1.data(), c1_p.data(), c1.size()); + eq_mask.select_n(e_bytes.data(), e_bytes.data(), m_key->s().data(), m_key->s().size()); + b = eq_mask.select(b, 0); + } + + hash_func->update(b); + hash_func->update(e_bytes); + hash_func->update(encapsulated_key); + hash_func->final(out_shared_key); +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_decaps.h b/src/lib/pubkey/classic_mceliece/cmce_decaps.h new file mode 100644 index 00000000000..41c57d4b025 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_decaps.h @@ -0,0 +1,85 @@ +/* + * Classic McEliece Decapsulation + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_DECAPS_H_ +#define BOTAN_CMCE_DECAPS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Botan { + +/** + * Classic McEliece Decapsulation Operation + */ +class BOTAN_TEST_API Classic_McEliece_Decryptor final : public PK_Ops::KEM_Decryption_with_KDF { + public: + /** + * @brief Constructs a Classic_McEliece_Decryptor object with the given private key. + * @param key The private key used for decryption. + */ + Classic_McEliece_Decryptor(std::shared_ptr key, std::string_view kdf) : + KEM_Decryption_with_KDF(kdf), m_key(std::move(key)) {} + + size_t raw_kem_shared_key_length() const override { return m_key->params().hash_out_bytes(); } + + size_t encapsulated_key_length() const override { return m_key->params().ciphertext_size(); } + + void raw_kem_decrypt(std::span out_shared_key, std::span encapsulated_key) override; + + private: + /** + * @brief Computes the syndrome of a code word. + * + * Corresponds to H' * code_word of the spec, where H' is the syndrome computation matrix used for the + * Berlekamp's method of decoding. See https://tungchou.github.io/papers/mcbits.pdf for more information. + * + * @param params The McEliece parameters. + * @param goppa_poly The Goppa polynomial. + * @param ordering The field ordering. + * @param code_word The code word. + * @return The syndrome S(x) of the code word. + */ + Classic_McEliece_Polynomial compute_goppa_syndrome(const Classic_McEliece_Parameters& params, + const Classic_McEliece_Minimal_Polynomial& goppa_poly, + const Classic_McEliece_Field_Ordering& ordering, + const secure_bitvector& code_word) const; + + /** + * @brief Applies the Berlekamp-Massey algorithm to compute the error locator polynomial given a syndrome. + * + * The error locator polynomial C can be used for decoding, as C(a_i) = 0 <=> error at position i. + * + * @param params The McEliece parameters. + * @param syndrome The syndrome polynomial of the code word. + * @return The error locator polynomial. + */ + Classic_McEliece_Polynomial berlekamp_massey(const Classic_McEliece_Parameters& params, + const Classic_McEliece_Polynomial& syndrome) const; + + /** + * @brief Decodes a code word using Berlekamp's method. + * + * @param big_c The code word. + * @return A pair containing the decoded message and the error pattern. + */ + std::pair, CmceErrorVector> decode(CmceCodeWord big_c) const; + + std::shared_ptr m_key; +}; + +} // namespace Botan + +#endif // BOTAN_CMCE_DECAPS_H_ diff --git a/src/lib/pubkey/classic_mceliece/cmce_encaps.cpp b/src/lib/pubkey/classic_mceliece/cmce_encaps.cpp new file mode 100644 index 00000000000..e1a5570f206 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_encaps.cpp @@ -0,0 +1,125 @@ +/* + * Classic McEliece Encapsulation + * Based on the public domain reference implementation by the designers + * (https://classic.mceliece.org/impl.html - released in Oct 2022 for NISTPQC-R4) + * + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ +#include + +#include + +namespace Botan { + +CmceCodeWord Classic_McEliece_Encryptor::encode(const Classic_McEliece_Parameters& params, + const CmceErrorVector& e, + const Classic_McEliece_Matrix& mat) const { + return mat.mul(params, e); +} + +std::optional Classic_McEliece_Encryptor::fixed_weight_vector_gen( + const Classic_McEliece_Parameters& params, RandomNumberGenerator& rng) const { + const auto rand = rng.random_vec((params.sigma1() / 8) * params.tau()); + uint16_t mask_m = (uint32_t(1) << params.m()) - 1; // Only take m least significant bits + secure_vector a_values; + a_values.reserve(params.tau()); + BufferSlicer rand_slicer(rand); + + // Steps 2 & 3: Create d_j from uniform random bits. The first t d_j entries + // in range {0,...,n-1} are defined as a_0,...,a_(t-1). ... + for(size_t j = 0; j < params.tau(); ++j) { + auto d = load_le(rand_slicer.take(params.sigma1() / 8).data(), 0); + // This is not CT, but neither is the reference implementation here. + // This side channel only leaks which random elements are selected and which are dropped, + // but no information about their content is leaked. + d &= mask_m; + if(d < params.n() && a_values.size() < params.t()) { + a_values.push_back(d); + } + } + if(a_values.size() < params.t()) { + // Step 3: ... If fewer than t of such elements exist restart + return std::nullopt; + } + + // Step 4: Restart if not all a_i are distinct + for(size_t i = 1; i < params.t(); ++i) { + for(size_t j = 0; j < i; ++j) { + if(a_values.at(i) == a_values.at(j)) { + return std::nullopt; + } + } + } + + secure_vector a_value_byte(params.t()); + secure_vector e_bytes(ceil_tobytes(params.n())); + + // Step 5: Set all bits of e at the positions of a_values + // Prepare the associated byte in e_bytes that is represented by each bit index in a_values + // if we e is represented as a byte vector + for(size_t j = 0; j < a_values.size(); ++j) { + a_value_byte[j] = 1 << (a_values[j] % 8); + } + + for(size_t i = 0; i < params.n() / 8; ++i) { + for(size_t j = 0; j < a_values.size(); ++j) { + // If the current byte is the one that is represented by the current bit index in a_values + // then set the bit in e_bytes (in-byte position prepared above) + auto mask = CT::Mask::is_equal(static_cast(i), static_cast(a_values[j] >> 3)); + e_bytes[i] |= mask.if_set_return(a_value_byte[j]); + } + } + + return CmceErrorVector(secure_bitvector(e_bytes, params.n())); +} + +void Classic_McEliece_Encryptor::raw_kem_encrypt(std::span out_encapsulated_key, + std::span out_shared_key, + RandomNumberGenerator& rng) { + BOTAN_ARG_CHECK(out_encapsulated_key.size() == m_key->params().ciphertext_size(), + "Incorrect encapsulated key output length"); + BOTAN_ARG_CHECK(out_shared_key.size() == m_key->params().hash_out_bytes(), "Incorrect shared key output length"); + + const auto& params = m_key->params(); + + // Call fixed_weight until it is successful to + // create a random error vector e of weight tau + const CmceErrorVector e = [&] { + // Emergency abort in case unexpected logical error to prevent endless loops + // Success probability: >24% per attempt (25% that elements are distinct * 96% enough elements are in range) + // => 203 attempts for 2^(-80) fail probability + constexpr size_t MAX_ATTEMPTS = 203; + for(size_t attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { + if(auto maybe_e = fixed_weight_vector_gen(params, rng)) { + return maybe_e.value(); + } + } + throw Internal_Error("Cannot created fixed weight vector. Is your RNG broken?"); + }(); + + auto hash_func = params.hash_func(); + + BufferStuffer big_c_stuf(out_encapsulated_key); + const auto e_bytes = e.get().to_bytes(); + // Compute and store ciphertext C/C_0 from spec + const auto big_c_0 = encode(params, e, m_key->matrix()); + big_c_0.to_bytes(big_c_stuf.next(ceil_tobytes(big_c_0.size()))); + if(params.is_pc()) { + // Compute and store ciphertext C_1 from spec + hash_func->update(0x02); + hash_func->update(e_bytes); + hash_func->final(big_c_stuf.next(hash_func->output_length())); + } + BOTAN_ASSERT_NOMSG(big_c_stuf.full()); + + // Compute K = Hash(1,e,C) from spec + hash_func->update(0x01); + hash_func->update(e_bytes); + hash_func->update(out_encapsulated_key); + hash_func->final(out_shared_key); +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_encaps.h b/src/lib/pubkey/classic_mceliece/cmce_encaps.h new file mode 100644 index 00000000000..ef7b18a679b --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_encaps.h @@ -0,0 +1,60 @@ +/* +* Classic McEliece Encapsulation +* (C) 2023 Jack Lloyd +* 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +**/ + +#ifndef BOTAN_CMCE_ENCAPS_H_ +#define BOTAN_CMCE_ENCAPS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Botan { + +/** + * Classic McEliece Encapsulation Operation + */ +class BOTAN_TEST_API Classic_McEliece_Encryptor final : public PK_Ops::KEM_Encryption_with_KDF { + public: + Classic_McEliece_Encryptor(std::shared_ptr key, std::string_view kdf) : + KEM_Encryption_with_KDF(kdf), m_key(std::move(key)) {} + + size_t raw_kem_shared_key_length() const override { return m_key->params().hash_out_bytes(); } + + size_t encapsulated_key_length() const override { return m_key->params().ciphertext_size(); } + + void raw_kem_encrypt(std::span out_encapsulated_key, + std::span out_shared_key, + RandomNumberGenerator& rng) override; + + private: + std::shared_ptr m_key; + + /** + * @brief Encodes an error vector by multiplying it with the Classic McEliece matrix. + */ + CmceCodeWord encode(const Classic_McEliece_Parameters& params, + const CmceErrorVector& e, + const Classic_McEliece_Matrix& mat) const; + + /** + * @brief Fixed-weight-vector generation algorithm according to ISO McEliece. + */ + std::optional fixed_weight_vector_gen(const Classic_McEliece_Parameters& params, + RandomNumberGenerator& rng) const; +}; + +} // namespace Botan + +#endif // BOTAN_CMCE_ENCAPS_H_ diff --git a/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp b/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp new file mode 100644 index 00000000000..dfa5f5c47e6 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp @@ -0,0 +1,326 @@ +/* + * Classic McEliece Field Ordering Generation + * Based on the public domain reference implementation by the designers + * (https://classic.mceliece.org/impl.html - released in Oct 2022 for NISTPQC-R4) + * + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace CMCE_CT { + +template + requires(sizeof(T1) <= 8 && sizeof(T2) <= 8) +void cond_swap_pair(CT::Mask cond_mask, std::pair& a, std::pair& b) { + cond_mask.conditional_swap(a.first, b.first); + cond_mask.conditional_swap(a.second, b.second); +} + +template +void compare_and_swap_pair(std::span> a, size_t i, size_t k, size_t l) { + static_assert(sizeof(T1) <= sizeof(uint64_t) && sizeof(T2) <= sizeof(uint64_t), + "Types T1 and T2 must be at most 64 bits wide"); + if((i & k) == 0) { // i and k do not depend on secret data + auto swap_required_mask = CT::Mask::is_lt(a[l].first, a[i].first); + cond_swap_pair(swap_required_mask, a[i], a[l]); + } else { + auto swap_required_mask = CT::Mask::is_gt(a[l].first, a[i].first); + cond_swap_pair(swap_required_mask, a[i], a[l]); + } +} + +// Sorts a vector of pairs after the first element +template +void bitonic_sort_pair(std::span> a) { + const size_t n = a.size(); + BOTAN_ARG_CHECK(is_power_of_2(n), "Input vector size must be a power of 2"); + + for(size_t k = 2; k <= n; k *= 2) { + for(size_t j = k / 2; j > 0; j /= 2) { + for(size_t i = 0; i < n; ++i) { + const size_t l = i ^ j; + if(l > i) { + compare_and_swap_pair(a, i, k, l); + } + } + } + } +} + +template +T min(const T& a, const T& b) { + auto mask = CT::Mask::is_lt(a, b); + return mask.select(a, b); +} + +} // namespace CMCE_CT + +namespace { +template +std::vector> zip(const secure_vector& vec_1, const secure_vector& vec_2) { + BOTAN_ARG_CHECK(vec_1.size() == vec_2.size(), "Vectors' dimensions do not match"); + std::vector> vec_zipped; + vec_zipped.reserve(vec_1.size()); + for(size_t i = 0; i < vec_1.size(); ++i) { + vec_zipped.push_back(std::make_pair(vec_1[i], vec_2[i])); + } + return vec_zipped; +} + +template +std::pair, secure_vector> unzip(const std::vector>& vec_zipped) { + std::pair, secure_vector> res; + + res.first.reserve(vec_zipped.size()); + res.second.reserve(vec_zipped.size()); + + for(const auto& [elem1, elem2] : vec_zipped) { + res.first.push_back(elem1); + res.second.push_back(elem2); + } + return res; +} + +/// @returns (vec[0],0), ..., (vec[n-1],n-1) +std::vector> enumerate(std::span vec) { + std::vector> enumerated; + + std::transform(vec.begin(), vec.end(), std::back_inserter(enumerated), [ctr = uint16_t(0)](uint32_t elem) mutable { + return std::make_pair(elem, ctr++); + }); + + return enumerated; +} + +/** +* @brief Create permutation pi as in (Section 8.2, Step 3). +*/ +CmcePermutation create_pi(secure_vector& a) { + auto a_pi_zipped = enumerate(a); + CMCE_CT::bitonic_sort_pair(std::span(a_pi_zipped)); + + CmcePermutation pi_sorted; + std::tie(a, pi_sorted.get()) = unzip(a_pi_zipped); + + return pi_sorted; +} + +/** +* @brief Create a GF element from pi as in (Section 8.2, Step 4). +* Corresponds to the reverse bits of pi. +*/ +Classic_McEliece_GF from_pi(CmcePermutationElement pi_elem, CmceGfMod modulus, size_t m) { + std::bitset<16> bits(pi_elem.get()); + std::bitset<16> reversed_bits; + + for(int i = 0; i < 16; ++i) { + reversed_bits[i] = bits[15 - i]; + } + + reversed_bits >>= (sizeof(uint16_t) * 8 - m); + + return Classic_McEliece_GF(CmceGfElem(static_cast(reversed_bits.to_ulong())), modulus); +} + +/** + * @brief Part of field ordering generation according to ISO 9.2.10 + */ +secure_vector composeinv(const secure_vector& c, const secure_vector& pi) { + auto pi_c_zipped = zip(pi, c); + CMCE_CT::bitonic_sort_pair(std::span(pi_c_zipped)); + // Extract c from the sorted vector + secure_vector c_sorted; + std::transform(pi_c_zipped.begin(), pi_c_zipped.end(), std::back_inserter(c_sorted), [](const auto& pair) { + return pair.second; + }); + + return c_sorted; +} + +// p,q = composeinv(p,q),composeinv(q,p) +void simultaneous_composeinv(secure_vector& p, secure_vector& q) { + auto p_new = composeinv(p, q); + q = composeinv(q, p); + p = std::move(p_new); +} + +/** + * @brief Generate control bits as in ISO 9.2.10. + * + * TODO: This function can be optimized + */ +secure_vector generate_control_bits_internal(const secure_vector& pi) { + const auto n = pi.size(); + BOTAN_ASSERT_NOMSG(is_power_of_2(n)); + const size_t m = ceil_log2(n); + + if(m == 1) { + return secure_vector({pi.at(0)}); + } + secure_vector p(n); + for(size_t x = 0; x < n; ++x) { + p.at(x) = pi.at(x ^ 1); + } + secure_vector q(n); + for(size_t x = 0; x < n; ++x) { + q.at(x) = pi.at(x) ^ 1; + } + + secure_vector range_n(n); + std::iota(range_n.begin(), range_n.end(), 0); + auto piinv = composeinv(range_n, pi); + + simultaneous_composeinv(p, q); + + secure_vector c(n); + for(uint16_t x = 0; static_cast(x) < n; ++x) { + c.at(x) = CMCE_CT::min(x, p.at(x)); + } + + simultaneous_composeinv(p, q); + + for(size_t i = 1; i < m - 1; ++i) { + auto cp = composeinv(c, q); + simultaneous_composeinv(p, q); + for(size_t x = 0; x < n; ++x) { + c.at(x) = CMCE_CT::min(c.at(x), cp.at(x)); + } + } + + secure_vector f(n / 2); + for(size_t j = 0; j < n / 2; ++j) { + f.at(j) = c.at(2 * j) % 2; + } + + secure_vector big_f(n); + for(uint16_t x = 0; size_t(x) < n; ++x) { + big_f.at(x) = x ^ f.at(x / 2); + } + + auto fpi = composeinv(big_f, piinv); + + secure_vector l(n / 2); + for(size_t k = 0; k < n / 2; ++k) { + l.at(k) = fpi.at(2 * k) % 2; + } + + secure_vector big_l(n); + for(uint16_t y = 0; size_t(y) < n; ++y) { + big_l.at(y) = y ^ l.at(y / 2); + } + + auto big_m = composeinv(fpi, big_l); + + secure_vector subm0(n / 2); + secure_vector subm1(n / 2); + for(size_t j = 0; j < n / 2; ++j) { + subm0.at(j) = big_m.at(2 * j) / 2; + subm1.at(j) = big_m.at(2 * j + 1) / 2; + } + + auto subz0 = generate_control_bits_internal(subm0); + auto subz1 = generate_control_bits_internal(subm1); + + secure_vector z(subz0.size() + subz1.size()); + for(size_t j = 0; j < subz0.size(); ++j) { + z.at(2 * j) = subz0.at(j); + z.at(2 * j + 1) = subz1.at(j); + } + + return concat(f, z, l); +} + +} // anonymous namespace + +std::optional Classic_McEliece_Field_Ordering::create_field_ordering( + const Classic_McEliece_Parameters& params, StrongSpan random_bits) { + BOTAN_ARG_CHECK(random_bits.size() == (params.sigma2() * params.q()) / 8, "Wrong random bits size"); + + secure_vector a; // contains a_0, a_1, ... + for(size_t i = 0; i < params.q(); ++i) { + a.push_back(load_le(random_bits.data(), i)); // Utilizes sigma2 = 32 + } + + auto pi = create_pi(a); + if(std::adjacent_find(a.begin(), a.end()) != a.end()) { + return std::nullopt; + } + + return Classic_McEliece_Field_Ordering(std::move(pi), params.poly_f()); +} + +std::vector Classic_McEliece_Field_Ordering::alphas(size_t n) const { + BOTAN_ASSERT_NOMSG(m_poly_f.get() != 0); + BOTAN_ASSERT_NOMSG(m_pi.size() >= n); + + std::vector n_alphas_vec; + + std::transform(m_pi.begin(), m_pi.begin() + n, std::back_inserter(n_alphas_vec), [this](uint16_t pi_elem) { + return from_pi(CmcePermutationElement(pi_elem), m_poly_f, Classic_McEliece_GF::log_q_from_mod(m_poly_f)); + }); + + return n_alphas_vec; +} + +secure_bitvector Classic_McEliece_Field_Ordering::alphas_control_bits() const { + // Each vector element contains one bit of the control bits + const auto control_bits_as_words = generate_control_bits_internal(m_pi.get()); + auto control_bits = secure_bitvector(control_bits_as_words.size()); + for(size_t i = 0; i < control_bits.size(); ++i) { + control_bits.at(i) = control_bits_as_words.at(i); + } + + return control_bits; +} + +// Based on the Python code "permutation(c)" from Bernstein +// "Verified fast formulas for control bits for permutation networks" +Classic_McEliece_Field_Ordering Classic_McEliece_Field_Ordering::create_from_control_bits( + const Classic_McEliece_Parameters& params, const secure_bitvector& control_bits) { + BOTAN_ASSERT_NOMSG(control_bits.size() == (2 * params.m() - 1) << (params.m() - 1)); + const uint16_t n = uint16_t(1) << params.m(); + CmcePermutation pi(n); + std::iota(pi.begin(), pi.end(), 0); + for(size_t i = 0; i < 2 * params.m() - 1; ++i) { + const size_t gap = size_t(1) << std::min(i, 2 * params.m() - 2 - i); + for(size_t j = 0; j < size_t(n / 2); ++j) { + const size_t pos = (j % gap) + 2 * gap * (j / gap); + auto mask = CT::Mask::expand(control_bits[i * n / 2 + j].as()); + mask.conditional_swap(pi[pos], pi[pos + gap]); + } + } + + return Classic_McEliece_Field_Ordering(std::move(pi), params.poly_f()); +} + +void Classic_McEliece_Field_Ordering::permute_with_pivots(const Classic_McEliece_Parameters& params, + const CmceColumnSelection& pivots) { + auto col_offset = params.pk_no_rows() - Classic_McEliece_Parameters::mu(); + + for(size_t p_idx = 1; p_idx <= Classic_McEliece_Parameters::mu(); ++p_idx) { + size_t p_counter = 0; + for(size_t col = 0; col < Classic_McEliece_Parameters::nu(); ++col) { + auto mask_is_pivot_set = CT::Mask::expand(pivots.at(col).as()); + p_counter += CT::Mask::expand(pivots.at(col).as()).if_set_return(1); + auto mask_is_current_pivot = CT::Mask::is_equal(p_idx, p_counter); + (mask_is_pivot_set & mask_is_current_pivot) + .conditional_swap(m_pi.get().at(col_offset + col), m_pi.get().at(col_offset + p_idx - 1)); + } + } +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_field_ordering.h b/src/lib/pubkey/classic_mceliece/cmce_field_ordering.h new file mode 100644 index 00000000000..c5b366e2628 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_field_ordering.h @@ -0,0 +1,112 @@ +/* + * Classic McEliece Field Ordering Generation + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_FIELD_ORDERING_H_ +#define BOTAN_CMCE_FIELD_ORDERING_H_ + +#include +#include + +#include + +namespace Botan { + +/** + * @brief Represents a field ordering for the Classic McEliece cryptosystem. + * + * Field ordering corresponds to the permutation pi defining the alpha sequence in + * the Classic McEliece specification (see Classic McEliece ISO Sec. 8.2.). + */ +class BOTAN_TEST_API Classic_McEliece_Field_Ordering { + public: + /** + * @brief Creates a field ordering from a random bit sequence. Corresponds to + * the algorithm described in Classic McEliece ISO Sec. 8.2. + * + * @param params The McEliece parameters. + * @param random_bits The random bit sequence. + * @return The field ordering. + */ + static std::optional create_field_ordering( + const Classic_McEliece_Parameters& params, StrongSpan random_bits); + + /** + * @brief Create the field ordering from the control bits of a benes network. + * + * @param params The McEliece parameters. + * @param control_bits The control bits of the benes network. + * @return The field ordering. + */ + static Classic_McEliece_Field_Ordering create_from_control_bits(const Classic_McEliece_Parameters& params, + const secure_bitvector& control_bits); + + /** + * @brief Returns the field ordering as a vector of all alphas from alpha_0 to alpha_{n-1}. + * + * @param n The number of alphas to return. + * @return the vector of n alphas. + */ + std::vector alphas(size_t n) const; + + /** + * @brief Generates the control bits of the benes network corresponding to the field ordering. + * + * @return the control bits. + */ + secure_bitvector alphas_control_bits() const; + + /** + * @brief The pi values representing the field ordering. + * + * @return pi values. + */ + CmcePermutation& pi_ref() { return m_pi; } + + /** + * @brief The pi values representing the field ordering. + * + * @return pi values. + */ + const CmcePermutation& pi_ref() const { return m_pi; } + + /** + * @brief Constant time comparison of two field orderings. + * + * @param other The other field ordering. + * @return Mask of equality value + */ + CT::Mask ct_is_equal(const Classic_McEliece_Field_Ordering& other) const { + BOTAN_ARG_CHECK(other.pi_ref().size() == pi_ref().size(), "Field orderings must have the same size"); + return CT::is_equal(pi_ref().data(), other.pi_ref().data(), pi_ref().size()); + } + + /** + * @brief Permute the field ordering with the given pivots. + * + * For example: If the pivot vector is 10101, the first, third and fifth element of the field ordering + * are permuted to positions 0, 1 and 2, respectively. The remaining elements are put at the end. + * + * The permutation is done for the elements from position m*t - mu,..., m*t + mu (excl.). + * This function implements Classic McEliece ISO Sec. 7.2.3 Steps 4-5. + * + * @param params The McEliece parameters. + * @param pivots The pivot vector. + */ + void permute_with_pivots(const Classic_McEliece_Parameters& params, const CmceColumnSelection& pivots); + + private: + Classic_McEliece_Field_Ordering(CmcePermutation pi, CmceGfMod poly_f) : m_pi(std::move(pi)), m_poly_f(poly_f) {} + + private: + CmcePermutation m_pi; + CmceGfMod m_poly_f; +}; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/classic_mceliece/cmce_gf.cpp b/src/lib/pubkey/classic_mceliece/cmce_gf.cpp new file mode 100644 index 00000000000..508233df3a7 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_gf.cpp @@ -0,0 +1,92 @@ +/* +* Classic McEliece GF arithmetic +* Based on the public domain reference implementation by the designers +* (https://classic.mceliece.org/impl.html - released in Oct 2022 for NISTPQC-R4) +* +* (C) 2023 Jack Lloyd +* 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +**/ + +#include + +namespace Botan { + +namespace { +inline CmceGfElem internal_reduce(uint32_t x, CmceGfMod mod) { + // Optimization for the specific moduli used in Classic McEliece + // Taken from the reference implementation + if(mod == 0b0010000000011011) { + uint32_t t = x & 0x1FF0000; + x ^= (t >> 9) ^ (t >> 10) ^ (t >> 12) ^ (t >> 13); + + t = x & 0x000E000; + x ^= (t >> 9) ^ (t >> 10) ^ (t >> 12) ^ (t >> 13); + + return CmceGfElem(x & 0x1fff); + } else if(mod == 0b0001000000001001) { + uint32_t t = x & 0x7FC000; + x ^= t >> 9; + x ^= t >> 12; + + t = x & 0x3000; + x ^= t >> 9; + x ^= t >> 12; + + x &= 0xfff; + + return CmceGfElem(static_cast(x & 0xfff)); + } else { + // TODO: Only for test instances. Remove on final PR + size_t m = Classic_McEliece_GF::log_q_from_mod(mod); + + for(int i = static_cast(m) - 2; i >= 0; --i) { + x ^= CT::Mask::expand((uint32_t(1) << (i + m)) & x) + .if_set_return(static_cast(mod.get()) << i); + } + + return CmceGfElem(static_cast(x)); + } +} + +} // namespace + +Classic_McEliece_GF Classic_McEliece_GF::operator*(Classic_McEliece_GF other) const { + BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus); + + uint32_t a = m_elem.get(); + uint32_t b = other.m_elem.get(); + + uint32_t acc = a * (b & 1); + + for(size_t i = 1; i < log_q(); i++) { + acc ^= (a * (b & (1 << i))); + } + + return Classic_McEliece_GF(internal_reduce(acc, m_modulus), m_modulus); +} + +Classic_McEliece_GF Classic_McEliece_GF::inv() const { + // Compute the inverse using fermat's little theorem: a^(q-1) = 1 => a^(q-2) = a^-1 + + // exponent = (q-2). This is public information, therefore the workflow is constant time. + size_t exponent = (size_t(1) << log_q()) - 2; + Classic_McEliece_GF base = *this; + + // Compute base^exponent using the square-and-multiply algorithm + Classic_McEliece_GF result = {CmceGfElem(1), m_modulus}; + while(exponent > 0) { + if(exponent % 2 == 1) { + // multiply + result = (result * base); + } + // square + base = base.square(); + exponent /= 2; + } + + return result; +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_gf.h b/src/lib/pubkey/classic_mceliece/cmce_gf.h new file mode 100644 index 00000000000..722221d8b0e --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_gf.h @@ -0,0 +1,207 @@ +/* + * Classic McEliece GF arithmetic + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_GF_H_ +#define BOTAN_CMCE_GF_H_ + +#include +#include +#include +#include +#include + +namespace Botan { + +/** + * @brief Represents an element of the finite field GF(q) for q = 2^m. + * + * This class implements the finite field GF(q) for q = 2^m via the irreducible + * polynomial f(z) of degree m. The elements of GF(q) are represented as polynomials + * of degree m-1 with coefficients in GF(2). Each element and the modulus is + * represented by a uint16_t, where the i-th least significant bit corresponds to + * the coefficient of z^i. For example, the element (z^3 + z^2 + 1) is represented + * by the uint16_t 0b1101. + */ +class BOTAN_TEST_API Classic_McEliece_GF { + public: + /** + * @brief Creates an element of GF(q) from a uint16_t. + * + * Each element and the modulus is represented by a uint16_t, where the i-th least significant bit + * corresponds to the coefficient of z^i. + * + * @param elem The element as a uint16_t. Must be less than 2^m. + * @param modulus The modulus of GF(q). + */ + Classic_McEliece_GF(CmceGfElem elem, CmceGfMod modulus) : m_elem(elem), m_modulus(modulus) { + BOTAN_DEBUG_ASSERT(elem <= (size_t(1) << log_q()) - 1); + } + + /** + * @brief Get m. + * + * For a given irreducible polynomial @p modulus f(z) representing the modulus of a finite field GF(q) = GF(2^m), + * get the degree log_q of f(z) which corresponds to m. + * + * @param modulus The modulus of GF(q). + * @return size_t The degree log_q of the modulus (m for GF(2^m)). + */ + static size_t log_q_from_mod(CmceGfMod modulus) { return floor_log2(modulus.get()); } + + /** + * @brief Get m, the degree of the element's modulus. + * + * @return size_t The degree log_q of the modulus (m for GF(2^m)). + */ + size_t log_q() const { return log_q_from_mod(m_modulus); } + + /** + * @brief Get the GF(q) element as a GF_Elem. + * + * @return the element as a GF_Elem. + */ + CmceGfElem elem() const { return m_elem; } + + /** + * @brief Get the modulus f(z) of GF(q) as a GF_Mod. + * + * @return the modulus as a GF_Mod. + */ + CmceGfMod modulus() const { return m_modulus; } + + /** + * @brief Change the element to @p elem. + */ + Classic_McEliece_GF& operator=(const CmceGfElem elem) { + m_elem = elem & CmceGfElem((size_t(1) << log_q()) - 1); + return *this; + } + + /** + * @brief Divide the element by @p other in GF(q). Constant time. + */ + Classic_McEliece_GF operator/(Classic_McEliece_GF other) const { + BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus); + return *this * other.inv(); + } + + /** + * @brief Add @p other to the element. Constant time. + */ + Classic_McEliece_GF operator+(Classic_McEliece_GF other) const { + BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus); + return Classic_McEliece_GF(m_elem ^ other.m_elem, m_modulus); + } + + /** + * @brief Add @p other to the element. Constant time. + */ + Classic_McEliece_GF& operator+=(Classic_McEliece_GF other) { + BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus); + m_elem ^= other.m_elem; + return *this; + } + + /** + * @brief XOR assign a GF_Elem to the element of this. Constant time. + */ + Classic_McEliece_GF& operator^=(CmceGfElem other) { + m_elem ^= other; + return *this; + } + + /** + * @brief Multiply the element by @p other in GF(q). Constant time. + */ + Classic_McEliece_GF& operator*=(Classic_McEliece_GF other) { + BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus); + *this = *this * other; + return *this; + } + + /** + * @brief Multiply the element by @p other in GF(q). Constant time. + */ + Classic_McEliece_GF operator*(Classic_McEliece_GF other) const; + + /** + * @brief Check if the element is equal to @p other. Modulus is ignored. + */ + bool operator==(Classic_McEliece_GF other) const { return elem() == other.elem(); } + + /** + * @brief Square the element. Constant time. + */ + Classic_McEliece_GF square() const { return (*this) * (*this); } + + /** + * @brief Invert the element. Constant time. + */ + Classic_McEliece_GF inv() const; + + /** + * @brief Check if the element is zero. + */ + bool is_zero() const { return elem() == 0; } + + private: + CmceGfElem m_elem; + + CmceGfMod m_modulus; +}; + +/** + * @brief Constant time mask wrapper for GF(q) elements. + */ +class BOTAN_TEST_API GF_Mask final { + public: + static GF_Mask expand(Classic_McEliece_GF v) { return GF_Mask(CT::Mask::expand(v.elem().get())); } + + static GF_Mask is_zero(Classic_McEliece_GF v) { return GF_Mask(CT::Mask::is_zero(v.elem().get())); } + + static GF_Mask is_lte(Classic_McEliece_GF a, Classic_McEliece_GF b) { + return GF_Mask(CT::Mask::is_lte(a.elem().get(), b.elem().get())); + } + + static GF_Mask is_equal(Classic_McEliece_GF a, Classic_McEliece_GF b) { + return GF_Mask(CT::Mask::is_equal(a.elem().get(), b.elem().get())); + } + + static GF_Mask set() { return GF_Mask(CT::Mask::set()); } + + GF_Mask(CT::Mask underlying_mask) : m_mask(underlying_mask) {} + + Classic_McEliece_GF if_set_return(const Classic_McEliece_GF x) const { + return Classic_McEliece_GF(CmceGfElem(m_mask.if_set_return(x.elem().get())), x.modulus()); + } + + Classic_McEliece_GF select(const Classic_McEliece_GF x, const Classic_McEliece_GF y) const { + return Classic_McEliece_GF(CmceGfElem(m_mask.select(x.elem().get(), y.elem().get())), x.modulus()); + } + + Classic_McEliece_GF select(const Classic_McEliece_GF x, CmceGfElem y) const { + return Classic_McEliece_GF(CmceGfElem(m_mask.select(x.elem().get(), y.get())), x.modulus()); + } + + uint16_t select(uint16_t x, uint16_t y) const { return m_mask.select(x, y); } + + GF_Mask& operator&=(const GF_Mask& o) { + m_mask &= o.m_mask; + return (*this); + } + + bool as_bool() const { return m_mask.as_bool(); } + + CT::Mask& elem_mask() { return m_mask; } + + private: + CT::Mask m_mask; +}; + +} // namespace Botan +#endif diff --git a/src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp b/src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp new file mode 100644 index 00000000000..29b08b9f4f3 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp @@ -0,0 +1,133 @@ +/* + * Classic McEliece key generation with Internal Private and Public Key classes + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#include + +namespace Botan { + +namespace { + +/** + * @brief Try to generate a Classic McEliece keypair for a given seed. + * + * @param[out] out_next_seed The next seed to use for key generation, if this iteration fails + * @param params Classic McEliece parameters + * @param seed The seed to used for this key generation iteration + * @return a keypair on success, std::nullopt otherwise + */ +std::optional try_generate_keypair(std::span out_next_seed, + const Classic_McEliece_Parameters& params, + CmceKeyGenSeed seed) { + BOTAN_ASSERT_EQUAL(seed.size(), 32, "Valid seed length"); + BOTAN_ASSERT_EQUAL(out_next_seed.size(), 32, "Valid output seed length"); + + auto big_e_xof = params.prg(seed); + + auto s = big_e_xof->output(params.n() / 8); + auto ordering_bits = big_e_xof->output((params.sigma2() * params.q()) / 8); + auto irreducible_bits = big_e_xof->output((params.sigma1() * params.t()) / 8); + big_e_xof->output(out_next_seed); + + // Field-ordering generation - Classic McEliece ISO 8.2 + auto field_ordering = Classic_McEliece_Field_Ordering::create_field_ordering(params, ordering_bits); + if(!field_ordering) { + return std::nullopt; + } + + // Irreducible-polynomial generation - Classic McEliece ISO 8.1 + auto g = params.poly_ring().compute_minimal_polynomial(irreducible_bits); + if(!g) { + return std::nullopt; + } + + // Matrix generation for Goppa codes - Classic McEliece ISO 7.2 + auto pk_matrix_and_pivots = + Classic_McEliece_Matrix::create_matrix_and_apply_pivots(params, field_ordering.value(), g.value()); + if(!pk_matrix_and_pivots) { + return std::nullopt; + } + auto& [pk_matrix, pivots] = pk_matrix_and_pivots.value(); + + // Key generation was successful - Create and return keys + return Classic_McEliece_KeyPair_Internal{ + .private_key = std::make_shared( + params, std::move(seed), pivots, std::move(g.value()), std::move(field_ordering.value()), std::move(s)), + .public_key = std::make_shared(params, std::move(pk_matrix))}; +} + +} // namespace + +Classic_McEliece_PrivateKeyInternal Classic_McEliece_PrivateKeyInternal::from_bytes( + const Classic_McEliece_Parameters& params, std::span sk_bytes) { + BOTAN_ASSERT(sk_bytes.size() == params.sk_size_bytes(), "Valid private key size"); + BufferSlicer sk_slicer(sk_bytes); + + auto delta = sk_slicer.copy(params.seed_len()); + auto c = CmceColumnSelection(sk_slicer.take(params.sk_c_bytes())); + auto g = Classic_McEliece_Minimal_Polynomial::from_bytes(sk_slicer.take(params.sk_poly_g_bytes()), params.poly_f()); + auto field_ordering = Classic_McEliece_Field_Ordering::create_from_control_bits( + params, secure_bitvector(sk_slicer.take(params.sk_alpha_control_bytes()))); + auto s = sk_slicer.copy(params.sk_s_bytes()); + BOTAN_ASSERT_NOMSG(sk_slicer.empty()); + + return Classic_McEliece_PrivateKeyInternal( + params, std::move(delta), std::move(c), std::move(g), std::move(field_ordering), std::move(s)); +} + +secure_vector Classic_McEliece_PrivateKeyInternal::serialize() const { + auto control_bits = m_field_ordering.alphas_control_bits(); + + /* NIST Impl. guide 6.1 Control-Bit Gen: + * As low-cost protection against faults in the control-bit computation, implementors are advised + * to check after the computation that applying the Benes network produces pi, and to + * restart key generation if this test fails; applying the Benes network is very fast. + * + * Here, we just assert that applying the Benes network produces pi. + */ + BOTAN_ASSERT(Classic_McEliece_Field_Ordering::create_from_control_bits(m_params, control_bits) + .ct_is_equal(m_field_ordering) + .as_bool(), + "Control Bit Computation Check"); + + return concat(m_delta.get(), m_c.get().to_bytes(), m_g.serialize(), control_bits.to_bytes(), m_s); +} + +std::shared_ptr Classic_McEliece_PublicKeyInternal::create_from_private_key( + const Classic_McEliece_PrivateKeyInternal& sk) { + auto pk_matrix_and_pivot = Classic_McEliece_Matrix::create_matrix(sk.params(), sk.field_ordering(), sk.g()); + if(!pk_matrix_and_pivot.has_value()) { + throw Decoding_Error("Cannot create public key from private key. Private key is invalid."); + } + auto& [pk_matrix, pivot] = pk_matrix_and_pivot.value(); + if(!pivot.subvector(0, pivot.size() / 2).all() || !pivot.subvector(pivot.size() / 2).none()) { + // There should not be a pivot other than 0xff ff ff ff 00 00 00 00. Otherwise + // the gauss algorithm failed effectively. + throw Decoding_Error("Cannot create public key from private key. Private key is invalid."); + } + + auto pk = std::make_shared(sk.params(), std::move(pk_matrix)); + + return pk; +} + +Classic_McEliece_KeyPair_Internal Classic_McEliece_KeyPair_Internal::generate(const Classic_McEliece_Parameters& params, + StrongSpan seed) { + BOTAN_ASSERT_EQUAL(seed.size(), params.seed_len(), "Valid seed length"); + + CmceKeyGenSeed next_seed(seed.size()); + CmceKeyGenSeed current_seed(seed.begin(), seed.end()); + + while(true) { + if(auto keypair = try_generate_keypair(next_seed, params, std::move(current_seed))) { + return keypair.value(); + } + current_seed = next_seed; + } +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_keys_internal.h b/src/lib/pubkey/classic_mceliece/cmce_keys_internal.h new file mode 100644 index 00000000000..a4fc84d3b53 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_keys_internal.h @@ -0,0 +1,196 @@ +/* + * Classic McEliece key generation with Internal Private and Public Key classes + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_KEYS_INTERNAL_H_ +#define BOTAN_CMCE_KEYS_INTERNAL_H_ + +#include +#include +#include +#include +#include + +namespace Botan { + +class Classic_McEliece_PrivateKeyInternal; + +/** + * @brief Representation of a Classic McEliece public key. + * + * This class represents a Classic McEliece public key. It is used internally by the Classic McEliece + * public key class and contains the following data: + * - The Classic McEliece parameters + * - The public key matrix + */ +class BOTAN_TEST_API Classic_McEliece_PublicKeyInternal { + public: + /** + * @brief Construct a Classic McEliece public key. + * + * @param params The Classic McEliece parameters + * @param matrix The public key matrix + */ + Classic_McEliece_PublicKeyInternal(const Classic_McEliece_Parameters& params, Classic_McEliece_Matrix matrix) : + m_params(params), m_matrix(std::move(matrix)) { + BOTAN_ASSERT_NOMSG(m_matrix.bytes().size() == m_params.pk_size_bytes()); + } + + /** + * @brief Create a Classic McEliece public key from a private key. + * + * Create the matrix from the private key values. Expects that the private key is valid, i.e. + * the matrix creation works. + * + * @param sk The private key + * @return The public key as a shared pointer + */ + static std::shared_ptr create_from_private_key( + const Classic_McEliece_PrivateKeyInternal& sk); + + /** + * @brief Serializes the Classic McEliece public key as defined in Classic McEliece ISO Section 9.2.7. + */ + std::vector serialize() const { return m_matrix.bytes(); } + + /** + * @brief The Classic McEliece matrix. + */ + const Classic_McEliece_Matrix& matrix() const { return m_matrix; } + + /** + * @brief The Classic McEliece parameters. + */ + const Classic_McEliece_Parameters& params() const { return m_params; } + + private: + Classic_McEliece_Parameters m_params; + Classic_McEliece_Matrix m_matrix; +}; + +/** + * @brief Representation of a Classic McEliece private key. + * + * This class represents a Classic McEliece private key. It is used internally by the Classic McEliece + * private key class and contains the following data (see Classic McEliece ISO Section 9.2.12): + * - The Classic McEliece parameters + * - The seed delta + * - The column selection pivot vector c + * - The minimal polynomial g + * - The field ordering alpha + * - The seed s for implicit rejection + */ +class BOTAN_TEST_API Classic_McEliece_PrivateKeyInternal { + public: + /** + * @brief Construct a Classic McEliece private key. + * + * @param params The Classic McEliece parameters + * @param delta The seed delta + * @param c The column selection pivot vector c + * @param g The minimal polynomial g + * @param alpha The field ordering alpha + * @param s The seed s for implicit rejection + */ + Classic_McEliece_PrivateKeyInternal(const Classic_McEliece_Parameters& params, + CmceKeyGenSeed delta, + CmceColumnSelection c, + Classic_McEliece_Minimal_Polynomial g, + Classic_McEliece_Field_Ordering alpha, + CmceRejectionSeed s) : + m_params(params), + m_delta(std::move(delta)), + m_c(std::move(c)), + m_g(std::move(g)), + m_field_ordering(std::move(alpha)), + m_s(std::move(s)) {} + + /** + * @brief Parses a Classic McEliece private key from a byte sequence. + * + * It also creates the field ordering from the control bits in @p sk_bytes. + * + * @param params The Classic McEliece parameters + * @param sk_bytes The secret key byte sequence + * @return the Classic McEliece private key + */ + static Classic_McEliece_PrivateKeyInternal from_bytes(const Classic_McEliece_Parameters& params, + std::span sk_bytes); + + /** + * @brief Serializes the Classic McEliece private key as defined in Classic McEliece ISO Section 9.2.12. + * + * @return the serialized Classic McEliece private key + */ + secure_vector serialize() const; + + /** + * @brief The seed delta that was used to create the private key. + */ + const CmceKeyGenSeed& delta() const { return m_delta; } + + /** + * @brief The column selection pivot vector c as defined in Classic McEliece ISO Section 9.2.11. + */ + const CmceColumnSelection& c() const { return m_c; } + + /** + * @brief The minimal polynomial g. + */ + const Classic_McEliece_Minimal_Polynomial& g() const { return m_g; } + + /** + * @brief The field ordering alpha. + */ + const Classic_McEliece_Field_Ordering& field_ordering() const { return m_field_ordering; } + + /** + * @brief The seed s for implicit rejection on decryption failure. + */ + const CmceRejectionSeed& s() const { return m_s; } + + /** + * @brief The Classic McEliece parameters. + */ + const Classic_McEliece_Parameters& params() const { return m_params; } + + private: + Classic_McEliece_Parameters m_params; + CmceKeyGenSeed m_delta; + CmceColumnSelection m_c; + Classic_McEliece_Minimal_Polynomial m_g; + Classic_McEliece_Field_Ordering m_field_ordering; + CmceRejectionSeed m_s; +}; + +/** + * @brief Representation of a Classic McEliece key pair. + */ +struct BOTAN_TEST_API Classic_McEliece_KeyPair_Internal { + std::shared_ptr private_key; + std::shared_ptr public_key; + + /** + * @brief Generate a Classic McEliece key pair using the algorithm described + * in Classic McEliece ISO Section 8.3 + */ + static Classic_McEliece_KeyPair_Internal generate(const Classic_McEliece_Parameters& params, + StrongSpan seed); + + /** + * @brief Decompose the key pair into a pair of shared pointers to the private and public key. + */ + std::pair, + std::shared_ptr> + decompose_to_pair() && { + return {std::move(private_key), std::move(public_key)}; + } +}; + +} // namespace Botan + +#endif // BOTAN_CMCE_KEYS_INTERNAL_H_ diff --git a/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp b/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp new file mode 100644 index 00000000000..f6f826f9c03 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp @@ -0,0 +1,259 @@ +/* + * Classic McEliece Matrix Logic + * Based on the public domain reference implementation by the designers + * (https://classic.mceliece.org/impl.html - released in Oct 2022 for NISTPQC-R4) + * + * + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#include + +#include + +namespace Botan { + +namespace { + +// Strong types for matrix used internally by Classic_McEliece_Matrix +using CmceMatrixRow = Strong; +using CmceMatrix = Strong, struct CmceMatrix_>; + +} // Anonymous namespace + +namespace { +size_t count_lsb_zeros(const secure_bitvector& n) { + size_t res = 0; + auto found_only_zeros = Botan::CT::Mask::set(); + for(const auto& bit : n) { + auto bit_set_mask = Botan::CT::Mask::expand(bit.as()); + found_only_zeros &= ~bit_set_mask; + res += found_only_zeros.if_set_return(1); + } + + return res; +} + +CmceMatrix init_matrix_with_alphas(const Classic_McEliece_Parameters& params, + const Classic_McEliece_Field_Ordering& field_ordering, + const Classic_McEliece_Minimal_Polynomial& g) { + auto alphas = field_ordering.alphas(params.n()); + std::vector inv_g_of_alpha; + inv_g_of_alpha.reserve(params.n()); + for(const auto& alpha : alphas) { + inv_g_of_alpha.push_back(g(alpha).inv()); + } + CmceMatrix mat(std::vector(params.pk_no_rows(), CmceMatrixRow(params.n()))); + + for(size_t i = 0; i < params.t(); ++i) { + for(size_t j = 0; j < params.n(); ++j) { + for(size_t alpha_i_j_bit = 0; alpha_i_j_bit < params.m(); ++alpha_i_j_bit) { + mat[i * params.m() + alpha_i_j_bit][j] = (uint16_t(1) << alpha_i_j_bit) & inv_g_of_alpha[j].elem().get(); + } + } + // Update for the next i so that: + // inv_g_of_alpha[j] = h_i_j = alpha_j^i/g(alpha_j) + for(size_t j = 0; j < params.n(); ++j) { + inv_g_of_alpha.at(j) *= alphas.at(j); + } + } + + return mat; +} + +std::optional move_columns(CmceMatrix& mat, const Classic_McEliece_Parameters& params) { + BOTAN_ASSERT(mat.size() == params.pk_no_rows(), "Matrix has incorrect number of rows"); + BOTAN_ASSERT(mat.get().at(0).size() == params.n(), "Matrix has incorrect number of columns"); + static_assert(Classic_McEliece_Parameters::nu() == 64, + "nu needs to be 64"); // Since we use uint64_t to represent rows in the mu x nu sub-matrix + // A 32x64 sub-matrix of mat containing the elements mat[m*t-32][m*t-32] at the top left + std::vector sub_mat(Classic_McEliece_Parameters::mu(), secure_bitvector()); + + const size_t pos_offset = params.pk_no_rows() - Classic_McEliece_Parameters::mu(); + + // Extract the bottom mu x nu matrix at offset pos_offset + for(size_t i = 0; i < Classic_McEliece_Parameters::mu(); i++) { + sub_mat.at(i) = mat[pos_offset + i].subvector(pos_offset, Classic_McEliece_Parameters::nu()); + } + + std::array pivot_indices = {0}; // ctz_list + + // Identify the pivot indices, i.e., the indices of the leading ones for all rows + // when transforming the matrix into semi-systematic form. This algorithm is a modified + // Gauss algorithm. + for(size_t row_idx = 0; row_idx < Classic_McEliece_Parameters::mu(); ++row_idx) { + // Identify pivots (index of first 1) by OR-ing all subsequent rows into row_acc + auto row_acc = sub_mat.at(row_idx); + for(size_t next_row = row_idx + 1; next_row < Classic_McEliece_Parameters::mu(); ++next_row) { + row_acc |= sub_mat.at(next_row); + } + + if(row_acc.none()) { + // If the current row and all subsequent rows are zero + // we cannot create a semi-systematic matrix + return std::nullopt; + } + + // Using the row accumulator we can predict the index of the pivot + // bit for the current row, i.e., the first index where we can set + // the bit to one row by adding any subsequent row + pivot_indices.at(row_idx) = count_lsb_zeros(row_acc); + + // Add subsequent rows to the current row, until the pivot + // bit is set. + for(size_t next_row = row_idx + 1; next_row < Classic_McEliece_Parameters::mu(); ++next_row) { + sub_mat.at(row_idx).ct_conditional_xor(!sub_mat.at(row_idx).at(pivot_indices.at(row_idx)), + sub_mat.at(next_row)); + } + + // Add the (new) current row to all subsequent rows, where the leading + // bit of the current bit is one. Therefore, the column of the leading + // bit becomes zero. + // Note: In normal gauss, we would also add the current row to rows + // above the current one. However, here we only need to identify + // the columns to swap. Therefore, we can ignore the upper rows. + for(size_t next_row = row_idx + 1; next_row < Classic_McEliece_Parameters::mu(); ++next_row) { + sub_mat.at(next_row).ct_conditional_xor(sub_mat.at(next_row).at(pivot_indices.at(row_idx)), + sub_mat.at(row_idx)); + } + } + + // Create pivot bitvector from the pivot index vector + CmceColumnSelection pivots(Classic_McEliece_Parameters::nu()); + for(auto pivot_idx : pivot_indices) { + for(size_t i = 0; i < Classic_McEliece_Parameters::nu(); ++i) { + auto mask_is_at_current_idx = Botan::CT::Mask::is_equal(i, pivot_idx); + pivots.at(i) = mask_is_at_current_idx.select(1, pivots.at(i).as()); + } + } + + // Update matrix by swapping columns + for(size_t mat_row = 0; mat_row < params.pk_no_rows(); ++mat_row) { + for(size_t j = 0; j < Classic_McEliece_Parameters::mu(); ++j) { + // Swap bit j with bit pivot_indices[j] + size_t col_j = pos_offset + j; + size_t col_pivot_j = pos_offset + pivot_indices.at(j); + auto sum = mat[mat_row][col_j] ^ mat[mat_row][col_pivot_j]; + mat[mat_row][col_j] = mat[mat_row][col_j] ^ sum; + mat[mat_row][col_pivot_j] = mat[mat_row][col_pivot_j] ^ sum; + } + } + + return pivots; +} + +std::optional apply_gauss(const Classic_McEliece_Parameters& params, CmceMatrix& mat) { + BOTAN_ASSERT(mat.size() == params.pk_no_rows(), "Matrix has incorrect number of rows"); + BOTAN_ASSERT(mat.get().at(0).size() == params.n(), "Matrix has incorrect number of columns"); + // Initialized for systematic form instances + // Is overridden for semi systematic instances + auto pivots = CmceColumnSelection({0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0}); + + // Gaussian Elimination + for(size_t diag_pos = 0; diag_pos < params.pk_no_rows(); ++diag_pos) { + if(params.is_f() && diag_pos == params.pk_no_rows() - params.mu()) { + auto ret_pivots = move_columns(mat, params); + if(!ret_pivots) { + return std::nullopt; + } else { + pivots = std::move(ret_pivots.value()); + } + } + + // Iterates over all rows next_row under row diag_pos. If the bit at column + // diag_pos differs between row diag_pos and row next_row, row next_row is added to row diag_pos. + // This achieves that the respective bit at the diagonal becomes 1 + // (if mat is systematic) + for(size_t next_row = diag_pos + 1; next_row < params.pk_no_rows(); ++next_row) { + mat[diag_pos].get().ct_conditional_xor(!mat[diag_pos].at(diag_pos), mat[next_row].get()); + } + + // If the current bit on the diagonal is not set at this point + // the matrix is not systematic. We abort the computation in this case. + if(!mat[diag_pos].at(diag_pos)) { + return std::nullopt; + } + + // Now the new row is added to all other rows, where the + // bit in the column of the current postion on the diagonal + // is still one + for(size_t row = 0; row < params.pk_no_rows(); ++row) { + if(row != diag_pos) { + mat[row].get().ct_conditional_xor(mat[row].at(diag_pos), mat[diag_pos].get()); + } + } + } + + return pivots; +} + +std::vector extract_pk_bytes_from_matrix(const Classic_McEliece_Parameters& params, const CmceMatrix& mat) { + // Store T of the matrix (I_mt|T) as a linear vector to represent the + // public key as defined in McEliece ISO 9.2.7 + std::vector big_t(params.pk_size_bytes()); + auto big_t_stuffer = BufferStuffer(big_t); + + for(size_t row = 0; row < params.pk_no_rows(); ++row) { + mat[row].subvector(params.pk_no_rows()).to_bytes(big_t_stuffer.next(params.pk_row_size_bytes())); + } + + BOTAN_ASSERT_NOMSG(big_t_stuffer.full()); + + return big_t; +} + +} // namespace + +std::optional> Classic_McEliece_Matrix::create_matrix( + const Classic_McEliece_Parameters& params, + const Classic_McEliece_Field_Ordering& field_ordering, + const Classic_McEliece_Minimal_Polynomial& g) { + auto mat = init_matrix_with_alphas(params, field_ordering, g); + auto pivots = apply_gauss(params, mat); + + if(!pivots) { + return std::nullopt; + } + + auto pk_mat_bytes = extract_pk_bytes_from_matrix(params, mat); + return std::make_pair(Classic_McEliece_Matrix(params, std::move(pk_mat_bytes)), pivots.value()); +} + +std::optional> +Classic_McEliece_Matrix::create_matrix_and_apply_pivots(const Classic_McEliece_Parameters& params, + Classic_McEliece_Field_Ordering& field_ordering, + const Classic_McEliece_Minimal_Polynomial& g) { + auto pk_matrix_and_pivots = create_matrix(params, field_ordering, g); + + if(!pk_matrix_and_pivots) { + return std::nullopt; + } + + auto& [_, pivots] = pk_matrix_and_pivots.value(); + + if(params.is_f()) { + field_ordering.permute_with_pivots(params, pivots); + } + + return pk_matrix_and_pivots; +} + +CmceCodeWord Classic_McEliece_Matrix::mul(const Classic_McEliece_Parameters& params, const CmceErrorVector& e) const { + auto s = e.subvector(0, params.pk_no_rows()); + auto e_T = e.subvector(params.pk_no_rows()); + auto pk_slicer = BufferSlicer(m_mat_bytes); + + for(size_t i = 0; i < params.pk_no_rows(); ++i) { + auto pk_current_bytes = pk_slicer.take(params.pk_row_size_bytes()); + auto row = secure_bitvector(pk_current_bytes, params.n() - params.pk_no_rows()); + row &= e_T; + s[i] ^= row.has_odd_hamming_weight(); + } + + BOTAN_ASSERT_NOMSG(pk_slicer.empty()); + return s; +} +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_matrix.h b/src/lib/pubkey/classic_mceliece/cmce_matrix.h new file mode 100644 index 00000000000..fde27b36f5c --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_matrix.h @@ -0,0 +1,114 @@ +/* + * Classic McEliece Matrix Logic + * + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_MATRIX_H_ +#define BOTAN_CMCE_MATRIX_H_ + +#include +#include +#include +#include +#include + +namespace Botan { + +/** + * @brief Representation of the binary Classic McEliece matrix H, with H = (I_mt | T). + * + * Only the bytes of the submatrix T are stored. + */ +class BOTAN_TEST_API Classic_McEliece_Matrix { + public: + /** + * @brief Create the matrix H for a Classic McEliece instance given its + * parameters, field ordering and minimal polynomial. + * + * Output is a pair of the matrix and the pivot vector c that was used to + * create it in the semi-systematic form as described in Classic McEliece ISO + * Section 9.2.11. + * + * The update of alpha values as per Classic McEliece ISO Section 7.2.3 Step 5 + * is not performed by this method because it is only used for public key loading + * where the values are already permuted and field_ordering cannot be altered. + * + * @param params Classic McEliece parameters + * @param field_ordering Field ordering + * @param g Minimal polynomial + * @return Pair(the matrix H, pivot vector c) + */ + static std::optional> create_matrix( + const Classic_McEliece_Parameters& params, + const Classic_McEliece_Field_Ordering& field_ordering, + const Classic_McEliece_Minimal_Polynomial& g); + + /** + * @brief Create the matrix H for a Classic McEliece instance given its + * parameters, field ordering and minimal polynomial. + * + * Output is a pair of the matrix and the pivot vector c that was used to + * create it in the semi-systematic form as described in Classic McEliece ISO + * Section 9.2.11. + * + * This method directly updates the field ordering values as described in Classic McEliece + * ISO Section 7.2.3 Step 5 (for f parameter sets). + * + * @param params Classic McEliece parameters + * @param field_ordering Field ordering (will be updated) + * @param g Minimal polynomial + * @return Pair(the matrix H, pivot vector c) + */ + static std::optional> create_matrix_and_apply_pivots( + const Classic_McEliece_Parameters& params, + Classic_McEliece_Field_Ordering& field_ordering, + const Classic_McEliece_Minimal_Polynomial& g); + + /** + * @brief The bytes of the submatrix T, with H=(I_mt, T) as defined in Classic + * McEliece ISO Section 9.2.7. + * + * @return The matrix bytes + */ + const std::vector& bytes() const { return m_mat_bytes; } + + /** + * @brief Create a Classic_McEliece_Matrix from bytes. + * + * @param mat_bytes The bytes of the submatrix T as defined in Classic McEliece ISO Section 9.2.7. + */ + Classic_McEliece_Matrix(const Classic_McEliece_Parameters& params, std::vector mat_bytes) : + m_mat_bytes(std::move(mat_bytes)) { + BOTAN_ARG_CHECK(m_mat_bytes.size() == params.pk_size_bytes(), "Invalid byte size for matrix"); + if(params.pk_no_cols() % 8 == 0) { + return; + } + // Check padding of mat_bytes rows + BOTAN_ASSERT_NOMSG(m_mat_bytes.size() == params.pk_no_rows() * params.pk_row_size_bytes()); + for(size_t row = 0; row < params.pk_no_rows(); ++row) { + uint8_t padded_byte = m_mat_bytes[(row + 1) * params.pk_row_size_bytes() - 1]; + BOTAN_ARG_CHECK(padded_byte >> (params.pk_no_cols() % 8) == 0, "Valid padding of unused bytes"); + } + } + + /** + * @brief Multiply the Classic McEliece matrix H with a bitvector e. + * + * @param params Classic McEliece parameters + * @param e The bitvector e + * @return H*e + */ + CmceCodeWord mul(const Classic_McEliece_Parameters& params, const CmceErrorVector& e) const; + + private: + /// The bytes of the submatrix T + const std::vector m_mat_bytes; // can we use bitvector? +}; + +} // namespace Botan + +#endif // BOTAN_CMCE_MATRIX_H_ diff --git a/src/lib/pubkey/classic_mceliece/cmce_parameter_set.cpp b/src/lib/pubkey/classic_mceliece/cmce_parameter_set.cpp new file mode 100644 index 00000000000..a9b41042447 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_parameter_set.cpp @@ -0,0 +1,108 @@ +/* + * Classic McEliece Parameters + * (C) 2024 Jack Lloyd + * 2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#include + +namespace Botan { + +Classic_McEliece_Parameter_Set cmce_param_set_from_str(std::string_view param_name) { + if(param_name == "mceliece348864") { + return Classic_McEliece_Parameter_Set::mceliece348864; + } + if(param_name == "mceliece348864f") { + return Classic_McEliece_Parameter_Set::mceliece348864f; + } + if(param_name == "mceliece460896") { + return Classic_McEliece_Parameter_Set::mceliece460896; + } + if(param_name == "mceliece460896f") { + return Classic_McEliece_Parameter_Set::mceliece460896f; + } + if(param_name == "mceliece6688128") { + return Classic_McEliece_Parameter_Set::mceliece6688128; + } + if(param_name == "mceliece6688128f") { + return Classic_McEliece_Parameter_Set::mceliece6688128f; + } + if(param_name == "mceliece6688128pc") { + return Classic_McEliece_Parameter_Set::mceliece6688128pc; + } + if(param_name == "mceliece6688128pcf") { + return Classic_McEliece_Parameter_Set::mceliece6688128pcf; + } + if(param_name == "mceliece6960119") { + return Classic_McEliece_Parameter_Set::mceliece6960119; + } + if(param_name == "mceliece6960119f") { + return Classic_McEliece_Parameter_Set::mceliece6960119f; + } + if(param_name == "mceliece6960119pc") { + return Classic_McEliece_Parameter_Set::mceliece6960119pc; + } + if(param_name == "mceliece6960119pcf") { + return Classic_McEliece_Parameter_Set::mceliece6960119pcf; + } + if(param_name == "mceliece8192128") { + return Classic_McEliece_Parameter_Set::mceliece8192128; + } + if(param_name == "mceliece8192128f") { + return Classic_McEliece_Parameter_Set::mceliece8192128f; + } + if(param_name == "mceliece8192128pc") { + return Classic_McEliece_Parameter_Set::mceliece8192128pc; + } + if(param_name == "mceliece8192128pcf") { + return Classic_McEliece_Parameter_Set::mceliece8192128pcf; + } + + throw Decoding_Error("Cannot convert string to CMCE parameter set"); +} + +std::string cmce_str_from_param_set(Classic_McEliece_Parameter_Set param) { + switch(param) { + case Classic_McEliece_Parameter_Set::mceliece348864: + return "mceliece348864"; + case Classic_McEliece_Parameter_Set::mceliece348864f: + return "mceliece348864f"; + case Classic_McEliece_Parameter_Set::mceliece460896: + return "mceliece460896"; + case Classic_McEliece_Parameter_Set::mceliece460896f: + return "mceliece460896f"; + case Classic_McEliece_Parameter_Set::mceliece6688128: + return "mceliece6688128"; + case Classic_McEliece_Parameter_Set::mceliece6688128f: + return "mceliece6688128f"; + case Classic_McEliece_Parameter_Set::mceliece6688128pc: + return "mceliece6688128pc"; + case Classic_McEliece_Parameter_Set::mceliece6688128pcf: + return "mceliece6688128pcf"; + case Classic_McEliece_Parameter_Set::mceliece6960119: + return "mceliece6960119"; + case Classic_McEliece_Parameter_Set::mceliece6960119f: + return "mceliece6960119f"; + case Classic_McEliece_Parameter_Set::mceliece6960119pc: + return "mceliece6960119pc"; + case Classic_McEliece_Parameter_Set::mceliece6960119pcf: + return "mceliece6960119pcf"; + case Classic_McEliece_Parameter_Set::mceliece8192128: + return "mceliece8192128"; + case Classic_McEliece_Parameter_Set::mceliece8192128f: + return "mceliece8192128f"; + case Classic_McEliece_Parameter_Set::mceliece8192128pc: + return "mceliece8192128pc"; + case Classic_McEliece_Parameter_Set::mceliece8192128pcf: + return "mceliece8192128pcf"; + } + BOTAN_ASSERT_UNREACHABLE(); +} + +Classic_McEliece_Parameter_Set cmce_param_set_from_oid(const OID& oid) { + return cmce_param_set_from_str(oid.to_formatted_string()); +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_parameter_set.h b/src/lib/pubkey/classic_mceliece/cmce_parameter_set.h new file mode 100644 index 00000000000..4f93d1112ab --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_parameter_set.h @@ -0,0 +1,66 @@ +/* + * Classic McEliece Parameters + * (C) 2024 Jack Lloyd + * 2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_PARAMETER_SET_H_ +#define BOTAN_CMCE_PARAMETER_SET_H_ + +#include + +namespace Botan { + +/** + * All Classic McEliece parameter sets defined in the NIST Round 4 + * submission and the Classic McEliece ISO Draft. + * + * Instances are defined in the following format: + * mceliece{n}{t}{[pc]}{[f]} + * + * Instance with 'pc' use plaintext confirmation as defined in the ISO Draft. + * Instance with 'f' use matrix reduction with the semi-systematic form. + */ +enum class Classic_McEliece_Parameter_Set { + mceliece348864, // NIST + mceliece348864f, // NIST + + mceliece460896, // NIST + mceliece460896f, // NIST + + mceliece6688128, // ISO + NIST + mceliece6688128f, // ISO + NIST + mceliece6688128pc, // ISO + mceliece6688128pcf, // ISO + + mceliece6960119, // ISO + NIST + mceliece6960119f, // ISO + NIST + mceliece6960119pc, // ISO + mceliece6960119pcf, // ISO + + mceliece8192128, // ISO + NIST + mceliece8192128f, // ISO + NIST + mceliece8192128pc, // ISO + mceliece8192128pcf, // ISO +}; + +/** + * @brief Get the parameter set for a given parameter set name. + */ +BOTAN_TEST_API Classic_McEliece_Parameter_Set cmce_param_set_from_str(std::string_view param_name); + +/** + * @brief Get the parameter set name for a given parameter set. + */ +BOTAN_TEST_API std::string cmce_str_from_param_set(Classic_McEliece_Parameter_Set param); + +/** + * @brief Get the parameter set for a given OID. + */ +BOTAN_TEST_API Classic_McEliece_Parameter_Set cmce_param_set_from_oid(const OID& oid); + +} // namespace Botan + +#endif // BOTAN_CMCE_PARAMETER_SET_H_ diff --git a/src/lib/pubkey/classic_mceliece/cmce_parameters.cpp b/src/lib/pubkey/classic_mceliece/cmce_parameters.cpp new file mode 100644 index 00000000000..c96f81b2299 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_parameters.cpp @@ -0,0 +1,189 @@ +/* + * Classic McEliece Parameters + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#include +#include + +namespace Botan { + +namespace { + +CmceGfMod determine_poly_f(Classic_McEliece_Parameter_Set param_set) { + switch(param_set) { + case Classic_McEliece_Parameter_Set::mceliece348864: + case Classic_McEliece_Parameter_Set::mceliece348864f: + // z^12 + z^3 + 1 + return CmceGfMod(0b0001000000001001); + case Classic_McEliece_Parameter_Set::mceliece460896: + case Classic_McEliece_Parameter_Set::mceliece460896f: + case Classic_McEliece_Parameter_Set::mceliece6688128: + case Classic_McEliece_Parameter_Set::mceliece6688128f: + case Classic_McEliece_Parameter_Set::mceliece6688128pc: + case Classic_McEliece_Parameter_Set::mceliece6688128pcf: + case Classic_McEliece_Parameter_Set::mceliece6960119: + case Classic_McEliece_Parameter_Set::mceliece6960119f: + case Classic_McEliece_Parameter_Set::mceliece6960119pc: + case Classic_McEliece_Parameter_Set::mceliece6960119pcf: + case Classic_McEliece_Parameter_Set::mceliece8192128: + case Classic_McEliece_Parameter_Set::mceliece8192128f: + case Classic_McEliece_Parameter_Set::mceliece8192128pc: + case Classic_McEliece_Parameter_Set::mceliece8192128pcf: + // z^12 + z^3 + 1 + return CmceGfMod(0b0010000000011011); + } + BOTAN_ASSERT_UNREACHABLE(); +} + +Classic_McEliece_Polynomial_Ring determine_poly_ring(Classic_McEliece_Parameter_Set param_set) { + CmceGfMod poly_f = determine_poly_f(param_set); + + switch(param_set) { + case Classic_McEliece_Parameter_Set::mceliece348864: + case Classic_McEliece_Parameter_Set::mceliece348864f: + // y^64 + y^3 + y + z + return {{{3, Classic_McEliece_GF(CmceGfElem(1), poly_f)}, + {1, Classic_McEliece_GF(CmceGfElem(1), poly_f)}, + {0, Classic_McEliece_GF(CmceGfElem(2), poly_f)}}, + poly_f, + 64}; + case Classic_McEliece_Parameter_Set::mceliece460896: + case Classic_McEliece_Parameter_Set::mceliece460896f: + // y^96 + y^10 + y^9 + y^6 + 1 + return {{{10, Classic_McEliece_GF(CmceGfElem(1), poly_f)}, + {9, Classic_McEliece_GF(CmceGfElem(1), poly_f)}, + {6, Classic_McEliece_GF(CmceGfElem(1), poly_f)}, + {0, Classic_McEliece_GF(CmceGfElem(1), poly_f)}}, + poly_f, + 96}; + case Classic_McEliece_Parameter_Set::mceliece6960119: + case Classic_McEliece_Parameter_Set::mceliece6960119f: + case Classic_McEliece_Parameter_Set::mceliece6960119pc: + case Classic_McEliece_Parameter_Set::mceliece6960119pcf: + // y^119 + y^8 + 1 + // clang-format off + return {{{8, Classic_McEliece_GF(CmceGfElem(1), poly_f)}, + {0, Classic_McEliece_GF(CmceGfElem(1), poly_f)}}, + poly_f, + 119}; + // clang-format on + case Classic_McEliece_Parameter_Set::mceliece6688128: + case Classic_McEliece_Parameter_Set::mceliece6688128f: + case Classic_McEliece_Parameter_Set::mceliece6688128pc: + case Classic_McEliece_Parameter_Set::mceliece6688128pcf: + case Classic_McEliece_Parameter_Set::mceliece8192128: + case Classic_McEliece_Parameter_Set::mceliece8192128f: + case Classic_McEliece_Parameter_Set::mceliece8192128pc: + case Classic_McEliece_Parameter_Set::mceliece8192128pcf: + // y^128 + y^7 + y^2 + y + 1 + return {{{7, Classic_McEliece_GF(CmceGfElem(1), poly_f)}, + {2, Classic_McEliece_GF(CmceGfElem(1), poly_f)}, + {1, Classic_McEliece_GF(CmceGfElem(1), poly_f)}, + {0, Classic_McEliece_GF(CmceGfElem(1), poly_f)}}, + poly_f, + 128}; + } + BOTAN_ASSERT_UNREACHABLE(); +} + +} //namespace + +Classic_McEliece_Parameters Classic_McEliece_Parameters::create(Classic_McEliece_Parameter_Set set) { + auto poly_ring = determine_poly_ring(set); + + switch(set) { + case Classic_McEliece_Parameter_Set::mceliece348864: + case Classic_McEliece_Parameter_Set::mceliece348864f: + return Classic_McEliece_Parameters(set, 12, 3488, std::move(poly_ring)); + + case Classic_McEliece_Parameter_Set::mceliece460896: + case Classic_McEliece_Parameter_Set::mceliece460896f: + return Classic_McEliece_Parameters(set, 13, 4608, std::move(poly_ring)); + + case Classic_McEliece_Parameter_Set::mceliece6688128: + case Classic_McEliece_Parameter_Set::mceliece6688128f: + case Classic_McEliece_Parameter_Set::mceliece6688128pc: + case Classic_McEliece_Parameter_Set::mceliece6688128pcf: + return Classic_McEliece_Parameters(set, 13, 6688, std::move(poly_ring)); + + case Classic_McEliece_Parameter_Set::mceliece6960119: + case Classic_McEliece_Parameter_Set::mceliece6960119f: + case Classic_McEliece_Parameter_Set::mceliece6960119pc: + case Classic_McEliece_Parameter_Set::mceliece6960119pcf: + return Classic_McEliece_Parameters(set, 13, 6960, std::move(poly_ring)); + + case Classic_McEliece_Parameter_Set::mceliece8192128: + case Classic_McEliece_Parameter_Set::mceliece8192128f: + case Classic_McEliece_Parameter_Set::mceliece8192128pc: + case Classic_McEliece_Parameter_Set::mceliece8192128pcf: + return Classic_McEliece_Parameters(set, 13, 8192, std::move(poly_ring)); + } + BOTAN_ASSERT_UNREACHABLE(); +} + +Classic_McEliece_Parameters Classic_McEliece_Parameters::create(std::string_view name) { + return Classic_McEliece_Parameters::create(cmce_param_set_from_str(name)); +} + +Classic_McEliece_Parameters Classic_McEliece_Parameters::create(const OID& oid) { + return create(cmce_param_set_from_oid(oid)); +} + +OID Classic_McEliece_Parameters::object_identifier() const { + return OID::from_string(cmce_str_from_param_set(m_set)); +} + +Classic_McEliece_Parameters::Classic_McEliece_Parameters(Classic_McEliece_Parameter_Set param_set, + size_t m, + size_t n, + Classic_McEliece_Polynomial_Ring poly_ring) : + m_set(param_set), m_m(m), m_n(n), m_poly_ring(std::move(poly_ring)) { + BOTAN_ASSERT(n % 8 == 0, "We require that n is a multiple of 8"); +} + +size_t Classic_McEliece_Parameters::estimated_strength() const { + // Classic McEliece NIST Round 4 submission, Guide for security reviewers, Table 1: + // For each instance, the minimal strength against the best attack (with free memory access) + // is used as the overall security strength estimate. The strength is capped at 256, since the + // seed is only 256 bits long. + switch(m_set) { + case Botan::Classic_McEliece_Parameter_Set::mceliece348864: + case Botan::Classic_McEliece_Parameter_Set::mceliece348864f: + return 140; + case Botan::Classic_McEliece_Parameter_Set::mceliece460896: + case Botan::Classic_McEliece_Parameter_Set::mceliece460896f: + return 179; + case Botan::Classic_McEliece_Parameter_Set::mceliece6688128: + case Botan::Classic_McEliece_Parameter_Set::mceliece6688128f: + case Botan::Classic_McEliece_Parameter_Set::mceliece6688128pc: + case Botan::Classic_McEliece_Parameter_Set::mceliece6688128pcf: + return 246; + case Botan::Classic_McEliece_Parameter_Set::mceliece6960119: + case Botan::Classic_McEliece_Parameter_Set::mceliece6960119f: + case Botan::Classic_McEliece_Parameter_Set::mceliece6960119pc: + case Botan::Classic_McEliece_Parameter_Set::mceliece6960119pcf: + return 245; + case Botan::Classic_McEliece_Parameter_Set::mceliece8192128: + case Botan::Classic_McEliece_Parameter_Set::mceliece8192128f: + case Botan::Classic_McEliece_Parameter_Set::mceliece8192128pc: + case Botan::Classic_McEliece_Parameter_Set::mceliece8192128pcf: + return 256; // 275 in the document. Capped at 256 because of the seed length. + } + BOTAN_ASSERT_UNREACHABLE(); +} + +std::unique_ptr Classic_McEliece_Parameters::prg(std::span seed) const { + BOTAN_ASSERT_EQUAL(seed.size(), 32, "Valid seed length"); + auto xof = XOF::create_or_throw("SHAKE-256"); + + xof->update(std::array({64})); + xof->update(seed); + + return xof; +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_parameters.h b/src/lib/pubkey/classic_mceliece/cmce_parameters.h new file mode 100644 index 00000000000..ea224642b22 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_parameters.h @@ -0,0 +1,290 @@ +/* + * Classic McEliece Parameters + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_PARAMS_H_ +#define BOTAN_CMCE_PARAMS_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Botan { + +struct Classic_McEliece_Big_F_Coefficient; +class Classic_McEliece_Polynomial_Ring; + +/** + * @returns ceil(n/d) + * TODO: Remove once LMS is merged + */ +constexpr size_t ceil_div(size_t n, size_t d) { + return (n + d - 1) / d; +} + +/** + * Container for all Classic McEliece parameters. + */ +class BOTAN_TEST_API Classic_McEliece_Parameters final { + public: + /** + * @brief Create Classic McEliece parameters from a parameter set. + */ + static Classic_McEliece_Parameters create(Classic_McEliece_Parameter_Set set); + + /** + * @brief Create Classic McEliece parameters from a parameter set name. + */ + static Classic_McEliece_Parameters create(std::string_view name); + + /** + * @brief Create Classic McEliece parameters from an OID. + */ + static Classic_McEliece_Parameters create(const OID& oid); + + /** + * @brief The parameter set for this Classic McEliece instance. + */ + Classic_McEliece_Parameter_Set parameter_set() const { return m_set; } + + /** + * @brief The OID for the Classic McEliece instance. + */ + OID object_identifier() const; + + /** + * @returns true iff the instance is a plaintext confirmation (PC) instance. + */ + bool is_pc() const { + return (m_set == Classic_McEliece_Parameter_Set::mceliece6688128pc) || + (m_set == Classic_McEliece_Parameter_Set::mceliece6688128pcf) || + (m_set == Classic_McEliece_Parameter_Set::mceliece6960119pc) || + (m_set == Classic_McEliece_Parameter_Set::mceliece6960119pcf) || + (m_set == Classic_McEliece_Parameter_Set::mceliece8192128pc) || + (m_set == Classic_McEliece_Parameter_Set::mceliece8192128pcf); + } + + /** + * @returns true iff the instance is a fast (F) instance, i.e. if the semi-systematic + * matrix creation is used. + */ + bool is_f() const { + return (m_set == Classic_McEliece_Parameter_Set::mceliece348864f) || + (m_set == Classic_McEliece_Parameter_Set::mceliece460896f) || + (m_set == Classic_McEliece_Parameter_Set::mceliece6688128f) || + (m_set == Classic_McEliece_Parameter_Set::mceliece6688128pcf) || + (m_set == Classic_McEliece_Parameter_Set::mceliece6960119f) || + (m_set == Classic_McEliece_Parameter_Set::mceliece6960119pcf) || + (m_set == Classic_McEliece_Parameter_Set::mceliece8192128f) || + (m_set == Classic_McEliece_Parameter_Set::mceliece8192128pcf); + } + + /** + * @brief The degree of the Classic McEliece instance's underlying Galois Field, i.e. GF(q) = GF(2^m). + */ + size_t m() const { return m_m; } + + /** + * @brief The field size of the Classic McEliece instance's underlying Galois Field, i.e. + * GF(q) is the underlying field. + */ + size_t q() const { return (size_t(1) << m_m); } + + /** + * @brief The code length of the Classic McEliece instance. + * + * E.g. the Classic McEliece matrix H is of size m*t x n, + * the encoded error vector is, therefore, of size n. + */ + size_t n() const { return m_n; } + + /** + * @brief The weight of the error vector e. + */ + size_t t() const { return m_poly_ring.degree(); } + + /** + * @brief Bit output length of the hash function H. + */ + static constexpr size_t ell() { return 256; } + + /** + * @brief The number of bits each GF element is encoded with. + */ + static constexpr size_t sigma1() { return 16; } + + /** + * @brief Constant for field-ordering generation. (see Classic McEliece ISO 8.2) + */ + static constexpr size_t sigma2() { return 32; } + + /** + * @brief Constant mu for semi-systematic matrix creation. (see Classic McEliece ISO 7.2.3) + */ + static constexpr size_t mu() { return 32; } + + /** + * @brief Constant nu for semi-systematic matrix creation. (see Classic McEliece ISO 7.2.3) + */ + static constexpr size_t nu() { return 64; } + + /** + * @brief Constant tau for fixed-weight vector generation. (see Classic McEliece ISO 8.4) + */ + size_t tau() const { + // Section 8.4 of ISO: + // The integer tau is defined as t if n=q; as 2t if q/2<=n prg(std::span seed) const; + + /** + * @brief Create an instance of the hash function Hash(x) used in Classic McEliece's + * Decaps and Encaps algorithms. + * + * @return a new instance of the hash function. + */ + std::unique_ptr hash_func() const { return HashFunction::create_or_throw("SHAKE-256(256)"); } + + /** + * @brief Create a GF(q) element using the modulus for the current instance. + * + * @param elem The GF(q) element value. + * @return The GF(q) element. + */ + Classic_McEliece_GF gf(CmceGfElem elem) const { return Classic_McEliece_GF(elem, poly_f()); } + + private: + Classic_McEliece_Parameters(Classic_McEliece_Parameter_Set param_set, + size_t m, + size_t n, + Classic_McEliece_Polynomial_Ring poly_ring); + + Classic_McEliece_Parameter_Set m_set; + size_t m_m; + size_t m_n; + Classic_McEliece_Polynomial_Ring m_poly_ring; +}; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/classic_mceliece/cmce_poly.cpp b/src/lib/pubkey/classic_mceliece/cmce_poly.cpp new file mode 100644 index 00000000000..30b76754610 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_poly.cpp @@ -0,0 +1,176 @@ +/* + * Classic McEliece Polynomials + * Based on the public domain reference implementation by the designers + * (https://classic.mceliece.org/impl.html - released in Oct 2022 for NISTPQC-R4) + * + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#include +#include +#include + +namespace Botan { + +namespace { + +/** + * @brief loads @p bytes into a GF_Elem vector in Little Endian. + * + * Replaces load_le as it cannot be compiled on Big Endian architectures + * + * @param bytes input bytes + * @return The vector of GF_Elem elements + */ +std::vector load_le_gf_vec(std::span bytes) { + std::vector result; + BufferSlicer bs(bytes); + result.reserve(bytes.size() / sizeof(CmceGfElem)); + for(size_t i = 0; i < bytes.size() / sizeof(CmceGfElem); ++i) { + result.emplace_back(load_le(bs.take())); + } + return result; +} + +} // namespace + +Classic_McEliece_GF Classic_McEliece_Polynomial::operator()(Classic_McEliece_GF a) const { + BOTAN_ASSERT(a.modulus() == coef_at(0).modulus(), "Galois fields match"); + + Classic_McEliece_GF r(CmceGfElem(0), a.modulus()); + for(auto it = m_coef.rbegin(); it != m_coef.rend(); ++it) { + r *= a; + r += *it; + } + + return r; +} + +Classic_McEliece_Polynomial Classic_McEliece_Polynomial_Ring::multiply(const Classic_McEliece_Polynomial& a, + const Classic_McEliece_Polynomial& b) const { + std::vector prod(m_t * 2 - 1, {CmceGfElem(0), m_poly_f}); + + for(size_t i = 0; i < m_t; ++i) { + for(size_t j = 0; j < m_t; ++j) { + prod.at(i + j) += (a.coef_at(i) * b.coef_at(j)); + } + } + + for(size_t i = (m_t - 1) * 2; i >= m_t; --i) { + for(auto& [idx, coef] : m_position_map) { + prod.at(i - m_t + idx) += coef * prod.at(i); + } + } + + prod.erase(prod.begin() + m_t, prod.end()); + + return Classic_McEliece_Polynomial(std::move(prod)); +} + +Classic_McEliece_Polynomial Classic_McEliece_Polynomial_Ring::create_element_from_bytes( + std::span bytes) const { + BOTAN_ARG_CHECK(bytes.size() == m_t * 2, "Correct input size"); + auto coef = load_le_gf_vec(bytes); + return create_element_from_coef(coef); +} + +Classic_McEliece_Polynomial Classic_McEliece_Polynomial_Ring::create_element_from_coef( + const std::vector& coeff_vec) const { + std::vector coeff_vec_gf; + CmceGfElem coeff_mask = CmceGfElem((uint16_t(1) << Classic_McEliece_GF::log_q_from_mod(m_poly_f)) - 1); + std::transform(coeff_vec.begin(), coeff_vec.end(), std::back_inserter(coeff_vec_gf), [&](auto& coeff) { + return Classic_McEliece_GF(coeff & coeff_mask, m_poly_f); + }); + return Classic_McEliece_Polynomial(coeff_vec_gf); +} + +bool operator==(const Classic_McEliece_Polynomial_Ring::Big_F_Coefficient& lhs, + const Classic_McEliece_Polynomial_Ring::Big_F_Coefficient& rhs) { + return lhs.coeff == rhs.coeff && lhs.idx == rhs.idx; +} + +std::optional Classic_McEliece_Polynomial_Ring::compute_minimal_polynomial( + StrongSpan seed) const { + auto polynomial = create_element_from_bytes(seed); + std::vector mat; + + mat.push_back(create_element_from_coef(concat_as>( + std::vector{CmceGfElem(1)}, std::vector(degree() - 1, CmceGfElem(0))))); + + mat.push_back(polynomial); + + for(size_t j = 2; j <= degree(); ++j) { + mat.push_back(multiply(mat.at(j - 1), polynomial)); + } + + // Gaussian + for(size_t j = 0; j < degree(); ++j) { + for(size_t k = j + 1; k < degree(); ++k) { + auto cond = GF_Mask::is_zero(mat.at(j).coef_at(j)); + + for(size_t c = j; c < degree() + 1; ++c) { + mat.at(c).coef_at(j) += cond.if_set_return(mat.at(c).coef_at(k)); + } + } + + if(mat.at(j).coef_at(j).is_zero()) { + // Fail if not systematic. + return std::nullopt; + } + + auto inv = mat.at(j).coef_at(j).inv(); + + for(size_t c = j; c < degree() + 1; ++c) { + mat.at(c).coef_at(j) *= inv; + } + + for(size_t k = 0; k < degree(); ++k) { + if(k != j) { + const auto t = mat.at(j).coef_at(k); + + for(size_t c = j; c < degree() + 1; ++c) { + mat.at(c).coef_at(k) += mat.at(c).coef_at(j) * t; + } + } + } + } + + auto minimal_poly_coeffs = mat.at(degree()).coef(); + // Add coefficient 1 since polynomial is monic + minimal_poly_coeffs.emplace_back(CmceGfElem(1), poly_f()); + + return Classic_McEliece_Minimal_Polynomial(std::move(minimal_poly_coeffs)); +} + +secure_vector Classic_McEliece_Minimal_Polynomial::serialize() const { + BOTAN_ASSERT_NOMSG(!coef().empty()); + auto& all_coeffs = coef(); + // Store all except coef for monomial x^t since polynomial is monic (ISO Spec Section 9.2.9) + auto coeffs_to_store = std::span(all_coeffs).first(all_coeffs.size() - 1); + secure_vector bytes(sizeof(uint16_t) * coeffs_to_store.size()); + BufferStuffer bytes_stuf(bytes); + for(auto& coef : coeffs_to_store) { + store_le(bytes_stuf.next(), coef.elem().get()); + } + BOTAN_ASSERT_NOMSG(bytes_stuf.full()); + return bytes; +} + +Classic_McEliece_Minimal_Polynomial Classic_McEliece_Minimal_Polynomial::from_bytes(std::span bytes, + CmceGfMod poly_f) { + BOTAN_ASSERT_NOMSG(bytes.size() % 2 == 0); + auto coef_vec = load_le_gf_vec(bytes); + std::vector coeff_vec_gf; + std::transform(coef_vec.begin(), coef_vec.end(), std::back_inserter(coeff_vec_gf), [poly_f](auto& coeff) { + return Classic_McEliece_GF(coeff, poly_f); + }); + + coeff_vec_gf.emplace_back(CmceGfElem(1), poly_f); // x^t as polynomial is monic + + return Classic_McEliece_Minimal_Polynomial(coeff_vec_gf); +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_poly.h b/src/lib/pubkey/classic_mceliece/cmce_poly.h new file mode 100644 index 00000000000..d16b275774c --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_poly.h @@ -0,0 +1,172 @@ +/* + * Classic McEliece Polynomials + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_POLY_H_ +#define BOTAN_CMCE_POLY_H_ + +#include +#include +#include + +#include + +namespace Botan { + +/** + * @brief Representation of a Classic McEliece polynomial. + * + * This class represents a polynomial in the ring GF(q)[y]. E.g an example element of degree 2 could be: + * a = (z^3+1)y^2 + (z)y + (z^4+z^3) + * The degree of the polynomial is given by the size of the coefficient vector given to + * the constructor, even if the leading coefficient is zero. Coefficients are stored from + * lowest to highest monomial degree (coef_at(0) = (z^4+z^3) in the example above). + * + * This class is merely a container. The modulus and the operations with Polynomials (e.g. multiplication) + * is handled by the Classic_McEliece_Polynomial_Ring class. + */ +class BOTAN_TEST_API Classic_McEliece_Polynomial { + public: + /** + * @brief Construct a polynomial given its coefficients. + * + * @param coef The coefficients of the polynomial. The first element is the coefficient of the lowest monomial. + */ + Classic_McEliece_Polynomial(std::vector coef) : m_coef(std::move(coef)) {} + + /** + * @brief Evaluate the polynomial P(x) at a given point a, i.e., compute P(a). + */ + Classic_McEliece_GF operator()(Classic_McEliece_GF a) const; + + /** + * @brief Get the coefficient of the i-th monomial as a reference (from low to high degree). + */ + Classic_McEliece_GF& coef_at(size_t i) { return m_coef.at(i); } + + /** + * @brief Get the coefficient of the i-th monomial (from low to high degree). + */ + const Classic_McEliece_GF& coef_at(size_t i) const { return m_coef.at(i); } + + /** + * @brief Get the entire coefficients vector of the polynomial. + */ + const std::vector& coef() const { return m_coef; } + + /** + * @brief Get the degree of the polynomial. + * + * Note that the degree is given by the size of the coefficient vector, even if the leading coefficient is zero. + */ + size_t degree() const { return m_coef.size() + 1; } + + private: + std::vector m_coef; +}; + +/** + * @brief Representation of a minimal polynomial in GF(q)[y]. + * + * It represents the monic irreducible degree-t polynomial of the goppa code. + */ +class BOTAN_TEST_API Classic_McEliece_Minimal_Polynomial : public Classic_McEliece_Polynomial { + public: + Classic_McEliece_Minimal_Polynomial(std::vector coef) : + Classic_McEliece_Polynomial(std::move(coef)) {} + + /** + * @brief Serialize the polynomial to bytes according to ISO Section 9.2.9. + */ + secure_vector serialize() const; + + /** + * @brief Create a polynomial from bytes according to ISO Section 9.2.9. + */ + static Classic_McEliece_Minimal_Polynomial from_bytes(std::span bytes, CmceGfMod poly_f); +}; + +/** + * @brief Represents the polynomial ring GF(q)[y]/F(y) where F(y) is the modulus polynomial in + * GF(q)[y] of degree t. + * + * This class contains a modulus polynomial F(y) and the GF(q) modulus f(z). It is used + * to create and operate with Classic_McEliece_Polynomials. + */ +class BOTAN_TEST_API Classic_McEliece_Polynomial_Ring { + public: + /** + * @brief Represents a non-zero coefficient of the modulus F(y) (which is in GF(q)[y]). + * + * E.g. {.idx = 4, .coeff = (z+1)} represents the monomial (z+1)y^4. + */ + struct BOTAN_TEST_API Big_F_Coefficient { + size_t idx; + Classic_McEliece_GF coeff; + }; + + /** + * @brief Construct a polynomial ring GF(q)[y]/F(y) by defining the polynomial modulus F(y), + * the GF(q) modulus f(z) and the degree of F(y). + * + * F(y) is given by a vector of Big_F_Coefficients, where each one represents a monomial of F(y). + * However, the highest monomial must not be specified, since it is always 1. + * + * @param poly_big_f_coef The non-zero coefficients of F(y) in GF(q)[y] WITHOUT the highest monomial. + * @param poly_f The modulus f(z) of GF(q). + * @param t The polynomial degree of the ring (and of F(y)). + */ + Classic_McEliece_Polynomial_Ring(std::vector poly_big_f_coef, CmceGfMod poly_f, size_t t) : + m_position_map(std::move(poly_big_f_coef)), m_t(t), m_poly_f(poly_f) {} + + CmceGfMod poly_f() const { return m_poly_f; } + + /** + * @brief The degree of polynomials in this ring (and of F(y)). + */ + size_t degree() const { return m_t; } + + /** + * @returns a*b over GF(q)[y]/F(y). + */ + Classic_McEliece_Polynomial multiply(const Classic_McEliece_Polynomial& a, + const Classic_McEliece_Polynomial& b) const; + + /** + * @brief Compute the minimal polynomial g of polynomial created from a @p seed. + * + * @param seed over the ring GF(q)[y] according to ISO Section 8.1 Step 3. + * + * @return g or std::nullopt if g has not full degree. + */ + std::optional compute_minimal_polynomial( + StrongSpan seed) const; + + private: + // Creates a ring element from a coefficient vector + Classic_McEliece_Polynomial create_element_from_coef(const std::vector& coeff_vec) const; + + /** + * @brief Create a polynomial from bytes according to ISO Section 8.1 step 1 and 2. + */ + Classic_McEliece_Polynomial create_element_from_bytes(std::span bytes) const; + + /// Represents F(y) by storing the non-zero terms + std::vector m_position_map; + + /// t in spec, i.e., degree of F(y) + size_t m_t; + + // f(z) in spec + CmceGfMod m_poly_f; +}; + +bool operator==(const Classic_McEliece_Polynomial_Ring::Big_F_Coefficient& lhs, + const Classic_McEliece_Polynomial_Ring::Big_F_Coefficient& rhs); + +} // namespace Botan +#endif diff --git a/src/lib/pubkey/classic_mceliece/cmce_types.h b/src/lib/pubkey/classic_mceliece/cmce_types.h new file mode 100644 index 00000000000..3754902bb5f --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_types.h @@ -0,0 +1,55 @@ +/* + * Classic McEliece Types + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_TYPES_H_ +#define BOTAN_CMCE_TYPES_H_ + +#include +#include +#include + +namespace Botan { + +/// Represents a GF(q) element +using CmceGfElem = Strong; + +/// Represents a GF(q) modulus +using CmceGfMod = Strong; + +/// Represents an element of a permuation (pi in spec). Used in field ordering creation. +using CmcePermutationElement = Strong; + +/// Represents a permutation (pi in spec). Used in field ordering creation. +using CmcePermutation = Strong, struct CmcePermutation_>; + +/// Represents initial delta of keygen +using CmceInitialSeed = Strong, struct CmceInitialSeed_>; + +/// Represents a delta (can be altered; final value stored in private key) +using CmceKeyGenSeed = Strong, struct CmceKeyGenSeed_>; + +// Represents the sigma_2*q bits of E=PRG(delta) used by the field ordering algorithm (see CMCE ISO 8.3 Step 4) +using CmceOrderingBits = Strong, struct CmceOrderingBits_>; + +// Represents the sigma_1*t bits of E=PRG(delta) used by the irreducible algorithm (see CMCE ISO 8.3 Step 5) +using CmceIrreducibleBits = Strong, struct CmceIrreducibleBits_>; + +/// Represents s of private key +using CmceRejectionSeed = Strong, struct CmceRejectionSeed_>; + +/// Represents c of private key +using CmceColumnSelection = Strong; + +/// Represents e of encapsulation +using CmceErrorVector = Strong; + +/// Represents C of decapsulation +using CmceCodeWord = Strong; + +} // namespace Botan +#endif // BOTAN_CMCE_TYPES_H_ diff --git a/src/lib/pubkey/classic_mceliece/info.txt b/src/lib/pubkey/classic_mceliece/info.txt new file mode 100644 index 00000000000..85472dad2f5 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/info.txt @@ -0,0 +1,30 @@ + +CLASSICMCELIECE -> 20231023 + + + +name -> "Classic McEliece" + + + +shake +shake_xof + + + +cmce_parameters.h +cmce_poly.h +cmce_matrix.h +cmce_gf.h +cmce_field_ordering.h +cmce_encaps.h +cmce_decaps.h +cmce_keys_internal.h +cmce_types.h + + + +cmce.h +cmce_parameter_set.h + + diff --git a/src/lib/pubkey/pk_algs.cpp b/src/lib/pubkey/pk_algs.cpp index 4f62db6150d..a58b882bac0 100644 --- a/src/lib/pubkey/pk_algs.cpp +++ b/src/lib/pubkey/pk_algs.cpp @@ -14,6 +14,10 @@ #include #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) + #include +#endif + #if defined(BOTAN_HAS_DSA) #include #endif @@ -206,6 +210,12 @@ std::unique_ptr load_public_key(const AlgorithmIdentifier& alg_id, } #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) || defined(BOTAN_HAS_CLASSICMCELIECE) + if(alg_name.starts_with("mceliece")) { + return std::make_unique(alg_id, key_bits); + } +#endif + throw Decoding_Error(fmt("Unknown or unavailable public key algorithm '{}'", alg_name)); } @@ -323,6 +333,12 @@ std::unique_ptr load_private_key(const AlgorithmIdentifier& alg_id, } #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) || defined(BOTAN_HAS_CLASSICMCELIECE) + if(alg_name.starts_with("mceliece")) { + return std::make_unique(alg_id, key_bits); + } +#endif + throw Decoding_Error(fmt("Unknown or unavailable public key algorithm '{}'", alg_name)); } @@ -413,6 +429,13 @@ std::unique_ptr create_private_key(std::string_view alg_name, return std::make_unique(rng, n, t); } #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) + if(alg_name == "ClassicMcEliece") { + auto cmce_params_set = + params.empty() ? Classic_McEliece_Parameter_Set::mceliece6960119f : cmce_param_set_from_str(params); + return std::make_unique(rng, cmce_params_set); + } +#endif #if defined(BOTAN_HAS_FRODOKEM) if(alg_name == "FrodoKEM") { diff --git a/src/lib/utils/bit_ops.h b/src/lib/utils/bit_ops.h index 504c363ef9e..888192b6583 100644 --- a/src/lib/utils/bit_ops.h +++ b/src/lib/utils/bit_ops.h @@ -118,6 +118,14 @@ inline constexpr size_t ctz(T n) return lb; } +template +inline constexpr T floor_log2(T n) + requires(std::is_unsigned::value) +{ + BOTAN_ARG_CHECK(n != 0, "log2(0) is not defined"); + return static_cast(high_bit(n) - 1); +} + template constexpr uint8_t ceil_log2(T x) requires(std::is_integral::value && sizeof(T) < 32) @@ -196,6 +204,40 @@ inline constexpr T majority(T a, T b, T c) { return choose(a ^ b, c, b); } +/** + * Calculates the number of 1-bits in an unsigned integer in constant-time. + * This operation is also known as "population count" or hamming weight. + * + * Modern compilers will recognize this pattern and replace it by a hardware + * instruction, if available. This is the SWAR (SIMD within a register) + * algorithm. See: https://nimrod.blog/posts/algorithms-behind-popcount/#swar-algorithm + * + * Note: C++20 provides std::popcount(), but there's no guarantee that this + * is implemented in constant-time. + * + * @param x an unsigned integer + * @returns the number of 1-bits in the provided value + */ +template +inline constexpr uint8_t ct_popcount(T x) { + constexpr size_t s = sizeof(T); + if constexpr(s < 4) { + return ct_popcount(static_cast(x)); + } else if constexpr(s == 4) { + x = x - ((x >> 1) & 0x55555555); + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); + x = (x + (x >> 4)) & 0x0F0F0F0F; + return (x * 0x01010101) >> 24; + } else if constexpr(s == 8) { + x = x - ((x >> 1) & 0x5555555555555555); + x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333); + x = (x + (x >> 4)) & 0xF0F0F0F0F0F0F0F; + return (x * 0x101010101010101) >> 56; + } else { + static_assert(!std::unsigned_integral, "T is not a suitable unsigned integer value"); + } +} + } // namespace Botan #endif diff --git a/src/lib/utils/bitvector.h b/src/lib/utils/bitvector.h new file mode 100644 index 00000000000..f4d22f8b5c6 --- /dev/null +++ b/src/lib/utils/bitvector.h @@ -0,0 +1,1296 @@ +/* + * An abstraction for an arbitrarily large bitvector that can + * optionally use the secure_allocator. + * + * (C) 2023-2024 Jack Lloyd + * (C) 2023-2024 René Meusel, Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_BIT_VECTOR_H_ +#define BOTAN_BIT_VECTOR_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace Botan { + +template