diff --git a/doc/api_ref/pubkey.rst b/doc/api_ref/pubkey.rst
index c8299aee6f..5bae0da9e6 100644
--- a/doc/api_ref/pubkey.rst
+++ b/doc/api_ref/pubkey.rst
@@ -212,6 +212,11 @@ McEliece
Post-quantum secure key encapsulation scheme based on the hardness of certain
decoding problems.
+Classic McEliece
+~~~~~~~~~~~~~~~~
+
+Post-quantum secure, code-based key encapsulation scheme.
+
ElGamal
~~~~~~~~
@@ -1205,6 +1210,7 @@ Botan implements the following KEM schemes:
#. ML-KEM (Kyber)
#. FrodoKEM
#. McEliece
+#. Classic McEliece
.. _mlkem_example:
@@ -1271,6 +1277,48 @@ parameters n and t, and have the corresponding key sizes listed:
You can check the speed of McEliece with the suggested parameters above
using ``botan speed McEliece``
+Classic McEliece KEM
+--------------------
+
+`Classic McEliece `_ is an IND-CCA2 secure key
+encapsulation algorithm based on the McEliece cryptosystem introduced in 1978.
+It is a code-based scheme that relies on conservative security assumptions and
+is considered secure against quantum computers. It is an alternative to
+lattice-based schemes.
+
+Other advantages of Classic McEliece are the small ciphertext size and the fast
+encapsulation. Key generation and decapsulation are slower than in lattice-based
+schemes. The main disadvantage of Classic McEliece is the large public key size,
+ranging from 0.26 MB to 1.36 MB, depending on the instance. Due to its large key
+size, Classic McEliece is recommended for applications where the public key is
+stored for a long time, and memory is not a critical resource. Usage with
+ephemeral keys is not recommended.
+
+Botan's implementation covers the parameter sets of the `NIST round 4
+specification `_
+and the `Classic McEliece ISO draft specification
+`_.
+These are the following:
+
++------------------+-------------------+-------------------+--------------------+-------------------+
+| Set without f/pc | Set with f | Set with pc | Set with pcf | Public Key Size |
++==================+===================+===================+====================+===================+
+| mceliece348864 | mceliece348864f | | | 0.26 MB |
++------------------+-------------------+-------------------+--------------------+-------------------+
+| mceliece460896 | mceliece460896f | | | 0.52 MB |
++------------------+-------------------+-------------------+--------------------+-------------------+
+| mceliece6688128 | mceliece6688128f | mceliece6688128pc | mceliece6688128pcf | 1.04 MB |
++------------------+-------------------+-------------------+--------------------+-------------------+
+| mceliece6960119 | mceliece6960119f | mceliece6960119pc | mceliece6960119pcf | 1.05 MB |
++------------------+-------------------+-------------------+--------------------+-------------------+
+| mceliece8192128 | mceliece8192128f | mceliece8192128pc | mceliece8192128pcf | 1.36 MB |
++------------------+-------------------+-------------------+--------------------+-------------------+
+
+The instances with the suffix 'f' use a faster key generation algorithm that is more consistent in
+runtime. The instances with the suffix 'pc' use plaintext confirmation, which is only specified in
+the ISO document. The instances mceliece348864(f) and mceliece460896(f) are only defined in the
+NIST round 4 submission.
+
eXtended Merkle Signature Scheme (XMSS)
----------------------------------------
diff --git a/doc/credits.rst b/doc/credits.rst
index da9a8a240f..4bd9a553cf 100644
--- a/doc/credits.rst
+++ b/doc/credits.rst
@@ -15,7 +15,7 @@ snail-mail address (S), and Bitcoin address (B).
N: Fabian Albert
E: fabian.albert@rohde-schwarz.com
W: https://www.rohde-schwarz.com/cybersecurity
- D: SLH-DSA, Ed/X448, HSS/LMS, ML-KEM, ML-DSA, TLS-Anvil tests
+ D: SLH-DSA, Ed/X448, HSS/LMS, ML-KEM, ML-DSA, Classic McEliece, TLS-Anvil tests
S: Bochum, Germany
N: Alexander Bluhm
@@ -159,7 +159,7 @@ snail-mail address (S), and Bitcoin address (B).
N: Amos Treiber
E: amos.treiber@rohde-schwarz.com
W: https://www.rohde-schwarz.com/cybersecurity
- D: SLH-DSA, TPM 2.0, FrodoKEM, ML-KEM, ML-DSA
+ D: SLH-DSA, TPM 2.0, FrodoKEM, Classic McEliece, ML-KEM, ML-DSA
S: Cologne, Germany
N: Daniel Seither
diff --git a/doc/dev_ref/oids.rst b/doc/dev_ref/oids.rst
index b6c31bda7d..e5debc4bdd 100644
--- a/doc/dev_ref/oids.rst
+++ b/doc/dev_ref/oids.rst
@@ -86,6 +86,15 @@ Values currently assigned are::
HSS-LMS-Private-Key OBJECT IDENTIFIER ::= { publicKey 13 }
+ mceliece OBJECT IDENTIFIER ::= { publicKey 18 }
+
+ mceliece6688128pc OBJECT IDENTIFIER ::= { mceliece 1 }
+ mceliece6688128pcf OBJECT IDENTIFIER ::= { mceliece 2 }
+ mceliece6960119pc OBJECT IDENTIFIER ::= { mceliece 3 }
+ mceliece6960119pcf OBJECT IDENTIFIER ::= { mceliece 4 }
+ mceliece8192128pc OBJECT IDENTIFIER ::= { mceliece 5 }
+ mceliece8192128pcf OBJECT IDENTIFIER ::= { mceliece 6 }
+
symmetricKey OBJECT IDENTIFIER ::= { randombit 3 }
ocbModes OBJECT IDENTIFIER ::= { symmetricKey 2 }
diff --git a/src/build-data/oids.txt b/src/build-data/oids.txt
index df8914fc56..94edfc773f 100644
--- a/src/build-data/oids.txt
+++ b/src/build-data/oids.txt
@@ -1,6 +1,6 @@
# Regenerate with ./src/scripts/dev_tools/gen_oids.py oids > src/lib/asn1/oid_maps.cpp
# AND ./src/scripts/dev_tools/gen_oids.py dn_ub > src/lib/x509/x509_dn_ub.cpp
-# (if you modified something under [dn]
+# (if you modified something under [dn])
# Public key types
[pubkey]
@@ -98,6 +98,26 @@
1.3.6.1.4.1.25258.1.12.3.5 = SphincsPlus-haraka-256s-r3.1
1.3.6.1.4.1.25258.1.12.3.6 = SphincsPlus-haraka-256f-r3.1
+# Classic McEliece OID selection from IETF Hackathon/BouncyCastle for non PC instances
+1.3.6.1.4.1.22554.5.1.1 = mceliece348864
+1.3.6.1.4.1.22554.5.1.2 = mceliece348864f
+1.3.6.1.4.1.22554.5.1.3 = mceliece460896
+1.3.6.1.4.1.22554.5.1.4 = mceliece460896f
+1.3.6.1.4.1.22554.5.1.5 = mceliece6688128
+1.3.6.1.4.1.22554.5.1.6 = mceliece6688128f
+1.3.6.1.4.1.22554.5.1.7 = mceliece6960119
+1.3.6.1.4.1.22554.5.1.8 = mceliece6960119f
+1.3.6.1.4.1.22554.5.1.9 = mceliece8192128
+1.3.6.1.4.1.22554.5.1.10 = mceliece8192128f
+
+# Classic McEliece PC OIDs are currently in Botan's private arc
+1.3.6.1.4.1.25258.1.18.1 = mceliece6688128pc
+1.3.6.1.4.1.25258.1.18.2 = mceliece6688128pcf
+1.3.6.1.4.1.25258.1.18.3 = mceliece6960119pc
+1.3.6.1.4.1.25258.1.18.4 = mceliece6960119pcf
+1.3.6.1.4.1.25258.1.18.5 = mceliece8192128pc
+1.3.6.1.4.1.25258.1.18.6 = mceliece8192128pcf
+
# XMSS
1.3.6.1.4.1.25258.1.5 = XMSS-draft6
1.3.6.1.4.1.25258.1.8 = XMSS-draft12
diff --git a/src/cli/perf_pk_kem.cpp b/src/cli/perf_pk_kem.cpp
index d0b88c626a..091701554f 100644
--- a/src/cli/perf_pk_kem.cpp
+++ b/src/cli/perf_pk_kem.cpp
@@ -150,4 +150,37 @@ BOTAN_REGISTER_PERF_TEST("FrodoKEM", PerfTest_FrodoKEM);
#endif
+#if defined(BOTAN_HAS_CLASSICMCELIECE)
+
+class PerfTest_Classic_McEliece final : public PerfTest_PK_KEM {
+ public:
+ std::string algo() const override { return "ClassicMcEliece"; }
+
+ std::vector keygen_params(const PerfConfig& config) const override {
+ BOTAN_UNUSED(config);
+ return {
+ "mceliece348864",
+ "mceliece348864f",
+ "mceliece460896",
+ "mceliece460896f",
+ "mceliece6688128",
+ "mceliece6688128f",
+ "mceliece6688128pc",
+ "mceliece6688128pcf",
+ "mceliece6960119",
+ "mceliece6960119f",
+ "mceliece6960119pc",
+ "mceliece6960119pcf",
+ "mceliece8192128",
+ "mceliece8192128f",
+ "mceliece8192128pc",
+ "mceliece8192128pcf",
+ };
+ }
+};
+
+BOTAN_REGISTER_PERF_TEST("ClassicMcEliece", PerfTest_Classic_McEliece);
+
+#endif
+
} // namespace Botan_CLI
diff --git a/src/lib/asn1/oid_maps.cpp b/src/lib/asn1/oid_maps.cpp
index ce97ee52c0..56b02e1c09 100644
--- a/src/lib/asn1/oid_maps.cpp
+++ b/src/lib/asn1/oid_maps.cpp
@@ -1,7 +1,7 @@
/*
* OID maps
*
-* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2024-10-15
+* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2024-10-18
*
* All manual edits to this file will be lost. Edit the script
* then regenerate this source file.
@@ -142,6 +142,16 @@ std::unordered_map OID_Map::load_oid2str_map() {
{"1.3.36.3.3.2.8.1.1.9", "brainpool320r1"},
{"1.3.6.1.4.1.11591.15.1", "OpenPGP.Ed25519"},
{"1.3.6.1.4.1.11591.4.11", "Scrypt"},
+ {"1.3.6.1.4.1.22554.5.1.1", "mceliece348864"},
+ {"1.3.6.1.4.1.22554.5.1.10", "mceliece8192128f"},
+ {"1.3.6.1.4.1.22554.5.1.2", "mceliece348864f"},
+ {"1.3.6.1.4.1.22554.5.1.3", "mceliece460896"},
+ {"1.3.6.1.4.1.22554.5.1.4", "mceliece460896f"},
+ {"1.3.6.1.4.1.22554.5.1.5", "mceliece6688128"},
+ {"1.3.6.1.4.1.22554.5.1.6", "mceliece6688128f"},
+ {"1.3.6.1.4.1.22554.5.1.7", "mceliece6960119"},
+ {"1.3.6.1.4.1.22554.5.1.8", "mceliece6960119f"},
+ {"1.3.6.1.4.1.22554.5.1.9", "mceliece8192128"},
{"1.3.6.1.4.1.25258.1.10.1", "Dilithium-4x4-AES-r3"},
{"1.3.6.1.4.1.25258.1.10.2", "Dilithium-6x5-AES-r3"},
{"1.3.6.1.4.1.25258.1.10.3", "Dilithium-8x7-AES-r3"},
@@ -179,6 +189,12 @@ std::unordered_map OID_Map::load_oid2str_map() {
{"1.3.6.1.4.1.25258.1.17.1", "eFrodoKEM-640-AES"},
{"1.3.6.1.4.1.25258.1.17.2", "eFrodoKEM-976-AES"},
{"1.3.6.1.4.1.25258.1.17.3", "eFrodoKEM-1344-AES"},
+ {"1.3.6.1.4.1.25258.1.18.1", "mceliece6688128pc"},
+ {"1.3.6.1.4.1.25258.1.18.2", "mceliece6688128pcf"},
+ {"1.3.6.1.4.1.25258.1.18.3", "mceliece6960119pc"},
+ {"1.3.6.1.4.1.25258.1.18.4", "mceliece6960119pcf"},
+ {"1.3.6.1.4.1.25258.1.18.5", "mceliece8192128pc"},
+ {"1.3.6.1.4.1.25258.1.18.6", "mceliece8192128pcf"},
{"1.3.6.1.4.1.25258.1.3", "McEliece"},
{"1.3.6.1.4.1.25258.1.5", "XMSS-draft6"},
{"1.3.6.1.4.1.25258.1.6.1", "GOST-34.10-2012-256/SHA-256"},
@@ -621,6 +637,22 @@ std::unordered_map OID_Map::load_str2oid_map() {
{"gost_256B", OID({1, 2, 643, 7, 1, 2, 1, 1, 2})},
{"gost_512A", OID({1, 2, 643, 7, 1, 2, 1, 2, 1})},
{"gost_512B", OID({1, 2, 643, 7, 1, 2, 1, 2, 2})},
+ {"mceliece348864", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 1})},
+ {"mceliece348864f", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 2})},
+ {"mceliece460896", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 3})},
+ {"mceliece460896f", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 4})},
+ {"mceliece6688128", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 5})},
+ {"mceliece6688128f", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 6})},
+ {"mceliece6688128pc", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 1})},
+ {"mceliece6688128pcf", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 2})},
+ {"mceliece6960119", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 7})},
+ {"mceliece6960119f", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 8})},
+ {"mceliece6960119pc", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 3})},
+ {"mceliece6960119pcf", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 4})},
+ {"mceliece8192128", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 9})},
+ {"mceliece8192128f", OID({1, 3, 6, 1, 4, 1, 22554, 5, 1, 10})},
+ {"mceliece8192128pc", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 5})},
+ {"mceliece8192128pcf", OID({1, 3, 6, 1, 4, 1, 25258, 1, 18, 6})},
{"numsp256d1", OID({1, 3, 6, 1, 4, 1, 25258, 4, 1})},
{"numsp384d1", OID({1, 3, 6, 1, 4, 1, 25258, 4, 2})},
{"numsp512d1", OID({1, 3, 6, 1, 4, 1, 25258, 4, 3})},
diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h
index c9341501bc..f97f222a07 100644
--- a/src/lib/ffi/ffi.h
+++ b/src/lib/ffi/ffi.h
@@ -1556,6 +1556,22 @@ int botan_privkey_load_frodokem(botan_privkey_t* key, const uint8_t privkey[], s
BOTAN_FFI_EXPORT(3, 6)
int botan_pubkey_load_frodokem(botan_pubkey_t* key, const uint8_t pubkey[], size_t key_len, const char* frodo_mode);
+/**
+* Algorithm specific key operation: Classic McEliece
+*/
+
+BOTAN_FFI_EXPORT(3, 6)
+int botan_privkey_load_classic_mceliece(botan_privkey_t* key,
+ const uint8_t privkey[],
+ size_t key_len,
+ const char* cmce_mode);
+
+BOTAN_FFI_EXPORT(3, 6)
+int botan_pubkey_load_classic_mceliece(botan_pubkey_t* key,
+ const uint8_t pubkey[],
+ size_t key_len,
+ const char* cmce_mode);
+
/*
* Algorithm specific key operations: ML-KEM
*/
diff --git a/src/lib/ffi/ffi_pkey_algs.cpp b/src/lib/ffi/ffi_pkey_algs.cpp
index e9e914f666..201a65b9f7 100644
--- a/src/lib/ffi/ffi_pkey_algs.cpp
+++ b/src/lib/ffi/ffi_pkey_algs.cpp
@@ -91,6 +91,10 @@
#include
#endif
+#if defined(BOTAN_HAS_CLASSICMCELIECE)
+ #include
+#endif
+
namespace {
#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO)
@@ -1243,6 +1247,56 @@ int botan_pubkey_load_frodokem(botan_pubkey_t* key, const uint8_t pubkey[], size
#endif
}
+/*
+* Algorithm specific key operations : Classic McEliece
+*/
+
+int botan_privkey_load_classic_mceliece(botan_privkey_t* key,
+ const uint8_t privkey[],
+ size_t key_len,
+ const char* cmce_mode) {
+#if defined(BOTAN_HAS_CLASSICMCELIECE)
+ if(key == nullptr || privkey == nullptr || cmce_mode == nullptr) {
+ return BOTAN_FFI_ERROR_NULL_POINTER;
+ }
+
+ *key = nullptr;
+
+ return ffi_guard_thunk(__func__, [=]() -> int {
+ const auto mode = Botan::Classic_McEliece_Parameter_Set::from_string(cmce_mode);
+ auto cmce_key = std::make_unique(std::span{privkey, key_len}, mode);
+ *key = new botan_privkey_struct(std::move(cmce_key));
+ return BOTAN_FFI_SUCCESS;
+ });
+#else
+ BOTAN_UNUSED(key, privkey, key_len, cmce_mode);
+ return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+int botan_pubkey_load_classic_mceliece(botan_pubkey_t* key,
+ const uint8_t pubkey[],
+ size_t key_len,
+ const char* cmce_mode) {
+#if defined(BOTAN_HAS_CLASSICMCELIECE)
+ if(key == nullptr || pubkey == nullptr || cmce_mode == nullptr) {
+ return BOTAN_FFI_ERROR_NULL_POINTER;
+ }
+
+ *key = nullptr;
+
+ return ffi_guard_thunk(__func__, [=]() -> int {
+ const auto mode = Botan::Classic_McEliece_Parameter_Set::from_string(cmce_mode);
+ auto cmce_key = std::make_unique(std::span{pubkey, key_len}, mode);
+ *key = new botan_pubkey_struct(std::move(cmce_key));
+ return BOTAN_FFI_SUCCESS;
+ });
+#else
+ BOTAN_UNUSED(key, pubkey, key_len, cmce_mode);
+ return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
int botan_pubkey_view_ec_public_point(const botan_pubkey_t key, botan_view_ctx ctx, botan_view_bin_fn view) {
#if defined(BOTAN_HAS_ECC_PUBLIC_KEY_CRYPTO)
return BOTAN_FFI_VISIT(key, [=](const auto& k) -> int {
diff --git a/src/lib/pubkey/classic_mceliece/cmce.cpp b/src/lib/pubkey/classic_mceliece/cmce.cpp
new file mode 100644
index 0000000000..aaf24fb0c1
--- /dev/null
+++ b/src/lib/pubkey/classic_mceliece/cmce.cpp
@@ -0,0 +1,142 @@
+/*
+ * Classic McEliece Key Generation
+ * (C) 2023 Jack Lloyd
+ * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ **/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+namespace Botan {
+
+Classic_McEliece_PublicKey::Classic_McEliece_PublicKey(const AlgorithmIdentifier& alg_id,
+ std::span key_bits) :
+ Classic_McEliece_PublicKey(key_bits, Classic_McEliece_Parameter_Set::from_oid(alg_id.oid())) {}
+
+Classic_McEliece_PublicKey::Classic_McEliece_PublicKey(std::span key_bits,
+ Classic_McEliece_Parameter_Set param_set) {
+ auto params = Classic_McEliece_Parameters::create(param_set);
+ BOTAN_ARG_CHECK(key_bits.size() == params.pk_size_bytes(), "Wrong public key length");
+ m_public = std::make_shared(
+ params, Classic_McEliece_Matrix(params, {key_bits.begin(), key_bits.end()}));
+}
+
+Classic_McEliece_PublicKey::Classic_McEliece_PublicKey(const Classic_McEliece_PublicKey& other) {
+ m_public = std::make_shared(*other.m_public);
+}
+
+Classic_McEliece_PublicKey& Classic_McEliece_PublicKey::operator=(const Classic_McEliece_PublicKey& other) {
+ if(this != &other) {
+ m_public = std::make_shared(*other.m_public);
+ }
+ return *this;
+}
+
+AlgorithmIdentifier Classic_McEliece_PublicKey::algorithm_identifier() const {
+ return AlgorithmIdentifier(object_identifier(), AlgorithmIdentifier::USE_EMPTY_PARAM);
+}
+
+OID Classic_McEliece_PublicKey::object_identifier() const {
+ return m_public->params().object_identifier();
+}
+
+size_t Classic_McEliece_PublicKey::key_length() const {
+ // The key length is the dimension k of the goppa code (i.e. the code has 2^k codewords)
+ return m_public->params().pk_no_cols();
+}
+
+size_t Classic_McEliece_PublicKey::estimated_strength() const {
+ return m_public->params().estimated_strength();
+}
+
+std::vector Classic_McEliece_PublicKey::public_key_bits() const {
+ return raw_public_key_bits();
+}
+
+std::vector Classic_McEliece_PublicKey::raw_public_key_bits() const {
+ return m_public->matrix().bytes();
+}
+
+bool Classic_McEliece_PublicKey::check_key(RandomNumberGenerator&, bool) const {
+ return true;
+}
+
+std::unique_ptr Classic_McEliece_PublicKey::generate_another(RandomNumberGenerator& rng) const {
+ return std::make_unique(rng, m_public->params().parameter_set());
+}
+
+std::unique_ptr Classic_McEliece_PublicKey::create_kem_encryption_op(
+ std::string_view params, std::string_view provider) const {
+ if(provider.empty() || provider == "base") {
+ return std::make_unique(this->m_public, params);
+ }
+ throw Provider_Not_Found(algo_name(), provider);
+}
+
+Classic_McEliece_PrivateKey::Classic_McEliece_PrivateKey(RandomNumberGenerator& rng,
+ Classic_McEliece_Parameter_Set param_set) {
+ auto params = Classic_McEliece_Parameters::create(param_set);
+ const auto seed = rng.random_vec(params.seed_len());
+ CT::poison(seed);
+ std::tie(m_private, m_public) = Classic_McEliece_KeyPair_Internal::generate(params, seed).decompose_to_pair();
+
+ BOTAN_ASSERT_NONNULL(m_private);
+ BOTAN_ASSERT_NONNULL(m_public);
+ CT::unpoison_all(*m_private, *m_public);
+}
+
+Classic_McEliece_PrivateKey::Classic_McEliece_PrivateKey(std::span sk,
+ Classic_McEliece_Parameter_Set param_set) {
+ auto scope = CT::scoped_poison(sk);
+ auto params = Classic_McEliece_Parameters::create(param_set);
+ auto sk_internal = Classic_McEliece_PrivateKeyInternal::from_bytes(params, sk);
+ m_private = std::make_shared(std::move(sk_internal));
+ // This creates and loads the public key, which is very large. Potentially, we could only load
+ // it on demand (since one may use the private key only for decapsulation without needing the public key).
+ // TODO: consider building a load-on-demand mechanism for the public key
+ m_public = Classic_McEliece_PublicKeyInternal::create_from_private_key(*m_private);
+ CT::unpoison_all(*m_public, *m_private);
+}
+
+Classic_McEliece_PrivateKey::Classic_McEliece_PrivateKey(const AlgorithmIdentifier& alg_id,
+ std::span key_bits) :
+ Classic_McEliece_PrivateKey(key_bits, Classic_McEliece_Parameter_Set::from_oid(alg_id.oid())) {}
+
+std::unique_ptr Classic_McEliece_PrivateKey::public_key() const {
+ return std::make_unique(*this);
+}
+
+secure_vector Classic_McEliece_PrivateKey::private_key_bits() const {
+ return raw_private_key_bits();
+}
+
+secure_vector Classic_McEliece_PrivateKey::raw_private_key_bits() const {
+ return m_private->serialize();
+}
+
+bool Classic_McEliece_PrivateKey::check_key(RandomNumberGenerator&, bool) const {
+ return m_private->check_key();
+}
+
+std::unique_ptr Classic_McEliece_PrivateKey::create_kem_decryption_op(
+ RandomNumberGenerator& rng, std::string_view params, std::string_view provider) const {
+ BOTAN_UNUSED(rng);
+ if(provider.empty() || provider == "base") {
+ return std::make_unique(this->m_private, params);
+ }
+ throw Provider_Not_Found(algo_name(), provider);
+}
+
+} // namespace Botan
diff --git a/src/lib/pubkey/classic_mceliece/cmce.h b/src/lib/pubkey/classic_mceliece/cmce.h
new file mode 100644
index 0000000000..7c7ee4cf06
--- /dev/null
+++ b/src/lib/pubkey/classic_mceliece/cmce.h
@@ -0,0 +1,145 @@
+/*
+ * Classic McEliece Key Generation
+ * (C) 2023 Jack Lloyd
+ * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ **/
+
+#ifndef BOTAN_CMCE_H_
+#define BOTAN_CMCE_H_
+
+#include
+
+#include
+
+namespace Botan {
+
+class Classic_McEliece_PublicKeyInternal;
+class Classic_McEliece_PrivateKeyInternal;
+
+/**
+ * Classic McEliece is a Code-Based KEM. It is a round 4 candidate in NIST's PQC competition.
+ * It is endorsed by the German Federal Office for Information Security (BSI) for its conservative security
+ * assumptions and a corresponding draft for an ISO standard has been prepared. Both NIST and ISO parameter
+ * sets are implemented here. See https://classic.mceliece.org/ for the specifications and other details.
+ *
+ * Advantages of Classic McEliece:
+ * - Conservative post-quantum security assumptions
+ * - Very fast encapsulation
+ * - Fast decapsulation
+ *
+ * Disadvantages of Classic McEliece:
+ * - Very large public keys (0.26 MB - 1.36 MB)
+ * - Relatively slow key generation
+ * - Algorithm is complex and hard to implement side-channel resistant
+ */
+class BOTAN_PUBLIC_API(3, 7) Classic_McEliece_PublicKey : public virtual Public_Key {
+ public:
+ /**
+ * @brief Load a Classic McEliece public key from bytes.
+ *
+ * @param alg_id The algorithm identifier
+ * @param key_bits The public key bytes
+ */
+ Classic_McEliece_PublicKey(const AlgorithmIdentifier& alg_id, std::span key_bits);
+
+ /**
+ * @brief Load a Classic McEliece public key from bytes.
+ *
+ * @param key_bits The public key bytes
+ * @param param_set The parameter set
+ */
+ Classic_McEliece_PublicKey(std::span key_bits, Classic_McEliece_Parameter_Set param_set);
+
+ Classic_McEliece_PublicKey(const Classic_McEliece_PublicKey& other);
+ Classic_McEliece_PublicKey& operator=(const Classic_McEliece_PublicKey& other);
+ Classic_McEliece_PublicKey(Classic_McEliece_PublicKey&&) = default;
+ Classic_McEliece_PublicKey& operator=(Classic_McEliece_PublicKey&&) = default;
+
+ ~Classic_McEliece_PublicKey() override = default;
+
+ std::string algo_name() const override { return "ClassicMcEliece"; }
+
+ AlgorithmIdentifier algorithm_identifier() const override;
+
+ OID object_identifier() const override;
+
+ size_t key_length() const override;
+
+ size_t estimated_strength() const override;
+
+ std::vector public_key_bits() const override;
+
+ std::vector raw_public_key_bits() const override;
+
+ bool check_key(RandomNumberGenerator&, bool) const override;
+
+ bool supports_operation(PublicKeyOperation op) const override {
+ return (op == PublicKeyOperation::KeyEncapsulation);
+ }
+
+ std::unique_ptr generate_another(RandomNumberGenerator& rng) const final;
+
+ std::unique_ptr create_kem_encryption_op(std::string_view params,
+ std::string_view provider) const override;
+
+ protected:
+ Classic_McEliece_PublicKey() = default;
+
+ protected:
+ std::shared_ptr
+ m_public; // NOLINT(misc-non-private-member-variables-in-classes)
+};
+
+BOTAN_DIAGNOSTIC_PUSH
+BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE
+
+class BOTAN_PUBLIC_API(3, 7) Classic_McEliece_PrivateKey final : public virtual Classic_McEliece_PublicKey,
+ public virtual Private_Key {
+ public:
+ /**
+ * @brief Create a new Classic McEliece private key for a specified parameter set.
+ *
+ * @param rng A random number generator
+ * @param param_set The parameter set to use
+ */
+ Classic_McEliece_PrivateKey(RandomNumberGenerator& rng, Classic_McEliece_Parameter_Set param_set);
+
+ /**
+ * @brief Load a Classic McEliece private key from bytes.
+ *
+ * @param sk The private key bytes
+ * @param param_set The parameter set to use
+ */
+ Classic_McEliece_PrivateKey(std::span sk, Classic_McEliece_Parameter_Set param_set);
+
+ /**
+ * @brief Load a Classic McEliece private key from bytes.
+ *
+ * @param alg_id The algorithm identifier
+ * @param key_bits The private key bytes
+ */
+ Classic_McEliece_PrivateKey(const AlgorithmIdentifier& alg_id, std::span key_bits);
+
+ std::unique_ptr public_key() const override;
+
+ secure_vector private_key_bits() const override;
+
+ secure_vector raw_private_key_bits() const override;
+
+ bool check_key(RandomNumberGenerator&, bool) const override;
+
+ std::unique_ptr create_kem_decryption_op(RandomNumberGenerator& rng,
+ std::string_view params,
+ std::string_view provider) const override;
+
+ private:
+ std::shared_ptr m_private;
+};
+
+BOTAN_DIAGNOSTIC_POP
+
+} // namespace Botan
+
+#endif // BOTAN_CMCE_H_
diff --git a/src/lib/pubkey/classic_mceliece/cmce_decaps.cpp b/src/lib/pubkey/classic_mceliece/cmce_decaps.cpp
new file mode 100644
index 0000000000..c31a65ad47
--- /dev/null
+++ b/src/lib/pubkey/classic_mceliece/cmce_decaps.cpp
@@ -0,0 +1,168 @@
+/*
+ * Classic McEliece Decapsulation
+ * Based on the public domain reference implementation by the designers
+ * (https://classic.mceliece.org/impl.html - released in Oct 2022 for NISTPQC-R4)
+ *
+ * (C) 2023 Jack Lloyd
+ * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ **/
+
+#include
+
+namespace Botan {
+
+Classic_McEliece_Polynomial Classic_McEliece_Decryptor::compute_goppa_syndrome(
+ const Classic_McEliece_Parameters& params,
+ const Classic_McEliece_Minimal_Polynomial& goppa_poly,
+ const Classic_McEliece_Field_Ordering& ordering,
+ const secure_bitvector& code_word) const {
+ BOTAN_ASSERT(params.n() == code_word.size(), "Correct code word size");
+ std::vector syndrome(2 * params.t(), params.gf(CmceGfElem(0)));
+
+ auto alphas = ordering.alphas(params.n());
+
+ for(size_t i = 0; i < params.n(); ++i) {
+ auto g_alpha = goppa_poly(alphas[i]);
+ auto r = (g_alpha * g_alpha).inv();
+
+ auto c_mask = GF_Mask::expand(static_cast(code_word.at(i)));
+
+ for(size_t j = 0; j < 2 * params.t(); ++j) {
+ syndrome[j] += c_mask.if_set_return(r);
+ r = r * alphas[i];
+ }
+ }
+
+ return Classic_McEliece_Polynomial(syndrome);
+}
+
+Classic_McEliece_Polynomial Classic_McEliece_Decryptor::berlekamp_massey(
+ const Classic_McEliece_Parameters& params, const Classic_McEliece_Polynomial& syndrome) const {
+ // Represents coefficients of corresponding polynomials
+ std::vector big_c(params.t() + 1, params.gf(CmceGfElem(0)));
+ std::vector big_b(params.t() + 1, params.gf(CmceGfElem(0)));
+
+ auto b = params.gf(CmceGfElem(1));
+
+ // Start with x^m for m=1, see pseudocode of https://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm
+ big_b.at(1) = CmceGfElem(1);
+ big_c.at(0) = CmceGfElem(1);
+
+ for(size_t big_n = 0, big_l = 0; big_n < 2 * params.t(); ++big_n) {
+ auto d = params.gf(CmceGfElem(0));
+ for(size_t i = 0; i <= std::min(big_n, params.t()); ++i) {
+ d += big_c.at(i) * syndrome.coef_at(big_n - i);
+ }
+
+ // Pseudocode branch if (d == 0)
+ auto d_not_zero = GF_Mask::expand(d);
+
+ // Pseudocode branch else if (2* L <= N)
+ auto adjust_big_c = GF_Mask(CT::Mask::is_lte(uint16_t(2 * big_l), uint16_t(big_n)));
+ adjust_big_c &= d_not_zero;
+
+ auto big_t = big_c; // Copy
+ auto f = d / b;
+
+ for(size_t i = 0; i <= params.t(); ++i) {
+ // Occurs for all other d!=0 branches in the pseudocode
+ big_c.at(i) += d_not_zero.if_set_return((f * big_b.at(i)));
+ }
+
+ big_l = adjust_big_c.select(uint16_t((big_n + 1) - big_l), uint16_t(big_l));
+
+ for(size_t i = 0; i <= params.t(); ++i) {
+ big_b.at(i) = adjust_big_c.select(big_t.at(i), big_b.at(i));
+ }
+
+ b = adjust_big_c.select(d, b);
+
+ // Rotate big_b one to the right (multiplies with x), replaces increments of m in pseudocode
+ std::rotate(big_b.rbegin(), big_b.rbegin() + 1, big_b.rend());
+ }
+
+ std::reverse(big_c.begin(), big_c.end());
+
+ return Classic_McEliece_Polynomial(big_c);
+}
+
+std::pair, CmceErrorVector> Classic_McEliece_Decryptor::decode(CmceCodeWord big_c) const {
+ BOTAN_ASSERT(big_c.size() == m_key->params().m() * m_key->params().t(), "Correct ciphertext input size");
+ big_c.resize(m_key->params().n());
+
+ const auto syndrome =
+ compute_goppa_syndrome(m_key->params(), m_key->g(), m_key->field_ordering(), big_c.as());
+ const auto locator = berlekamp_massey(m_key->params(), syndrome);
+
+ std::vector images;
+ const auto alphas = m_key->field_ordering().alphas(m_key->params().n());
+ std::transform(
+ alphas.begin(), alphas.end(), std::back_inserter(images), [&](const auto& alpha) { return locator(alpha); });
+
+ // Obtain e and check whether wt(e) = t. locator(alpha_i) = 0 <=> error at position i
+ CmceErrorVector e;
+ e.get().reserve(m_key->params().n());
+ auto decode_success = CT::Mask::set(); // Avoid bool to avoid possible compiler optimizations
+ for(const auto& image : images) {
+ e.push_back(GF_Mask::is_zero(image).as_bool());
+ }
+ decode_success &= CT::Mask(CT::Mask::is_equal(e.hamming_weight(), m_key->params().t()));
+
+ // Check the error vector by checking H'C = H'e <=> H'(C + e) = 0; see guide for implementors Sec. 6.3
+ const auto syndrome_from_e = compute_goppa_syndrome(m_key->params(), m_key->g(), m_key->field_ordering(), e.get());
+ auto syndromes_are_eq = GF_Mask::set();
+ for(size_t i = 0; i < syndrome.degree() - 1; ++i) {
+ syndromes_are_eq &= GF_Mask::is_equal(syndrome.coef_at(i), syndrome_from_e.coef_at(i));
+ }
+
+ decode_success &= syndromes_are_eq.elem_mask();
+
+ return {decode_success, std::move(e)};
+}
+
+void Classic_McEliece_Decryptor::raw_kem_decrypt(std::span out_shared_key,
+ std::span encapsulated_key) {
+ BOTAN_ARG_CHECK(out_shared_key.size() == m_key->params().hash_out_bytes(), "Invalid shared key output size");
+ BOTAN_ARG_CHECK(encapsulated_key.size() == m_key->params().ciphertext_size(), "Invalid ciphertext size");
+
+ auto scope = CT::scoped_poison(*m_key);
+
+ auto [ct, c1] = [&]() -> std::pair> {
+ if(m_key->params().is_pc()) {
+ BufferSlicer encaps_key_slicer(encapsulated_key);
+ auto c0_ret = encaps_key_slicer.take(m_key->params().encode_out_size());
+ auto c1_ret = encaps_key_slicer.take(m_key->params().hash_out_bytes());
+ BOTAN_ASSERT_NOMSG(encaps_key_slicer.empty());
+ return {CmceCodeWord(secure_bitvector(c0_ret, m_key->params().m() * m_key->params().t())), c1_ret};
+ } else {
+ return {CmceCodeWord(secure_bitvector(encapsulated_key, m_key->params().m() * m_key->params().t())), {}};
+ }
+ }();
+
+ auto [decode_success_mask, maybe_e] = decode(ct);
+
+ secure_vector e_bytes(m_key->s().size());
+ decode_success_mask.select_n(e_bytes.data(), maybe_e.get().to_bytes().data(), m_key->s().data(), m_key->s().size());
+ uint8_t b = decode_success_mask.select(1, 0);
+
+ auto hash_func = m_key->params().hash_func();
+
+ if(m_key->params().is_pc()) {
+ hash_func->update(0x02);
+ hash_func->update(e_bytes);
+ const auto c1_p = hash_func->final_stdvec();
+ const CT::Mask eq_mask = CT::is_equal(c1.data(), c1_p.data(), c1.size());
+ eq_mask.select_n(e_bytes.data(), e_bytes.data(), m_key->s().data(), m_key->s().size());
+ b = eq_mask.select(b, 0);
+ }
+
+ hash_func->update(b);
+ hash_func->update(e_bytes);
+ hash_func->update(encapsulated_key);
+ hash_func->final(out_shared_key);
+ CT::unpoison(out_shared_key);
+}
+
+} // namespace Botan
diff --git a/src/lib/pubkey/classic_mceliece/cmce_decaps.h b/src/lib/pubkey/classic_mceliece/cmce_decaps.h
new file mode 100644
index 0000000000..501560fbfc
--- /dev/null
+++ b/src/lib/pubkey/classic_mceliece/cmce_decaps.h
@@ -0,0 +1,85 @@
+/*
+ * Classic McEliece Decapsulation
+ * (C) 2023 Jack Lloyd
+ * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ **/
+
+#ifndef BOTAN_CMCE_DECAPS_H_
+#define BOTAN_CMCE_DECAPS_H_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace Botan {
+
+/**
+ * Classic McEliece Decapsulation Operation
+ */
+class BOTAN_TEST_API Classic_McEliece_Decryptor final : public PK_Ops::KEM_Decryption_with_KDF {
+ public:
+ /**
+ * @brief Constructs a Classic_McEliece_Decryptor object with the given private key.
+ * @param key The private key used for decryption.
+ */
+ Classic_McEliece_Decryptor(std::shared_ptr key, std::string_view kdf) :
+ KEM_Decryption_with_KDF(kdf), m_key(std::move(key)) {}
+
+ size_t raw_kem_shared_key_length() const override { return m_key->params().hash_out_bytes(); }
+
+ size_t encapsulated_key_length() const override { return m_key->params().ciphertext_size(); }
+
+ void raw_kem_decrypt(std::span out_shared_key, std::span encapsulated_key) override;
+
+ private:
+ /**
+ * @brief Computes the syndrome of a code word.
+ *
+ * Corresponds to H' * code_word of the spec, where H' is the syndrome computation matrix used for the
+ * Berlekamp's method of decoding. See https://tungchou.github.io/papers/mcbits.pdf for more information.
+ *
+ * @param params The McEliece parameters.
+ * @param goppa_poly The Goppa polynomial.
+ * @param ordering The field ordering.
+ * @param code_word The code word.
+ * @return The syndrome S(x) of the code word.
+ */
+ Classic_McEliece_Polynomial compute_goppa_syndrome(const Classic_McEliece_Parameters& params,
+ const Classic_McEliece_Minimal_Polynomial& goppa_poly,
+ const Classic_McEliece_Field_Ordering& ordering,
+ const secure_bitvector& code_word) const;
+
+ /**
+ * @brief Applies the Berlekamp-Massey algorithm to compute the error locator polynomial given a syndrome.
+ *
+ * The error locator polynomial C can be used for decoding, as C(a_i) = 0 <=> error at position i.
+ *
+ * @param params The McEliece parameters.
+ * @param syndrome The syndrome polynomial of the code word.
+ * @return The error locator polynomial.
+ */
+ Classic_McEliece_Polynomial berlekamp_massey(const Classic_McEliece_Parameters& params,
+ const Classic_McEliece_Polynomial& syndrome) const;
+
+ /**
+ * @brief Decodes a code word using Berlekamp's method.
+ *
+ * @param big_c The code word.
+ * @return A pair containing the decoded message and the error pattern.
+ */
+ std::pair, CmceErrorVector> decode(CmceCodeWord big_c) const;
+
+ std::shared_ptr m_key;
+};
+
+} // namespace Botan
+
+#endif // BOTAN_CMCE_DECAPS_H_
diff --git a/src/lib/pubkey/classic_mceliece/cmce_encaps.cpp b/src/lib/pubkey/classic_mceliece/cmce_encaps.cpp
new file mode 100644
index 0000000000..de88c5430b
--- /dev/null
+++ b/src/lib/pubkey/classic_mceliece/cmce_encaps.cpp
@@ -0,0 +1,131 @@
+/*
+ * Classic McEliece Encapsulation
+ * Based on the public domain reference implementation by the designers
+ * (https://classic.mceliece.org/impl.html - released in Oct 2022 for NISTPQC-R4)
+ *
+ * (C) 2023 Jack Lloyd
+ * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ **/
+#include
+
+#include
+
+namespace Botan {
+
+CmceCodeWord Classic_McEliece_Encryptor::encode(const Classic_McEliece_Parameters& params,
+ const CmceErrorVector& e,
+ const Classic_McEliece_Matrix& mat) const {
+ return mat.mul(params, e);
+}
+
+std::optional Classic_McEliece_Encryptor::fixed_weight_vector_gen(
+ const Classic_McEliece_Parameters& params, RandomNumberGenerator& rng) const {
+ const auto rand = rng.random_vec((params.sigma1() / 8) * params.tau());
+ CT::poison(rand);
+ uint16_t mask_m = (uint32_t(1) << params.m()) - 1; // Only take m least significant bits
+ secure_vector a_values;
+ a_values.reserve(params.tau());
+ BufferSlicer rand_slicer(rand);
+
+ // Steps 2 & 3: Create d_j from uniform random bits. The first t d_j entries
+ // in range {0,...,n-1} are defined as a_0,...,a_(t-1). ...
+ for(size_t j = 0; j < params.tau(); ++j) {
+ auto d = load_le(rand_slicer.take(params.sigma1() / 8).data(), 0);
+ // This is not CT, but neither is the reference implementation here.
+ // This side channel only leaks which random elements are selected and which are dropped,
+ // but no information about their content is leaked.
+ d &= mask_m;
+ bool d_in_range = d < params.n();
+ CT::unpoison(d_in_range);
+ if(d_in_range && a_values.size() < params.t()) {
+ a_values.push_back(d);
+ }
+ }
+ if(a_values.size() < params.t()) {
+ // Step 3: ... If fewer than t of such elements exist restart
+ return std::nullopt;
+ }
+
+ // Step 4: Restart if not all a_i are distinct
+ for(size_t i = 1; i < params.t(); ++i) {
+ for(size_t j = 0; j < i; ++j) {
+ bool a_i_j_equal = a_values.at(i) == a_values.at(j);
+ CT::unpoison(a_i_j_equal);
+ if(a_i_j_equal) {
+ return std::nullopt;
+ }
+ }
+ }
+
+ secure_vector a_value_byte(params.t());
+ secure_vector e_bytes(ceil_tobytes(params.n()));
+
+ // Step 5: Set all bits of e at the positions of a_values
+ // Prepare the associated byte in e_bytes that is represented by each bit index in a_values
+ // if we e is represented as a byte vector
+ for(size_t j = 0; j < a_values.size(); ++j) {
+ a_value_byte[j] = 1 << (a_values[j] % 8);
+ }
+
+ for(size_t i = 0; i < params.n() / 8; ++i) {
+ for(size_t j = 0; j < a_values.size(); ++j) {
+ // If the current byte is the one that is represented by the current bit index in a_values
+ // then set the bit in e_bytes (in-byte position prepared above)
+ auto mask = CT::Mask::is_equal(static_cast(i), static_cast(a_values[j] >> 3));
+ e_bytes[i] |= mask.if_set_return(a_value_byte[j]);
+ }
+ }
+
+ return CmceErrorVector(secure_bitvector(e_bytes, params.n()));
+}
+
+void Classic_McEliece_Encryptor::raw_kem_encrypt(std::span out_encapsulated_key,
+ std::span out_shared_key,
+ RandomNumberGenerator& rng) {
+ BOTAN_ARG_CHECK(out_encapsulated_key.size() == m_key->params().ciphertext_size(),
+ "Incorrect encapsulated key output length");
+ BOTAN_ARG_CHECK(out_shared_key.size() == m_key->params().hash_out_bytes(), "Incorrect shared key output length");
+
+ const auto& params = m_key->params();
+
+ // Call fixed_weight until it is successful to
+ // create a random error vector e of weight tau
+ const CmceErrorVector e = [&] {
+ // Emergency abort in case unexpected logical error to prevent endless loops
+ // Success probability: >24% per attempt (25% that elements are distinct * 96% enough elements are in range)
+ // => 203 attempts for 2^(-80) fail probability
+ constexpr size_t MAX_ATTEMPTS = 203;
+ for(size_t attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
+ if(auto maybe_e = fixed_weight_vector_gen(params, rng)) {
+ return maybe_e.value();
+ }
+ }
+ throw Internal_Error("Cannot created fixed weight vector. Is your RNG broken?");
+ }();
+
+ auto hash_func = params.hash_func();
+
+ BufferStuffer big_c_stuf(out_encapsulated_key);
+ const auto e_bytes = e.get().to_bytes();
+ // Compute and store ciphertext C/C_0 from spec
+ const auto big_c_0 = encode(params, e, m_key->matrix());
+ big_c_0.to_bytes(big_c_stuf.next(ceil_tobytes(big_c_0.size())));
+ if(params.is_pc()) {
+ // Compute and store ciphertext C_1 from spec
+ hash_func->update(0x02);
+ hash_func->update(e_bytes);
+ hash_func->final(big_c_stuf.next(hash_func->output_length()));
+ }
+ BOTAN_ASSERT_NOMSG(big_c_stuf.full());
+
+ // Compute K = Hash(1,e,C) from spec
+ hash_func->update(0x01);
+ hash_func->update(e_bytes);
+ hash_func->update(out_encapsulated_key);
+ hash_func->final(out_shared_key);
+ CT::unpoison_all(out_encapsulated_key, out_shared_key);
+}
+
+} // namespace Botan
diff --git a/src/lib/pubkey/classic_mceliece/cmce_encaps.h b/src/lib/pubkey/classic_mceliece/cmce_encaps.h
new file mode 100644
index 0000000000..d483bb868d
--- /dev/null
+++ b/src/lib/pubkey/classic_mceliece/cmce_encaps.h
@@ -0,0 +1,60 @@
+/*
+* Classic McEliece Encapsulation
+* (C) 2023 Jack Lloyd
+* 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+**/
+
+#ifndef BOTAN_CMCE_ENCAPS_H_
+#define BOTAN_CMCE_ENCAPS_H_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace Botan {
+
+/**
+ * Classic McEliece Encapsulation Operation
+ */
+class BOTAN_TEST_API Classic_McEliece_Encryptor final : public PK_Ops::KEM_Encryption_with_KDF {
+ public:
+ Classic_McEliece_Encryptor(std::shared_ptr key, std::string_view kdf) :
+ KEM_Encryption_with_KDF(kdf), m_key(std::move(key)) {}
+
+ size_t raw_kem_shared_key_length() const override { return m_key->params().hash_out_bytes(); }
+
+ size_t encapsulated_key_length() const override { return m_key->params().ciphertext_size(); }
+
+ void raw_kem_encrypt(std::span out_encapsulated_key,
+ std::span out_shared_key,
+ RandomNumberGenerator& rng) override;
+
+ private:
+ std::shared_ptr m_key;
+
+ /**
+ * @brief Encodes an error vector by multiplying it with the Classic McEliece matrix.
+ */
+ CmceCodeWord encode(const Classic_McEliece_Parameters& params,
+ const CmceErrorVector& e,
+ const Classic_McEliece_Matrix& mat) const;
+
+ /**
+ * @brief Fixed-weight-vector generation algorithm according to ISO McEliece.
+ */
+ std::optional fixed_weight_vector_gen(const Classic_McEliece_Parameters& params,
+ RandomNumberGenerator& rng) const;
+};
+
+} // namespace Botan
+
+#endif // BOTAN_CMCE_ENCAPS_H_
diff --git a/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp b/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp
new file mode 100644
index 0000000000..d563edb371
--- /dev/null
+++ b/src/lib/pubkey/classic_mceliece/cmce_field_ordering.cpp
@@ -0,0 +1,328 @@
+/*
+ * Classic McEliece Field Ordering Generation
+ * Based on the public domain reference implementation by the designers
+ * (https://classic.mceliece.org/impl.html - released in Oct 2022 for NISTPQC-R4)
+ *
+ * (C) 2023 Jack Lloyd
+ * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ **/
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+namespace Botan {
+
+namespace CMCE_CT {
+
+template
+ requires(sizeof(T1) <= 8 && sizeof(T2) <= 8)
+void cond_swap_pair(CT::Mask cond_mask, std::pair& a, std::pair& b) {
+ cond_mask.conditional_swap(a.first, b.first);
+ cond_mask.conditional_swap(a.second, b.second);
+}
+
+template
+void compare_and_swap_pair(std::span> a, size_t i, size_t k, size_t l) {
+ static_assert(sizeof(T1) <= sizeof(uint64_t) && sizeof(T2) <= sizeof(uint64_t),
+ "Types T1 and T2 must be at most 64 bits wide");
+ if((i & k) == 0) { // i and k do not depend on secret data
+ auto swap_required_mask = CT::Mask::is_lt(a[l].first, a[i].first);
+ cond_swap_pair(swap_required_mask, a[i], a[l]);
+ } else {
+ auto swap_required_mask = CT::Mask::is_gt(a[l].first, a[i].first);
+ cond_swap_pair(swap_required_mask, a[i], a[l]);
+ }
+}
+
+// Sorts a vector of pairs after the first element
+template
+void bitonic_sort_pair(std::span> a) {
+ const size_t n = a.size();
+ BOTAN_ARG_CHECK(is_power_of_2(n), "Input vector size must be a power of 2");
+
+ for(size_t k = 2; k <= n; k *= 2) {
+ for(size_t j = k / 2; j > 0; j /= 2) {
+ for(size_t i = 0; i < n; ++i) {
+ const size_t l = i ^ j;
+ if(l > i) {
+ compare_and_swap_pair(a, i, k, l);
+ }
+ }
+ }
+ }
+}
+
+template
+T min(const T& a, const T& b) {
+ auto mask = CT::Mask::is_lt(a, b);
+ return mask.select(a, b);
+}
+
+} // namespace CMCE_CT
+
+namespace {
+template
+std::vector> zip(std::span vec_1, std::span vec_2) {
+ BOTAN_ARG_CHECK(vec_1.size() == vec_2.size(), "Vectors' dimensions do not match");
+ std::vector> vec_zipped;
+ vec_zipped.reserve(vec_1.size());
+ for(size_t i = 0; i < vec_1.size(); ++i) {
+ vec_zipped.push_back(std::make_pair(vec_1[i], vec_2[i]));
+ }
+ return vec_zipped;
+}
+
+template
+std::pair, secure_vector> unzip(const std::span>& vec_zipped) {
+ std::pair, secure_vector> res;
+
+ res.first.reserve(vec_zipped.size());
+ res.second.reserve(vec_zipped.size());
+
+ for(const auto& [elem1, elem2] : vec_zipped) {
+ res.first.push_back(elem1);
+ res.second.push_back(elem2);
+ }
+ return res;
+}
+
+/// @returns (vec[0],0), ..., (vec[n-1],n-1)
+std::vector> enumerate(std::span vec) {
+ BOTAN_DEBUG_ASSERT(vec.size() < std::numeric_limits::max());
+
+ std::vector> enumerated;
+
+ std::transform(vec.begin(), vec.end(), std::back_inserter(enumerated), [ctr = uint16_t(0)](uint32_t elem) mutable {
+ return std::make_pair(elem, ctr++);
+ });
+
+ return enumerated;
+}
+
+/**
+ * @brief Create permutation pi as in (Section 8.2, Step 3).
+ *
+ * @param a The vector that is sorted
+ *
+ * @return (pi sorted after a, a sorted after pi)
+ */
+std::pair, CmcePermutation> create_pi(secure_vector a) {
+ auto a_pi_zipped = enumerate(a);
+ CMCE_CT::bitonic_sort_pair(std::span(a_pi_zipped));
+
+ CmcePermutation pi_sorted;
+ std::tie(a, pi_sorted.get()) = unzip(std::span(a_pi_zipped));
+
+ return std::make_pair(a, pi_sorted);
+}
+
+/**
+* @brief Create a GF element from pi as in (Section 8.2, Step 4).
+* Corresponds to the reverse bits of pi.
+*/
+Classic_McEliece_GF from_pi(CmcePermutationElement pi_elem, CmceGfMod modulus, size_t m) {
+ auto reversed_bits = ct_reverse_bits(pi_elem.get());
+ reversed_bits >>= (sizeof(uint16_t) * 8 - m);
+ return Classic_McEliece_GF(CmceGfElem(reversed_bits), modulus);
+}
+
+/**
+ * @brief Part of field ordering generation according to ISO 9.2.10
+ */
+secure_vector composeinv(std::span c, std::span pi) {
+ auto pi_c_zipped = zip(pi, c);
+ CMCE_CT::bitonic_sort_pair(std::span(pi_c_zipped));
+ // Extract c from the sorted vector
+ secure_vector c_sorted;
+ std::transform(pi_c_zipped.begin(), pi_c_zipped.end(), std::back_inserter(c_sorted), [](const auto& pair) {
+ return pair.second;
+ });
+
+ return c_sorted;
+}
+
+// p,q = composeinv(p,q),composeinv(q,p)
+void simultaneous_composeinv(secure_vector& p, secure_vector& q) {
+ auto p_new = composeinv(p, q);
+ q = composeinv(q, p);
+ p = std::move(p_new);
+}
+
+/**
+ * @brief Generate control bits as in ISO 9.2.10.
+ *
+ * TODO: This function can be optimized (see Classic McEliece reference implementation)
+ */
+secure_vector generate_control_bits_internal(const secure_vector& pi) {
+ const auto n = pi.size();
+ BOTAN_ASSERT_NOMSG(is_power_of_2(n));
+ const size_t m = ceil_log2(n);
+
+ if(m == 1) {
+ return secure_vector({pi.at(0)});
+ }
+ secure_vector p(n);
+ for(size_t x = 0; x < n; ++x) {
+ p.at(x) = pi.at(x ^ 1);
+ }
+ secure_vector q(n);
+ for(size_t x = 0; x < n; ++x) {
+ q.at(x) = pi.at(x) ^ 1;
+ }
+
+ secure_vector range_n(n);
+ std::iota(range_n.begin(), range_n.end(), 0);
+ auto piinv = composeinv(range_n, pi);
+
+ simultaneous_composeinv(p, q);
+
+ secure_vector c(n);
+ for(uint16_t x = 0; static_cast(x) < n; ++x) {
+ c.at(x) = CMCE_CT::min(x, p.at(x));
+ }
+
+ simultaneous_composeinv(p, q);
+
+ for(size_t i = 1; i < m - 1; ++i) {
+ auto cp = composeinv(c, q);
+ simultaneous_composeinv(p, q);
+ for(size_t x = 0; x < n; ++x) {
+ c.at(x) = CMCE_CT::min(c.at(x), cp.at(x));
+ }
+ }
+
+ secure_vector f(n / 2);
+ for(size_t j = 0; j < n / 2; ++j) {
+ f.at(j) = c.at(2 * j) % 2;
+ }
+
+ secure_vector big_f(n);
+ for(uint16_t x = 0; size_t(x) < n; ++x) {
+ big_f.at(x) = x ^ f.at(x / 2);
+ }
+
+ auto fpi = composeinv(big_f, piinv);
+
+ secure_vector l(n / 2);
+ for(size_t k = 0; k < n / 2; ++k) {
+ l.at(k) = fpi.at(2 * k) % 2;
+ }
+
+ secure_vector big_l(n);
+ for(uint16_t y = 0; size_t(y) < n; ++y) {
+ big_l.at(y) = y ^ l.at(y / 2);
+ }
+
+ auto big_m = composeinv(fpi, big_l);
+
+ secure_vector subm0(n / 2);
+ secure_vector subm1(n / 2);
+ for(size_t j = 0; j < n / 2; ++j) {
+ subm0.at(j) = big_m.at(2 * j) / 2;
+ subm1.at(j) = big_m.at(2 * j + 1) / 2;
+ }
+
+ auto subz0 = generate_control_bits_internal(subm0);
+ auto subz1 = generate_control_bits_internal(subm1);
+
+ secure_vector z(subz0.size() + subz1.size());
+ for(size_t j = 0; j < subz0.size(); ++j) {
+ z.at(2 * j) = subz0.at(j);
+ z.at(2 * j + 1) = subz1.at(j);
+ }
+
+ return concat(f, z, l);
+}
+
+CT::Choice ct_has_adjacent_duplicates(std::span vec) {
+ CT::Mask mask = CT::Mask::cleared();
+ for(size_t i = 0; i < vec.size() - 1; ++i) {
+ mask |= CT::Mask::is_equal(vec[i], vec[i + 1]);
+ }
+ return mask.as_choice();
+}
+
+} // anonymous namespace
+
+std::optional Classic_McEliece_Field_Ordering::create_field_ordering(
+ const Classic_McEliece_Parameters& params, StrongSpan random_bits) {
+ BOTAN_ARG_CHECK(random_bits.size() == (params.sigma2() * params.q()) / 8, "Wrong random bits size");
+
+ auto a = load_le>(random_bits); // contains a_0, a_1, ...
+ auto [sorted_a, pi] = create_pi(std::move(a));
+ if(ct_has_adjacent_duplicates(sorted_a).as_bool()) {
+ return std::nullopt;
+ }
+
+ return Classic_McEliece_Field_Ordering(std::move(pi), params.poly_f());
+}
+
+std::vector Classic_McEliece_Field_Ordering::alphas(size_t n) const {
+ BOTAN_ASSERT_NOMSG(m_poly_f.get() != 0);
+ BOTAN_ASSERT_NOMSG(m_pi.size() >= n);
+
+ std::vector n_alphas_vec;
+
+ std::transform(m_pi.begin(), m_pi.begin() + n, std::back_inserter(n_alphas_vec), [this](uint16_t pi_elem) {
+ return from_pi(CmcePermutationElement(pi_elem), m_poly_f, Classic_McEliece_GF::log_q_from_mod(m_poly_f));
+ });
+
+ return n_alphas_vec;
+}
+
+secure_bitvector Classic_McEliece_Field_Ordering::alphas_control_bits() const {
+ // Each vector element contains one bit of the control bits
+ const auto control_bits_as_words = generate_control_bits_internal(m_pi.get());
+ auto control_bits = secure_bitvector(control_bits_as_words.size());
+ for(size_t i = 0; i < control_bits.size(); ++i) {
+ control_bits.at(i) = control_bits_as_words.at(i);
+ }
+
+ return control_bits;
+}
+
+// Based on the Python code "permutation(c)" from Bernstein
+// "Verified fast formulas for control bits for permutation networks"
+Classic_McEliece_Field_Ordering Classic_McEliece_Field_Ordering::create_from_control_bits(
+ const Classic_McEliece_Parameters& params, const secure_bitvector& control_bits) {
+ BOTAN_ASSERT_NOMSG(control_bits.size() == (2 * params.m() - 1) << (params.m() - 1));
+ const uint16_t n = uint16_t(1) << params.m();
+ CmcePermutation pi(n);
+ std::iota(pi.begin(), pi.end(), 0);
+ for(size_t i = 0; i < 2 * params.m() - 1; ++i) {
+ const size_t gap = size_t(1) << std::min(i, 2 * params.m() - 2 - i);
+ for(size_t j = 0; j < size_t(n / 2); ++j) {
+ const size_t pos = (j % gap) + 2 * gap * (j / gap);
+ auto mask = CT::Mask::expand(control_bits[i * n / 2 + j]);
+ mask.conditional_swap(pi[pos], pi[pos + gap]);
+ }
+ }
+
+ return Classic_McEliece_Field_Ordering(std::move(pi), params.poly_f());
+}
+
+void Classic_McEliece_Field_Ordering::permute_with_pivots(const Classic_McEliece_Parameters& params,
+ const CmceColumnSelection& pivots) {
+ auto col_offset = params.pk_no_rows() - Classic_McEliece_Parameters::mu();
+
+ for(size_t p_idx = 1; p_idx <= Classic_McEliece_Parameters::mu(); ++p_idx) {
+ size_t p_counter = 0;
+ for(size_t col = 0; col < Classic_McEliece_Parameters::nu(); ++col) {
+ auto mask_is_pivot_set = CT::Mask::expand(pivots.at(col));
+ p_counter += CT::Mask::expand(pivots.at(col)).if_set_return(1);
+ auto mask_is_current_pivot = CT::Mask::is_equal(p_idx, p_counter);
+ (mask_is_pivot_set & mask_is_current_pivot)
+ .conditional_swap(m_pi.get().at(col_offset + col), m_pi.get().at(col_offset + p_idx - 1));
+ }
+ }
+}
+
+} // namespace Botan
diff --git a/src/lib/pubkey/classic_mceliece/cmce_field_ordering.h b/src/lib/pubkey/classic_mceliece/cmce_field_ordering.h
new file mode 100644
index 0000000000..fed4bea44d
--- /dev/null
+++ b/src/lib/pubkey/classic_mceliece/cmce_field_ordering.h
@@ -0,0 +1,116 @@
+/*
+ * Classic McEliece Field Ordering Generation
+ * (C) 2023 Jack Lloyd
+ * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ **/
+
+#ifndef BOTAN_CMCE_FIELD_ORDERING_H_
+#define BOTAN_CMCE_FIELD_ORDERING_H_
+
+#include
+#include
+
+#include
+
+namespace Botan {
+
+/**
+ * @brief Represents a field ordering for the Classic McEliece cryptosystem.
+ *
+ * Field ordering corresponds to the permutation pi defining the alpha sequence in
+ * the Classic McEliece specification (see Classic McEliece ISO Sec. 8.2.).
+ */
+class BOTAN_TEST_API Classic_McEliece_Field_Ordering {
+ public:
+ /**
+ * @brief Creates a field ordering from a random bit sequence. Corresponds to
+ * the algorithm described in Classic McEliece ISO Sec. 8.2.
+ *
+ * @param params The McEliece parameters.
+ * @param random_bits The random bit sequence.
+ * @return The field ordering.
+ */
+ static std::optional create_field_ordering(
+ const Classic_McEliece_Parameters& params, StrongSpan random_bits);
+
+ /**
+ * @brief Create the field ordering from the control bits of a benes network.
+ *
+ * @param params The McEliece parameters.
+ * @param control_bits The control bits of the benes network.
+ * @return The field ordering.
+ */
+ static Classic_McEliece_Field_Ordering create_from_control_bits(const Classic_McEliece_Parameters& params,
+ const secure_bitvector& control_bits);
+
+ /**
+ * @brief Returns the field ordering as a vector of all alphas from alpha_0 to alpha_{n-1}.
+ *
+ * @param n The number of alphas to return.
+ * @return the vector of n alphas.
+ */
+ std::vector alphas(size_t n) const;
+
+ /**
+ * @brief Generates the control bits of the benes network corresponding to the field ordering.
+ *
+ * @return the control bits.
+ */
+ secure_bitvector alphas_control_bits() const;
+
+ /**
+ * @brief The pi values representing the field ordering.
+ *
+ * @return pi values.
+ */
+ CmcePermutation& pi_ref() { return m_pi; }
+
+ /**
+ * @brief The pi values representing the field ordering.
+ *
+ * @return pi values.
+ */
+ const CmcePermutation& pi_ref() const { return m_pi; }
+
+ /**
+ * @brief Constant time comparison of two field orderings.
+ *
+ * @param other The other field ordering.
+ * @return Mask of equality value
+ */
+ CT::Mask ct_is_equal(const Classic_McEliece_Field_Ordering& other) const {
+ BOTAN_ARG_CHECK(other.pi_ref().size() == pi_ref().size(), "Field orderings must have the same size");
+ return CT::is_equal(pi_ref().data(), other.pi_ref().data(), pi_ref().size());
+ }
+
+ /**
+ * @brief Permute the field ordering with the given pivots.
+ *
+ * For example: If the pivot vector is 10101, the first, third and fifth element of the field ordering
+ * are permuted to positions 0, 1 and 2, respectively. The remaining elements are put at the end.
+ *
+ * The permutation is done for the elements from position m*t - mu,..., m*t + mu (excl.).
+ * This function implements Classic McEliece ISO Sec. 7.2.3 Steps 4-5.
+ *
+ * @param params The McEliece parameters.
+ * @param pivots The pivot vector.
+ */
+ void permute_with_pivots(const Classic_McEliece_Parameters& params, const CmceColumnSelection& pivots);
+
+ void _const_time_poison() const { CT::poison(m_pi); }
+
+ void _const_time_unpoison() const { CT::unpoison(m_pi); }
+
+ private:
+ Classic_McEliece_Field_Ordering(CmcePermutation pi, CmceGfMod poly_f) : m_pi(std::move(pi)), m_poly_f(poly_f) {}
+
+ private:
+ CmcePermutation m_pi;
+ CmceGfMod m_poly_f;
+};
+
+} // namespace Botan
+
+#endif
diff --git a/src/lib/pubkey/classic_mceliece/cmce_gf.cpp b/src/lib/pubkey/classic_mceliece/cmce_gf.cpp
new file mode 100644
index 0000000000..b388d1b743
--- /dev/null
+++ b/src/lib/pubkey/classic_mceliece/cmce_gf.cpp
@@ -0,0 +1,84 @@
+/*
+* Classic McEliece GF arithmetic
+* Based on the public domain reference implementation by the designers
+* (https://classic.mceliece.org/impl.html - released in Oct 2022 for NISTPQC-R4)
+*
+* (C) 2023 Jack Lloyd
+* 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+**/
+
+#include
+
+namespace Botan {
+
+namespace {
+// Only for moduli 0b0010000000011011 and 0b0001000000001001
+inline CmceGfElem internal_reduce(uint32_t x, CmceGfMod mod) {
+ // Optimization for the specific moduli used in Classic McEliece
+ // Taken from the reference implementation
+ if(mod == 0b0010000000011011) {
+ uint32_t t = x & 0x1FF0000;
+ x ^= (t >> 9) ^ (t >> 10) ^ (t >> 12) ^ (t >> 13);
+
+ t = x & 0x000E000;
+ x ^= (t >> 9) ^ (t >> 10) ^ (t >> 12) ^ (t >> 13);
+
+ return CmceGfElem(x & 0x1fff);
+ } else if(mod == 0b0001000000001001) {
+ uint32_t t = x & 0x7FC000;
+ x ^= t >> 9;
+ x ^= t >> 12;
+
+ t = x & 0x3000;
+ x ^= t >> 9;
+ x ^= t >> 12;
+
+ x &= 0xfff;
+
+ return CmceGfElem(static_cast(x & 0xfff));
+ }
+ BOTAN_ASSERT_UNREACHABLE();
+}
+
+} // namespace
+
+Classic_McEliece_GF Classic_McEliece_GF::operator*(Classic_McEliece_GF other) const {
+ BOTAN_ASSERT_NOMSG(m_modulus == other.m_modulus);
+
+ uint32_t a = m_elem.get();
+ uint32_t b = other.m_elem.get();
+
+ uint32_t acc = a * CT::value_barrier(b & 1);
+
+ for(size_t i = 1; i < log_q(); i++) {
+ acc ^= (a * (b & (1 << i)));
+ }
+
+ return Classic_McEliece_GF(internal_reduce(acc, m_modulus), m_modulus);
+}
+
+Classic_McEliece_GF Classic_McEliece_GF::inv() const {
+ // Compute the inverse using fermat's little theorem: a^(q-1) = 1 => a^(q-2) = a^-1
+
+ // exponent = (q-2). This is public information, therefore the workflow is constant time.
+ size_t exponent = (size_t(1) << log_q()) - 2;
+ Classic_McEliece_GF base = *this;
+
+ // Compute base^exponent using the square-and-multiply algorithm
+ Classic_McEliece_GF result = {CmceGfElem(1), m_modulus};
+ while(exponent > 0) {
+ if(exponent % 2 == 1) {
+ // multiply
+ result = (result * base);
+ }
+ // square
+ base = base.square();
+ exponent /= 2;
+ }
+
+ return result;
+}
+
+} // namespace Botan
diff --git a/src/lib/pubkey/classic_mceliece/cmce_gf.h b/src/lib/pubkey/classic_mceliece/cmce_gf.h
new file mode 100644
index 0000000000..ad56ee4660
--- /dev/null
+++ b/src/lib/pubkey/classic_mceliece/cmce_gf.h
@@ -0,0 +1,204 @@
+/*
+ * Classic McEliece GF arithmetic
+ * (C) 2023 Jack Lloyd
+ * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ **/
+
+#ifndef BOTAN_CMCE_GF_H_
+#define BOTAN_CMCE_GF_H_
+
+#include
+#include
+#include
+#include
+#include
+
+namespace Botan {
+
+/**
+ * @brief Represents an element of the finite field GF(q) for q = 2^m.
+ *
+ * This class implements the finite field GF(q) for q = 2^m via the irreducible
+ * polynomial f(z) of degree m. The elements of GF(q) are represented as polynomials
+ * of degree m-1 with coefficients in GF(2). Each element and the modulus is
+ * represented by a uint16_t, where the i-th least significant bit corresponds to
+ * the coefficient of z^i. For example, the element (z^3 + z^2 + 1) is represented
+ * by the uint16_t 0b1101.
+ */
+class BOTAN_TEST_API Classic_McEliece_GF {
+ public:
+ /**
+ * @brief Creates an element of GF(q) from a uint16_t.
+ *
+ * Each element and the modulus is represented by a uint16_t, where the i-th least significant bit
+ * corresponds to the coefficient of z^i.
+ *
+ * @param elem The element as a uint16_t. Must be less than 2^m.
+ * @param modulus The modulus of GF(q).
+ */
+ Classic_McEliece_GF(CmceGfElem elem, CmceGfMod modulus) : m_elem(elem), m_modulus(modulus) {
+ BOTAN_DEBUG_ASSERT(elem <= (size_t(1) << log_q()) - 1);
+ }
+
+ /**
+ * @brief Get m.
+ *
+ * For a given irreducible polynomial @p modulus f(z) representing the modulus of a finite field GF(q) = GF(2^m),
+ * get the degree log_q of f(z) which corresponds to m.
+ *
+ * @param modulus The modulus of GF(q).
+ * @return size_t The degree log_q of the modulus (m for GF(2^m)).
+ */
+ static size_t log_q_from_mod(CmceGfMod modulus) { return floor_log2(modulus.get()); }
+
+ /**
+ * @brief Get m, the degree of the element's modulus.
+ *
+ * @return size_t The degree log_q of the modulus (m for GF(2^m)).
+ */
+ size_t log_q() const { return log_q_from_mod(m_modulus); }
+
+ /**
+ * @brief Get the GF(q) element as a GF_Elem.
+ *
+ * @return the element as a GF_Elem.
+ */
+ CmceGfElem elem() const { return m_elem; }
+
+ /**
+ * @brief Get the modulus f(z) of GF(q) as a GF_Mod.
+ *
+ * @return the modulus as a GF_Mod.
+ */
+ CmceGfMod modulus() const { return m_modulus; }
+
+ /**
+ * @brief Change the element to @p elem.
+ */
+ Classic_McEliece_GF& operator=(const CmceGfElem elem) {
+ m_elem = elem & CmceGfElem((size_t(1) << log_q()) - 1);
+ return *this;
+ }
+
+ /**
+ * @brief Divide the element by @p other in GF(q). Constant time.
+ */
+ Classic_McEliece_GF operator/(Classic_McEliece_GF other) const {
+ BOTAN_DEBUG_ASSERT(m_modulus == other.m_modulus);
+ return *this * other.inv();
+ }
+
+ /**
+ * @brief Add @p other to the element. Constant time.
+ */
+ Classic_McEliece_GF operator+(Classic_McEliece_GF other) const {
+ BOTAN_DEBUG_ASSERT(m_modulus == other.m_modulus);
+ return Classic_McEliece_GF(m_elem ^ other.m_elem, m_modulus);
+ }
+
+ /**
+ * @brief Add @p other to the element. Constant time.
+ */
+ Classic_McEliece_GF& operator+=(Classic_McEliece_GF other) {
+ BOTAN_DEBUG_ASSERT(m_modulus == other.m_modulus);
+ m_elem ^= other.m_elem;
+ return *this;
+ }
+
+ /**
+ * @brief Multiply the element by @p other in GF(q). Constant time.
+ */
+ Classic_McEliece_GF& operator*=(Classic_McEliece_GF other) {
+ BOTAN_DEBUG_ASSERT(m_modulus == other.m_modulus);
+ *this = *this * other;
+ return *this;
+ }
+
+ /**
+ * @brief Multiply the element by @p other in GF(q). Constant time.
+ */
+ Classic_McEliece_GF operator*(Classic_McEliece_GF other) const;
+
+ /**
+ * @brief Check if the element is equal to @p other. Modulus is ignored.
+ */
+ bool operator==(Classic_McEliece_GF other) const { return elem() == other.elem(); }
+
+ /**
+ * @brief Square the element. Constant time.
+ */
+ Classic_McEliece_GF square() const { return (*this) * (*this); }
+
+ /**
+ * @brief Invert the element. Constant time.
+ */
+ Classic_McEliece_GF inv() const;
+
+ /**
+ * @brief Check if the element is zero.
+ */
+ bool is_zero() const { return elem() == 0; }
+
+ private:
+ CmceGfElem m_elem;
+
+ CmceGfMod m_modulus;
+};
+
+/**
+ * @brief Constant time mask wrapper for GF(q) elements.
+ */
+class BOTAN_TEST_API GF_Mask final {
+ public:
+ template
+ static GF_Mask expand(T v) {
+ return GF_Mask(CT::Mask::expand(v));
+ }
+
+ static GF_Mask expand(Classic_McEliece_GF v) { return expand(v.elem().get()); }
+
+ static GF_Mask is_zero(Classic_McEliece_GF v) { return GF_Mask(CT::Mask::is_zero(v.elem().get())); }
+
+ static GF_Mask is_lte(Classic_McEliece_GF a, Classic_McEliece_GF b) {
+ return GF_Mask(CT::Mask::is_lte(a.elem().get(), b.elem().get()));
+ }
+
+ static GF_Mask is_equal(Classic_McEliece_GF a, Classic_McEliece_GF b) {
+ return GF_Mask(CT::Mask::is_equal(a.elem().get(), b.elem().get()));
+ }
+
+ static GF_Mask set() { return GF_Mask(CT::Mask::set()); }
+
+ GF_Mask(CT::Mask underlying_mask) : m_mask(underlying_mask) {}
+
+ Classic_McEliece_GF if_set_return(const Classic_McEliece_GF x) const {
+ return Classic_McEliece_GF(CmceGfElem(m_mask.if_set_return(x.elem().get())), x.modulus());
+ }
+
+ Classic_McEliece_GF select(const Classic_McEliece_GF x, const Classic_McEliece_GF y) const {
+ return Classic_McEliece_GF(CmceGfElem(m_mask.select(x.elem().get(), y.elem().get())), x.modulus());
+ }
+
+ Classic_McEliece_GF select(const Classic_McEliece_GF x, CmceGfElem y) const {
+ return Classic_McEliece_GF(CmceGfElem(m_mask.select(x.elem().get(), y.get())), x.modulus());
+ }
+
+ uint16_t select(uint16_t x, uint16_t y) const { return m_mask.select(x, y); }
+
+ GF_Mask& operator&=(const GF_Mask& o) {
+ m_mask &= o.m_mask;
+ return (*this);
+ }
+
+ bool as_bool() const { return m_mask.as_bool(); }
+
+ CT::Mask& elem_mask() { return m_mask; }
+
+ private:
+ CT::Mask m_mask;
+};
+
+} // namespace Botan
+#endif
diff --git a/src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp b/src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp
new file mode 100644
index 0000000000..8d1b3ba6f8
--- /dev/null
+++ b/src/lib/pubkey/classic_mceliece/cmce_keys_internal.cpp
@@ -0,0 +1,169 @@
+/*
+ * Classic McEliece key generation with Internal Private and Public Key classes
+ * (C) 2023 Jack Lloyd
+ * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ **/
+
+#include
+
+namespace Botan {
+
+namespace {
+
+/**
+ * @brief Try to generate a Classic McEliece keypair for a given seed.
+ *
+ * @param[out] out_next_seed The next seed to use for key generation, if this iteration fails
+ * @param params Classic McEliece parameters
+ * @param seed The seed to used for this key generation iteration
+ * @return a keypair on success, std::nullopt otherwise
+ */
+std::optional try_generate_keypair(std::span out_next_seed,
+ const Classic_McEliece_Parameters& params,
+ CmceKeyGenSeed seed) {
+ BOTAN_ASSERT_EQUAL(seed.size(), 32, "Valid seed length");
+ BOTAN_ASSERT_EQUAL(out_next_seed.size(), 32, "Valid output seed length");
+
+ auto big_e_xof = params.prg(seed);
+
+ auto s = big_e_xof->output(params.n() / 8);
+ auto ordering_bits = big_e_xof->output((params.sigma2() * params.q()) / 8);
+ auto irreducible_bits = big_e_xof->output((params.sigma1() * params.t()) / 8);
+ big_e_xof->output(out_next_seed);
+
+ // Field-ordering generation - Classic McEliece ISO 8.2
+ auto field_ordering = Classic_McEliece_Field_Ordering::create_field_ordering(params, ordering_bits);
+ if(!field_ordering) {
+ return std::nullopt;
+ }
+
+ // Irreducible-polynomial generation - Classic McEliece ISO 8.1
+ auto g = params.poly_ring().compute_minimal_polynomial(irreducible_bits);
+ if(!g) {
+ return std::nullopt;
+ }
+
+ // Matrix generation for Goppa codes - Classic McEliece ISO 7.2
+ auto pk_matrix_and_pivots =
+ Classic_McEliece_Matrix::create_matrix_and_apply_pivots(params, field_ordering.value(), g.value());
+ if(!pk_matrix_and_pivots) {
+ return std::nullopt;
+ }
+ auto& [pk_matrix, pivots] = pk_matrix_and_pivots.value();
+
+ // Key generation was successful - Create and return keys
+ return Classic_McEliece_KeyPair_Internal{
+ .private_key = std::make_shared(
+ params, std::move(seed), pivots, std::move(g.value()), std::move(field_ordering.value()), std::move(s)),
+ .public_key = std::make_shared(params, std::move(pk_matrix))};
+}
+
+} // namespace
+
+Classic_McEliece_PrivateKeyInternal Classic_McEliece_PrivateKeyInternal::from_bytes(
+ const Classic_McEliece_Parameters& params, std::span sk_bytes) {
+ BOTAN_ASSERT(sk_bytes.size() == params.sk_size_bytes(), "Valid private key size");
+ BufferSlicer sk_slicer(sk_bytes);
+
+ auto delta = sk_slicer.copy(params.seed_len());
+ auto c = CmceColumnSelection(sk_slicer.take(params.sk_c_bytes()));
+ auto g = Classic_McEliece_Minimal_Polynomial::from_bytes(sk_slicer.take(params.sk_poly_g_bytes()), params.poly_f());
+ auto field_ordering = Classic_McEliece_Field_Ordering::create_from_control_bits(
+ params, secure_bitvector(sk_slicer.take(params.sk_alpha_control_bytes())));
+ auto s = sk_slicer.copy(params.sk_s_bytes());
+ BOTAN_ASSERT_NOMSG(sk_slicer.empty());
+
+ return Classic_McEliece_PrivateKeyInternal(
+ params, std::move(delta), std::move(c), std::move(g), std::move(field_ordering), std::move(s));
+}
+
+secure_vector Classic_McEliece_PrivateKeyInternal::serialize() const {
+ auto control_bits = m_field_ordering.alphas_control_bits();
+
+ /* NIST Impl. guide 6.1 Control-Bit Gen:
+ * As low-cost protection against faults in the control-bit computation, implementors are advised
+ * to check after the computation that applying the Benes network produces pi, and to
+ * restart key generation if this test fails; applying the Benes network is very fast.
+ *
+ * Here, we just assert that applying the Benes network produces pi.
+ */
+ BOTAN_ASSERT(Classic_McEliece_Field_Ordering::create_from_control_bits(m_params, control_bits)
+ .ct_is_equal(m_field_ordering)
+ .as_bool(),
+ "Control Bit Computation Check");
+
+ return concat(m_delta.get(), m_c.get().to_bytes(), m_g.serialize(), control_bits.to_bytes(), m_s);
+}
+
+bool Classic_McEliece_PrivateKeyInternal::check_key() const {
+ auto prg = m_params.prg(m_delta);
+
+ const auto s = prg->output(m_params.n() / 8);
+ const auto ordering_bits = prg->output((m_params.sigma2() * m_params.q()) / 8);
+ const auto irreducible_bits = prg->output((m_params.sigma1() * m_params.t()) / 8);
+
+ // Recomputing s as hash of delta
+ auto ret = CT::Mask::expand(CT::is_equal(s.data(), m_s.data(), m_params.n() / 8));
+
+ // Checking weight of c
+ ret &= CT::Mask::is_equal(c().hamming_weight(), 32);
+
+ if(auto g = m_params.poly_ring().compute_minimal_polynomial(irreducible_bits)) {
+ for(size_t i = 0; i < g->degree() - 1; ++i) {
+ ret &= CT::Mask::expand(GF_Mask::is_equal(g->coef_at(i), m_g.coef_at(i)).elem_mask());
+ }
+ } else {
+ ret = CT::Mask::cleared();
+ }
+
+ // Check alpha control bits
+ if(auto field_ord_from_seed = Classic_McEliece_Field_Ordering::create_field_ordering(m_params, ordering_bits)) {
+ field_ord_from_seed->permute_with_pivots(m_params, c());
+ ret &= CT::Mask::expand(field_ord_from_seed->ct_is_equal(field_ordering()));
+ } else {
+ ret = CT::Mask::cleared();
+ }
+
+ return ret.as_bool();
+}
+
+std::shared_ptr Classic_McEliece_PublicKeyInternal::create_from_private_key(
+ const Classic_McEliece_PrivateKeyInternal& sk) {
+ auto pk_matrix_and_pivot = Classic_McEliece_Matrix::create_matrix(sk.params(), sk.field_ordering(), sk.g());
+ if(!pk_matrix_and_pivot.has_value()) {
+ throw Decoding_Error("Cannot create public key from private key. Private key is invalid.");
+ }
+ auto& [pk_matrix, pivot] = pk_matrix_and_pivot.value();
+
+ // There should not be a pivot other than 0xff ff ff ff 00 00 00 00.
+ // Otherwise the gauss algorithm failed effectively.
+ const auto pivot_is_valid = (CT::Mask::expand(pivot.subvector(0, pivot.size() / 2).all()) &
+ CT::Mask::expand(pivot.subvector(pivot.size() / 2).none()))
+ .as_choice();
+ if(!pivot_is_valid.as_bool()) {
+ throw Decoding_Error("Cannot create public key from private key. Private key is invalid.");
+ }
+
+ auto pk = std::make_shared(sk.params(), std::move(pk_matrix));
+
+ return pk;
+}
+
+Classic_McEliece_KeyPair_Internal Classic_McEliece_KeyPair_Internal::generate(const Classic_McEliece_Parameters& params,
+ StrongSpan seed) {
+ BOTAN_ASSERT_EQUAL(seed.size(), params.seed_len(), "Valid seed length");
+
+ CmceKeyGenSeed next_seed(seed.size());
+ CmceKeyGenSeed current_seed(seed.begin(), seed.end());
+
+ while(true) {
+ if(auto keypair = try_generate_keypair(next_seed, params, std::move(current_seed))) {
+ return keypair.value();
+ }
+ current_seed = next_seed;
+ }
+}
+
+} // namespace Botan
diff --git a/src/lib/pubkey/classic_mceliece/cmce_keys_internal.h b/src/lib/pubkey/classic_mceliece/cmce_keys_internal.h
new file mode 100644
index 0000000000..0f41a8a434
--- /dev/null
+++ b/src/lib/pubkey/classic_mceliece/cmce_keys_internal.h
@@ -0,0 +1,212 @@
+/*
+ * Classic McEliece key generation with Internal Private and Public Key classes
+ * (C) 2023 Jack Lloyd
+ * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ **/
+
+#ifndef BOTAN_CMCE_KEYS_INTERNAL_H_
+#define BOTAN_CMCE_KEYS_INTERNAL_H_
+
+#include
+#include
+#include
+#include
+#include
+
+namespace Botan {
+
+class Classic_McEliece_PrivateKeyInternal;
+
+/**
+ * @brief Representation of a Classic McEliece public key.
+ *
+ * This class represents a Classic McEliece public key. It is used internally by the Classic McEliece
+ * public key class and contains the following data:
+ * - The Classic McEliece parameters
+ * - The public key matrix
+ */
+class BOTAN_TEST_API Classic_McEliece_PublicKeyInternal {
+ public:
+ /**
+ * @brief Construct a Classic McEliece public key.
+ *
+ * @param params The Classic McEliece parameters
+ * @param matrix The public key matrix
+ */
+ Classic_McEliece_PublicKeyInternal(const Classic_McEliece_Parameters& params, Classic_McEliece_Matrix matrix) :
+ m_params(params), m_matrix(std::move(matrix)) {
+ BOTAN_ASSERT_NOMSG(m_matrix.bytes().size() == m_params.pk_size_bytes());
+ }
+
+ /**
+ * @brief Create a Classic McEliece public key from a private key.
+ *
+ * Create the matrix from the private key values. Expects that the private key is valid, i.e.
+ * the matrix creation works.
+ *
+ * @param sk The private key
+ * @return The public key as a shared pointer
+ */
+ static std::shared_ptr create_from_private_key(
+ const Classic_McEliece_PrivateKeyInternal& sk);
+
+ /**
+ * @brief Serializes the Classic McEliece public key as defined in Classic McEliece ISO Section 9.2.7.
+ */
+ std::vector serialize() const { return m_matrix.bytes(); }
+
+ /**
+ * @brief The Classic McEliece matrix.
+ */
+ const Classic_McEliece_Matrix& matrix() const { return m_matrix; }
+
+ /**
+ * @brief The Classic McEliece parameters.
+ */
+ const Classic_McEliece_Parameters& params() const { return m_params; }
+
+ constexpr void _const_time_unpoison() const { CT::unpoison(m_matrix); }
+
+ private:
+ Classic_McEliece_Parameters m_params;
+ Classic_McEliece_Matrix m_matrix;
+};
+
+/**
+ * @brief Representation of a Classic McEliece private key.
+ *
+ * This class represents a Classic McEliece private key. It is used internally by the Classic McEliece
+ * private key class and contains the following data (see Classic McEliece ISO Section 9.2.12):
+ * - The Classic McEliece parameters
+ * - The seed delta
+ * - The column selection pivot vector c
+ * - The minimal polynomial g
+ * - The field ordering alpha
+ * - The seed s for implicit rejection
+ */
+class BOTAN_TEST_API Classic_McEliece_PrivateKeyInternal {
+ public:
+ /**
+ * @brief Construct a Classic McEliece private key.
+ *
+ * @param params The Classic McEliece parameters
+ * @param delta The seed delta
+ * @param c The column selection pivot vector c
+ * @param g The minimal polynomial g
+ * @param alpha The field ordering alpha
+ * @param s The seed s for implicit rejection
+ */
+ Classic_McEliece_PrivateKeyInternal(const Classic_McEliece_Parameters& params,
+ CmceKeyGenSeed delta,
+ CmceColumnSelection c,
+ Classic_McEliece_Minimal_Polynomial g,
+ Classic_McEliece_Field_Ordering alpha,
+ CmceRejectionSeed s) :
+ m_params(params),
+ m_delta(std::move(delta)),
+ m_c(std::move(c)),
+ m_g(std::move(g)),
+ m_field_ordering(std::move(alpha)),
+ m_s(std::move(s)) {}
+
+ /**
+ * @brief Parses a Classic McEliece private key from a byte sequence.
+ *
+ * It also creates the field ordering from the control bits in @p sk_bytes.
+ *
+ * @param params The Classic McEliece parameters
+ * @param sk_bytes The secret key byte sequence
+ * @return the Classic McEliece private key
+ */
+ static Classic_McEliece_PrivateKeyInternal from_bytes(const Classic_McEliece_Parameters& params,
+ std::span sk_bytes);
+
+ /**
+ * @brief Serializes the Classic McEliece private key as defined in Classic McEliece ISO Section 9.2.12.
+ *
+ * @return the serialized Classic McEliece private key
+ */
+ secure_vector serialize() const;
+
+ /**
+ * @brief The seed delta that was used to create the private key.
+ */
+ const CmceKeyGenSeed& delta() const { return m_delta; }
+
+ /**
+ * @brief The column selection pivot vector c as defined in Classic McEliece ISO Section 9.2.11.
+ */
+ const CmceColumnSelection& c() const { return m_c; }
+
+ /**
+ * @brief The minimal polynomial g.
+ */
+ const Classic_McEliece_Minimal_Polynomial& g() const { return m_g; }
+
+ /**
+ * @brief The field ordering alpha.
+ */
+ const Classic_McEliece_Field_Ordering& field_ordering() const { return m_field_ordering; }
+
+ /**
+ * @brief The seed s for implicit rejection on decryption failure.
+ */
+ const CmceRejectionSeed& s() const { return m_s; }
+
+ /**
+ * @brief The Classic McEliece parameters.
+ */
+ const Classic_McEliece_Parameters& params() const { return m_params; }
+
+ /**
+ * @brief Checks the private key for consistency with the first component delta, i.e.,
+ * recomputes s as a hash of delta and checks equivalence with sk.s, checks the weight of c,
+ * and checks the control bits. It also recomputes beta based on delta and recomputes g based on beta,
+ * checking that g is equal to the value sk.s
+ *
+ * See NIST Impl. guide 6.3 Double-Checks on Private Keys.
+ */
+ bool check_key() const;
+
+ constexpr void _const_time_poison() const { CT::poison_all(m_delta, m_c, m_g, m_field_ordering, m_s); }
+
+ constexpr void _const_time_unpoison() const { CT::unpoison_all(m_delta, m_c, m_g, m_field_ordering, m_s); }
+
+ private:
+ Classic_McEliece_Parameters m_params;
+ CmceKeyGenSeed m_delta;
+ CmceColumnSelection m_c;
+ Classic_McEliece_Minimal_Polynomial m_g;
+ Classic_McEliece_Field_Ordering m_field_ordering;
+ CmceRejectionSeed m_s;
+};
+
+/**
+ * @brief Representation of a Classic McEliece key pair.
+ */
+struct BOTAN_TEST_API Classic_McEliece_KeyPair_Internal {
+ std::shared_ptr private_key;
+ std::shared_ptr public_key;
+
+ /**
+ * @brief Generate a Classic McEliece key pair using the algorithm described
+ * in Classic McEliece ISO Section 8.3
+ */
+ static Classic_McEliece_KeyPair_Internal generate(const Classic_McEliece_Parameters& params,
+ StrongSpan seed);
+
+ /**
+ * @brief Decompose the key pair into a pair of shared pointers to the private and public key.
+ */
+ std::pair,
+ std::shared_ptr>
+ decompose_to_pair() && {
+ return {std::move(private_key), std::move(public_key)};
+ }
+};
+
+} // namespace Botan
+
+#endif // BOTAN_CMCE_KEYS_INTERNAL_H_
diff --git a/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp b/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp
new file mode 100644
index 0000000000..fbd1c8f4e1
--- /dev/null
+++ b/src/lib/pubkey/classic_mceliece/cmce_matrix.cpp
@@ -0,0 +1,296 @@
+/*
+ * Classic McEliece Matrix Logic
+ * Based on the public domain reference implementation by the designers
+ * (https://classic.mceliece.org/impl.html - released in Oct 2022 for NISTPQC-R4)
+ *
+ *
+ * (C) 2023 Jack Lloyd
+ * 2023,2024 Fabian Albert, Amos Treiber - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ **/
+
+#include
+
+#include
+
+namespace Botan {
+
+namespace {
+
+// Strong types for matrix used internally by Classic_McEliece_Matrix
+using CmceMatrixRow = Strong;
+using CmceMatrix = Strong, struct CmceMatrix_>;
+
+} // Anonymous namespace
+
+namespace {
+
+CT::Mask