diff --git a/doc/api_ref/pubkey.rst b/doc/api_ref/pubkey.rst index c8299aee6f..5bae0da9e6 100644 --- a/doc/api_ref/pubkey.rst +++ b/doc/api_ref/pubkey.rst @@ -212,6 +212,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 ~~~~~~~~ @@ -1205,6 +1210,7 @@ Botan implements the following KEM schemes: #. ML-KEM (Kyber) #. FrodoKEM #. McEliece +#. Classic McEliece .. _mlkem_example: @@ -1271,6 +1277,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 da9a8a240f..4bd9a553cf 100644 --- a/doc/credits.rst +++ b/doc/credits.rst @@ -15,7 +15,7 @@ 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: SLH-DSA, Ed/X448, HSS/LMS, ML-KEM, ML-DSA, TLS-Anvil tests + D: SLH-DSA, Ed/X448, HSS/LMS, ML-KEM, ML-DSA, Classic McEliece, TLS-Anvil tests S: Bochum, Germany N: Alexander Bluhm @@ -159,7 +159,7 @@ snail-mail address (S), and Bitcoin address (B). N: Amos Treiber E: amos.treiber@rohde-schwarz.com W: https://www.rohde-schwarz.com/cybersecurity - D: SLH-DSA, TPM 2.0, FrodoKEM, ML-KEM, ML-DSA + D: SLH-DSA, TPM 2.0, FrodoKEM, Classic McEliece, ML-KEM, ML-DSA S: Cologne, Germany N: Daniel Seither diff --git a/doc/dev_ref/oids.rst b/doc/dev_ref/oids.rst index b6c31bda7d..e5debc4bdd 100644 --- a/doc/dev_ref/oids.rst +++ b/doc/dev_ref/oids.rst @@ -86,6 +86,15 @@ Values currently assigned are:: HSS-LMS-Private-Key OBJECT IDENTIFIER ::= { publicKey 13 } + 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 df8914fc56..94edfc773f 100644 --- a/src/build-data/oids.txt +++ b/src/build-data/oids.txt @@ -1,6 +1,6 @@ # 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] +# (if you modified something under [dn]) # Public key types [pubkey] @@ -98,6 +98,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/perf_pk_kem.cpp b/src/cli/perf_pk_kem.cpp index d0b88c626a..091701554f 100644 --- a/src/cli/perf_pk_kem.cpp +++ b/src/cli/perf_pk_kem.cpp @@ -150,4 +150,37 @@ BOTAN_REGISTER_PERF_TEST("FrodoKEM", PerfTest_FrodoKEM); #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) + +class PerfTest_Classic_McEliece final : public PerfTest_PK_KEM { + public: + std::string algo() const override { return "ClassicMcEliece"; } + + std::vector keygen_params(const PerfConfig& config) const override { + BOTAN_UNUSED(config); + return { + "mceliece348864", + "mceliece348864f", + "mceliece460896", + "mceliece460896f", + "mceliece6688128", + "mceliece6688128f", + "mceliece6688128pc", + "mceliece6688128pcf", + "mceliece6960119", + "mceliece6960119f", + "mceliece6960119pc", + "mceliece6960119pcf", + "mceliece8192128", + "mceliece8192128f", + "mceliece8192128pc", + "mceliece8192128pcf", + }; + } +}; + +BOTAN_REGISTER_PERF_TEST("ClassicMcEliece", PerfTest_Classic_McEliece); + +#endif + } // namespace Botan_CLI diff --git a/src/lib/asn1/oid_maps.cpp b/src/lib/asn1/oid_maps.cpp index ce97ee52c0..56b02e1c09 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 2024-10-15 +* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2024-10-18 * * All manual edits to this file will be lost. Edit the script * then regenerate this source file. @@ -142,6 +142,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"}, @@ -179,6 +189,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"}, @@ -621,6 +637,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})}, {"numsp256d1", OID({1, 3, 6, 1, 4, 1, 25258, 4, 1})}, {"numsp384d1", OID({1, 3, 6, 1, 4, 1, 25258, 4, 2})}, {"numsp512d1", OID({1, 3, 6, 1, 4, 1, 25258, 4, 3})}, diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index c9341501bc..f97f222a07 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -1556,6 +1556,22 @@ int botan_privkey_load_frodokem(botan_privkey_t* key, const uint8_t privkey[], s BOTAN_FFI_EXPORT(3, 6) int botan_pubkey_load_frodokem(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* frodo_mode); +/** +* Algorithm specific key operation: Classic McEliece +*/ + +BOTAN_FFI_EXPORT(3, 6) +int botan_privkey_load_classic_mceliece(botan_privkey_t* key, + const uint8_t privkey[], + size_t key_len, + const char* cmce_mode); + +BOTAN_FFI_EXPORT(3, 6) +int botan_pubkey_load_classic_mceliece(botan_pubkey_t* key, + const uint8_t pubkey[], + size_t key_len, + const char* cmce_mode); + /* * Algorithm specific key operations: ML-KEM */ diff --git a/src/lib/ffi/ffi_pkey_algs.cpp b/src/lib/ffi/ffi_pkey_algs.cpp index e9e914f666..201a65b9f7 100644 --- a/src/lib/ffi/ffi_pkey_algs.cpp +++ b/src/lib/ffi/ffi_pkey_algs.cpp @@ -91,6 +91,10 @@ #include #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) + #include +#endif + namespace { #if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO) @@ -1243,6 +1247,56 @@ int botan_pubkey_load_frodokem(botan_pubkey_t* key, const uint8_t pubkey[], size #endif } +/* +* Algorithm specific key operations : Classic McEliece +*/ + +int botan_privkey_load_classic_mceliece(botan_privkey_t* key, + const uint8_t privkey[], + size_t key_len, + const char* cmce_mode) { +#if defined(BOTAN_HAS_CLASSICMCELIECE) + if(key == nullptr || privkey == nullptr || cmce_mode == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + *key = nullptr; + + return ffi_guard_thunk(__func__, [=]() -> int { + const auto mode = Botan::Classic_McEliece_Parameter_Set::from_string(cmce_mode); + auto cmce_key = std::make_unique(std::span{privkey, key_len}, mode); + *key = new botan_privkey_struct(std::move(cmce_key)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(key, privkey, key_len, cmce_mode); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_pubkey_load_classic_mceliece(botan_pubkey_t* key, + const uint8_t pubkey[], + size_t key_len, + const char* cmce_mode) { +#if defined(BOTAN_HAS_CLASSICMCELIECE) + if(key == nullptr || pubkey == nullptr || cmce_mode == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + *key = nullptr; + + return ffi_guard_thunk(__func__, [=]() -> int { + const auto mode = Botan::Classic_McEliece_Parameter_Set::from_string(cmce_mode); + auto cmce_key = std::make_unique(std::span{pubkey, key_len}, mode); + *key = new botan_pubkey_struct(std::move(cmce_key)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(key, pubkey, key_len, cmce_mode); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + int botan_pubkey_view_ec_public_point(const botan_pubkey_t key, botan_view_ctx ctx, botan_view_bin_fn view) { #if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO) return BOTAN_FFI_VISIT(key, [=](const auto& k) -> int { diff --git a/src/lib/pubkey/classic_mceliece/cmce.cpp b/src/lib/pubkey/classic_mceliece/cmce.cpp new file mode 100644 index 0000000000..aaf24fb0c1 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce.cpp @@ -0,0 +1,142 @@ +/* + * 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, Classic_McEliece_Parameter_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 { + // The key length is the dimension k of the goppa code (i.e. the code has 2^k codewords) + return m_public->params().pk_no_cols(); +} + +size_t Classic_McEliece_PublicKey::estimated_strength() const { + return m_public->params().estimated_strength(); +} + +std::vector Classic_McEliece_PublicKey::public_key_bits() const { + return raw_public_key_bits(); +} + +std::vector Classic_McEliece_PublicKey::raw_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); + const auto seed = rng.random_vec(params.seed_len()); + CT::poison(seed); + std::tie(m_private, m_public) = Classic_McEliece_KeyPair_Internal::generate(params, seed).decompose_to_pair(); + + BOTAN_ASSERT_NONNULL(m_private); + BOTAN_ASSERT_NONNULL(m_public); + CT::unpoison_all(*m_private, *m_public); +} + +Classic_McEliece_PrivateKey::Classic_McEliece_PrivateKey(std::span sk, + Classic_McEliece_Parameter_Set param_set) { + auto scope = CT::scoped_poison(sk); + 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); + CT::unpoison_all(*m_public, *m_private); +} + +Classic_McEliece_PrivateKey::Classic_McEliece_PrivateKey(const AlgorithmIdentifier& alg_id, + std::span key_bits) : + Classic_McEliece_PrivateKey(key_bits, Classic_McEliece_Parameter_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 { + return m_private->check_key(); +} + +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 0000000000..7c7ee4cf06 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce.h @@ -0,0 +1,145 @@ +/* + * 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, 7) 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; + + std::vector raw_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, 7) 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; + + 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_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 0000000000..c31a65ad47 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_decaps.cpp @@ -0,0 +1,168 @@ +/* + * 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::expand(static_cast(code_word.at(i))); + + 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.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 scope = CT::scoped_poison(*m_key); + + 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); + CT::unpoison(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 0000000000..501560fbfc --- /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 0000000000..de88c5430b --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_encaps.cpp @@ -0,0 +1,131 @@ +/* + * 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()); + CT::poison(rand); + 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; + bool d_in_range = d < params.n(); + CT::unpoison(d_in_range); + if(d_in_range && 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) { + bool a_i_j_equal = a_values.at(i) == a_values.at(j); + CT::unpoison(a_i_j_equal); + if(a_i_j_equal) { + 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); + CT::unpoison_all(out_encapsulated_key, 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 0000000000..d483bb868d --- /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 0000000000..d563edb371 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp @@ -0,0 +1,328 @@ +/* + * 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 + +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(std::span vec_1, std::span 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::span>& 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) { + BOTAN_DEBUG_ASSERT(vec.size() < std::numeric_limits::max()); + + 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). + * + * @param a The vector that is sorted + * + * @return (pi sorted after a, a sorted after pi) + */ +std::pair, 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(std::span(a_pi_zipped)); + + return std::make_pair(a, 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) { + auto reversed_bits = ct_reverse_bits(pi_elem.get()); + reversed_bits >>= (sizeof(uint16_t) * 8 - m); + return Classic_McEliece_GF(CmceGfElem(reversed_bits), modulus); +} + +/** + * @brief Part of field ordering generation according to ISO 9.2.10 + */ +secure_vector composeinv(std::span c, std::span 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 (see Classic McEliece reference implementation) + */ +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); +} + +CT::Choice ct_has_adjacent_duplicates(std::span vec) { + CT::Mask mask = CT::Mask::cleared(); + for(size_t i = 0; i < vec.size() - 1; ++i) { + mask |= CT::Mask::is_equal(vec[i], vec[i + 1]); + } + return mask.as_choice(); +} + +} // 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"); + + auto a = load_le>(random_bits); // contains a_0, a_1, ... + auto [sorted_a, pi] = create_pi(std::move(a)); + if(ct_has_adjacent_duplicates(sorted_a).as_bool()) { + 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]); + 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)); + p_counter += CT::Mask::expand(pivots.at(col)).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 0000000000..fed4bea44d --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_field_ordering.h @@ -0,0 +1,116 @@ +/* + * 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); + + void _const_time_poison() const { CT::poison(m_pi); } + + void _const_time_unpoison() const { CT::unpoison(m_pi); } + + 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 0000000000..b388d1b743 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_gf.cpp @@ -0,0 +1,84 @@ +/* +* 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 { +// Only for moduli 0b0010000000011011 and 0b0001000000001001 +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)); + } + BOTAN_ASSERT_UNREACHABLE(); +} + +} // 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 * CT::value_barrier(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 0000000000..ad56ee4660 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_gf.h @@ -0,0 +1,204 @@ +/* + * 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_DEBUG_ASSERT(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_DEBUG_ASSERT(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_DEBUG_ASSERT(m_modulus == other.m_modulus); + m_elem ^= other.m_elem; + return *this; + } + + /** + * @brief Multiply the element by @p other in GF(q). Constant time. + */ + Classic_McEliece_GF& operator*=(Classic_McEliece_GF other) { + BOTAN_DEBUG_ASSERT(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: + template + static GF_Mask expand(T v) { + return GF_Mask(CT::Mask::expand(v)); + } + + static GF_Mask expand(Classic_McEliece_GF v) { return 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 0000000000..8d1b3ba6f8 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp @@ -0,0 +1,169 @@ +/* + * 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); +} + +bool Classic_McEliece_PrivateKeyInternal::check_key() const { + auto prg = m_params.prg(m_delta); + + const auto s = prg->output(m_params.n() / 8); + const auto ordering_bits = prg->output((m_params.sigma2() * m_params.q()) / 8); + const auto irreducible_bits = prg->output((m_params.sigma1() * m_params.t()) / 8); + + // Recomputing s as hash of delta + auto ret = CT::Mask::expand(CT::is_equal(s.data(), m_s.data(), m_params.n() / 8)); + + // Checking weight of c + ret &= CT::Mask::is_equal(c().hamming_weight(), 32); + + if(auto g = m_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_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_params, ordering_bits)) { + field_ord_from_seed->permute_with_pivots(m_params, c()); + ret &= CT::Mask::expand(field_ord_from_seed->ct_is_equal(field_ordering())); + } else { + ret = CT::Mask::cleared(); + } + + return ret.as_bool(); +} + +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(); + + // There should not be a pivot other than 0xff ff ff ff 00 00 00 00. + // Otherwise the gauss algorithm failed effectively. + const auto pivot_is_valid = (CT::Mask::expand(pivot.subvector(0, pivot.size() / 2).all()) & + CT::Mask::expand(pivot.subvector(pivot.size() / 2).none())) + .as_choice(); + if(!pivot_is_valid.as_bool()) { + 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 0000000000..0f41a8a434 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_keys_internal.h @@ -0,0 +1,212 @@ +/* + * 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; } + + constexpr void _const_time_unpoison() const { CT::unpoison(m_matrix); } + + 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; } + + /** + * @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() const; + + constexpr void _const_time_poison() const { CT::poison_all(m_delta, m_c, m_g, m_field_ordering, m_s); } + + constexpr void _const_time_unpoison() const { CT::unpoison_all(m_delta, m_c, m_g, m_field_ordering, m_s); } + + 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 0000000000..fbd1c8f4e1 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp @@ -0,0 +1,296 @@ +/* + * 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 { + +CT::Mask bit_at_mask(uint64_t val, size_t pos) { + return CT::Mask::expand((static_cast(1) << pos) & val); +} + +/// Swaps bit i with bit j in val +void swap_bits(uint64_t& val, size_t i, size_t j) { + uint64_t bit_i = CT::value_barrier((val >> i) & 1); + uint64_t bit_j = CT::value_barrier((val >> j) & 1); + uint64_t xor_sum = bit_i ^ bit_j; + val ^= (xor_sum << i); + val ^= (xor_sum << j); +} + +size_t count_lsb_zeros(uint64_t n) { + size_t res = 0; + auto found_only_zeros = Botan::CT::Mask::set(); + for(size_t bit_pos = 0; bit_pos < sizeof(uint64_t) * 8; ++bit_pos) { + auto bit_set_mask = bit_at_mask(n, bit_pos); + found_only_zeros &= ~bit_set_mask; + res += static_cast(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"); + + const size_t pos_offset = params.pk_no_rows() - Classic_McEliece_Parameters::mu(); + + // Get the area of the matrix that needs to be (potentially) swapped. + // Its the sub m*t x nu matrix at column m*t - mu. For const time reasons, + // the sub-matrix is represented as an array of uint64_ts, where the 1st + // bit is the least significant bit + std::vector matrix_swap_area; + matrix_swap_area.reserve(params.pk_no_rows()); + for(size_t i = 0; i < params.pk_no_rows(); ++i) { + matrix_swap_area.push_back(mat[i].subvector(pos_offset)); + } + + // To find which columns need to be swapped to allow for a systematic matrix form, we need to + // investigate how a gauss algorithm affects the last mu rows of the swap area. + std::array sub_mat; + + // Extract the bottom mu x nu matrix at offset pos_offset + for(size_t i = 0; i < Classic_McEliece_Parameters::mu(); i++) { + sub_mat[i] = matrix_swap_area[pos_offset + i]; + } + + 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); + } + + auto semi_systematic_form_failed = CT::Mask::is_zero(row_acc); + if(semi_systematic_form_failed.as_choice().as_bool()) { + // 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 + size_t current_pivot_idx = count_lsb_zeros(row_acc); + pivot_indices.at(row_idx) = current_pivot_idx; + + // 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) { + // Add next row if the pivot bit is still zero + auto add_next_row_mask = ~bit_at_mask(sub_mat.at(row_idx), current_pivot_idx); + sub_mat.at(row_idx) ^= add_next_row_mask.if_set_return(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) { + // Add the current row to next_row if the pivot bit of next_row is set + auto add_to_next_row_mask = bit_at_mask(sub_mat.at(next_row), current_pivot_idx); + sub_mat.at(next_row) ^= add_to_next_row_mask.if_set_return(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()); + } + } + + // Swap the rows so the matrix can be transformed into systematic form + for(size_t mat_row = 0; mat_row < params.pk_no_rows(); ++mat_row) { + for(size_t col = 0; col < Classic_McEliece_Parameters::mu(); ++col) { + swap_bits(matrix_swap_area.at(mat_row), col, pivot_indices.at(col)); + } + } + + // Reinsert the swapped columns into the matrix + for(size_t row = 0; row < params.pk_no_rows(); ++row) { + mat[row].subvector_replace(pos_offset, matrix_swap_area[row]); + } + + 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); + bool move_columns_failed = !ret_pivots.has_value(); + CT::unpoison(move_columns_failed); + if(move_columns_failed) { + 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).as_choice(), 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. + bool diag_bit_zero = !mat[diag_pos].at(diag_pos); + CT::unpoison(diag_bit_zero); + if(diag_bit_zero) { + 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).as_choice(), 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); + + auto gauss_failed = !pivots.has_value(); + CT::unpoison(gauss_failed); + if(gauss_failed) { + 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); + + bool matrix_creation_failed = !pk_matrix_and_pivots.has_value(); + CT::unpoison(matrix_creation_failed); + if(matrix_creation_failed) { + 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().as_bool(); + } + + 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 0000000000..3d524b5635 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_matrix.h @@ -0,0 +1,116 @@ +/* + * 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; + + constexpr void _const_time_unpoison() const { CT::unpoison(m_mat_bytes); } + + 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 0000000000..0306cd8e2e --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_parameter_set.cpp @@ -0,0 +1,111 @@ +/* + * 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 Classic_McEliece_Parameter_Set::from_string(std::string_view param_name) { + Code code = [&] { + if(param_name == "mceliece348864") { + return mceliece348864; + } + if(param_name == "mceliece348864f") { + return mceliece348864f; + } + if(param_name == "mceliece460896") { + return mceliece460896; + } + if(param_name == "mceliece460896f") { + return mceliece460896f; + } + if(param_name == "mceliece6688128") { + return mceliece6688128; + } + if(param_name == "mceliece6688128f") { + return mceliece6688128f; + } + if(param_name == "mceliece6688128pc") { + return mceliece6688128pc; + } + if(param_name == "mceliece6688128pcf") { + return mceliece6688128pcf; + } + if(param_name == "mceliece6960119") { + return mceliece6960119; + } + if(param_name == "mceliece6960119f") { + return mceliece6960119f; + } + if(param_name == "mceliece6960119pc") { + return mceliece6960119pc; + } + if(param_name == "mceliece6960119pcf") { + return mceliece6960119pcf; + } + if(param_name == "mceliece8192128") { + return mceliece8192128; + } + if(param_name == "mceliece8192128f") { + return mceliece8192128f; + } + if(param_name == "mceliece8192128pc") { + return mceliece8192128pc; + } + if(param_name == "mceliece8192128pcf") { + return mceliece8192128pcf; + } + + throw Decoding_Error("Cannot convert string to CMCE parameter set"); + }(); + return Classic_McEliece_Parameter_Set(code); +} + +std::string Classic_McEliece_Parameter_Set::to_string() const { + switch(m_code) { + case mceliece348864: + return "mceliece348864"; + case mceliece348864f: + return "mceliece348864f"; + case mceliece460896: + return "mceliece460896"; + case mceliece460896f: + return "mceliece460896f"; + case mceliece6688128: + return "mceliece6688128"; + case mceliece6688128f: + return "mceliece6688128f"; + case mceliece6688128pc: + return "mceliece6688128pc"; + case mceliece6688128pcf: + return "mceliece6688128pcf"; + case mceliece6960119: + return "mceliece6960119"; + case mceliece6960119f: + return "mceliece6960119f"; + case mceliece6960119pc: + return "mceliece6960119pc"; + case mceliece6960119pcf: + return "mceliece6960119pcf"; + case mceliece8192128: + return "mceliece8192128"; + case mceliece8192128f: + return "mceliece8192128f"; + case mceliece8192128pc: + return "mceliece8192128pc"; + case mceliece8192128pcf: + return "mceliece8192128pcf"; + } + BOTAN_ASSERT_UNREACHABLE(); +} + +Classic_McEliece_Parameter_Set Classic_McEliece_Parameter_Set::from_oid(const OID& oid) { + return from_string(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 0000000000..28d514f6d7 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_parameter_set.h @@ -0,0 +1,83 @@ +/* + * 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. + */ +class BOTAN_PUBLIC_API(3, 4) Classic_McEliece_Parameter_Set { + public: + enum class Code { + 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 + }; + + using enum Code; + + Classic_McEliece_Parameter_Set(Code code) : m_code(code) {} + + /** + * @brief Get the parameter set for a given parameter set name. + */ + static Classic_McEliece_Parameter_Set from_string(std::string_view param_name); + + /** + * @brief Get the parameter set name for a given parameter set. + */ + std::string to_string() const; + + /** + * @brief Get the parameter set for a given OID. + */ + static Classic_McEliece_Parameter_Set from_oid(const OID& oid); + + /** + * @brief Get the code for a given parameter set. + */ + Code code() const { return m_code; } + + bool operator==(const Classic_McEliece_Parameter_Set& other) const { return m_code == other.m_code; } + + private: + const Code m_code; +}; + +} // 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 0000000000..2303739a7b --- /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.code()) { + 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.code()) { + 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.code()) { + 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(Classic_McEliece_Parameter_Set::from_string(name)); +} + +Classic_McEliece_Parameters Classic_McEliece_Parameters::create(const OID& oid) { + return create(Classic_McEliece_Parameter_Set::from_oid(oid)); +} + +OID Classic_McEliece_Parameters::object_identifier() const { + return OID::from_string(m_set.to_string()); +} + +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.code()) { + 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 0000000000..3dcc08111e --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_parameters.h @@ -0,0 +1,281 @@ +/* + * 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 + +namespace Botan { + +struct Classic_McEliece_Big_F_Coefficient; +class Classic_McEliece_Polynomial_Ring; + +/** + * 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(m() * t(), 8); } + + /** + * @brief The byte size of the hash output. + * + * This is also the size of the shared key K that is a hash output. + */ + static constexpr size_t hash_out_bytes() { return ell() / 8; } + + /** + * @brief The byte size of the ciphertext. + */ + size_t ciphertext_size() const { + if(is_pc()) { + // C_0 + C_1 + return encode_out_size() + hash_out_bytes(); + } else { + return encode_out_size(); + } + } + + /** + * @brief The underlying polynomial ring. + */ + const Classic_McEliece_Polynomial_Ring& poly_ring() const { return m_poly_ring; } + + /** + * @brief Create a seeded XOF object representing Classic McEliece's PRG. + * See Classic McEliece ISO 9.1. + * + * @param seed The seed used for the XOF. + */ + std::unique_ptr 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 0000000000..db2ea95af7 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_poly.cpp @@ -0,0 +1,155 @@ +/* + * 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 { + +Classic_McEliece_GF Classic_McEliece_Polynomial::operator()(Classic_McEliece_GF a) const { + BOTAN_DEBUG_ASSERT(a.modulus() == coef_at(0).modulus()); + + 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"); + return create_element_from_coef(load_le>(bytes)); +} + +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>( + 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)); + } + } + + const bool is_zero_at_diagonal = mat.at(j).coef_at(j).is_zero(); + CT::unpoison(is_zero_at_diagonal); + if(is_zero_at_diagonal) { + // Fail if not systematic. New rejection sampling iteration starts. + 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); + const auto coef_vec = load_le>(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 0000000000..38f308b250 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_poly.h @@ -0,0 +1,176 @@ +/* + * 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; } + + void _const_time_poison() const { CT::poison(m_coef); } + + void _const_time_unpoison() const { CT::unpoison(m_coef); } + + 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 0000000000..3754902bb5 --- /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 0000000000..85472dad2f --- /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 134ff8d0bb..e4a9504f3f 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 @@ -266,6 +270,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)); } @@ -419,6 +429,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)); } @@ -515,6 +531,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 + : Classic_McEliece_Parameter_Set::from_string(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/types.h b/src/lib/utils/types.h index 4d08213425..521913a21a 100644 --- a/src/lib/utils/types.h +++ b/src/lib/utils/types.h @@ -57,7 +57,7 @@ namespace Botan { * @ref dlies.h "DLIES", @ref ecies.h "ECIES", @ref elgamal.h "ElGamal", * @ref rsa.h "RSA", @ref mceliece.h "McEliece", @ref sm2.h "SM2" *
Key Encapsulation Mechanisms
-* @ref frodokem.h "FrodoKEM", @ref kyber.h "ML-KEM/Kyber", @ref rsa.h "RSA" +* @ref cmce.h "Classic McEliece", @ref frodokem.h "FrodoKEM", @ref kyber.h "ML-KEM/Kyber", @ref rsa.h "RSA" *
Public Key Signature Schemes
* @ref dsa.h "DSA", @ref dilithium.h "ML-DSA/Dilithium", @ref ecdsa.h "ECDSA", @ref ecgdsa.h "ECGDSA", * @ref eckcdsa.h "ECKCDSA", @ref gost_3410.h "GOST 34.10-2001", @ref hss_lms.h "HSS/LMS", @ref sm2.h "SM2", diff --git a/src/python/botan3.py b/src/python/botan3.py index 2ea33e46ed..4bfc63fa0d 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -371,6 +371,8 @@ def ffi_api(fn, args, allowed_errors=None): ffi_api(dll.botan_pubkey_load_ml_kem, [c_void_p, c_void_p, c_int, c_char_p]) ffi_api(dll.botan_privkey_load_frodokem, [c_void_p, c_void_p, c_int, c_char_p]) ffi_api(dll.botan_pubkey_load_frodokem, [c_void_p, c_void_p, c_int, c_char_p]) + ffi_api(dll.botan_privkey_load_classic_mceliece, [c_void_p, c_void_p, c_int, c_char_p]) + ffi_api(dll.botan_pubkey_load_classic_mceliece, [c_void_p, c_void_p, c_int, c_char_p]) ffi_api(dll.botan_privkey_load_ecdsa, [c_void_p, c_void_p, c_char_p]) ffi_api(dll.botan_pubkey_load_ecdsa, [c_void_p, c_void_p, c_void_p, c_char_p]) ffi_api(dll.botan_pubkey_load_ecdh, [c_void_p, c_void_p, c_void_p, c_char_p]) @@ -1264,6 +1266,12 @@ def load_frodokem(cls, frodo_mode, key): _DLL.botan_pubkey_load_frodokem(byref(obj), key, len(key), _ctype_str(frodo_mode)) return PublicKey(obj) + @classmethod + def load_classic_mceliece(cls, cmce_mode, key): + obj = c_void_p(0) + _DLL.botan_pubkey_load_classic_mceliece(byref(obj), key, len(key), _ctype_str(cmce_mode)) + return PublicKey(obj) + def __del__(self): _DLL.botan_pubkey_destroy(self.__obj) @@ -1447,6 +1455,12 @@ def load_frodokem(cls, frodo_mode, key): _DLL.botan_privkey_load_frodokem(byref(obj), key, len(key), _ctype_str(frodo_mode)) return PrivateKey(obj) + @classmethod + def load_classic_mceliece(cls, cmce_mode, key): + obj = c_void_p(0) + _DLL.botan_privkey_load_classic_mceliece(byref(obj), key, len(key), _ctype_str(cmce_mode)) + return PrivateKey(obj) + def __del__(self): _DLL.botan_privkey_destroy(self.__obj) diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 33262f1055..ddca625566 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -993,6 +993,21 @@ def test_frodokem_raw_keys(self): self.assertEqual(sk_read.to_raw(), sk_bits) self.assertEqual(pk_read.to_raw(), pk_bits) + def test_classic_mceliece_raw_keys(self): + cmce_mode = "mceliece348864f" + sk = botan.PrivateKey.create("ClassicMcEliece", cmce_mode, botan.RandomNumberGenerator("user")) + pk = sk.get_public_key() + + sk_bits = sk.to_raw() + pk_bits = pk.to_raw() + + sk_read = botan.PrivateKey.load_classic_mceliece(cmce_mode, sk_bits) + pk_read = botan.PublicKey.load_classic_mceliece(cmce_mode, pk_bits) + + self.assertEqual(sk_read.to_raw(), sk_bits) + self.assertEqual(pk_read.to_raw(), pk_bits) + + class BotanPythonZfecTests(unittest.TestCase): """ Tests relating to the ZFEC bindings diff --git a/src/tests/data/pubkey/cmce_kat_hashed.vec b/src/tests/data/pubkey/cmce_kat_hashed.vec new file mode 100644 index 0000000000..5d5f6852b3 --- /dev/null +++ b/src/tests/data/pubkey/cmce_kat_hashed.vec @@ -0,0 +1,148 @@ +# The first KAT for all ClaSSic McEliece instances (source: https://claSSic.mceliece.org/nist.html) +# The plaintext confirmation (pc) KATs are created using a modified reference implementation +# The public and secret keys are hashed via SHAKE256(512) + +[mceliece348864] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = B4F9FF1E4390E3BE0BBCEBFF9A525AE83B191211896AA8786CE8BC511C9F78C3 +PK = 2615e458cdda9626d09719ae81a1abf2ca9295d51b256843eb73faead8bcad60ee4fbe5419b2c906ae00d9c60328ff835697b19f78a6974269e8dd7c89027ca8 +SK = e7a139f9670fff672f75b37b303a289fa45e50acb038d43f655a475053d130334713d965f0c55741d1d866321a17b7918b759ceb235be5368844ad532264b568 +CT = DEF61908A70A3099E45B4D5D91957ADE70F571D210D525D655DB7294515F91D97795F2353615BC7CDF13502181E5BCC8C9ABFEF31819D66DD2760363694F789602264A3E24445681A0183CE343A2264FDFF96C82AB318AE888D105D52D59BC1B + +[mceliece348864f] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = 4B5EA75DD51BE56BE739F6EC6BABC2CBE538683303B05934D33D93256D1AB6EF +PK = 77bdeafa05a162bc2ceb46b8e548022684eaef422e6ce123cb990edf44093fd225dc0d4045be3162c8f7062f545217a2858b41c092c915d5f15b05954c0360dd +SK = 3617d714cd395b19d4f08c6bc255666444c3f40a07f02d32abe725db1fbb7db2ef9b3e17f723c5f20c8b1e256738383442e634111320fb751f44b8fe858a1d6b +CT = E205BB2814DED1582864F2B1D2A26397411EE4E61F6998FF61CD55E4C4FB35AB99788D00F42D2D3B79B0820035749776CAA82730B1EBE2B81230424FCBCB8B5A804B0FA3025B108175456F80F4ABD1786C5DB02C6564333DE9FE67ED4A92D6FE + +# TODO: Not standardized +# [mceliece348864pc] +# Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +# SS = 56EA8D2982F408DF1DE8465FFD9A77DE027CC22374C007809F3691D97613812C +# PK = 2615e458cdda9626d09719ae81a1abf2ca9295d51b256843eb73faead8bcad60ee4fbe5419b2c906ae00d9c60328ff835697b19f78a6974269e8dd7c89027ca8 +# SK = e7a139f9670fff672f75b37b303a289fa45e50acb038d43f655a475053d130334713d965f0c55741d1d866321a17b7918b759ceb235be5368844ad532264b568 +# CT = DEF61908A70A3099E45B4D5D91957ADE70F571D210D525D655DB7294515F91D97795F2353615BC7CDF13502181E5BCC8C9ABFEF31819D66DD2760363694F789602264A3E24445681A0183CE343A2264FDFF96C82AB318AE888D105D52D59BC1BB2A44DB7A3CF1FBFFFEB7E0625701D97B78638E8ECC3E91FEF7327CD118397C0 + +# TODO: Not standardized +# [mceliece348864pcf] +# Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +# SS = 5EA83EC4E9A96CC843CDDD0B9133134FB59EC348EB53440844A2C6D634FE677C +# PK = 77bdeafa05a162bc2ceb46b8e548022684eaef422e6ce123cb990edf44093fd225dc0d4045be3162c8f7062f545217a2858b41c092c915d5f15b05954c0360dd +# SK = 3617d714cd395b19d4f08c6bc255666444c3f40a07f02d32abe725db1fbb7db2ef9b3e17f723c5f20c8b1e256738383442e634111320fb751f44b8fe858a1d6b +# CT = E205BB2814DED1582864F2B1D2A26397411EE4E61F6998FF61CD55E4C4FB35AB99788D00F42D2D3B79B0820035749776CAA82730B1EBE2B81230424FCBCB8B5A804B0FA3025B108175456F80F4ABD1786C5DB02C6564333DE9FE67ED4A92D6FEB2A44DB7A3CF1FBFFFEB7E0625701D97B78638E8ECC3E91FEF7327CD118397C0 + +[mceliece460896] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = 132D477D0C24306181C6AD01590D39BE9B2404ED32CCBE0EB1F169680212CC1C +PK = 9b3fdd46b5c7bd7aa0c637b37675af5cc376a6f7fcf0bde389c8cedaa764913a0c3a22044e70b56f54f456db717f985d3f3894d65ff2010d7727b3ab8c2e01ed +SK = 1b3274243449643ed4d7cb4364cd094a3f602a9c3d9762896d215df673ee3460ae8dd85022d217bef729e5eb279f2e6c40745e38ef2c4db3d70de1cd1489e573 +CT = CF78C42A38795E0F5D6BAC38ACDEE6C4C9536F93BCC32E08B8CE0B886E737AA5AD51CC0E2E5B9176B67F0327EA117334DCD5664ADCFFB39F1932C498B210A56EB5C9E9C7C5DB03DC46C5D2450D1F05C152533BE30AA544F20FF11CAC1FFEBB919D69B033642AC0ABC1C174AFCBE9F22433A5D3E2048621A7982CC08D5D9E37BC65ABE96DF8A651758894B6E58A34E42CB82798BE3FD7B3D96DE27E65 + +[mceliece460896f] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = 89F6BDB539A46E0DF0D8BE3BEDABCF11A1D0C8F68E707F97081826B5A78A7EA5 +PK = ef60b9e0d9b8b34ec6001c4711d66daeadf41198746869b10baf63c0e5a24ac184b5a1cc595c56bdb696651032cbcf038cd2da5da5448cb9bdcfe42cf945ae61 +SK = e51b4f6e8c8e61d3b9c751fde190b17ba899f96803f73b85613d62a650492af4c7829341befee710105ed5713f01a5035ed9b6aed4dfcb94c818c680c8466778 +CT = BCF3C98E2EC96F127540B844F4DF0B176E2460C97D6EB82423B3833AEFF0680FC4B3F758E3A6FA03A23D8419CA0B464191AB245CA5C7E112DF24FCE728C40B414DA2F6B058796774DA463966AC5FD21476350E46C3CCD07A317A33DC29132809BAA255A41D6456D01301AC08C94B2D57148CEA41E7AFE036F17D3CE62F46EC31C7FEB07DED1767F861389EC89180E107698AFAFA0976381F04A0CA06 + +# TODO: Not standardized +# [mceliece460896pc] +# Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +# SS = F6EB7975CC7AD7438DBE220C72DE9FDB7717161D8A6AA461666B767455847EE9 +# PK = 9b3fdd46b5c7bd7aa0c637b37675af5cc376a6f7fcf0bde389c8cedaa764913a0c3a22044e70b56f54f456db717f985d3f3894d65ff2010d7727b3ab8c2e01ed +# SK = 1b3274243449643ed4d7cb4364cd094a3f602a9c3d9762896d215df673ee3460ae8dd85022d217bef729e5eb279f2e6c40745e38ef2c4db3d70de1cd1489e573 +# CT = CF78C42A38795E0F5D6BAC38ACDEE6C4C9536F93BCC32E08B8CE0B886E737AA5AD51CC0E2E5B9176B67F0327EA117334DCD5664ADCFFB39F1932C498B210A56EB5C9E9C7C5DB03DC46C5D2450D1F05C152533BE30AA544F20FF11CAC1FFEBB919D69B033642AC0ABC1C174AFCBE9F22433A5D3E2048621A7982CC08D5D9E37BC65ABE96DF8A651758894B6E58A34E42CB82798BE3FD7B3D96DE27E651585121A060E712178A6218AF3907BC3F8BCDE02E8EAF5769C9E790274267B37 + +# TODO: Not standardized +# [mceliece460896pcf] +# Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +# SS = 5ADC9C0EE763A675F49DD50B9B59C9C6F920D6B9BD298F6106A40E19B5CAD2AC +# PK = ef60b9e0d9b8b34ec6001c4711d66daeadf41198746869b10baf63c0e5a24ac184b5a1cc595c56bdb696651032cbcf038cd2da5da5448cb9bdcfe42cf945ae61 +# SK = e51b4f6e8c8e61d3b9c751fde190b17ba899f96803f73b85613d62a650492af4c7829341befee710105ed5713f01a5035ed9b6aed4dfcb94c818c680c8466778 +# CT = BCF3C98E2EC96F127540B844F4DF0B176E2460C97D6EB82423B3833AEFF0680FC4B3F758E3A6FA03A23D8419CA0B464191AB245CA5C7E112DF24FCE728C40B414DA2F6B058796774DA463966AC5FD21476350E46C3CCD07A317A33DC29132809BAA255A41D6456D01301AC08C94B2D57148CEA41E7AFE036F17D3CE62F46EC31C7FEB07DED1767F861389EC89180E107698AFAFA0976381F04A0CA061585121A060E712178A6218AF3907BC3F8BCDE02E8EAF5769C9E790274267B37 + +[mceliece6688128] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = 7B35200A8387A2BB376394A68473E7ABE5CE392484DABE6C1EF0EE2CD9F68022 +PK = 46489d7bd19b860311fdb17693b920ebd58e356ca01a2c157e00c15fe2be6e0ceac35f767b812464ce9f14a87f4b5d0d1cb8ae5c7daed6a952d3eee732ea60f7 +SK = 024c1f80388fa4f60dd19e12eef03f1435cd173b891dfdd4586fb75eecfde6fb1868b979485268df3c075f2faf248b8e160606252b45ccd9a396316a00fb8ec0 +CT = 01278F7400972FD05AA6368A4F8662497A5A31A3E968BF81B49EBDFB8331769EA1BB5275AD46D33F8D6624C2F305F961DC8812850B20C2FE3C7E8FB0393BBBFFFC0458A01765EC519AB332DA952047B8A87C618D3BF28046B94F82872A75D1C090DBE768168DF6D7D6755FAFB5AE050AE520BF7ED641C90161DFB70E4A5EF9A8D64856CAC821D98B00E8145D3462A4DB6CF2E0C002DBA11257D7716E22F18F8E28113CDF5FE7581CC82854165AB93E36D4080F8E7B8116667E9C12D515A443EA002E609C6F5EE839FF282D8EAAF6BB8C + +[mceliece6688128f] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = 29F45674CFB52E295CD31E5303B7387515699A764777742B5A487798D41218C8 +PK = 0c453cc87eb310694cad01065624cdf723b42b7ffc7aa72afa86956013427e83c23c1061ed9c8393e07e37b2186286a096140b2e571deaf4d037deafb41d7235 +SK = 9c02fbffd0401c9dcae1a977cd0d52d59178357fcc055e2ba5c6691bb9fbcd3cf7e3d6ccc3ca23213f6cdefb4823e592dff809e1ea8df55f78636da17c8cfd78 +CT = 640B4DA81C3198D4707E02CAD713E8EB6BE431076E3EE7D6AA5323A9C551FEFE8BDC978052A55244D9347C2DB4A5EF76C6FFF4EE3F3E973ACBD58C0E03665DAF1857B2987CF463994CC31E95645F81CF2E18F7D5EBBC1212689B6F8765692DDD0F7852FACED8471BDA55737ED4E3129ADE84E246C20D02780D590D47D6D90BB2A6FA7141B72290DB4EE1478E09B1B48B7D8CCE4F37E329A1ED8F9BBAC4DAC6040358CED8B4B96289AB5BE27A95FB35A0D603DCC7E94D8C9A9728A3896D1EE556F5E185DC542DA1CB07A7480D5618D647 + +[mceliece6688128pc] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = 18A3E9906E03926AA87E0E910C570F5874549B0B1DE9E60D50C4031B5EB0B0F6 +PK = 46489d7bd19b860311fdb17693b920ebd58e356ca01a2c157e00c15fe2be6e0ceac35f767b812464ce9f14a87f4b5d0d1cb8ae5c7daed6a952d3eee732ea60f7 +SK = 024c1f80388fa4f60dd19e12eef03f1435cd173b891dfdd4586fb75eecfde6fb1868b979485268df3c075f2faf248b8e160606252b45ccd9a396316a00fb8ec0 +CT = 01278F7400972FD05AA6368A4F8662497A5A31A3E968BF81B49EBDFB8331769EA1BB5275AD46D33F8D6624C2F305F961DC8812850B20C2FE3C7E8FB0393BBBFFFC0458A01765EC519AB332DA952047B8A87C618D3BF28046B94F82872A75D1C090DBE768168DF6D7D6755FAFB5AE050AE520BF7ED641C90161DFB70E4A5EF9A8D64856CAC821D98B00E8145D3462A4DB6CF2E0C002DBA11257D7716E22F18F8E28113CDF5FE7581CC82854165AB93E36D4080F8E7B8116667E9C12D515A443EA002E609C6F5EE839FF282D8EAAF6BB8CEA79099D6282BAD1AF5B0CA919D112A35B12FD483FA90F9FD8B72D15E668E442 + +[mceliece6688128pcf] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = B954FAD8A4BD4905AD0D2D30E1AF7A7ECD705B94F7BAA713FFEA1583C96DE70F +PK = 0c453cc87eb310694cad01065624cdf723b42b7ffc7aa72afa86956013427e83c23c1061ed9c8393e07e37b2186286a096140b2e571deaf4d037deafb41d7235 +SK = 9c02fbffd0401c9dcae1a977cd0d52d59178357fcc055e2ba5c6691bb9fbcd3cf7e3d6ccc3ca23213f6cdefb4823e592dff809e1ea8df55f78636da17c8cfd78 +CT = 640B4DA81C3198D4707E02CAD713E8EB6BE431076E3EE7D6AA5323A9C551FEFE8BDC978052A55244D9347C2DB4A5EF76C6FFF4EE3F3E973ACBD58C0E03665DAF1857B2987CF463994CC31E95645F81CF2E18F7D5EBBC1212689B6F8765692DDD0F7852FACED8471BDA55737ED4E3129ADE84E246C20D02780D590D47D6D90BB2A6FA7141B72290DB4EE1478E09B1B48B7D8CCE4F37E329A1ED8F9BBAC4DAC6040358CED8B4B96289AB5BE27A95FB35A0D603DCC7E94D8C9A9728A3896D1EE556F5E185DC542DA1CB07A7480D5618D647EA79099D6282BAD1AF5B0CA919D112A35B12FD483FA90F9FD8B72D15E668E442 + +[mceliece6960119] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = ACE16B9D437E56401128EDE4EE3A1C45CFE13D8E8288A3754DB4D9B78C5A3DDF +PK = 52244c54404fa392d586faec5a55ebe48c78386a3efce108684b89cc9b9c173a57ccfd6748712fcb6226f7a2add4506e1483107b88172e88589dfdaecd3a0948 +SK = 53ca3551c31cd51be0074fc928264765de6bdef00810fc6bdb63ccd69d9e15b168c45a9dfce7bb48dbb131bbe772371b100285d2b282bad4f50b328b81e84826 +CT = 63C39D29314866A0FE528B3D5DE37D5C6F72279EE711036198B0C2CA1F293D3541E0D1467D63D2E5C92B8060001CF002017F60B954C5DC457BA63C59BBE330BB66BC8726E605ACD0E90CD7167376F68CC071D4F931349564EF28D7EAB3D1FF61563EE1DEFD95A548004979736AB1B39BE08D57A49F39988F23574A5A06FC4C317F08C1B842EF844773BE74701E57EC91107DE40C6EEB222630621A6FBF2A4CB8CCB9C395ABD85FDC03C0FBE0E56EC9F7052B90608E21653FA2DE1AD62C68C2656C06 + +[mceliece6960119f] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = 2FDCA51B72431A9534E670D9ED6C8C085D57AA409C41E21668E03ED0C569BA43 +PK = da30411922f1c72600f5e5846f6d0815f41fee4aed87226e61a6d8c28045fe332859f24e9245f095f253ae30ac70b220436ee21d14f58859aa5da5c0fa61b8be +SK = 0f9a9255f9d120d3b53e87032baeaf7802db8abd5a24de9def0a1a2b3934737da5e48c1236bd75ff1811aa63e66a5c16b4c272f480c7c260c36d184c615d67a5 +CT = 39444056B95687CF222EFC56C4FEBD99D0EF6EF718376889840DCB35721B04960FEF47473B538C512D3CFB2E78A378CAA7B20986ED4F0D13670282DD64110E06C71ECE1B05E0D0CDFA0389EEDC1454F8D14430CB3C3339C754FDB36B8EBE611D12A6117751FD2A834444B0B0ED1AD8464C328424958BF8B75A2AB8E7D537E40ABB33FC775F4BEE8EA92C8439698C99105D7B520D6398684C1DB9B0421A89AB514C75914B5D8C3C511E0B55BBA6F2B5E27C64D8C2E2AFA5A12B66DF5946BAEBD28804 + +[mceliece6960119pc] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = 35D4BE047205AFF8339FCF19935D5F3F3C09BAFC6E418448214D5F159915DED7 +PK = 52244c54404fa392d586faec5a55ebe48c78386a3efce108684b89cc9b9c173a57ccfd6748712fcb6226f7a2add4506e1483107b88172e88589dfdaecd3a0948 +SK = 53ca3551c31cd51be0074fc928264765de6bdef00810fc6bdb63ccd69d9e15b168c45a9dfce7bb48dbb131bbe772371b100285d2b282bad4f50b328b81e84826 +CT = 63C39D29314866A0FE528B3D5DE37D5C6F72279EE711036198B0C2CA1F293D3541E0D1467D63D2E5C92B8060001CF002017F60B954C5DC457BA63C59BBE330BB66BC8726E605ACD0E90CD7167376F68CC071D4F931349564EF28D7EAB3D1FF61563EE1DEFD95A548004979736AB1B39BE08D57A49F39988F23574A5A06FC4C317F08C1B842EF844773BE74701E57EC91107DE40C6EEB222630621A6FBF2A4CB8CCB9C395ABD85FDC03C0FBE0E56EC9F7052B90608E21653FA2DE1AD62C68C2656C068CC5C37FC0AFD9B145CB3C4E7C30EF4D4C9F404E6FFFFB179AED0CF18B3BDA14 + +[mceliece6960119pcf] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = 7ADF6895DBBC6AC1621374116E0D9EA53184601EDF88B53E55BEC013103F9269 +PK = da30411922f1c72600f5e5846f6d0815f41fee4aed87226e61a6d8c28045fe332859f24e9245f095f253ae30ac70b220436ee21d14f58859aa5da5c0fa61b8be +SK = 0f9a9255f9d120d3b53e87032baeaf7802db8abd5a24de9def0a1a2b3934737da5e48c1236bd75ff1811aa63e66a5c16b4c272f480c7c260c36d184c615d67a5 +CT = 39444056B95687CF222EFC56C4FEBD99D0EF6EF718376889840DCB35721B04960FEF47473B538C512D3CFB2E78A378CAA7B20986ED4F0D13670282DD64110E06C71ECE1B05E0D0CDFA0389EEDC1454F8D14430CB3C3339C754FDB36B8EBE611D12A6117751FD2A834444B0B0ED1AD8464C328424958BF8B75A2AB8E7D537E40ABB33FC775F4BEE8EA92C8439698C99105D7B520D6398684C1DB9B0421A89AB514C75914B5D8C3C511E0B55BBA6F2B5E27C64D8C2E2AFA5A12B66DF5946BAEBD288048CC5C37FC0AFD9B145CB3C4E7C30EF4D4C9F404E6FFFFB179AED0CF18B3BDA14 + +[mceliece8192128] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = 82351702A2C3973644CB735FC9B6CEA8FE526D7D729EE134FC12C0201690E854 +PK = d9c71f2e10a72ff75517b6b209e2826e53ab8d1f4c16985ae039413a1b117713c668dd8c24ca53d0b0089664a419eeadc7778ad13ccc27ea9dc6b25b1ce290fb +SK = 705a3860712eadd0fbd892b10e46e82d32e678080165a4ec6b5b22ffc878e62d157b18ca7513dbafca4b7abb5f2f34992cb003d9617230f0d7de62ea7b63e806 +CT = AD9728E7519C5F851FDA1148CF652893C8884288930995416F95798C4F2E0151FF617828CBCBC74BA3870D04E41FB875BE651A8070E23B89D47362833D899ABB57D25886FD9B71C2027C3F32FB5D699922053BA4E7297E9EE87838DBC06677E0B4EB4D9EDEA0945A6D0A01020BB30C33CF0498373B9AF3517DD20331FFB1F8177946251EFA80BE477E96D8ACAF5F2AB93DE67868DE506B44E0A1FA058176450A380901A5AA0E033642A7ECCD50C77916268AD225AFB3B7A1560FAF4CF476ACFFBBFA30D1EFF17FBD73B109CF9FF2ECC0 + +[mceliece8192128f] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = BC1E92FBD34B7907C0FA2568C5E5FA936AF7A6F0C2EE642BDFC760D894683F92 +PK = 490df50a0c816ea209ef617e9eebe374cbac7fc8c26b4be415f499630fda470db300fade9d7a07da1f6f6548c6e6db779ca6300a9ba07981bf4dbe7b2f393f05 +SK = 1b15567eacdd9608decd182a295d8753c048a898df84d1d6b731e4012c1dd64396801c2d423e2baf10f5b37372dcb214ee4b9b23e4167a8535fde363d30a7b8f +CT = F220F073D58E77C3AF5C366C94CEDFF259E4144C8FBA8ECBF833582C2922429431D7BCCA15D587405CF646411CE113950DE7B15E92ACFF8BDB99385BE1917F7EE68CBA58C32505282C568D67EE29C84B07988C9D4D02CD5A21544A3050D24B7001B3232FBC534F2033AB7A10AB4E5C816A0CE7B1FBDB46D2DBB5FAC934BCFA57C675265564AF3400EA4DCED7E68BEDB0AF4C52A25BFBA6BE2162AA7ADB8EF685EFBC119407A6938AF904630B7E755A9D2F7496F06129EE7538D09144107BD51BC725D6D5A73F419D8277BBC195FF4C7F + +[mceliece8192128pc] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = 870B2D45FA3CCEA8186F3929DE0B68798F65A34D01353B2EBFD6B1FBC2707897 +PK = d9c71f2e10a72ff75517b6b209e2826e53ab8d1f4c16985ae039413a1b117713c668dd8c24ca53d0b0089664a419eeadc7778ad13ccc27ea9dc6b25b1ce290fb +SK = 705a3860712eadd0fbd892b10e46e82d32e678080165a4ec6b5b22ffc878e62d157b18ca7513dbafca4b7abb5f2f34992cb003d9617230f0d7de62ea7b63e806 +CT = AD9728E7519C5F851FDA1148CF652893C8884288930995416F95798C4F2E0151FF617828CBCBC74BA3870D04E41FB875BE651A8070E23B89D47362833D899ABB57D25886FD9B71C2027C3F32FB5D699922053BA4E7297E9EE87838DBC06677E0B4EB4D9EDEA0945A6D0A01020BB30C33CF0498373B9AF3517DD20331FFB1F8177946251EFA80BE477E96D8ACAF5F2AB93DE67868DE506B44E0A1FA058176450A380901A5AA0E033642A7ECCD50C77916268AD225AFB3B7A1560FAF4CF476ACFFBBFA30D1EFF17FBD73B109CF9FF2ECC03EDD086216C90F28C78E03C496B56E6659DC95C5F7C51A371D36BAD9BC1757C2 + +[mceliece8192128pcf] +Seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +SS = EC35D8E55EB7ACE9866694FC0915402EA0720A85C5A3DB8A93D627F0432A452E +PK = 490df50a0c816ea209ef617e9eebe374cbac7fc8c26b4be415f499630fda470db300fade9d7a07da1f6f6548c6e6db779ca6300a9ba07981bf4dbe7b2f393f05 +SK = 1b15567eacdd9608decd182a295d8753c048a898df84d1d6b731e4012c1dd64396801c2d423e2baf10f5b37372dcb214ee4b9b23e4167a8535fde363d30a7b8f +CT = F220F073D58E77C3AF5C366C94CEDFF259E4144C8FBA8ECBF833582C2922429431D7BCCA15D587405CF646411CE113950DE7B15E92ACFF8BDB99385BE1917F7EE68CBA58C32505282C568D67EE29C84B07988C9D4D02CD5A21544A3050D24B7001B3232FBC534F2033AB7A10AB4E5C816A0CE7B1FBDB46D2DBB5FAC934BCFA57C675265564AF3400EA4DCED7E68BEDB0AF4C52A25BFBA6BE2162AA7ADB8EF685EFBC119407A6938AF904630B7E755A9D2F7496F06129EE7538D09144107BD51BC725D6D5A73F419D8277BBC195FF4C7F3EDD086216C90F28C78E03C496B56E6659DC95C5F7C51A371D36BAD9BC1757C2 + diff --git a/src/tests/data/pubkey/cmce_negative.vec b/src/tests/data/pubkey/cmce_negative.vec new file mode 100644 index 0000000000..3763b2c686 --- /dev/null +++ b/src/tests/data/pubkey/cmce_negative.vec @@ -0,0 +1,22 @@ + +# Test the decapsulation of invalid keys. Test vectors are based on the first KAT +# some Classic McEliece instances (source: https://classic.mceliece.org/nist.html), where one bit +# is flipped in the (valid) ciphertext, invalidating it. These test vectors were created using +# a modification of the reference implementation. Private keys are created from the seed as in NIST's KAT +# tests. +# +# Note: For plaintext confirmation instances, an additional pair of invalid ciphertext and corresponding +# output shared secret is given (ct_invalid_c1/ss_invalid_c1), where one bit of C_1 is flipped +# (ciphertext = C_0 || C_1), which is handled by plaintext confirmation. + +[mceliece348864f] +seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +ct_invalid = E305BB2814DED1582864F2B1D2A26397411EE4E61F6998FF61CD55E4C4FB35AB99788D00F42D2D3B79B0820035749776CAA82730B1EBE2B81230424FCBCB8B5A804B0FA3025B108175456F80F4ABD1786C5DB02C6564333DE9FE67ED4A92D6FE +ss_invalid = 9AADA66ACAA96C4BCD5059155B23BE5DF7BC22527FE19161AAF0BF712F4F07EE + +[mceliece6688128pcf] +seed = 061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1 +ct_invalid_c1 = 640B4DA81C3198D4707E02CAD713E8EB6BE431076E3EE7D6AA5323A9C551FEFE8BDC978052A55244D9347C2DB4A5EF76C6FFF4EE3F3E973ACBD58C0E03665DAF1857B2987CF463994CC31E95645F81CF2E18F7D5EBBC1212689B6F8765692DDD0F7852FACED8471BDA55737ED4E3129ADE84E246C20D02780D590D47D6D90BB2A6FA7141B72290DB4EE1478E09B1B48B7D8CCE4F37E329A1ED8F9BBAC4DAC6040358CED8B4B96289AB5BE27A95FB35A0D603DCC7E94D8C9A9728A3896D1EE556F5E185DC542DA1CB07A7480D5618D647EA79099D6282BAD1AF5B0CA919D112A35B12FD483FA90F9FD8B72D15E668E443 +ss_invalid_c1 = F9EC0EA86FDCCEBBD90EF0394054F4631E187119B6379B2E2BC46986DD6D280A +ct_invalid = 650B4DA81C3198D4707E02CAD713E8EB6BE431076E3EE7D6AA5323A9C551FEFE8BDC978052A55244D9347C2DB4A5EF76C6FFF4EE3F3E973ACBD58C0E03665DAF1857B2987CF463994CC31E95645F81CF2E18F7D5EBBC1212689B6F8765692DDD0F7852FACED8471BDA55737ED4E3129ADE84E246C20D02780D590D47D6D90BB2A6FA7141B72290DB4EE1478E09B1B48B7D8CCE4F37E329A1ED8F9BBAC4DAC6040358CED8B4B96289AB5BE27A95FB35A0D603DCC7E94D8C9A9728A3896D1EE556F5E185DC542DA1CB07A7480D5618D647EA79099D6282BAD1AF5B0CA919D112A35B12FD483FA90F9FD8B72D15E668E442 +ss_invalid = B3E19CD4BED97A32B6DE87E006902DCB8DAAC069C8CF1B2C662911FCE5A24487 diff --git a/src/tests/test_cmce.cpp b/src/tests/test_cmce.cpp new file mode 100644 index 0000000000..82369f7b0c --- /dev/null +++ b/src/tests/test_cmce.cpp @@ -0,0 +1,345 @@ +/* +* Tests for Classic McEliece +* +* (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 "test_pubkey.h" +#include "test_pubkey_pqc.h" +#include "test_rng.h" +#include "tests.h" + +#if defined(BOTAN_HAS_CLASSICMCELIECE) + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + +namespace Botan_Tests { + +namespace { + +Botan::Classic_McEliece_Polynomial create_element_from_bytes(std::span bytes, + const Botan::Classic_McEliece_Polynomial_Ring& ring) { + BOTAN_ARG_CHECK(bytes.size() == ring.degree() * 2, "Correct input size"); + std::vector coef(ring.degree()); + Botan::load_le(coef.data(), bytes.data(), ring.degree()); + + std::vector coeff_vec_gf; + std::transform(coef.begin(), coef.end(), std::back_inserter(coeff_vec_gf), [&](auto& coeff) { + return Botan::Classic_McEliece_GF(Botan::CmceGfElem(coeff), ring.poly_f()); + }); + return Botan::Classic_McEliece_Polynomial(coeff_vec_gf); +} + +std::vector get_test_instances_all() { + return {// All instances + 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}; +} + +std::vector get_test_instances_min() { + return {// Testing with and without pc and f. Also testing 6960119 with m*t mod 8 != 0. + Botan::Classic_McEliece_Parameter_Set::mceliece348864, + Botan::Classic_McEliece_Parameter_Set::mceliece6960119pcf}; +} + +std::vector instances_to_test() { + if(Test::run_long_tests()) { + return get_test_instances_all(); + } else { + return get_test_instances_min(); + } +} + +bool skip_cmce_test(const std::string& params_str) { + auto params = Botan::Classic_McEliece_Parameters::create(params_str); + auto to_test = instances_to_test(); + return std::find(to_test.begin(), to_test.end(), params.parameter_set()) == to_test.end(); +} +} // namespace + +class CMCE_Utility_Tests final : public Test { + public: + Test::Result expand_seed_test() { + Test::Result result("Seed expansion"); + + auto params = + Botan::Classic_McEliece_Parameters::create(Botan::Classic_McEliece_Parameter_Set::mceliece348864); + + // Created using the reference implementation + auto seed = Botan::hex_decode_locked("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"); + + auto exp_first_and_last_bytes = Botan::hex_decode( + "543e2791fd98dbc1" // first 8 bytes + "d332a7c40776ca01"); // last 8 bytes + + size_t byte_length = + (params.n() + params.sigma2() * params.q() + params.sigma1() * params.t() + params.ell()) / 8; + + auto rand = params.prg(seed)->output_stdvec(byte_length); + rand.erase(rand.begin() + 8, rand.end() - 8); + + result.test_is_eq("Seed expansion", rand, exp_first_and_last_bytes); + + return result; + } + + Test::Result irreducible_poly_gen_test() { + Test::Result result("Irreducible Polynomial Generation"); + + auto params = + Botan::Classic_McEliece_Parameters::create(Botan::Classic_McEliece_Parameter_Set::mceliece348864); + + // Created using the reference implementation + auto random_bits = Botan::CmceIrreducibleBits(Botan::hex_decode( + "d9b8bb962a3f9dac0f832d243def581e7d26f4028de1ff9cd168460e5050ab095a32a372b40d720bd5d75389a6b3f08fa1d13cec60a4b716d4d6c240f2f80cd3" + "cbc76ae0dddca164c1130da185bd04e890f2256fb9f4754864811e14ea5a43b8b3612d59cecde1b2fdb6362659a0193d2b7d4b9d79aa1801dde3ca90dc300773")); + + auto exp_g = Botan::Classic_McEliece_Minimal_Polynomial::from_bytes( + Botan::hex_decode( + "8d00a50f520a0307b8007c06cb04b9073b0f4a0f800fb706a60f2a05910a670b460375091209fc060a09ab036c09e5085a0df90d3506b404a30fda041d09970f" + "1206d000e00aac01c00dc80f490cd80b4108330c0208cf00d602450ec00a21079806eb093f00de015f052905560917081b09270c820af002000c34094504cd03"), + params.poly_f()); + + auto g = params.poly_ring().compute_minimal_polynomial(random_bits); + result.confirm("Minimize polynomial successful", g.has_value()); + result.test_is_eq("Minimize polynomial", g.value().coef(), exp_g.coef()); + + return result; + } + + Test::Result gf_inv_test() { + Test::Result result("GF inv test"); + + auto params = + Botan::Classic_McEliece_Parameters::create(Botan::Classic_McEliece_Parameter_Set::mceliece348864); + + auto v = params.gf(Botan::CmceGfElem(42)); + auto v_inv = v.inv(); + result.test_is_eq("Control bits creation", (v * v_inv).elem(), Botan::CmceGfElem(1)); + + return result; + } + + Test::Result gf_poly_mul_test() { + Test::Result result("GF Poly Mul"); + + auto params = + Botan::Classic_McEliece_Parameters::create(Botan::Classic_McEliece_Parameter_Set::mceliece348864); + + const auto& field = params.poly_ring(); + + auto val1 = create_element_from_bytes( + Botan::hex_decode( + "bb02d40437094c0ae4034c00b10fed090a04850f660c3b0e110eb409810a86015b0f5804ca0e78089806e20b5b03aa0bc2020b05ea03710da902340c390f630b" + "bc07a70db20b9e0ee4038905a00a09090a0521045e0a0706370b5a00050a4100480c4d0e8f00730692093701fe04650dbe0fd00702011a04910360023f04fb0a"), + field); + + auto val2 = create_element_from_bytes( + Botan::hex_decode( + "060c630b170abb00020fef03e501020e89098108bf01f30dd30900000e0d3d0ca404ec01190760021f088c09b90b0a06a702d104500f0f02f00a580287010a09" + "4e01490d270c73051800bc0af303b901b202b50321002802b903ce0ab40806083f0a2d06d002df0f260811005c02a10b300e5c0ba20d14045003c50f2f02de02"), + field); + + auto exp_mul = create_element_from_bytes( + Botan::hex_decode( + "370d090b19008f0efb01f5011b04f9054b0d1f071d0457011e09cd0dfa093c004f08500e670abb0567090000f603770a3905bf044408b8025805930b25012201" + "8d0a560e840d960d9d0a280d1d06fc08d5078c06fe0cb406d0061e02c6090507d20eb10cb90146085c042e030c0e1a07910fcd0c5f0fda066c0cee061d01f40f"), + field); + + auto mul = field.multiply(val1, val2); // val1 * val2; + result.test_is_eq("GF multiplication", mul.coef(), exp_mul.coef()); + + return result; + } + + Test::Result rigged_rng_encryption_test() { + // RNG that always returns zero bytes + class All_Zero_RNG : public Botan::RandomNumberGenerator { + public: + void fill_bytes_with_input(std::span output, std::span /* ignored */) override { + std::fill(output.begin(), output.end(), 0); + } + + std::string name() const override { return "All_Zero_RNG"; } + + bool accepts_input() const override { return false; } + + void clear() override {} + + bool is_seeded() const override { return true; } + } rigged_rng; + + Test::Result result("No endless loop with rigged RNG"); + // Key creation should work even with a rigged RNG (PRNG is not used for key creation) + auto private_key = Botan::create_private_key("ClassicMcEliece", rigged_rng, "mceliece348864f"); + if(!private_key) { + result.test_failure("Key generation failed"); + return result; + } + auto enc = Botan::PK_KEM_Encryptor(*private_key, "Raw"); + result.test_throws("Many failed encryption attempts throws exception", [&] { enc.encrypt(rigged_rng); }); + + return result; + } + + std::vector run() override { + return {expand_seed_test(), + irreducible_poly_gen_test(), + gf_inv_test(), + gf_poly_mul_test(), + rigged_rng_encryption_test()}; + } +}; + + #if defined(BOTAN_HAS_AES) +class CMCE_Invalid_Test : public Text_Based_Test { + public: + CMCE_Invalid_Test() : + Text_Based_Test("pubkey/cmce_negative.vec", "seed,ct_invalid,ss_invalid", "ct_invalid_c1,ss_invalid_c1") {} + + Test::Result run_one_test(const std::string& params_str, const VarMap& vars) override { + Test::Result result("CMCE Invalid Ciphertext Test"); + + auto params = Botan::Classic_McEliece_Parameters::create(params_str); + + const auto kat_seed = Botan::lock(vars.get_req_bin("seed")); + const auto ct_invalid = vars.get_req_bin("ct_invalid"); + const auto ref_ss_invalid = Botan::lock(vars.get_req_bin("ss_invalid")); + + const auto test_rng = std::make_unique(kat_seed); + + auto private_key = Botan::create_private_key("ClassicMcEliece", *test_rng, params_str); + + // Decaps an invalid ciphertext + auto dec = Botan::PK_KEM_Decryptor(*private_key, *test_rng, "Raw"); + auto decaps_ct_invalid = dec.decrypt(ct_invalid); + + result.test_is_eq("Decaps an invalid encapsulated key", decaps_ct_invalid, ref_ss_invalid); + + if(params.is_pc()) { + // For pc variants, additionally check the plaintext confirmation (pc) logic by + // flipping a bit in the second part of the ciphertext (C_1 in pc). In this case + // C_0 is decoded correctly, but pc will change the shared secret, since C_1' != C_1. + const auto ct_invalid_c1 = vars.get_opt_bin("ct_invalid_c1"); + const auto ref_ss_invalid_c1 = Botan::lock(vars.get_opt_bin("ss_invalid_c1")); + auto decaps_ct_invalid_c1 = dec.decrypt(ct_invalid_c1); + + result.test_is_eq("Decaps with invalid C_1 in pc", decaps_ct_invalid_c1, ref_ss_invalid_c1); + } + + return result; + } +}; + #endif // BOTAN_HAS_AES + +class CMCE_Generic_Keygen_Tests final : public PK_Key_Generation_Test { + public: + std::vector keygen_params() const override { + auto to_test = get_test_instances_min(); + + std::vector res; + std::transform(to_test.begin(), to_test.end(), std::back_inserter(res), [](auto& param_set) { + return param_set.to_string(); + }); + + return res; + } + + std::unique_ptr public_key_from_raw(std::string_view keygen_params, + std::string_view /*provider*/, + std::span raw_key_bits) const override { + return std::make_unique( + raw_key_bits, Botan::Classic_McEliece_Parameter_Set::from_string(keygen_params)); + } + + std::string algo_name() const override { return "ClassicMcEliece"; } +}; + +class Classic_McEliece_KAT_Tests final : public Botan_Tests::PK_PQC_KEM_KAT_Test { + public: + Classic_McEliece_KAT_Tests() : PK_PQC_KEM_KAT_Test("ClassicMcEliece", "pubkey/cmce_kat_hashed.vec") {} + + private: + Botan::Classic_McEliece_Parameters get_params(const std::string& header) const { + return Botan::Classic_McEliece_Parameters::create(Botan::Classic_McEliece_Parameter_Set::from_string(header)); + } + + bool is_available(const std::string& alg_name) const final { return !skip_cmce_test(alg_name); } + + std::vector map_value(const std::string&, std::span value, VarType var_type) const final { + if(var_type == VarType::Ciphertext || var_type == VarType::SharedSecret) { + return {value.begin(), value.end()}; + } + auto hash = Botan::HashFunction::create_or_throw("SHAKE-256(512)"); + return hash->process>(value); + } + + Fixed_Output_RNG rng_for_keygen(const std::string&, Botan::RandomNumberGenerator& rng) const final { + const auto seed = rng.random_vec(Botan::Classic_McEliece_Parameters::seed_len()); + return Fixed_Output_RNG(seed); + } + + Fixed_Output_RNG rng_for_encapsulation(const std::string& alg_name, + Botan::RandomNumberGenerator& rng) const final { + // There is no way to tell exacly how much randomness is + // needed for encapsulation (rejection sampling) + // For testing we use a number that fits for all test cases + auto params = get_params(alg_name); + const size_t max_attempts = 100; + const size_t bits_per_attempt = (params.sigma1() / 8) * params.tau(); + + std::vector rand_buffer; + for(size_t attempt = 0; attempt < max_attempts; ++attempt) { + auto random_bytes = rng.random_vec(bits_per_attempt); + rand_buffer.insert(rand_buffer.end(), random_bytes.begin(), random_bytes.end()); + } + + return Fixed_Output_RNG(rand_buffer); + } + + void inspect_rng_after_encaps(const std::string&, const Fixed_Output_RNG&, Test::Result&) const final { + // Encaps uses any number of random bytes, so we cannot check the RNG + } +}; + +BOTAN_REGISTER_TEST("cmce", "cmce_utility", CMCE_Utility_Tests); +BOTAN_REGISTER_TEST("cmce", "cmce_generic_keygen", CMCE_Generic_Keygen_Tests); +BOTAN_REGISTER_TEST("cmce", "cmce_generic_kat", Classic_McEliece_KAT_Tests); + #if defined(BOTAN_HAS_AES) +BOTAN_REGISTER_TEST("cmce", "cmce_invalid", CMCE_Invalid_Test); + #endif + +} // namespace Botan_Tests + +#endif // BOTAN_HAS_CLASSICMCELIECE diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index 647e6ad10b..9694c75b55 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -3818,6 +3818,45 @@ class FFI_SLH_DSA_Test final : public FFI_Signature_Roundtrip_Test { const char* hash_algo_or_padding() const override { return ""; } }; +class FFI_Classic_McEliece_Test final : public FFI_KEM_Roundtrip_Test { + public: + std::string name() const override { return "FFI Classic McEliece"; } + + protected: + const char* algo() const override { return "ClassicMcEliece"; } + + privkey_loader_fn_t private_key_load_function() const override { return botan_privkey_load_classic_mceliece; } + + pubkey_loader_fn_t public_key_load_function() const override { return botan_pubkey_load_classic_mceliece; } + + std::vector modes() const override { + auto modes = std::vector{ + "mceliece348864f", + "mceliece460896f", + }; + if(Test::run_long_tests()) { + modes = Botan::concat(modes, + std::vector{ + "mceliece348864", + "mceliece460896", + "mceliece6688128", + "mceliece6688128f", + "mceliece6688128pc", + "mceliece6688128pcf", + "mceliece6960119", + "mceliece6960119f", + "mceliece6960119pc", + "mceliece6960119pcf", + "mceliece8192128", + "mceliece8192128f", + "mceliece8192128pc", + "mceliece8192128pcf", + }); + } + return modes; + } +}; + class FFI_ElGamal_Test final : public FFI_Test { public: std::string name() const override { return "FFI ElGamal"; } @@ -4064,6 +4103,7 @@ BOTAN_REGISTER_TEST("ffi", "ffi_ml_kem", FFI_ML_KEM_Test); BOTAN_REGISTER_TEST("ffi", "ffi_ml_dsa", FFI_ML_DSA_Test); BOTAN_REGISTER_TEST("ffi", "ffi_slh_dsa", FFI_SLH_DSA_Test); BOTAN_REGISTER_TEST("ffi", "ffi_frodokem", FFI_FrodoKEM_Test); +BOTAN_REGISTER_TEST("ffi", "ffi_cmce", FFI_Classic_McEliece_Test); BOTAN_REGISTER_TEST("ffi", "ffi_elgamal", FFI_ElGamal_Test); BOTAN_REGISTER_TEST("ffi", "ffi_dh", FFI_DH_Test); diff --git a/src/tests/test_pubkey.cpp b/src/tests/test_pubkey.cpp index 455e3d2218..8afbf6d76e 100644 --- a/src/tests/test_pubkey.cpp +++ b/src/tests/test_pubkey.cpp @@ -617,7 +617,7 @@ std::vector PK_Key_Generation_Test::run() { key.object_identifier().to_string()); } else { const bool exception = name == "Kyber" || name == "ML-KEM" || name == "ML-DSA" || name == "SLH-DSA" || - name == "FrodoKEM" || name == "SPHINCS+"; + name == "FrodoKEM" || name == "SPHINCS+" || name == "ClassicMcEliece"; if(!exception) { result.test_failure("Keys name " + name + " does not map to an OID"); } diff --git a/src/tests/unit_x509.cpp b/src/tests/unit_x509.cpp index 7520453af8..50b1c2a051 100644 --- a/src/tests/unit_x509.cpp +++ b/src/tests/unit_x509.cpp @@ -1200,7 +1200,7 @@ Test::Result test_valid_constraints(const Botan::Private_Key& key, const std::st result.test_eq("usage acceptable", key_agreement_decipher_only.compatible_with(key), true); result.test_eq("crl sign not permitted", crl_sign.compatible_with(key), false); result.test_eq("sign", sign_everything.compatible_with(key), false); - } else if(pk_algo == "Kyber" || pk_algo == "FrodoKEM" || pk_algo == "ML-KEM") { + } else if(pk_algo == "Kyber" || pk_algo == "FrodoKEM" || pk_algo == "ML-KEM" || pk_algo == "ClassicMcEliece") { // KEMs can encrypt and agree result.test_eq("all constraints not permitted", all.compatible_with(key), false); result.test_eq("cert sign not permitted", ca.compatible_with(key), false); @@ -1695,7 +1695,8 @@ class X509_Cert_Unit_Tests final : public Test { /* These are algos which cannot sign but can be included in certs */ - const std::vector enc_algos = {"DH", "ECDH", "ElGamal", "Kyber", "ML-KEM", "FrodoKEM"}; + const std::vector enc_algos = { + "DH", "ECDH", "ElGamal", "Kyber", "ML-KEM", "FrodoKEM", "ClassicMcEliece"}; for(const std::string& algo : enc_algos) { auto key = make_a_private_key(algo, rng);