From 190261d30d2bea7f4c9feb174c2505ad2dc83a1d Mon Sep 17 00:00:00 2001 From: Fabian Albert Date: Fri, 19 Jan 2024 15:43:58 +0100 Subject: [PATCH] Classic McEliece implementation This is an implementation of the Classic McEliece KEM according to the NIST Round 4 submission and the ISO draft 20230419. Co-Authored-By: Amos Treiber --- doc/api_ref/pubkey.rst | 48 +++ doc/dev_ref/oids.rst | 19 + src/build-data/oids.txt | 24 +- src/cli/speed.cpp | 44 ++ src/lib/asn1/oid_maps.cpp | 34 +- src/lib/pubkey/classic_mceliece/cmce.cpp | 172 ++++++++ src/lib/pubkey/classic_mceliece/cmce.h | 151 +++++++ .../pubkey/classic_mceliece/cmce_decaps.cpp | 166 ++++++++ src/lib/pubkey/classic_mceliece/cmce_decaps.h | 84 ++++ .../pubkey/classic_mceliece/cmce_encaps.cpp | 126 ++++++ src/lib/pubkey/classic_mceliece/cmce_encaps.h | 59 +++ .../classic_mceliece/cmce_field_ordering.cpp | 322 +++++++++++++++ .../classic_mceliece/cmce_field_ordering.h | 112 +++++ src/lib/pubkey/classic_mceliece/cmce_gf.cpp | 123 ++++++ src/lib/pubkey/classic_mceliece/cmce_gf.h | 191 +++++++++ .../classic_mceliece/cmce_keys_internal.cpp | 143 +++++++ .../classic_mceliece/cmce_keys_internal.h | 187 +++++++++ .../pubkey/classic_mceliece/cmce_matrix.cpp | 258 ++++++++++++ src/lib/pubkey/classic_mceliece/cmce_matrix.h | 113 ++++++ .../classic_mceliece/cmce_parameter_set.cpp | 132 ++++++ .../classic_mceliece/cmce_parameter_set.h | 77 ++++ .../classic_mceliece/cmce_parameters.cpp | 149 +++++++ .../pubkey/classic_mceliece/cmce_parameters.h | 293 ++++++++++++++ src/lib/pubkey/classic_mceliece/cmce_poly.cpp | 175 ++++++++ src/lib/pubkey/classic_mceliece/cmce_poly.h | 174 ++++++++ src/lib/pubkey/classic_mceliece/cmce_types.h | 43 ++ src/lib/pubkey/classic_mceliece/info.txt | 31 ++ src/lib/pubkey/pk_algs.cpp | 23 ++ src/tests/data/pubkey/cmce_kat_hashed.vec | 148 +++++++ src/tests/data/pubkey/cmce_negative.vec | 22 + src/tests/test_cmce.cpp | 383 ++++++++++++++++++ 31 files changed, 4022 insertions(+), 4 deletions(-) create mode 100644 src/lib/pubkey/classic_mceliece/cmce.cpp create mode 100644 src/lib/pubkey/classic_mceliece/cmce.h create mode 100644 src/lib/pubkey/classic_mceliece/cmce_decaps.cpp create mode 100644 src/lib/pubkey/classic_mceliece/cmce_decaps.h create mode 100644 src/lib/pubkey/classic_mceliece/cmce_encaps.cpp create mode 100644 src/lib/pubkey/classic_mceliece/cmce_encaps.h create mode 100644 src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp create mode 100644 src/lib/pubkey/classic_mceliece/cmce_field_ordering.h create mode 100644 src/lib/pubkey/classic_mceliece/cmce_gf.cpp create mode 100644 src/lib/pubkey/classic_mceliece/cmce_gf.h create mode 100644 src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp create mode 100644 src/lib/pubkey/classic_mceliece/cmce_keys_internal.h create mode 100644 src/lib/pubkey/classic_mceliece/cmce_matrix.cpp create mode 100644 src/lib/pubkey/classic_mceliece/cmce_matrix.h create mode 100644 src/lib/pubkey/classic_mceliece/cmce_parameter_set.cpp create mode 100644 src/lib/pubkey/classic_mceliece/cmce_parameter_set.h create mode 100644 src/lib/pubkey/classic_mceliece/cmce_parameters.cpp create mode 100644 src/lib/pubkey/classic_mceliece/cmce_parameters.h create mode 100644 src/lib/pubkey/classic_mceliece/cmce_poly.cpp create mode 100644 src/lib/pubkey/classic_mceliece/cmce_poly.h create mode 100644 src/lib/pubkey/classic_mceliece/cmce_types.h create mode 100644 src/lib/pubkey/classic_mceliece/info.txt create mode 100644 src/tests/data/pubkey/cmce_kat_hashed.vec create mode 100644 src/tests/data/pubkey/cmce_negative.vec create mode 100644 src/tests/test_cmce.cpp diff --git a/doc/api_ref/pubkey.rst b/doc/api_ref/pubkey.rst index 88f31c4c9d5..21f1045b6e8 100644 --- a/doc/api_ref/pubkey.rst +++ b/doc/api_ref/pubkey.rst @@ -142,6 +142,11 @@ McEliece Post-quantum secure key encapsulation scheme based on the hardness of certain decoding problems. +Classic McEliece +~~~~~~~~~~~~~~~~ + +Post-quantum secure, code-based key encapsulation scheme. + ElGamal ~~~~~~~~ @@ -1128,6 +1133,7 @@ Botan implements the following KEM schemes: #. Kyber #. FrodoKEM #. McEliece +#. Classic McEliece .. _kyber_example: @@ -1193,6 +1199,48 @@ parameters n and t, and have the corresponding key sizes listed: You can check the speed of McEliece with the suggested parameters above using ``botan speed McEliece`` +Classic McEliece KEM +-------------------- + +`Classic McEliece `_ is an IND-CCA2 secure key +encapsulation algorithm based on the McEliece cryptosystem introduced in 1978. +It is a code-based scheme that relies on conservative security assumptions and +is considered secure against quantum computers. It is an alternative to +lattice-based schemes. + +Other advantages of Classic McEliece are the small ciphertext size and the fast +encapsulation. Key generation and decapsulation are slower than in lattice-based +schemes. The main disadvantage of Classic McEliece is the large public key size, +ranging from 0.26 MB to 1.36 MB, depending on the instance. Due to its large key +size, Classic McEliece is recommended for applications where the public key is +stored for a long time, and memory is not a critical resource. Usage with +ephemeral keys is not recommended. + +Botan's implementation covers the parameter sets of the `NIST round 4 +specification `_ +and the `Classic McEliece ISO draft specification +`_. +These are the following: + ++------------------+-------------------+-------------------+--------------------+-------------------+ +| Set without f/pc | Set with f | Set with pc | Set with pcf | Public Key Size | ++==================+===================+===================+====================+===================+ +| mceliece348864 | mceliece348864f | | | 0.26 MB | ++------------------+-------------------+-------------------+--------------------+-------------------+ +| mceliece460896 | mceliece460896f | | | 0.52 MB | ++------------------+-------------------+-------------------+--------------------+-------------------+ +| mceliece6688128 | mceliece6688128f | mceliece6688128pc | mceliece6688128pcf | 1.04 MB | ++------------------+-------------------+-------------------+--------------------+-------------------+ +| mceliece6960119 | mceliece6960119f | mceliece6960119pc | mceliece6960119pcf | 1.05 MB | ++------------------+-------------------+-------------------+--------------------+-------------------+ +| mceliece8192128 | mceliece8192128f | mceliece8192128pc | mceliece8192128pcf | 1.36 MB | ++------------------+-------------------+-------------------+--------------------+-------------------+ + +The instances with the suffix 'f' use a faster key generation algorithm that is more consistent in +runtime. The instances with the suffix 'pc' use plaintext confirmation, which is only specified in +the ISO document. The instances mceliece348864(f) and mceliece460896(f) are only defined in the +NIST round 4 submission. + eXtended Merkle Signature Scheme (XMSS) ---------------------------------------- diff --git a/doc/dev_ref/oids.rst b/doc/dev_ref/oids.rst index 47683e9fc60..01dbc3c003e 100644 --- a/doc/dev_ref/oids.rst +++ b/doc/dev_ref/oids.rst @@ -84,6 +84,25 @@ Values currently assigned are:: SphincsPlus-haraka-256s-r3.1 OBJECT IDENTIFIER ::= { SphincsPlus-haraka 5 } SphincsPlus-haraka-256f-r3.1 OBJECT IDENTIFIER ::= { SphincsPlus-haraka 6 } + mceliece OBJECT IDENTIFIER ::= { publicKey 18 } + + mceliece348864 OBJECT IDENTIFIER ::= { mceliece 1 } + mceliece348864f OBJECT IDENTIFIER ::= { mceliece 2 } + mceliece460896 OBJECT IDENTIFIER ::= { mceliece 3 } + mceliece460896f OBJECT IDENTIFIER ::= { mceliece 4 } + mceliece6688128 OBJECT IDENTIFIER ::= { mceliece 5 } + mceliece6688128f OBJECT IDENTIFIER ::= { mceliece 6 } + mceliece6688128pc OBJECT IDENTIFIER ::= { mceliece 7 } + mceliece6688128pcf OBJECT IDENTIFIER ::= { mceliece 8 } + mceliece6960119 OBJECT IDENTIFIER ::= { mceliece 9 } + mceliece6960119f OBJECT IDENTIFIER ::= { mceliece 10 } + mceliece6960119pc OBJECT IDENTIFIER ::= { mceliece 11 } + mceliece6960119pcf OBJECT IDENTIFIER ::= { mceliece 12 } + mceliece8192128 OBJECT IDENTIFIER ::= { mceliece 13 } + mceliece8192128f OBJECT IDENTIFIER ::= { mceliece 14 } + mceliece8192128pc OBJECT IDENTIFIER ::= { mceliece 15 } + mceliece8192128pcf OBJECT IDENTIFIER ::= { mceliece 16 } + symmetricKey OBJECT IDENTIFIER ::= { randombit 3 } ocbModes OBJECT IDENTIFIER ::= { symmetricKey 2 } diff --git a/src/build-data/oids.txt b/src/build-data/oids.txt index b3146f4d5fb..c1d18f7ceb2 100644 --- a/src/build-data/oids.txt +++ b/src/build-data/oids.txt @@ -1,6 +1,6 @@ -# Regenerate with ./src/scripts/dev_tools/oids.py oids > src/lib/asn1/oid_maps.cpp -# AND ./src/scripts/dev_tools/oids.py dn_ub > src/lib/x509/x509_dn_ub.cpp -# (if you modified something under [dn] +# Regenerate with ./src/scripts/dev_tools/gen_oids.py oids > src/lib/asn1/oid_maps.cpp +# AND ./src/scripts/dev_tools/gen_oids.py dn_ub > src/lib/x509/x509_dn_ub.cpp +# (if you modified something under [dn]) # Public key types [pubkey] @@ -63,6 +63,24 @@ 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 OIDs are currently in Botan's private arc +1.3.6.1.4.1.25258.1.18.1 = mceliece348864 +1.3.6.1.4.1.25258.1.18.2 = mceliece348864f +1.3.6.1.4.1.25258.1.18.3 = mceliece460896 +1.3.6.1.4.1.25258.1.18.4 = mceliece460896f +1.3.6.1.4.1.25258.1.18.5 = mceliece6688128 +1.3.6.1.4.1.25258.1.18.6 = mceliece6688128f +1.3.6.1.4.1.25258.1.18.7 = mceliece6688128pc +1.3.6.1.4.1.25258.1.18.8 = mceliece6688128pcf +1.3.6.1.4.1.25258.1.18.9 = mceliece6960119 +1.3.6.1.4.1.25258.1.18.10 = mceliece6960119f +1.3.6.1.4.1.25258.1.18.11 = mceliece6960119pc +1.3.6.1.4.1.25258.1.18.12 = mceliece6960119pcf +1.3.6.1.4.1.25258.1.18.13 = mceliece8192128 +1.3.6.1.4.1.25258.1.18.14 = mceliece8192128f +1.3.6.1.4.1.25258.1.18.15 = mceliece8192128pc +1.3.6.1.4.1.25258.1.18.16 = mceliece8192128pcf + # XMSS 1.3.6.1.4.1.25258.1.5 = XMSS-draft6 1.3.6.1.4.1.25258.1.8 = XMSS-draft12 diff --git a/src/cli/speed.cpp b/src/cli/speed.cpp index 746adfada0c..aa903e19094 100644 --- a/src/cli/speed.cpp +++ b/src/cli/speed.cpp @@ -132,6 +132,10 @@ #include #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) + #include +#endif + #if defined(BOTAN_HAS_ECDSA) #include #endif @@ -624,6 +628,11 @@ class Speed final : public Command { bench_frodokem(provider, msec); } #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) + else if(algo == "ClassicMcEliece") { + bench_classic_mceliece(provider, msec); + } +#endif #if defined(BOTAN_HAS_SCRYPT) else if(algo == "scrypt") { bench_scrypt(provider, msec); @@ -2082,6 +2091,41 @@ class Speed final : public Command { } #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) + void bench_classic_mceliece(const std::string& provider, std::chrono::milliseconds msec) { + std::vector cmce_param_sets{ + Botan::Classic_McEliece_Parameter_Set::mceliece348864, + Botan::Classic_McEliece_Parameter_Set::mceliece348864f, + Botan::Classic_McEliece_Parameter_Set::mceliece460896, + Botan::Classic_McEliece_Parameter_Set::mceliece460896f, + Botan::Classic_McEliece_Parameter_Set::mceliece6688128, + Botan::Classic_McEliece_Parameter_Set::mceliece6688128f, + Botan::Classic_McEliece_Parameter_Set::mceliece6688128pc, + Botan::Classic_McEliece_Parameter_Set::mceliece6688128pcf, + Botan::Classic_McEliece_Parameter_Set::mceliece6960119, + Botan::Classic_McEliece_Parameter_Set::mceliece6960119f, + Botan::Classic_McEliece_Parameter_Set::mceliece6960119pc, + Botan::Classic_McEliece_Parameter_Set::mceliece6960119pcf, + Botan::Classic_McEliece_Parameter_Set::mceliece8192128, + Botan::Classic_McEliece_Parameter_Set::mceliece8192128f, + Botan::Classic_McEliece_Parameter_Set::mceliece8192128pc, + Botan::Classic_McEliece_Parameter_Set::mceliece8192128pcf, + }; + + for(auto cmce_set : cmce_param_sets) { + auto cmce_set_str = Botan::cmce_str_from_param_set(cmce_set); + + auto keygen_timer = make_timer(cmce_set_str, provider, "keygen"); + + auto key = keygen_timer->run([&] { return Botan::Classic_McEliece_PrivateKey(rng(), cmce_set); }); + + record_result(keygen_timer); + + bench_pk_kem(key, cmce_set_str, provider, "KDF2(SHA-256)", msec); + } + } +#endif + #if defined(BOTAN_HAS_XMSS_RFC8391) void bench_xmss(const std::string& provider, std::chrono::milliseconds msec) { /* diff --git a/src/lib/asn1/oid_maps.cpp b/src/lib/asn1/oid_maps.cpp index d879c2fe6f3..451972542db 100644 --- a/src/lib/asn1/oid_maps.cpp +++ b/src/lib/asn1/oid_maps.cpp @@ -1,7 +1,7 @@ /* * OID maps * -* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2023-11-02 +* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2024-01-10 * * All manual edits to this file will be lost. Edit the script * then regenerate this source file. @@ -174,6 +174,22 @@ 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", "mceliece348864"}, + {"1.3.6.1.4.1.25258.1.18.10", "mceliece6960119f"}, + {"1.3.6.1.4.1.25258.1.18.11", "mceliece6960119pc"}, + {"1.3.6.1.4.1.25258.1.18.12", "mceliece6960119pcf"}, + {"1.3.6.1.4.1.25258.1.18.13", "mceliece8192128"}, + {"1.3.6.1.4.1.25258.1.18.14", "mceliece8192128f"}, + {"1.3.6.1.4.1.25258.1.18.15", "mceliece8192128pc"}, + {"1.3.6.1.4.1.25258.1.18.16", "mceliece8192128pcf"}, + {"1.3.6.1.4.1.25258.1.18.2", "mceliece348864f"}, + {"1.3.6.1.4.1.25258.1.18.3", "mceliece460896"}, + {"1.3.6.1.4.1.25258.1.18.4", "mceliece460896f"}, + {"1.3.6.1.4.1.25258.1.18.5", "mceliece6688128"}, + {"1.3.6.1.4.1.25258.1.18.6", "mceliece6688128f"}, + {"1.3.6.1.4.1.25258.1.18.7", "mceliece6688128pc"}, + {"1.3.6.1.4.1.25258.1.18.8", "mceliece6688128pcf"}, + {"1.3.6.1.4.1.25258.1.18.9", "mceliece6960119"}, {"1.3.6.1.4.1.25258.1.3", "McEliece"}, {"1.3.6.1.4.1.25258.1.5", "XMSS-draft6"}, {"1.3.6.1.4.1.25258.1.6.1", "GOST-34.10-2012-256/SHA-256"}, @@ -569,6 +585,22 @@ std::unordered_map OID_Map::load_str2oid_map() { {"gost_256B", OID({1, 2, 643, 7, 1, 2, 1, 1, 2})}, {"gost_512A", OID({1, 2, 643, 7, 1, 2, 1, 2, 1})}, {"gost_512B", OID({1, 2, 643, 7, 1, 2, 1, 2, 2})}, + {"mceliece348864", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 1})}, + {"mceliece348864f", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 2})}, + {"mceliece460896", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 3})}, + {"mceliece460896f", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 4})}, + {"mceliece6688128", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 5})}, + {"mceliece6688128f", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 6})}, + {"mceliece6688128pc", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 7})}, + {"mceliece6688128pcf", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 8})}, + {"mceliece6960119", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 9})}, + {"mceliece6960119f", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 10})}, + {"mceliece6960119pc", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 11})}, + {"mceliece6960119pcf", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 12})}, + {"mceliece8192128", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 13})}, + {"mceliece8192128f", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 14})}, + {"mceliece8192128pc", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 15})}, + {"mceliece8192128pcf", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 16})}, {"secp160k1", OID({1, 3, 132, 0, 9})}, {"secp160r1", OID({1, 3, 132, 0, 8})}, {"secp160r2", OID({1, 3, 132, 0, 30})}, diff --git a/src/lib/pubkey/classic_mceliece/cmce.cpp b/src/lib/pubkey/classic_mceliece/cmce.cpp new file mode 100644 index 00000000000..2a0e5dc0ebb --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce.cpp @@ -0,0 +1,172 @@ +/* + * Classic McEliece Key Generation + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Botan { + +Classic_McEliece_PublicKey::Classic_McEliece_PublicKey(const AlgorithmIdentifier& alg_id, + std::span key_bits) : + Classic_McEliece_PublicKey(key_bits, cmce_param_set_from_oid(alg_id.oid())) {} + +Classic_McEliece_PublicKey::Classic_McEliece_PublicKey(std::span key_bits, + Classic_McEliece_Parameter_Set param_set) { + auto params = Classic_McEliece_Parameters::create(param_set); + BOTAN_ARG_CHECK(key_bits.size() == params.pk_size_bytes(), "Wrong public key length"); + std::vector key_bits_vec(key_bits.begin(), key_bits.end()); + m_public = std::make_shared( + params, Classic_McEliece_Matrix(params, std::move(key_bits_vec))); +} + +Classic_McEliece_PublicKey::Classic_McEliece_PublicKey(const Classic_McEliece_PublicKey& other) { + m_public = std::make_shared(*other.m_public); +} + +Classic_McEliece_PublicKey& Classic_McEliece_PublicKey::operator=(const Classic_McEliece_PublicKey& other) { + if(this != &other) { + m_public = std::make_shared(*other.m_public); + } + return *this; +} + +AlgorithmIdentifier Classic_McEliece_PublicKey::algorithm_identifier() const { + return AlgorithmIdentifier(object_identifier(), AlgorithmIdentifier::USE_EMPTY_PARAM); +} + +OID Classic_McEliece_PublicKey::object_identifier() const { + return m_public->params().object_identifier(); +} + +size_t Classic_McEliece_PublicKey::key_length() const { + return m_public->matrix().bytes().size(); +} + +size_t Classic_McEliece_PublicKey::estimated_strength() const { + return m_public->params().estimated_strength(); +} + +std::vector Classic_McEliece_PublicKey::public_key_bits() const { + return m_public->matrix().bytes(); +} + +bool Classic_McEliece_PublicKey::check_key(RandomNumberGenerator&, bool) const { + return true; +} + +std::unique_ptr Classic_McEliece_PublicKey::generate_another(RandomNumberGenerator& rng) const { + return std::make_unique(rng, m_public->params().set()); +} + +std::unique_ptr Classic_McEliece_PublicKey::create_kem_encryption_op( + std::string_view params, std::string_view provider) const { + if(provider.empty() || provider == "base") { + return std::make_unique(this->m_public, params); + } + throw Provider_Not_Found(algo_name(), provider); +} + +Classic_McEliece_PrivateKey::Classic_McEliece_PrivateKey(RandomNumberGenerator& rng, + Classic_McEliece_Parameter_Set param_set) { + auto params = Classic_McEliece_Parameters::create(param_set); + auto seed = rng.random_vec(params.seed_len()); + auto key_pair = Classic_McEliece_KeyPair_Internal::generate(params, seed); + + m_private = key_pair.private_key; + m_public = key_pair.public_key; +} + +Classic_McEliece_PrivateKey::Classic_McEliece_PrivateKey(std::span sk, + Classic_McEliece_Parameter_Set param_set) { + auto params = Classic_McEliece_Parameters::create(param_set); + auto sk_internal = Classic_McEliece_PrivateKeyInternal::from_bytes(params, sk); + m_private = std::make_shared(std::move(sk_internal)); + // This creates and loads the public key, which is very large. Potentially, we could only load + // it on demand (since one may use the private key only for decapsulation without needing the public key). + m_public = Classic_McEliece_PublicKeyInternal::create_from_private_key(*m_private); +} + +Classic_McEliece_PrivateKey::Classic_McEliece_PrivateKey(const AlgorithmIdentifier& alg_id, + std::span key_bits) : + Classic_McEliece_PrivateKey(key_bits, cmce_param_set_from_oid(alg_id.oid())) {} + +std::unique_ptr Classic_McEliece_PrivateKey::public_key() const { + return std::make_unique(*this); +} + +secure_vector Classic_McEliece_PrivateKey::private_key_bits() const { + return raw_private_key_bits(); +} + +secure_vector Classic_McEliece_PrivateKey::raw_private_key_bits() const { + return m_private->serialize(); +} + +bool Classic_McEliece_PrivateKey::check_key(RandomNumberGenerator&, bool) const { + auto prg = m_private->params().prg(m_private->delta()); + + auto s = prg->output(m_private->params().n() / 8); + auto ordering_seed = + prg->output>((m_private->params().sigma2() * m_private->params().q()) / 8); + auto irreducible_seed = + prg->output>((m_private->params().sigma1() * m_private->params().t()) / 8); + + // Recomputing s as hash of delta + auto ret = + CT::Mask::expand(CT::is_equal(s.data(), m_private->s().data(), m_private->params().n() / 8)); + + // Checking weight of c + size_t c_hamming_weight = 0; + for(size_t i = 0; i < m_private->c().get().size(); ++i) { + c_hamming_weight += CT::Mask::expand(m_private->c().get()[i]).if_set_return(1); + } + + ret &= CT::Mask::is_equal(c_hamming_weight, 32); + + auto g = m_private->params().poly_ring().compute_minimal_polynomial(irreducible_seed); + if(g) { + for(size_t i = 0; i < g->degree() - 1; ++i) { + ret &= CT::Mask::expand(GF_Mask::is_equal(g->coef_at(i), m_private->g().coef_at(i)).elem_mask()); + } + } else { + ret = CT::Mask::cleared(); + } + + // Check alpha control bits + auto field_ord_from_seed = + Classic_McEliece_Field_Ordering::create_field_ordering(m_private->params(), ordering_seed); + if(!field_ord_from_seed) { + ret = CT::Mask::cleared(); + } else { + field_ord_from_seed->permute_with_pivots(m_private->params(), m_private->c()); + ret &= CT::Mask::expand(field_ord_from_seed->ct_is_equal(m_private->field_ordering())); + } + + return ret.as_bool(); +} + +std::unique_ptr Classic_McEliece_PrivateKey::create_kem_decryption_op( + RandomNumberGenerator& rng, std::string_view params, std::string_view provider) const { + BOTAN_UNUSED(rng); + if(provider.empty() || provider == "base") { + return std::make_unique(this->m_private, params); + } + throw Provider_Not_Found(algo_name(), provider); +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce.h b/src/lib/pubkey/classic_mceliece/cmce.h new file mode 100644 index 00000000000..7eabe49123f --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce.h @@ -0,0 +1,151 @@ +/* + * Classic McEliece Key Generation + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_H_ +#define BOTAN_CMCE_H_ + +#include + +#include + +namespace Botan { + +class Classic_McEliece_PublicKeyInternal; +class Classic_McEliece_PrivateKeyInternal; + +/** + * Classic McEliece is a Code-Based KEM. It is a round 4 candidate in NIST's PQC competition. + * It is endorsed by the German Federal Office for Information Security 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. + * + * Advantages of Classic McEliece: + * - Conservative post-quantum security assumptions + * - Very fast encapsulation + * - Fast decapsulation + * + * Disadvantages of Classic McEliece: + * - Very large public keys (0.26 MB - 1.36 MB) + * - Relatively slow key generation + * - Algorithm is complex and hard to implement side-channel resistant + */ +class BOTAN_PUBLIC_API(3, 4) Classic_McEliece_PublicKey : public virtual Public_Key { + public: + /** + * @brief Load a Classic McEliece public key from bytes. + * + * @param alg_id The algorithm identifier + * @param key_bits The public key bytes + */ + Classic_McEliece_PublicKey(const AlgorithmIdentifier& alg_id, std::span key_bits); + + /** + * @brief Load a Classic McEliece public key from bytes. + * + * @param key_bits The public key bytes + * @param param_set The parameter set + */ + Classic_McEliece_PublicKey(std::span key_bits, Classic_McEliece_Parameter_Set param_set); + + Classic_McEliece_PublicKey(const Classic_McEliece_PublicKey& other); + Classic_McEliece_PublicKey& operator=(const Classic_McEliece_PublicKey& other); + Classic_McEliece_PublicKey(Classic_McEliece_PublicKey&&) = default; + Classic_McEliece_PublicKey& operator=(Classic_McEliece_PublicKey&&) = default; + + ~Classic_McEliece_PublicKey() override = default; + + std::string algo_name() const override { return "ClassicMcEliece"; } //TODO: Use "CMCE" or "Classic_McEliece"? + + AlgorithmIdentifier algorithm_identifier() const override; + + OID object_identifier() const override; + + size_t key_length() const override; + + size_t estimated_strength() const override; + + std::vector public_key_bits() const override; + + bool check_key(RandomNumberGenerator&, bool) const override; + + bool supports_operation(PublicKeyOperation op) const override { + return (op == PublicKeyOperation::KeyEncapsulation); + } + + std::unique_ptr generate_another(RandomNumberGenerator& rng) const final; + + std::unique_ptr create_kem_encryption_op(std::string_view params, + std::string_view provider) const override; + + protected: + Classic_McEliece_PublicKey() = default; + + protected: + std::shared_ptr + m_public; // NOLINT(misc-non-private-member-variables-in-classes) +}; + +BOTAN_DIAGNOSTIC_PUSH +BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE + +class BOTAN_PUBLIC_API(3, 4) Classic_McEliece_PrivateKey final : public virtual Classic_McEliece_PublicKey, + public virtual Private_Key { + public: + /** + * @brief Create a new Classic McEliece private key for a specified parameter set. + * + * @param rng A random number generator + * @param param_set The parameter set to use + */ + Classic_McEliece_PrivateKey(RandomNumberGenerator& rng, Classic_McEliece_Parameter_Set param_set); + + /** + * @brief Load a Classic McEliece private key from bytes. + * + * @param sk The private key bytes + * @param param_set The parameter set to use + */ + Classic_McEliece_PrivateKey(std::span sk, Classic_McEliece_Parameter_Set param_set); + + /** + * @brief Load a Classic McEliece private key from bytes. + * + * @param alg_id The algorithm identifier + * @param key_bits The private key bytes + */ + Classic_McEliece_PrivateKey(const AlgorithmIdentifier& alg_id, std::span key_bits); + + std::unique_ptr public_key() const override; + + secure_vector private_key_bits() const override; + + secure_vector raw_private_key_bits() const override; + + /** + * @brief Checks the private key for consistency with the first component delta, i.e., + * recomputes s as a hash of delta and checks equivalence with sk.s, checks the weight of c, + * and checks the control bits. It also recomputes beta based on delta and recomputes g based on beta, + * checking that g is equal to the value sk.s + * + * See NIST Impl. guide 6.3 Double-Checks on Private Keys. + */ + bool check_key(RandomNumberGenerator&, bool) const override; + + std::unique_ptr create_kem_decryption_op(RandomNumberGenerator& rng, + std::string_view params, + std::string_view provider) const override; + + private: + std::shared_ptr m_private; +}; + +BOTAN_DIAGNOSTIC_POP + +} // namespace Botan + +#endif // BOTAN_CMCE_GF_H_ diff --git a/src/lib/pubkey/classic_mceliece/cmce_decaps.cpp b/src/lib/pubkey/classic_mceliece/cmce_decaps.cpp new file mode 100644 index 00000000000..0b27876faae --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_decaps.cpp @@ -0,0 +1,166 @@ +/* + * 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) { + BOTAN_ASSERT(params.n() == code_word.size(), "Correct code word size"); + std::vector syndrome(2 * params.t(), params.gf(GF_Elem(0))); + + auto alphas = ordering.alphas(params.n()); + + for(size_t i = 0; i < params.n(); ++i) { + auto g_alpha = goppa_poly(alphas[i]); + auto r = (g_alpha * g_alpha).inv(); + + auto c_mask = GF_Mask(CT::Mask::expand(code_word.at(i))); + + 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) { + // Represents coefficients of corresponding polynomials + std::vector big_c(params.t() + 1, params.gf(GF_Elem(0))); + std::vector big_b(params.t() + 1, params.gf(GF_Elem(0))); + + auto b = params.gf(GF_Elem(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) = GF_Elem(1); + big_c.at(0) = GF_Elem(1); + + for(size_t big_n = 0, big_l = 0; big_n < 2 * params.t(); ++big_n) { + auto d = params.gf(GF_Elem(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, secure_bitvector> Classic_McEliece_Decryptor::decode(bitvector big_c) { + BOTAN_ASSERT(big_c.size() == m_key->params().m() * m_key->params().t(), "Correct ciphertext input size"); + big_c.resize(m_key->params().n()); + + auto syndrome = compute_goppa_syndrome(m_key->params(), m_key->g(), m_key->field_ordering(), big_c.as_locked()); + auto locator = berlekamp_massey(m_key->params(), syndrome); + + std::vector images; + 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 + secure_bitvector e; + size_t hamming_weight_e = 0; + auto decode_success = CT::Mask::set(); // Avoid bool to avoid possible compiler optimizations + for(const auto& image : images) { + auto is_zero_mask = GF_Mask::is_zero(image); + e.push_back(is_zero_mask.as_bool()); + hamming_weight_e += is_zero_mask.elem_mask().if_set_return(1); + } + decode_success &= CT::Mask(CT::Mask::is_equal(hamming_weight_e, 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 + auto syndrome_from_e = compute_goppa_syndrome(m_key->params(), m_key->g(), m_key->field_ordering(), e); + auto syndromes_are_eq = GF_Mask::set(); + for(size_t i = 0; i < syndrome.degree() - 1; ++i) { + syndromes_are_eq &= GF_Mask::is_equal(syndrome.coef_at(i), syndrome_from_e.coef_at(i)); + } + + decode_success &= syndromes_are_eq.elem_mask(); + + return {decode_success, std::move(e)}; +} + +void Classic_McEliece_Decryptor::raw_kem_decrypt(std::span out_shared_key, + std::span encapsulated_key) { + BOTAN_ARG_CHECK(out_shared_key.size() == m_key->params().hash_out_bytes(), "Invalid shared key output size"); + BOTAN_ARG_CHECK(encapsulated_key.size() == m_key->params().ciphertext_size(), "Invalid ciphertext size"); + + auto [ct, c1] = [&]() -> std::pair> { + if(m_key->params().is_pc()) { + BufferSlicer encaps_key_slicer(encapsulated_key); + auto c0_ret = encaps_key_slicer.take(m_key->params().encode_out_size()); + auto c1_ret = encaps_key_slicer.take(m_key->params().hash_out_bytes()); + BOTAN_ASSERT_NOMSG(encaps_key_slicer.empty()); + return {bitvector(c0_ret, m_key->params().m() * m_key->params().t()), c1_ret}; + } else { + return {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.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(2); + hash_func->update(e_bytes); + auto c1_p = hash_func->final_stdvec(); + CT::Mask eq_mask = CT::is_equal(c1.data(), c1_p.data(), c1.size()); + eq_mask.select_n(e_bytes.data(), e_bytes.data(), m_key->s().data(), m_key->s().size()); + b = eq_mask.select(b, 0); + } + + hash_func->update(b); + hash_func->update(e_bytes); + hash_func->update(encapsulated_key); + hash_func->final(out_shared_key); +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_decaps.h b/src/lib/pubkey/classic_mceliece/cmce_decaps.h new file mode 100644 index 00000000000..24eeab67689 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_decaps.h @@ -0,0 +1,84 @@ +/* + * 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 + +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); + + /** + * @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); + + /** + * @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, secure_bitvector> decode(bitvector big_c); + + std::shared_ptr m_key; +}; + +} // namespace Botan + +#endif // BOTAN_CMCE_DECAPS_H_ diff --git a/src/lib/pubkey/classic_mceliece/cmce_encaps.cpp b/src/lib/pubkey/classic_mceliece/cmce_encaps.cpp new file mode 100644 index 00000000000..2734453f385 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_encaps.cpp @@ -0,0 +1,126 @@ +/* + * 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 +#include + +namespace Botan { + +bitvector Classic_McEliece_Encryptor::encode(const Classic_McEliece_Parameters& params, + const secure_bitvector& e, + const Classic_McEliece_Matrix& mat) { + return mat.mul(params, e); +} + +std::optional Classic_McEliece_Encryptor::fixed_weight_vector_gen( + const Classic_McEliece_Parameters& params, const secure_vector& rand) { + BOTAN_ASSERT_NOMSG(rand.size() == params.tau() * (params.sigma1() / 8)); + 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) { + uint16_t d = load_le(rand_slicer.take(params.sigma1() / 8).data(), 0); + // This is not CT, but neither is the reference implementation here. + // This side channel only leaks which random elements are selected and which are dropped, + // but no information about their content is leaked. + d &= mask_m; + if(d < params.n() && a_values.size() < params.t()) { + a_values.push_back(d); + } + } + if(a_values.size() < params.t()) { + // Step 3: ... If fewer than t of such elements exist restart + return std::nullopt; + } + + // Step 4: Restart if not all a_i are distinct + for(size_t i = 1; i < params.t(); ++i) { + for(size_t j = 0; j < i; ++j) { + if(a_values.at(i) == a_values.at(j)) { + return std::nullopt; + } + } + } + + secure_vector a_value_byte(params.t()); + secure_vector e_bytes(ceil_tobytes(params.n())); + + // Step 5: Set all bits of e at the positions of a_values + // Prepare the associated byte in e_bytes that is represented by each bit index in a_values + // if we e is represented as a byte vector + for(size_t j = 0; j < a_values.size(); ++j) { + a_value_byte[j] = 1 << (a_values[j] % 8); + } + + for(size_t i = 0; i < params.n() / 8; ++i) { + for(size_t j = 0; j < a_values.size(); ++j) { + // If the current byte is the one that is represented by the current bit index in a_values + // then set the bit in e_bytes (in-byte position prepared above) + auto mask = CT::Mask::is_equal(static_cast(i), static_cast(a_values[j] >> 3)); + e_bytes[i] |= mask.if_set_return(a_value_byte[j]); + } + } + + return 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_ASSERT(out_encapsulated_key.size() == m_key->params().ciphertext_size(), + "Correct encapsulated key output length"); + BOTAN_ASSERT(out_shared_key.size() == m_key->params().hash_out_bytes(), "Correct shared key output length"); + + auto& params = m_key->params(); + + // Call fixed_weight until it is successful to + // create a random error vector e of weight tau + secure_bitvector 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 + const size_t MAX_ATTEMPTS = 203; + for(size_t attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { + if(auto maybe_e = fixed_weight_vector_gen(params, rng.random_vec((params.sigma1() / 8) * params.tau()))) { + return maybe_e.value(); + } + } + throw Internal_Error("Cannot created fixed weight vector."); + }(); + + auto big_c = encode(params, e, m_key->matrix()).to_bytes(); + auto hash_func = params.hash_func(); + + if(params.is_pc()) { + hash_func->update(2); + hash_func->update(e.to_bytes()); + auto big_c_1 = hash_func->final_stdvec(); + big_c = Botan::concat(big_c, big_c_1); + hash_func->clear(); + } + + hash_func->update(1); + hash_func->update(e.to_bytes()); + hash_func->update(big_c); + + auto big_k = hash_func->final>(); + hash_func->clear(); + + std::copy(big_c.begin(), big_c.end(), out_encapsulated_key.begin()); + std::copy(big_k.begin(), big_k.end(), out_shared_key.begin()); +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_encaps.h b/src/lib/pubkey/classic_mceliece/cmce_encaps.h new file mode 100644 index 00000000000..9320a14cea5 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_encaps.h @@ -0,0 +1,59 @@ +/* +* 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 + +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. + */ + bitvector encode(const Classic_McEliece_Parameters& params, + const secure_bitvector& e, + const Classic_McEliece_Matrix& mat); + + /** + * @brief Fixed-weight-vector generation algorithm according to ISO McEliece. + */ + std::optional fixed_weight_vector_gen(const Classic_McEliece_Parameters& params, + const secure_vector& rand); +}; + +} // namespace Botan + +#endif // BOTAN_CMCE_ENCAPS_H_ diff --git a/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp b/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp new file mode 100644 index 00000000000..c6bc0af410c --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp @@ -0,0 +1,322 @@ +/* + * Classic McEliece Field Ordering Generation + * Based on the public domain reference implementation by the designers + * (https://classic.mceliece.org/impl.html - released in Oct 2022 for NISTPQC-R4) + * + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace CMCE_CT { + +template +void cond_swap_pair(CT::Mask cond_mask, std::pair& a, std::pair& b) { + static_assert(sizeof(T1) <= sizeof(uint64_t) && sizeof(T2) <= sizeof(uint64_t), + "Types T1 and T2 must be at most 64 bits wide"); + 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) { + 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++) { + size_t l = i ^ j; + if(l > i) { + compare_and_swap_pair(a, i, k, l); + } + } + } + } +} + +template +T min(const T& a, const T& b) { + auto mask = CT::Mask::is_lt(a, b); + return mask.select(a, b); +} + +} // namespace CMCE_CT + +namespace { +template +std::vector> zip(const secure_vector& vec_1, const secure_vector& vec_2) { + BOTAN_ARG_CHECK(vec_1.size() == vec_2.size(), "Vectors' dimensions do not match"); + std::vector> vec_zipped; + vec_zipped.reserve(vec_1.size()); + for(size_t i = 0; i < vec_1.size(); ++i) { + vec_zipped.push_back(std::make_pair(vec_1.at(i), vec_2.at(i))); + } + return vec_zipped; +} + +template +std::pair, secure_vector> unzip(std::vector> vec_zipped) { + secure_vector vec_1; + secure_vector vec_2; + + vec_1.reserve(vec_zipped.size()); + vec_2.reserve(vec_zipped.size()); + + for(auto& [elem1, elem2] : vec_zipped) { + vec_1.push_back(elem1); + vec_2.push_back(elem2); + } + return std::make_pair(std::move(vec_1), std::move(vec_2)); +} + +/** +* @brief Create permutation pi as in (Section 8.2, Step 3). +*/ +Permutation create_pi(secure_vector& a) { + Permutation pi(a.size()); + std::iota(pi.begin(), pi.end(), 0); // contains 0, 1, ..., q-1 + + auto a_pi_zipped = zip(a, pi.get()); + CMCE_CT::bitonic_sort_pair(std::span(a_pi_zipped)); + + auto [a_sorted, pi_sorted] = unzip(std::move(a_pi_zipped)); + a = std::move(a_sorted); + + return Permutation(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(Permutation_Element pi_elem, GF_Mod modulus, size_t m) { + std::bitset<16> bits(pi_elem.get()); + std::bitset<16> reversed_bits; + + for(int i = 0; i < 16; ++i) { + reversed_bits[i] = bits[15 - i]; + } + + reversed_bits >>= (sizeof(uint16_t) * 8 - m); + + return Classic_McEliece_GF(GF_Elem(static_cast(reversed_bits.to_ulong())), modulus); +} + +/** + * @brief Part of field ordering generation according to ISO 9.2.10 + */ +secure_vector composeinv(const secure_vector& c, const secure_vector& pi) { + auto pi_c_zipped = zip(pi, c); + CMCE_CT::bitonic_sort_pair(std::span(pi_c_zipped)); + auto [pi_sorted, c_sorted] = unzip(pi_c_zipped); + + 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. + */ +secure_vector generate_control_bits_internal(const secure_vector& pi) { + auto n = pi.size(); + size_t m = 1; + + while(size_t(1) << m < n) { + m += 1; + } + + BOTAN_ASSERT_NOMSG(size_t(1) << m == 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; size_t(x) < n; ++x) { + c.at(x) = CMCE_CT::min(x, p.at(x)); + } + + simultaneous_composeinv(p, q); + + for(size_t i = 1; i < m - 1; ++i) { + auto cp = composeinv(c, q); + simultaneous_composeinv(p, q); + for(size_t x = 0; x < n; ++x) { + c.at(x) = CMCE_CT::min(c.at(x), cp.at(x)); + } + } + + secure_vector f(n / 2); + for(size_t j = 0; j < n / 2; ++j) { + f.at(j) = c.at(2 * j) % 2; + } + + secure_vector big_f(n); + for(uint16_t x = 0; size_t(x) < n; ++x) { + big_f.at(x) = x ^ f.at(x / 2); + } + + auto fpi = composeinv(big_f, piinv); + + secure_vector l(n / 2); + for(size_t k = 0; k < n / 2; ++k) { + l.at(k) = fpi.at(2 * k) % 2; + } + + secure_vector big_l(n); + for(uint16_t y = 0; size_t(y) < n; ++y) { + big_l.at(y) = y ^ l.at(y / 2); + } + + auto big_m = composeinv(fpi, big_l); + + secure_vector subm0(n / 2); + secure_vector subm1(n / 2); + for(size_t j = 0; j < n / 2; ++j) { + subm0.at(j) = big_m.at(2 * j) / 2; + subm1.at(j) = big_m.at(2 * j + 1) / 2; + } + + auto subz0 = generate_control_bits_internal(subm0); + auto subz1 = generate_control_bits_internal(subm1); + + secure_vector z(subz0.size() + subz1.size()); + for(size_t j = 0; j < subz0.size(); ++j) { + z.at(2 * j) = subz0.at(j); + z.at(2 * j + 1) = subz1.at(j); + } + + return concat(f, z, l); +} + +} // anonymous namespace + +std::optional Classic_McEliece_Field_Ordering::create_field_ordering( + const Classic_McEliece_Parameters& params, std::span random_bits) { + BOTAN_ARG_CHECK(random_bits.size() == (params.sigma2() * params.q()) / 8, "Wrong random bits size"); + + secure_vector a; // contains a_0, a_1, ... + for(size_t i = 0; i < params.q(); ++i) { + a.push_back(load_le(random_bits.data(), i)); // Utilizes sigma2 = 32 + } + + auto pi = create_pi(a); + + for(size_t i = 1; i < params.q(); ++i) { + if(a.at(i - 1) == a.at(i)) { // Check for duplicate elements utilizing sorting + return std::nullopt; // Abort (Step 2) + } + } + + 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(Permutation_Element(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 + 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)); + uint16_t n = static_cast(1) << params.m(); + Permutation pi(n); + std::iota(pi.begin(), pi.end(), 0); + for(size_t i = 0; i < 2 * params.m() - 1; ++i) { + size_t gap = static_cast(1) << std::min(i, 2 * params.m() - 2 - i); + for(size_t j = 0; j < size_t(n / 2); ++j) { + 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 Column_Selection& 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.get().at(col)); + p_counter += CT::Mask::expand(pivots.get().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 00000000000..82796019351 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_field_ordering.h @@ -0,0 +1,112 @@ +/* + * Classic McEliece Field Ordering Generation + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_FIELD_ORDERING_H_ +#define BOTAN_CMCE_FIELD_ORDERING_H_ + +#include +#include + +#include + +namespace Botan { + +/** + * @brief Represents a field ordering for the Classic McEliece cryptosystem. + * + * Field ordering corresponds to the permutation pi defining the alpha sequence in + * the Classic McEliece specification (see Classic McEliece ISO Sec. 8.2.). + */ +class BOTAN_TEST_API Classic_McEliece_Field_Ordering { + public: + /** + * @brief Creates a field ordering from a random bit sequence. Corresponds to + * the algorithm described in Classic McEliece ISO Sec. 8.2. + * + * @param params The McEliece parameters. + * @param random_bits The random bit sequence. + * @return The field ordering. + */ + static std::optional create_field_ordering( + const Classic_McEliece_Parameters& params, std::span 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. + */ + Permutation& pi_ref() { return m_pi; } + + /** + * @brief The pi values representing the field ordering. + * + * @return pi values. + */ + const Permutation& 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 Column_Selection& pivots); + + private: + Classic_McEliece_Field_Ordering(Permutation pi, GF_Mod poly_f) : m_pi(std::move(pi)), m_poly_f(poly_f) {} + + private: + Permutation m_pi; + GF_Mod m_poly_f; +}; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/classic_mceliece/cmce_gf.cpp b/src/lib/pubkey/classic_mceliece/cmce_gf.cpp new file mode 100644 index 00000000000..308027da0c0 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_gf.cpp @@ -0,0 +1,123 @@ +/* +* Classic McEliece GF arithmetic +* Based on the public domain reference implementation by the designers +* (https://classic.mceliece.org/impl.html - released in Oct 2022 for NISTPQC-R4) +* +* (C) 2023 Jack Lloyd +* 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +**/ + +#include + +namespace Botan { + +namespace { +inline GF_Elem internal_reduce(uint32_t x, GF_Mod 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 GF_Elem(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 GF_Elem(static_cast(x & 0xfff)); + } else { + // TODO: Only for test instances. Remove on final PR + size_t m = Classic_McEliece_GF::log_q_from_mod(mod); + + for(int i = static_cast(m) - 2; i >= 0; --i) { + x ^= CT::Mask::expand((uint32_t(1) << (i + m)) & x) + .if_set_return(static_cast(mod.get()) << i); + } + + return GF_Elem(static_cast(x)); + } +} + +} // namespace + +Classic_McEliece_GF Classic_McEliece_GF::operator/(Classic_McEliece_GF other) const { + BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus); + return *this * other.inv(); +} + +Classic_McEliece_GF Classic_McEliece_GF::operator+(Classic_McEliece_GF other) const { + BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus); + return Classic_McEliece_GF(m_elem ^ other.m_elem, m_modulus); +} + +Classic_McEliece_GF& Classic_McEliece_GF::operator+=(Classic_McEliece_GF other) { + BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus); + m_elem ^= other.m_elem; + return *this; +} + +Classic_McEliece_GF& Classic_McEliece_GF::operator^=(GF_Elem other) { + m_elem ^= other; + return *this; +} + +Classic_McEliece_GF& Classic_McEliece_GF::operator*=(Classic_McEliece_GF other) { + BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus); + *this = *this * other; + return *this; +} + +Classic_McEliece_GF Classic_McEliece_GF::operator*(Classic_McEliece_GF other) const { + BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus); + + uint32_t a = m_elem.get(); + uint32_t b = other.m_elem.get(); + + uint32_t acc = a * (b & 1); + + for(size_t i = 1; i < log_q(); i++) { + acc ^= (a * (b & (1 << i))); + } + + return Classic_McEliece_GF(internal_reduce(acc, m_modulus), m_modulus); +} + +Classic_McEliece_GF Classic_McEliece_GF::square() const { + return (*this) * (*this); +} + +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 = {GF_Elem(1), m_modulus}; + while(exponent > 0) { + if(exponent % 2 == 1) { + // multiply + result = (result * base); + } + // square + base = base.square(); + exponent /= 2; + } + + return result; +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_gf.h b/src/lib/pubkey/classic_mceliece/cmce_gf.h new file mode 100644 index 00000000000..73b16fd2a4e --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_gf.h @@ -0,0 +1,191 @@ +/* + * 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. + * @param modulus The modulus of GF(q). + */ + Classic_McEliece_GF(GF_Elem elem, GF_Mod modulus) : m_elem(elem), m_modulus(modulus) { + m_elem &= GF_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(GF_Mod 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. + */ + GF_Elem 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. + */ + GF_Mod modulus() const { return m_modulus; } + + /** + * @brief Change the element to @p elem. + */ + Classic_McEliece_GF& operator=(const GF_Elem elem) { + m_elem = elem & GF_Elem((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; + + /** + * @brief Add @p other to the element. Constant time. + */ + Classic_McEliece_GF operator+(Classic_McEliece_GF other) const; + + /** + * @brief Add @p other to the element. Constant time. + */ + Classic_McEliece_GF& operator+=(Classic_McEliece_GF other); + + /** + * @brief XOR assign a GF_Elem to the element of this. Constant time. + */ + Classic_McEliece_GF& operator^=(GF_Elem other); + + /** + * @brief Multiply the element by @p other in GF(q). Constant time. + */ + Classic_McEliece_GF& operator*=(Classic_McEliece_GF other); + + /** + * @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; + + /** + * @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: + GF_Elem m_elem; + + GF_Mod m_modulus; +}; + +/** + * @brief Constant time mask wrapper for GF(q) elements. + */ +class BOTAN_TEST_API GF_Mask final { + public: + static GF_Mask expand(Classic_McEliece_GF v) { return GF_Mask(CT::Mask::expand(v.elem().get())); } + + static GF_Mask is_zero(Classic_McEliece_GF v) { return GF_Mask(CT::Mask::is_zero(v.elem().get())); } + + static GF_Mask is_lte(Classic_McEliece_GF a, Classic_McEliece_GF b) { + return GF_Mask(CT::Mask::is_lte(a.elem().get(), b.elem().get())); + } + + static GF_Mask is_equal(Classic_McEliece_GF a, Classic_McEliece_GF b) { + return GF_Mask(CT::Mask::is_equal(a.elem().get(), b.elem().get())); + } + + static GF_Mask set() { return GF_Mask(CT::Mask::set()); } + + GF_Mask(CT::Mask underlying_mask) : m_mask(underlying_mask) {} + + Classic_McEliece_GF if_set_return(const Classic_McEliece_GF x) const { + return Classic_McEliece_GF(GF_Elem(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(GF_Elem(m_mask.select(x.elem().get(), y.elem().get())), x.modulus()); + } + + Classic_McEliece_GF select(const Classic_McEliece_GF x, GF_Elem y) const { + return Classic_McEliece_GF(GF_Elem(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() { return m_mask.as_bool(); } + + CT::Mask& elem_mask() { return m_mask; } + + private: + CT::Mask m_mask; +}; + +} // namespace Botan +#endif diff --git a/src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp b/src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp new file mode 100644 index 00000000000..88087ce5d34 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp @@ -0,0 +1,143 @@ +/* + * 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, + Key_Gen_Seed seed) { + BOTAN_ASSERT_EQUAL(seed.size(), 32, "Valid seed length"); + BOTAN_ASSERT_EQUAL(out_next_seed.size(), 32, "Valid output seed length"); + + auto ring = params.poly_ring(); + + auto big_e_xof = params.prg(seed); + + auto s = big_e_xof->output(params.n() / 8); + auto ordering_seed = big_e_xof->output>((params.sigma2() * params.q()) / 8); + auto irreducible_seed = 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_seed); + if(!field_ordering) { + return std::nullopt; + } + + // Irreducible-polynomial generation - Classic McEliece ISO 8.1 + auto g = params.poly_ring().compute_minimal_polynomial(irreducible_seed); + 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 + auto sk = std::make_shared( + params, std::move(seed), pivots, std::move(g.value()), std::move(field_ordering.value()), std::move(s)); + + auto pk = std::make_shared(params, std::move(pk_matrix)); + + return Classic_McEliece_KeyPair_Internal{.private_key = sk, .public_key = pk}; +} + +} // 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 = Column_Selection(sk_slicer.take(params.sk_c_bytes())); + auto g = Classic_McEliece_Minimal_Polynomial::from_bytes(sk_slicer.take(params.sk_poly_g_bytes()), params.poly_f()); + auto field_ordering = Classic_McEliece_Field_Ordering::create_from_control_bits( + params, secure_bitvector(sk_slicer.take(params.sk_alpha_control_bytes()))); + auto s = sk_slicer.copy(params.sk_s_bytes()); + BOTAN_ASSERT_NOMSG(sk_slicer.empty()); + + return Classic_McEliece_PrivateKeyInternal( + params, std::move(delta), std::move(c), std::move(g), std::move(field_ordering), std::move(s)); +} + +secure_vector Classic_McEliece_PrivateKeyInternal::serialize() const { + auto control_bits = m_field_ordering.alphas_control_bits(); + + /* NIST Impl. guide 6.1 Control-Bit Gen: + * As low-cost protection against faults in the control-bit computation, implementors are advised + * to check after the computation that applying the Benes network produces pi, and to + * restart key generation if this test fails; applying the Benes network is very fast. + * + * Here, we just assert that applying the Benes network produces pi. + */ + BOTAN_ASSERT(Classic_McEliece_Field_Ordering::create_from_control_bits(m_params, control_bits) + .ct_is_equal(m_field_ordering) + .as_bool(), + "Control Bit Computation Check"); + + return concat(m_delta.get(), m_c.get().to_bytes(), m_g.serialize(), control_bits.to_bytes(), m_s); +} + +std::shared_ptr Classic_McEliece_PublicKeyInternal::create_from_private_key( + const Classic_McEliece_PrivateKeyInternal& sk) { + auto pk_matrix_and_pivot = Classic_McEliece_Matrix::create_matrix(sk.params(), sk.field_ordering(), sk.g()); + if(!pk_matrix_and_pivot.has_value()) { + throw Decoding_Error("Cannot create public key from private key. Private key is invalid."); + } + auto& [pk_matrix, pivot] = pk_matrix_and_pivot.value(); + if(!pivot.get().subvector(0, pivot.get().size() / 2).all() || + !pivot.get().subvector(pivot.get().size() / 2).none()) { + // There should not be a pivot other than 0xff ff ff ff 00 00 00 00. Otherwise + // the gauss algorithm failed effectively. + throw Decoding_Error("Cannot create public key from private key. Private key is invalid."); + } + + auto pk = std::make_shared(sk.params(), std::move(pk_matrix)); + + return pk; +} + +Classic_McEliece_KeyPair_Internal Classic_McEliece_KeyPair_Internal::generate(const Classic_McEliece_Parameters& params, + StrongSpan seed) { + BOTAN_ASSERT_EQUAL(seed.size(), params.seed_len(), "Valid seed length"); + + Key_Gen_Seed next_seed(seed.size()); + Key_Gen_Seed current_seed(seed.begin(), seed.end()); + + // Emergency abort in case unexpected logical error to prevent endless loops + // Success probability: >29% per attempt [>98% for 'f' instances] + // => 162 [15] attempts for 2^(-80) fail probability + const size_t MAX_ATTEMPTS = params.is_f() ? 15 : 162; + for(size_t attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { + if(auto keypair = try_generate_keypair(next_seed, params, std::move(current_seed))) { + return keypair.value(); + } + current_seed = next_seed; + } + throw Internal_Error("Key generation fails consistently. Something went wrong."); +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_keys_internal.h b/src/lib/pubkey/classic_mceliece/cmce_keys_internal.h new file mode 100644 index 00000000000..061bae72216 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_keys_internal.h @@ -0,0 +1,187 @@ +/* + * Classic McEliece key generation with Internal Private and Public Key classes + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_KEYS_INTERNAL_H_ +#define BOTAN_CMCE_KEYS_INTERNAL_H_ + +#include +#include +#include +#include +#include + +namespace Botan { + +class Classic_McEliece_PrivateKeyInternal; + +/** + * @brief Representation of a Classic McEliece public key. + * + * This class represents a Classic McEliece public key. It is used internally by the Classic McEliece + * public key class and contains the following data: + * - The Classic McEliece parameters + * - The public key matrix + */ +class BOTAN_TEST_API Classic_McEliece_PublicKeyInternal { + public: + /** + * @brief Construct a Classic McEliece public key. + * + * @param params The Classic McEliece parameters + * @param matrix The public key matrix + */ + Classic_McEliece_PublicKeyInternal(const Classic_McEliece_Parameters& params, Classic_McEliece_Matrix matrix) : + m_params(params), m_matrix(std::move(matrix)) { + BOTAN_ASSERT_NOMSG(m_matrix.bytes().size() == m_params.pk_size_bytes()); + } + + /** + * @brief Create a Classic McEliece public key from a private key. + * + * Create the matrix from the private key values. Expects that the private key is valid, i.e. + * the matrix creation works. + * + * @param sk The private key + * @return The public key as a shared pointer + */ + static std::shared_ptr create_from_private_key( + const Classic_McEliece_PrivateKeyInternal& sk); + + /** + * @brief Serializes the Classic McEliece public key as defined in Classic McEliece ISO Section 9.2.7. + */ + std::vector serialize() const { return m_matrix.bytes(); } + + /** + * @brief The Classic McEliece matrix. + */ + const Classic_McEliece_Matrix& matrix() const { return m_matrix; } + + /** + * @brief The Classic McEliece parameters. + */ + const Classic_McEliece_Parameters& params() const { return m_params; } + + private: + Classic_McEliece_Parameters m_params; + Classic_McEliece_Matrix m_matrix; +}; + +/** + * @brief Representation of a Classic McEliece private key. + * + * This class represents a Classic McEliece private key. It is used internally by the Classic McEliece + * private key class and contains the following data (see Classic McEliece ISO Section 9.2.12): + * - The Classic McEliece parameters + * - The seed delta + * - The column selection pivot vector c + * - The minimal polynomial g + * - The field ordering alpha + * - The seed s for implicit rejection + */ +class BOTAN_TEST_API Classic_McEliece_PrivateKeyInternal { + public: + /** + * @brief Construct a Classic McEliece private key. + * + * @param params The Classic McEliece parameters + * @param delta The seed delta + * @param c The column selection pivot vector c + * @param g The minimal polynomial g + * @param alpha The field ordering alpha + * @param s The seed s for implicit rejection + */ + Classic_McEliece_PrivateKeyInternal(const Classic_McEliece_Parameters& params, + Key_Gen_Seed delta, + Column_Selection c, + Classic_McEliece_Minimal_Polynomial g, + Classic_McEliece_Field_Ordering alpha, + Rejection_Seed 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 Key_Gen_Seed& delta() const { return m_delta; } + + /** + * @brief The column selection pivot vector c as defined in Classic McEliece ISO Section 9.2.11. + */ + const Column_Selection& 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 Rejection_Seed& s() const { return m_s; } + + /** + * @brief The Classic McEliece parameters. + */ + const Classic_McEliece_Parameters& params() const { return m_params; } + + private: + Classic_McEliece_Parameters m_params; + Key_Gen_Seed m_delta; + Column_Selection m_c; + Classic_McEliece_Minimal_Polynomial m_g; + Classic_McEliece_Field_Ordering m_field_ordering; + Rejection_Seed 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); +}; + +} // namespace Botan + +#endif // BOTAN_CMCE_KEYS_INTERNAL_H_ diff --git a/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp b/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp new file mode 100644 index 00000000000..e8c3637b771 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp @@ -0,0 +1,258 @@ +/* + * 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 + +namespace { +using word_t = uint64_t; + +} // Anonymous namespace + +namespace Botan { + +//TODO Renéquest: This file could use MatrixRow and Matrix strong types that implement .at() etc. +namespace { +size_t count_lsb_zeros(secure_bitvector n) { + size_t res = 0; + auto found_only_zeros = Botan::CT::Mask::set(); + for(size_t bit_pos = 0; bit_pos < n.size(); ++bit_pos) { + auto bit_set_mask = Botan::CT::Mask::expand(n.at(bit_pos)); + found_only_zeros &= ~bit_set_mask; + res += found_only_zeros.if_set_return(size_t(1)); + } + + return res; +} + +std::vector 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()); + } + std::vector mat(params.pk_no_rows(), secure_bitvector(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(std::vector& mat, + const Classic_McEliece_Parameters& params) { + static_assert(Classic_McEliece_Parameters::nu() == 64, + "nu needs to be 64"); // Since we use uint64_t to represent rows in the mu x nu sub-matrix + // A 32x64 sub-matrix of mat containing the elements mat[m*t-32][m*t-32] at the top left + std::vector sub_mat(Classic_McEliece_Parameters::mu(), secure_bitvector()); + + size_t pos_offset = params.pk_no_rows() - Classic_McEliece_Parameters::mu(); + + // Extract the bottom mu x nu matrix at offset row_offset + for(size_t i = 0; i < 32; i++) { + sub_mat.at(i) = mat.at(pos_offset + i).subvector(pos_offset, Classic_McEliece_Parameters::nu()); + } + + std::array pivot_indices = {0}; // ctz_list + + // Identify the pivot indices, i.e., the indices of the leading ones for all rows + // when transforming the matrix into semi-systematic form. This algorithm is a modified + // Gauss algorithm. + for(size_t row_idx = 0; row_idx < 32; ++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 < 32; ++next_row) { + row_acc |= sub_mat.at(next_row); + } + + if(row_acc.none()) { + // If the current row and all subsequent rows are zero + // we cannot create a semi-systematic matrix + return std::nullopt; + } + + // Using the row accumulator we can predict the index of the pivot + // bit for the current row, i.e., the first index where we can set + // the bit to one row by adding any subsequent row + pivot_indices.at(row_idx) = count_lsb_zeros(row_acc); + + // Add subsequent rows to the current row, until the pivot + // bit is set. + for(size_t next_row = row_idx + 1; next_row < Classic_McEliece_Parameters::mu(); ++next_row) { + sub_mat.at(row_idx).ct_conditional_xor(!sub_mat.at(row_idx).at(pivot_indices.at(row_idx)), + sub_mat.at(next_row)); + } + + // Add the (new) current row to all subsequent rows, where the leading + // bit of the current bit is one. Therefore, the column of the leading + // bit becomes zero. + // Note: In normal gauss, we would also add the current row to rows + // above the current one. However, here we only need to identify + // the columns to swap. Therefore, we can ignore the upper rows. + for(size_t next_row = row_idx + 1; next_row < Classic_McEliece_Parameters::mu(); ++next_row) { + sub_mat.at(next_row).ct_conditional_xor(sub_mat.at(next_row).at(pivot_indices.at(row_idx)), + sub_mat.at(row_idx)); + } + } + + // Create pivot bitvector from the pivot index vector + Column_Selection pivots((secure_bitvector(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.get().at(i) = mask_is_at_current_idx.select(1, pivots.get().at(i)); + } + } + + // Update matrix by swapping columns + for(size_t mat_row = 0; mat_row < params.pk_no_rows(); ++mat_row) { + for(size_t j = 0; j < Classic_McEliece_Parameters::mu(); ++j) { + // Swap bit j with bit pivot_indices[j] + size_t col_j = pos_offset + j; + size_t col_pivot_j = pos_offset + pivot_indices.at(j); + // TODO: uint8_t instead of bool (also in bitvector) + auto sum = mat.at(mat_row).at(col_j) ^ mat.at(mat_row).at(col_pivot_j); + mat.at(mat_row).at(col_j) = mat.at(mat_row).at(col_j) ^ sum; + mat.at(mat_row).at(col_pivot_j) = mat.at(mat_row).at(col_pivot_j) ^ sum; + } + } + + return pivots; +} + +std::optional apply_gauss(const Classic_McEliece_Parameters& params, + std::vector& mat) { + // Initialized for systematic form instances + // Is overridden for semi systematic instances + auto pivots = Column_Selection(secure_bitvector(std::vector({0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0}), 64)); + + // Gaussian Elimination + for(size_t diag_pos = 0; diag_pos < params.pk_no_rows(); ++diag_pos) { + if(params.is_f() && diag_pos == params.pk_no_rows() - params.mu()) { + auto ret_pivots = move_columns(mat, params); + if(!ret_pivots) { + return std::nullopt; + } else { + pivots = std::move(ret_pivots.value()); + } + } + + // Iterates over all rows k under r. If the bit at column + // word_bits*i+j differs between row r and k, row k is added to row r. + // 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.at(diag_pos).ct_conditional_xor(!mat.at(diag_pos).at(diag_pos), mat.at(next_row)); + } + + // If the current bit on the diagonal is not set at this point + // the matrix is not systematic. We abort the computation in this case. + if(!mat.at(diag_pos).at(diag_pos)) { + return std::nullopt; + } + + // Now the new row is added to all other rows, where the + // bit in the column of the current postion on the diagonal + // is still one + for(size_t row = 0; row < params.pk_no_rows(); ++row) { + if(row != diag_pos) { + mat.at(row).ct_conditional_xor(mat.at(row).at(diag_pos), mat.at(diag_pos)); + } + } + } + + return pivots; +} + +std::vector extract_pk_bytes_from_matrix(const Classic_McEliece_Parameters& params, + const std::vector& 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) { + auto pk_row_bits = mat.at(row).subvector(params.pk_no_rows()); + auto row_bytes = pk_row_bits.to_bytes(); + + big_t_stuffer.append(row_bytes); + } + + BOTAN_ASSERT_NOMSG(big_t_stuffer.full()); + + return big_t; +} + +} // namespace + +std::optional> Classic_McEliece_Matrix::create_matrix( + const Classic_McEliece_Parameters& params, + const Classic_McEliece_Field_Ordering& field_ordering, + const Classic_McEliece_Minimal_Polynomial& g) { + auto mat = init_matrix_with_alphas(params, field_ordering, g); + auto pivots = apply_gauss(params, mat); + + if(!pivots) { + return std::nullopt; + } + + auto pk_mat_bytes = extract_pk_bytes_from_matrix(params, mat); + return std::make_pair(Classic_McEliece_Matrix(params, std::move(pk_mat_bytes)), pivots.value()); +} + +std::optional> +Classic_McEliece_Matrix::create_matrix_and_apply_pivots(const Classic_McEliece_Parameters& params, + Classic_McEliece_Field_Ordering& field_ordering, + const Classic_McEliece_Minimal_Polynomial& g) { + auto pk_matrix_and_pivots = create_matrix(params, field_ordering, g); + + if(!pk_matrix_and_pivots) { + return std::nullopt; + } + + auto& [_, pivots] = pk_matrix_and_pivots.value(); + + if(params.is_f()) { + field_ordering.permute_with_pivots(params, pivots); + } + + return pk_matrix_and_pivots; +} + +bitvector Classic_McEliece_Matrix::mul(const Classic_McEliece_Parameters& params, const secure_bitvector& 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.at(i) = s.at(i) ^ row.has_odd_hamming_weight(); + } + + BOTAN_ASSERT_NOMSG(pk_slicer.empty()); + return s.as_unlocked(); +} +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_matrix.h b/src/lib/pubkey/classic_mceliece/cmce_matrix.h new file mode 100644 index 00000000000..efa15ad9915 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_matrix.h @@ -0,0 +1,113 @@ +/* + * 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 + +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 + */ + 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 + */ + bitvector mul(const Classic_McEliece_Parameters& params, const secure_bitvector& e) const; + + private: + /// The bytes of the submatrix T + const std::vector m_mat_bytes; // can we use bitvector? +}; + +} // namespace Botan + +#endif // BOTAN_CMCE_MATRIX_H_ diff --git a/src/lib/pubkey/classic_mceliece/cmce_parameter_set.cpp b/src/lib/pubkey/classic_mceliece/cmce_parameter_set.cpp new file mode 100644 index 00000000000..cf407016b03 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_parameter_set.cpp @@ -0,0 +1,132 @@ +/* + * Classic McEliece Parameters + * (C) 2024 Jack Lloyd + * 2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#include + +namespace Botan { + +Classic_McEliece_Parameter_Set cmce_param_set_from_str(std::string_view param_name) { + if(param_name == "mceliece348864") { + return Classic_McEliece_Parameter_Set::mceliece348864; + } + if(param_name == "mceliece348864f") { + return Classic_McEliece_Parameter_Set::mceliece348864f; + } + if(param_name == "mceliece460896") { + return Classic_McEliece_Parameter_Set::mceliece460896; + } + if(param_name == "mceliece460896f") { + return Classic_McEliece_Parameter_Set::mceliece460896f; + } + if(param_name == "mceliece6688128") { + return Classic_McEliece_Parameter_Set::mceliece6688128; + } + if(param_name == "mceliece6688128f") { + return Classic_McEliece_Parameter_Set::mceliece6688128f; + } + if(param_name == "mceliece6688128pc") { + return Classic_McEliece_Parameter_Set::mceliece6688128pc; + } + if(param_name == "mceliece6688128pcf") { + return Classic_McEliece_Parameter_Set::mceliece6688128pcf; + } + if(param_name == "mceliece6960119") { + return Classic_McEliece_Parameter_Set::mceliece6960119; + } + if(param_name == "mceliece6960119f") { + return Classic_McEliece_Parameter_Set::mceliece6960119f; + } + if(param_name == "mceliece6960119pc") { + return Classic_McEliece_Parameter_Set::mceliece6960119pc; + } + if(param_name == "mceliece6960119pcf") { + return Classic_McEliece_Parameter_Set::mceliece6960119pcf; + } + if(param_name == "mceliece8192128") { + return Classic_McEliece_Parameter_Set::mceliece8192128; + } + if(param_name == "mceliece8192128f") { + return Classic_McEliece_Parameter_Set::mceliece8192128f; + } + if(param_name == "mceliece8192128pc") { + return Classic_McEliece_Parameter_Set::mceliece8192128pc; + } + if(param_name == "mceliece8192128pcf") { + return Classic_McEliece_Parameter_Set::mceliece8192128pcf; + } + // TODO: Remove on final PR + if(param_name == "test") { + return Classic_McEliece_Parameter_Set::test; + } + if(param_name == "testf") { + return Classic_McEliece_Parameter_Set::testf; + } + if(param_name == "testpc") { + return Classic_McEliece_Parameter_Set::testpc; + } + if(param_name == "testpcf") { + return Classic_McEliece_Parameter_Set::testpcf; + } + + throw Decoding_Error("Cannot convert string to CMCE parameter set"); +} + +std::string cmce_str_from_param_set(Classic_McEliece_Parameter_Set param) { + switch(param) { + case Classic_McEliece_Parameter_Set::mceliece348864: + return "mceliece348864"; + case Classic_McEliece_Parameter_Set::mceliece348864f: + return "mceliece348864f"; + case Classic_McEliece_Parameter_Set::mceliece460896: + return "mceliece460896"; + case Classic_McEliece_Parameter_Set::mceliece460896f: + return "mceliece460896f"; + case Classic_McEliece_Parameter_Set::mceliece6688128: + return "mceliece6688128"; + case Classic_McEliece_Parameter_Set::mceliece6688128f: + return "mceliece6688128f"; + case Classic_McEliece_Parameter_Set::mceliece6688128pc: + return "mceliece6688128pc"; + case Classic_McEliece_Parameter_Set::mceliece6688128pcf: + return "mceliece6688128pcf"; + case Classic_McEliece_Parameter_Set::mceliece6960119: + return "mceliece6960119"; + case Classic_McEliece_Parameter_Set::mceliece6960119f: + return "mceliece6960119f"; + case Classic_McEliece_Parameter_Set::mceliece6960119pc: + return "mceliece6960119pc"; + case Classic_McEliece_Parameter_Set::mceliece6960119pcf: + return "mceliece6960119pcf"; + case Classic_McEliece_Parameter_Set::mceliece8192128: + return "mceliece8192128"; + case Classic_McEliece_Parameter_Set::mceliece8192128f: + return "mceliece8192128f"; + case Classic_McEliece_Parameter_Set::mceliece8192128pc: + return "mceliece8192128pc"; + case Classic_McEliece_Parameter_Set::mceliece8192128pcf: + return "mceliece8192128pcf"; + // TODO: Remove on final PR + case Classic_McEliece_Parameter_Set::test: + return "test"; + case Classic_McEliece_Parameter_Set::testf: + return "testf"; + case Classic_McEliece_Parameter_Set::testpc: + return "testpc"; + case Classic_McEliece_Parameter_Set::testpcf: + return "testpcf"; + default: + throw Decoding_Error("Parameter set not supported"); + } + BOTAN_ASSERT_UNREACHABLE(); +} + +Classic_McEliece_Parameter_Set cmce_param_set_from_oid(const OID& oid) { + return cmce_param_set_from_str(oid.to_formatted_string()); +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_parameter_set.h b/src/lib/pubkey/classic_mceliece/cmce_parameter_set.h new file mode 100644 index 00000000000..c680d918be3 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_parameter_set.h @@ -0,0 +1,77 @@ +/* + * Classic McEliece Parameters + * (C) 2024 Jack Lloyd + * 2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_PARAMETER_SET_H_ +#define BOTAN_CMCE_PARAMETER_SET_H_ + +#include + +namespace Botan { + +/** + * All Classic McEliece parameter sets defined in the NIST Round 4 + * submission and the Classic McEliece ISO Draft. + * + * Instances are defined in the following format: + * mceliece{n}{t}{[pc]}{[f]} + * + * Instance with 'pc' use plaintext confirmation as defined in the ISO Draft. + * Instance with 'f' use matrix reduction with the semi-systematic form. + */ +enum class Classic_McEliece_Parameter_Set { + mceliece348864, // NIST + mceliece348864f, // NIST + + mceliece460896, // NIST + mceliece460896f, // NIST + + mceliece6688128, // ISO + NIST + mceliece6688128f, // ISO + NIST + mceliece6688128pc, // ISO + mceliece6688128pcf, // ISO + + mceliece6960119, // ISO + NIST + mceliece6960119f, // ISO + NIST + mceliece6960119pc, // ISO + mceliece6960119pcf, // ISO + + mceliece8192128, // ISO + NIST + mceliece8192128f, // ISO + NIST + mceliece8192128pc, // ISO + mceliece8192128pcf, // ISO + + /// Reduced instances for side channel analysis (Self-created test instance with + /// m=8, n=128, t=8, f(z)=z^8+z^7+z^2+z+1, F(y)=y^8+y^4+y^3+y^2+1) + /// Minimal instance without semi-systematic matrix creation and no plaintext confirmation + test, + /// Minimal instance with semi-systematic matrix creation and no plaintext confirmation + testf, + /// Minimal instance without semi-systematic matrix creation and with plaintext confirmation + testpc, + /// Minimal instance with semi-systematic matrix creation and with plaintext confirmation + testpcf +}; + +/** + * @brief Get the parameter set for a given parameter set name. + */ +BOTAN_TEST_API Classic_McEliece_Parameter_Set cmce_param_set_from_str(std::string_view param_name); + +/** + * @brief Get the parameter set name for a given parameter set. + */ +BOTAN_TEST_API std::string cmce_str_from_param_set(Classic_McEliece_Parameter_Set param); + +/** + * @brief Get the parameter set for a given OID. + */ +BOTAN_TEST_API Classic_McEliece_Parameter_Set cmce_param_set_from_oid(const OID& oid); + +} // namespace Botan + +#endif // BOTAN_CMCE_PARAMETER_SET_H_ diff --git a/src/lib/pubkey/classic_mceliece/cmce_parameters.cpp b/src/lib/pubkey/classic_mceliece/cmce_parameters.cpp new file mode 100644 index 00000000000..7c35f7bd512 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_parameters.cpp @@ -0,0 +1,149 @@ +/* + * 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 { + +std::vector determine_big_f_coef(size_t t, GF_Mod modulus) { + std::vector big_f_coef; + switch(t) { + // TODO: Remove test instance on final PR + case 8: //y^8 + y^4 + y^3 + y^2 + 1 (test instances) + big_f_coef.push_back({0, Classic_McEliece_GF(GF_Elem(1), modulus)}); + big_f_coef.push_back({2, Classic_McEliece_GF(GF_Elem(1), modulus)}); + big_f_coef.push_back({3, Classic_McEliece_GF(GF_Elem(1), modulus)}); + big_f_coef.push_back({4, Classic_McEliece_GF(GF_Elem(1), modulus)}); + break; + case 64: //y^64 + y^3 + y + z + big_f_coef.push_back({3, Classic_McEliece_GF(GF_Elem(1), modulus)}); + big_f_coef.push_back({1, Classic_McEliece_GF(GF_Elem(1), modulus)}); + big_f_coef.push_back({0, Classic_McEliece_GF(GF_Elem(2), modulus)}); + break; + case 96: //y^96 + y^10 + y^9 + y^6 + 1 + big_f_coef.push_back({10, Classic_McEliece_GF(GF_Elem(1), modulus)}); + big_f_coef.push_back({9, Classic_McEliece_GF(GF_Elem(1), modulus)}); + big_f_coef.push_back({6, Classic_McEliece_GF(GF_Elem(1), modulus)}); + big_f_coef.push_back({0, Classic_McEliece_GF(GF_Elem(1), modulus)}); + break; + case 119: //y^119 + y^8 + 1 + big_f_coef.push_back({8, Classic_McEliece_GF(GF_Elem(1), modulus)}); + big_f_coef.push_back({0, Classic_McEliece_GF(GF_Elem(1), modulus)}); + break; + case 128: // y^128 + y^7 + y^2 + y + 1 + big_f_coef.push_back({7, Classic_McEliece_GF(GF_Elem(1), modulus)}); + big_f_coef.push_back({2, Classic_McEliece_GF(GF_Elem(1), modulus)}); + big_f_coef.push_back({1, Classic_McEliece_GF(GF_Elem(1), modulus)}); + big_f_coef.push_back({0, Classic_McEliece_GF(GF_Elem(1), modulus)}); + break; + default: + throw Decoding_Error("No instance with this t value is supported"); + } + + return big_f_coef; +} +} //namespace + +Classic_McEliece_Parameters Classic_McEliece_Parameters::create(Classic_McEliece_Parameter_Set set) { + switch(set) { + case Classic_McEliece_Parameter_Set::mceliece348864: + case Classic_McEliece_Parameter_Set::mceliece348864f: + return Classic_McEliece_Parameters(set, 12, 3488, 64, GF_Mod(0b0001000000001001)); + + case Classic_McEliece_Parameter_Set::mceliece460896: + case Classic_McEliece_Parameter_Set::mceliece460896f: + return Classic_McEliece_Parameters(set, 13, 4608, 96, GF_Mod(0b0010000000011011)); + + 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, 128, GF_Mod(0b0010000000011011)); + + 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, 119, GF_Mod(0b0010000000011011)); + + 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, 128, GF_Mod(0b0010000000011011)); + + // TODO: Remove on final PR + case Botan::Classic_McEliece_Parameter_Set::test: + case Botan::Classic_McEliece_Parameter_Set::testf: + case Botan::Classic_McEliece_Parameter_Set::testpc: + case Botan::Classic_McEliece_Parameter_Set::testpcf: + return Classic_McEliece_Parameters(set, 8, 128, 8, GF_Mod(0b0000000110000111)); + } + + BOTAN_ASSERT_UNREACHABLE(); +} + +Classic_McEliece_Parameters Classic_McEliece_Parameters::create(std::string_view name) { + return Classic_McEliece_Parameters::create(cmce_param_set_from_str(name)); +} + +Classic_McEliece_Parameters Classic_McEliece_Parameters::create(const OID& oid) { + auto param_set = cmce_param_set_from_oid(oid); + return create(param_set); +} + +OID Classic_McEliece_Parameters::object_identifier() const { + return OID::from_string(cmce_str_from_param_set(m_set)); +} + +Classic_McEliece_Parameters::Classic_McEliece_Parameters( + Classic_McEliece_Parameter_Set param_set, size_t m, size_t n, size_t t, GF_Mod poly_f) : + m_set(param_set), + m_m(m), + m_n(n), + m_t(t), + m_poly_f(poly_f), + m_poly_ring(determine_big_f_coef(t, poly_f), poly_f, t) { + 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(n()) { + case 3488: + return 140; + case 4608: + return 179; + case 6688: + return 246; + case 6960: + return 245; + case 8192: + return 256; // 275 in the document. Capped at 256 because of the seed length. + default: + throw Decoding_Error("Strength for parameter set ist not registed."); + } +} + +std::unique_ptr Classic_McEliece_Parameters::prg(std::span seed) const { + BOTAN_ASSERT_EQUAL(seed.size(), 32, "Valid seed length"); + auto xof = XOF::create_or_throw("SHAKE-256"); + + xof->update(std::array({64})); + xof->update(seed); + + return xof; +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_parameters.h b/src/lib/pubkey/classic_mceliece/cmce_parameters.h new file mode 100644 index 00000000000..138cb29eb5e --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_parameters.h @@ -0,0 +1,293 @@ +/* + * Classic McEliece Parameters + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_PARAMS_H_ +#define BOTAN_CMCE_PARAMS_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Botan { + +struct Classic_McEliece_Big_F_Coefficient; +class Classic_McEliece_Polynomial_Ring; + +/** + * @returns ceil(n/d) + * TODO: Remove once LMS is merged + */ +constexpr size_t ceil_div(size_t n, size_t d) { + return (n + d - 1) / d; +} + +/** + * Container for all Classic McEliece parameters. + */ +class BOTAN_TEST_API Classic_McEliece_Parameters final { + public: + /** + * @brief Create Classic McEliece parameters from a parameter set. + */ + static Classic_McEliece_Parameters create(Classic_McEliece_Parameter_Set set); + + /** + * @brief Create Classic McEliece parameters from a parameter set name. + */ + static Classic_McEliece_Parameters create(std::string_view name); + + /** + * @brief Create Classic McEliece parameters from an OID. + */ + static Classic_McEliece_Parameters create(const OID& oid); + + /** + * @brief The parameter set for this Classic McEliece instance. + */ + Classic_McEliece_Parameter_Set 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) || + (m_set == Classic_McEliece_Parameter_Set::testpc); + } + + /** + * @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) || + (m_set == Classic_McEliece_Parameter_Set::testf); + } + + /** + * @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_t; } + + /** + * @brief Bit output length of the hash function H. + */ + static constexpr size_t ell() { return 256; } + + /** + * @brief The number of bits each GF element is encoded with. + */ + static constexpr size_t sigma1() { return 16; } + + /** + * @brief Constant for field-ordering generation. (see Classic McEliece ISO 8.2) + */ + static constexpr size_t sigma2() { return 32; } + + /** + * @brief Constant mu for semi-systematic matrix creation. (see Classic McEliece ISO 7.2.3) + */ + static constexpr size_t mu() { return 32; } + + /** + * @brief Constant nu for semi-systematic matrix creation. (see Classic McEliece ISO 7.2.3) + */ + static constexpr size_t nu() { return 64; } + + /** + * @brief Constant tau for fixed-weight vector generation. (see Classic McEliece ISO 8.4) + */ + size_t tau() const { + // Section 8.4 of ISO: + // The integer tau is defined as t if n=q; as 2t if q/2<=n prg(std::span seed) const; + + /** + * @brief Create an instance of the hash function Hash(x) used in Classic McEliece's + * Decaps and Encaps algorithms. + * + * @return a new instance of the hash function. + */ + std::unique_ptr hash_func() const { return HashFunction::create_or_throw("SHAKE-256(256)"); } + + /** + * @brief Create a GF(q) element using the modulus for the current instance. + * + * @param elem The GF(q) element value. + * @return The GF(q) element. + */ + Classic_McEliece_GF gf(GF_Elem elem) const { return Classic_McEliece_GF(elem, m_poly_f); } + + private: + Classic_McEliece_Parameters( + Classic_McEliece_Parameter_Set param_set, size_t m, size_t n, size_t t, GF_Mod poly_f); + + Classic_McEliece_Parameter_Set m_set; + + size_t m_m; + size_t m_n; + size_t m_t; + GF_Mod m_poly_f; + Classic_McEliece_Polynomial_Ring m_poly_ring; +}; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/classic_mceliece/cmce_poly.cpp b/src/lib/pubkey/classic_mceliece/cmce_poly.cpp new file mode 100644 index 00000000000..9f5807edb91 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_poly.cpp @@ -0,0 +1,175 @@ +/* + * Classic McEliece Polynomials + * Based on the public domain reference implementation by the designers + * (https://classic.mceliece.org/impl.html - released in Oct 2022 for NISTPQC-R4) + * + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#include +#include +#include + +namespace Botan { + +namespace { + +/** + * @brief loads @p bytes into a GF_Elem vector in Little Endian. + * + * Replaces load_le as it cannot be compiled on Big Endian architectures + * + * @param bytes input bytes + * @return The vector of GF_Elem elements + */ +std::vector load_le_gf_vec(std::span bytes) { + std::vector result; + BufferSlicer bs(bytes); + result.reserve(bytes.size() / sizeof(GF_Elem)); + for(size_t i = 0; i < bytes.size() / sizeof(GF_Elem); ++i) { + result.emplace_back(load_le(bs.take())); + } + return result; +} + +} // namespace + +Classic_McEliece_GF Classic_McEliece_Polynomial::operator()(Classic_McEliece_GF a) const { + BOTAN_ASSERT(a.modulus() == coef_at(0).modulus(), "Unmatching Galois fields"); + + Classic_McEliece_GF r(GF_Elem(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, {GF_Elem(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(prod); +} + +Classic_McEliece_Polynomial Classic_McEliece_Polynomial_Ring::create_element_from_bytes( + std::span bytes) const { + BOTAN_ARG_CHECK(bytes.size() == m_t * 2, "Correct input size"); + auto coef = load_le_gf_vec(bytes); + return create_element_from_coef(coef); +} + +Classic_McEliece_Polynomial Classic_McEliece_Polynomial_Ring::create_element_from_coef( + const std::vector& coeff_vec) const { + std::vector coeff_vec_gf; + std::transform(coeff_vec.begin(), coeff_vec.end(), std::back_inserter(coeff_vec_gf), [this](auto& coeff) { + return Classic_McEliece_GF(coeff, 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( + std::span seed) const { + auto polynomial = create_element_from_bytes(seed); + std::vector mat; + + mat.push_back(create_element_from_coef(concat_as>( + std::vector{GF_Elem(1)}, std::vector(degree() - 1, GF_Elem(0))))); + + mat.emplace_back(polynomial); + + for(size_t j = 2; j <= degree(); ++j) { + mat.push_back(multiply(mat.at(j - 1), polynomial)); + } + + // Gaussian + for(size_t j = 0; j < degree(); ++j) { + for(size_t k = j + 1; k < degree(); ++k) { + auto cond = GF_Mask::is_zero(mat.at(j).coef_at(j)); + + for(size_t c = j; c < degree() + 1; ++c) { + mat.at(c).coef_at(j) += cond.if_set_return(mat.at(c).coef_at(k)); + } + } + + if(mat.at(j).coef_at(j).is_zero()) { + // Fail if not systematic. + return std::nullopt; + } + + auto inv = mat.at(j).coef_at(j).inv(); + + for(size_t c = j; c < degree() + 1; ++c) { + mat.at(c).coef_at(j) *= inv; + } + + for(size_t k = 0; k < degree(); ++k) { + if(k != j) { + 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(GF_Elem(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).subspan(0, 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(coef.elem().get(), bytes_stuf.next(sizeof(GF_Elem)).data()); + } + BOTAN_ASSERT_NOMSG(bytes_stuf.full()); + return bytes; +} + +Classic_McEliece_Minimal_Polynomial Classic_McEliece_Minimal_Polynomial::from_bytes(std::span bytes, + GF_Mod poly_f) { + BOTAN_ASSERT_NOMSG(bytes.size() % 2 == 0); + auto coef_vec = load_le_gf_vec(bytes); + std::vector coeff_vec_gf; + std::transform(coef_vec.begin(), coef_vec.end(), std::back_inserter(coeff_vec_gf), [poly_f](auto& coeff) { + return Classic_McEliece_GF(coeff, poly_f); + }); + + coeff_vec_gf.emplace_back(GF_Elem(1), poly_f); // x^t as polynomial is monic + + return Classic_McEliece_Minimal_Polynomial(coeff_vec_gf); +} + +} // namespace Botan diff --git a/src/lib/pubkey/classic_mceliece/cmce_poly.h b/src/lib/pubkey/classic_mceliece/cmce_poly.h new file mode 100644 index 00000000000..4645263aedb --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_poly.h @@ -0,0 +1,174 @@ +/* + * Classic McEliece Polynomials + * (C) 2023 Jack Lloyd + * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_CMCE_POLY_H_ +#define BOTAN_CMCE_POLY_H_ + +#include +#include +#include + +#include + +namespace Botan { + +/** + * @brief Representation of a Classic McEliece polynomial. + * + * This class represents a polynomial in the ring GF(q)[y]. E.g an example element of degree 2 could be: + * a = (z^3+1)y^2 + (z)y + (z^4+z^3) + * The degree of the polynomial is given by the size of the coefficient vector given to + * the constructor, even if the leading coefficient is zero. Coefficients are stored from + * lowest to highest monomial degree (coef_at(0) = (z^4+z^3) in the example above). + * + * This class is merely a container. The modulus and the operations with Polynomials (e.g. multiplication) + * is handled by the Classic_McEliece_Polynomial_Ring class. + */ +class BOTAN_TEST_API Classic_McEliece_Polynomial { + public: + /** + * @brief Construct a polynomial given its coefficients. + * + * @param coef The coefficients of the polynomial. The first element is the coefficient of the lowest monomial. + */ + Classic_McEliece_Polynomial(std::vector coef) : m_coef(std::move(coef)) {} + + /** + * @brief Evaluate the polynomial P(x) at a given point a, i.e., compute P(a). + */ + Classic_McEliece_GF operator()(Classic_McEliece_GF a) const; + + /** + * @brief Get the coefficient of the i-th monomial as a reference (from low to high degree). + */ + Classic_McEliece_GF& coef_at(size_t i) { return m_coef.at(i); } + + /** + * @brief Get the coefficient of the i-th monomial (from low to high degree). + */ + const Classic_McEliece_GF& coef_at(size_t i) const { return m_coef.at(i); } + + /** + * @brief Get the entire coefficients vector of the polynomial. + */ + const std::vector& coef() const { return m_coef; } + + /** + * @brief Get the degree of the polynomial. + * + * Note that the degree is given by the size of the coefficient vector, even if the leading coefficient is zero. + */ + size_t degree() const { return m_coef.size() + 1; } + + private: + std::vector m_coef; +}; + +/** + * @brief Representation of a minimal polynomial in GF(q)[y]. + * + * It represents the monic irreducible degree-t polynomial of the goppa code. + */ +class BOTAN_TEST_API Classic_McEliece_Minimal_Polynomial : public Classic_McEliece_Polynomial { + public: + Classic_McEliece_Minimal_Polynomial(std::vector coef) : + Classic_McEliece_Polynomial(std::move(coef)) {} + + /** + * @brief Serialize the polynomial to bytes according to ISO Section 9.2.9. + */ + secure_vector serialize() const; + + /** + * @brief Create a polynomial from bytes according to ISO Section 9.2.9. + */ + static Classic_McEliece_Minimal_Polynomial from_bytes(std::span bytes, GF_Mod poly_f); +}; + +// Stores all auxiliary information and logic of FF_(q^t) via FF_q[y]/F(y) +/** + * @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(const std::vector& poly_big_f_coef, GF_Mod poly_f, size_t t) : + m_position_map(poly_big_f_coef), m_t(t), m_poly_f(poly_f) {} + + GF_Mod 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( + std::span 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 + GF_Mod m_poly_f; +}; + +bool operator==(const Classic_McEliece_Polynomial_Ring::Big_F_Coefficient& lhs, + const Classic_McEliece_Polynomial_Ring::Big_F_Coefficient& rhs); + +} // namespace Botan +#endif diff --git a/src/lib/pubkey/classic_mceliece/cmce_types.h b/src/lib/pubkey/classic_mceliece/cmce_types.h new file mode 100644 index 00000000000..6813a20f2c2 --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/cmce_types.h @@ -0,0 +1,43 @@ +/* + * 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 GF_Elem = Strong; + +/// Represents a GF(q) modulus +using GF_Mod = Strong; + +/// Represents an element of a permuation (pi in spec). Used in field ordering creation. +using Permutation_Element = Strong; + +/// Represents a permutation (pi in spec). Used in field ordering creation. +using Permutation = Strong, struct GF_Elem_>; + +/// Represents initial delta of keygen +using Initial_Seed = Strong, struct Initial_Seed_>; + +/// Represents a delta (can be altered; final value stored in private key) +using Key_Gen_Seed = Strong, struct Key_Gen_Seed_>; + +/// Represents c of private key +using Column_Selection = Strong; + +/// Represents s of private key +using Rejection_Seed = Strong, struct Rejection_Seed_>; + +} // namespace Botan +#endif // BOTAN_CMCE_TYPES_H_ diff --git a/src/lib/pubkey/classic_mceliece/info.txt b/src/lib/pubkey/classic_mceliece/info.txt new file mode 100644 index 00000000000..c537b5e628b --- /dev/null +++ b/src/lib/pubkey/classic_mceliece/info.txt @@ -0,0 +1,31 @@ + +CLASSICMCELIECE -> 20231023 + + + +name -> "Classic McEliece" + + + +xof +shake +shake_xof + + + +cmce_parameters.h +cmce_poly.h +cmce_matrix.h +cmce_gf.h +cmce_field_ordering.h +cmce_encaps.h +cmce_decaps.h +cmce_keys_internal.h +cmce_types.h + + + +cmce.h +cmce_parameter_set.h + + diff --git a/src/lib/pubkey/pk_algs.cpp b/src/lib/pubkey/pk_algs.cpp index 4f62db6150d..1c549212e20 100644 --- a/src/lib/pubkey/pk_algs.cpp +++ b/src/lib/pubkey/pk_algs.cpp @@ -14,6 +14,10 @@ #include #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) + #include +#endif + #if defined(BOTAN_HAS_DSA) #include #endif @@ -206,6 +210,12 @@ std::unique_ptr load_public_key(const AlgorithmIdentifier& alg_id, } #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) || defined(BOTAN_HAS_CLASSICMCELIECE) + if(alg_name.starts_with("mceliece")) { + return std::make_unique(alg_id, key_bits); + } +#endif + throw Decoding_Error(fmt("Unknown or unavailable public key algorithm '{}'", alg_name)); } @@ -323,6 +333,12 @@ std::unique_ptr load_private_key(const AlgorithmIdentifier& alg_id, } #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) || defined(BOTAN_HAS_CLASSICMCELIECE) + if(alg_name.starts_with("mceliece")) { + return std::make_unique(alg_id, key_bits); + } +#endif + throw Decoding_Error(fmt("Unknown or unavailable public key algorithm '{}'", alg_name)); } @@ -413,6 +429,13 @@ std::unique_ptr create_private_key(std::string_view alg_name, return std::make_unique(rng, n, t); } #endif +#if defined(BOTAN_HAS_CLASSICMCELIECE) + if(alg_name == "ClassicMcEliece") { + auto cmce_params_set = cmce_param_set_from_str(params); + + return std::make_unique(rng, cmce_params_set); + } +#endif #if defined(BOTAN_HAS_FRODOKEM) if(alg_name == "FrodoKEM") { diff --git a/src/tests/data/pubkey/cmce_kat_hashed.vec b/src/tests/data/pubkey/cmce_kat_hashed.vec new file mode 100644 index 00000000000..5d5f6852b31 --- /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 00000000000..3763b2c6860 --- /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 00000000000..c1d802fcdce --- /dev/null +++ b/src/tests/test_cmce.cpp @@ -0,0 +1,383 @@ +/* +* 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 + + #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::GF_Elem(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.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::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_test() { + Test::Result result("GF test"); + + auto exp_mul = Botan::GF_Elem(0x001e); //00011110 + + auto val1 = Botan::Classic_McEliece_GF(Botan::GF_Elem(0b00000000010110111), Botan::GF_Mod(0b100011011)); + auto val2 = Botan::Classic_McEliece_GF(Botan::GF_Elem(0b00000000011001001), Botan::GF_Mod(0b100011011)); + auto mul = val1 * val2; + result.test_is_eq("Control bits creation", mul.elem(), exp_mul); + + 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::GF_Elem(42)); + auto v_inv = v.inv(); + result.test_is_eq("Control bits creation", (v * v_inv).elem(), Botan::GF_Elem(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; + } + + std::vector run() override { + return {expand_seed_test(), irreducible_poly_gen_test(), gf_test(), gf_inv_test(), gf_poly_mul_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 = instances_to_test(); + + std::vector res; + std::transform(to_test.begin(), to_test.end(), std::back_inserter(res), [](auto& param_set) { + return Botan::cmce_str_from_param_set(param_set); + }); + + return res; + } + + 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::cmce_param_set_from_str(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 + } +}; + +/** + * TODO: Remove on final PR + * This is a test using a minimal instance. Since this instance is self constructed, + * we have no known answer to check against. However, this test may be useful for + * side channel analysis. + * + * Hints for SCA: + * Build only ClassicMcEliece with (for example): + * ./configure.py --compiler-cache=ccache --minimized-build --enable-modules=classic_mceliece --build-targets=static,tests --without-documentation --build-tool=ninja && ninja + * Run only this test without prints: + * ./botan-test cmce_minimal --test-threads=1 --no-stdout + */ +class CMCE_Minimal_Test final : public Test { + public: + Test::Result run_minimal_keygen_test(std::string_view param_set) { + Test::Result result("Minimal KeyGen Test"); + + const Botan::secure_vector rng_seed(48, 0); + const auto test_rng = std::make_unique(rng_seed); + + // Test Keygen + std::unique_ptr private_key; + result.test_no_throw("Key Generation", [&] { + private_key = Botan::create_private_key("ClassicMcEliece", *test_rng, param_set); + }); + if(!private_key) { + // Keygen failed + return result; + } + + // Test Encapsulation + std::optional encaps = std::nullopt; + result.test_no_throw("Encapsulation", [&] { + auto enc = Botan::PK_KEM_Encryptor(*private_key, "Raw", "base"); + encaps = enc.encrypt(*test_rng); + }); + + // Test Decapsulation + Botan::secure_vector decaps; + result.test_no_throw("Decapsulation", [&] { + auto dec = Botan::PK_KEM_Decryptor(*private_key, *test_rng, "Raw"); + decaps = dec.decrypt(encaps->encapsulated_shared_key()); + }); + + result.test_is_eq("Decapsulated secret is correct", decaps, encaps->shared_key()); + + return result; + } + + std::vector run() override { + std::vector results; + // The parameter decides which instance is tested, i.e. with our without + // semi-systematic gauss or plaintext confirmation. + results.push_back(run_minimal_keygen_test("test")); + results.push_back(run_minimal_keygen_test("testf")); + results.push_back(run_minimal_keygen_test("testpc")); + results.push_back(run_minimal_keygen_test("testpcf")); + + return results; + } +}; + +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 +BOTAN_REGISTER_TEST("cmce", "cmce_minimal", CMCE_Minimal_Test); + +} // namespace Botan_Tests + +#endif // BOTAN_HAS_CLASSICMCELIECE