Skip to content

Commit

Permalink
ML-KEM (Initial Public Draft)
Browse files Browse the repository at this point in the history
This adds ML-KEM-ipd as new modes in the Kyber implementation. It also
integrates the new algorithm into the library's higher level APIs under
the name "ML-KEM-ipd". Currently, neither TLS nor the FFI layer support
it.
  • Loading branch information
reneme committed Jul 18, 2024
1 parent 8e6bc3c commit f3481b2
Show file tree
Hide file tree
Showing 17 changed files with 821 additions and 43 deletions.
11 changes: 10 additions & 1 deletion doc/api_ref/pubkey.rst
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,16 @@ Post-quantum key encapsulation scheme based on (structured) lattices.
AES/SHA-2 instead of SHA-3 based primitives). The 90s mode Kyber is
deprecated and will be removed in a future release.

The final NIST specification version of Kyber is not yet implemented.
ML-KEM (Initial Public Draft)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Post-quantum key encapsulation scheme based on (structured) lattices.

.. note::

ML-KEM is currently a draft (NIST FIPS 203). It is not yet standardized,
and neither long-term interoperability nor support is guaranteed.


Ed25519 and Ed448
~~~~~~~~~~~~~~~~~
Expand Down
6 changes: 6 additions & 0 deletions src/build-data/oids.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
1.3.6.1.4.1.25258.1.11.2 = Kyber-768-90s-r3
1.3.6.1.4.1.25258.1.11.3 = Kyber-1024-90s-r3

# ML-KEM Initial Public Draft - Bouncy Castle's private OIDs
# (taken from the IETF hackathon: https://github.com/IETF-Hackathon/pqc-certificates/blob/f9ecf761c3b4f3a84520536b7ce3175e8c7726fd/docs/oid_mapping.md#nist-draft-standard-algorithm-oids)
1.3.6.1.4.1.22554.5.6.1 = ML-KEM-512-ipd
1.3.6.1.4.1.22554.5.6.2 = ML-KEM-768-ipd
1.3.6.1.4.1.22554.5.6.3 = ML-KEM-1024-ipd

# Dilithium OIDs are currently in Botan's private arc
1.3.6.1.4.1.25258.1.9.1 = Dilithium-4x4-r3
1.3.6.1.4.1.25258.1.9.2 = Dilithium-6x5-r3
Expand Down
50 changes: 36 additions & 14 deletions src/cli/speed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
#include <botan/mceliece.h>
#endif

#if defined(BOTAN_HAS_KYBER) || defined(BOTAN_HAS_KYBER_90S)
#if defined(BOTAN_HAS_KYBER) || defined(BOTAN_HAS_KYBER_90S) || defined(BOTAN_HAS_ML_KEM_INITIAL_PUBLIC_DRAFT)
#include <botan/kyber.h>
#endif

Expand Down Expand Up @@ -419,6 +419,7 @@ class Speed final : public Command {
"X448",
"McEliece",
"Kyber",
"ML-KEM-ipd",
"SPHINCS+",
"FrodoKEM",
"HSS-LMS",
Expand Down Expand Up @@ -634,7 +635,27 @@ class Speed final : public Command {
#endif
#if defined(BOTAN_HAS_KYBER) || defined(BOTAN_HAS_KYBER_90S)
else if(algo == "Kyber") {
bench_kyber(provider, msec);
bench_kyber(provider,
msec,
{
Botan::KyberMode::Kyber512_R3,
Botan::KyberMode::Kyber512_90s,
Botan::KyberMode::Kyber768_R3,
Botan::KyberMode::Kyber768_90s,
Botan::KyberMode::Kyber1024_R3,
Botan::KyberMode::Kyber1024_90s,
});
}
#endif
#if defined(BOTAN_HAS_ML_KEM_INITIAL_PUBLIC_DRAFT)
else if(algo == "ML-KEM-ipd") {
bench_kyber(provider,
msec,
{
Botan::KyberMode::ML_KEM_512_ipd,
Botan::KyberMode::ML_KEM_768_ipd,
Botan::KyberMode::ML_KEM_1024_ipd,
});
}
#endif
#if defined(BOTAN_HAS_DILITHIUM) || defined(BOTAN_HAS_DILITHIUM_AES)
Expand Down Expand Up @@ -2152,27 +2173,28 @@ class Speed final : public Command {
#endif

#if defined(BOTAN_HAS_KYBER) || defined(BOTAN_HAS_KYBER_90S)
void bench_kyber(const std::string& provider, std::chrono::milliseconds msec) {
const Botan::KyberMode::Mode all_modes[] = {
Botan::KyberMode::Kyber512_R3,
Botan::KyberMode::Kyber512_90s,
Botan::KyberMode::Kyber768_R3,
Botan::KyberMode::Kyber768_90s,
Botan::KyberMode::Kyber1024_R3,
Botan::KyberMode::Kyber1024_90s,
};

void bench_kyber(const std::string& provider,
std::chrono::milliseconds msec,
const std::vector<Botan::KyberMode::Mode>& all_modes) {
for(auto modet : all_modes) {
Botan::KyberMode mode(modet);

#if !defined(BOTAN_HAS_KYBER)
if(mode.is_modern())
if(mode.is_kyber_r3() && mode.is_modern()) {
continue;
}
#endif

#if !defined(BOTAN_HAS_KYBER_90S)
if(mode.is_90s())
if(mode.is_kyber_r3() && mode.is_90s()) {
continue;
}
#endif

#if !defined(BOTAN_HAS_ML_KEM_INITIAL_PUBLIC_DRAFT)
if(mode.is_ml_kem_ipd()) {
continue;
}
#endif

auto keygen_timer = make_timer(mode.to_string(), provider, "keygen");
Expand Down
8 changes: 7 additions & 1 deletion src/lib/asn1/oid_maps.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* OID maps
*
* This file was automatically generated by src/scripts/dev_tools/gen_oids.py on 2024-06-13
* This file was automatically generated by src/scripts/dev_tools/gen_oids.py on 2024-07-18
*
* All manual edits to this file will be lost. Edit the script
* then regenerate this source file.
Expand Down Expand Up @@ -141,6 +141,9 @@ std::unordered_map<std::string, std::string> 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.6.1", "ML-KEM-512-ipd"},
{"1.3.6.1.4.1.22554.5.6.2", "ML-KEM-768-ipd"},
{"1.3.6.1.4.1.22554.5.6.3", "ML-KEM-1024-ipd"},
{"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"},
Expand Down Expand Up @@ -423,6 +426,9 @@ std::unordered_map<std::string, OID> OID_Map::load_str2oid_map() {
{"Kyber-768-r3", OID({1, 3, 6, 1, 4, 1, 25258, 1, 7, 2})},
{"MD5", OID({1, 2, 840, 113549, 2, 5})},
{"MGF1", OID({1, 2, 840, 113549, 1, 1, 8})},
{"ML-KEM-1024-ipd", OID({1, 3, 6, 1, 4, 1, 22554, 5, 6, 3})},
{"ML-KEM-512-ipd", OID({1, 3, 6, 1, 4, 1, 22554, 5, 6, 1})},
{"ML-KEM-768-ipd", OID({1, 3, 6, 1, 4, 1, 22554, 5, 6, 2})},
{"McEliece", OID({1, 3, 6, 1, 4, 1, 25258, 1, 3})},
{"Microsoft SmartcardLogon", OID({1, 3, 6, 1, 4, 1, 311, 20, 2, 2})},
{"Microsoft UPN", OID({1, 3, 6, 1, 4, 1, 311, 20, 2, 3})},
Expand Down
44 changes: 43 additions & 1 deletion src/lib/pubkey/kyber/kyber_common/kyber.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
#include <botan/internal/kyber_encaps.h>
#endif

#if defined(BOTAN_HAS_ML_KEM_INITIAL_PUBLIC_DRAFT)
#include <botan/internal/ml_kem_ipd.h>
#endif

#include <memory>
#include <vector>

Expand All @@ -66,6 +70,15 @@ KyberMode::Mode kyber_mode_from_string(std::string_view str) {
if(str == "Kyber-1024-r3") {
return KyberMode::Kyber1024_R3;
}
if(str == "ML-KEM-512-ipd") {
return KyberMode::ML_KEM_512_ipd;
}
if(str == "ML-KEM-768-ipd") {
return KyberMode::ML_KEM_768_ipd;
}
if(str == "ML-KEM-1024-ipd") {
return KyberMode::ML_KEM_1024_ipd;
}

throw Invalid_Argument(fmt("'{}' is not a valid Kyber mode name", str));
}
Expand Down Expand Up @@ -96,6 +109,12 @@ std::string KyberMode::to_string() const {
return "Kyber-768-r3";
case Kyber1024_R3:
return "Kyber-1024-r3";
case ML_KEM_512_ipd:
return "ML-KEM-512-ipd";
case ML_KEM_768_ipd:
return "ML-KEM-768-ipd";
case ML_KEM_1024_ipd:
return "ML-KEM-1024-ipd";
}

BOTAN_ASSERT_UNREACHABLE();
Expand All @@ -109,6 +128,11 @@ bool KyberMode::is_modern() const {
return !is_90s();
}

bool KyberMode::is_ml_kem_ipd() const {
return m_mode == KyberMode::ML_KEM_512_ipd || m_mode == KyberMode::ML_KEM_768_ipd ||
m_mode == KyberMode::ML_KEM_1024_ipd;
}

bool KyberMode::is_kyber_round3() const {
return m_mode == KyberMode::Kyber512_R3 || m_mode == KyberMode::Kyber768_R3 || m_mode == KyberMode::Kyber1024_R3 ||
m_mode == KyberMode::Kyber512_90s || m_mode == KyberMode::Kyber768_90s || m_mode == KyberMode::Kyber1024_90s;
Expand All @@ -127,6 +151,12 @@ bool KyberMode::is_available() const {
}
#endif

#if defined(BOTAN_HAS_ML_KEM_INITIAL_PUBLIC_DRAFT)
if(is_ml_kem_ipd()) {
return true;
}
#endif

return false;
}

Expand All @@ -135,7 +165,7 @@ KyberMode Kyber_PublicKey::mode() const {
}

std::string Kyber_PublicKey::algo_name() const {
return "Kyber";
return mode().is_ml_kem_ipd() ? "ML-KEM-ipd" : "Kyber";
}

AlgorithmIdentifier Kyber_PublicKey::algorithm_identifier() const {
Expand Down Expand Up @@ -292,6 +322,12 @@ std::unique_ptr<PK_Ops::KEM_Encryption> Kyber_PublicKey::create_kem_encryption_o
}
#endif

#if defined(BOTAN_HAS_ML_KEM_INITIAL_PUBLIC_DRAFT)
if(mode().is_ml_kem_ipd()) {
return std::make_unique<ML_KEM_IPD_Encryptor>(m_public, params);
}
#endif

BOTAN_ASSERT_UNREACHABLE();
}
throw Provider_Not_Found(algo_name(), provider);
Expand All @@ -308,6 +344,12 @@ std::unique_ptr<PK_Ops::KEM_Decryption> Kyber_PrivateKey::create_kem_decryption_
}
#endif

#if defined(BOTAN_HAS_ML_KEM_INITIAL_PUBLIC_DRAFT)
if(mode().is_ml_kem_ipd()) {
return std::make_unique<ML_KEM_IPD_Decryptor>(m_private, m_public, params);
}
#endif

BOTAN_ASSERT_UNREACHABLE();
}
throw Provider_Not_Found(algo_name(), provider);
Expand Down
15 changes: 13 additions & 2 deletions src/lib/pubkey/kyber/kyber_common/kyber.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@

#include <span>

#if !defined(BOTAN_HAS_KYBER_90S) && !defined(BOTAN_HAS_KYBER)
static_assert(false, "botan module 'kyber_common' is useful only when enabling modules 'kyber', 'kyber_90s' or both");
#if !defined(BOTAN_HAS_KYBER_90S) && !defined(BOTAN_HAS_KYBER) && !defined(BOTAN_HAS_ML_KEM_INITIAL_PUBLIC_DRAFT)
static_assert(
false,
"botan module 'kyber_common' is useful only when enabling at least one of those modules: 'kyber', 'kyber_90s', 'ml_kem_ipd'");
#endif

namespace Botan {
Expand All @@ -41,6 +43,13 @@ class BOTAN_PUBLIC_API(3, 0) KyberMode {
Kyber768 BOTAN_DEPRECATED("Use Kyber768_R3") = Kyber768_R3,
Kyber1024 BOTAN_DEPRECATED("Use Kyber1024_R3") = Kyber1024_R3,

// ML-KEM as proposed by NIST in the initial public draft of the standard
ML_KEM_512_ipd,
// ML-KEM as proposed by NIST in the initial public draft of the standard
ML_KEM_768_ipd,
// ML-KEM as proposed by NIST in the initial public draft of the standard
ML_KEM_1024_ipd,

Kyber512_90s BOTAN_DEPRECATED("Kyber 90s mode is deprecated"),
Kyber768_90s BOTAN_DEPRECATED("Kyber 90s mode is deprecated"),
Kyber1024_90s BOTAN_DEPRECATED("Kyber 90s mode is deprecated"),
Expand All @@ -59,6 +68,8 @@ class BOTAN_PUBLIC_API(3, 0) KyberMode {

BOTAN_DEPRECATED("Kyber 90s mode is deprecated") bool is_modern() const;

bool is_ml_kem_ipd() const;

bool is_kyber_round3() const;

bool is_available() const;
Expand Down
13 changes: 13 additions & 0 deletions src/lib/pubkey/kyber/kyber_common/kyber_constants.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,17 @@
#include <botan/internal/kyber_90s.h>
#endif

#if defined(BOTAN_HAS_ML_KEM_INITIAL_PUBLIC_DRAFT)
#include <botan/internal/ml_kem_ipd.h>
#endif

namespace Botan {

KyberConstants::KyberConstants(KyberMode mode) : m_mode(mode) {
switch(mode.mode()) {
case KyberMode::Kyber512_R3:
case KyberMode::Kyber512_90s:
case KyberMode::ML_KEM_512_ipd:
m_nist_strength = KyberStrength::_128;
m_k = 2;
m_eta1 = KyberEta::_3;
Expand All @@ -36,6 +41,7 @@ KyberConstants::KyberConstants(KyberMode mode) : m_mode(mode) {

case KyberMode::Kyber768_R3:
case KyberMode::Kyber768_90s:
case KyberMode::ML_KEM_768_ipd:
m_nist_strength = KyberStrength::_192;
m_k = 3;
m_eta1 = KyberEta::_2;
Expand All @@ -45,6 +51,7 @@ KyberConstants::KyberConstants(KyberMode mode) : m_mode(mode) {

case KyberMode::Kyber1024_R3:
case KyberMode::Kyber1024_90s:
case KyberMode::ML_KEM_1024_ipd:
m_nist_strength = KyberStrength::_256;
m_k = 4;
m_eta1 = KyberEta::_2;
Expand All @@ -68,6 +75,12 @@ KyberConstants::KyberConstants(KyberMode mode) : m_mode(mode) {
}
#endif

#ifdef BOTAN_HAS_ML_KEM_INITIAL_PUBLIC_DRAFT
if(mode.is_ml_kem_ipd()) {
m_symmetric_primitives = std::make_unique<ML_KEM_IPD_Symmetric_Primitives>();
}
#endif

static_assert(N % 8 == 0);
m_polynomial_vector_bytes = (bitlen(Q) * (N / 8)) * k();
m_polynomial_vector_compressed_bytes = d_u() * k() * (N / 8);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ class Kyber_Symmetric_Primitives {
return G_split<KyberSharedSecret, KyberEncryptionRandomness>(msg, pubkey_hash);
}

KyberSharedSecret J(StrongSpan<const KyberImplicitRejectionValue> rejection_value,
StrongSpan<const KyberCompressedCiphertext> ciphertext) const {
auto& j = get_J();
j.update(rejection_value);
j.update(ciphertext);
return j.final<KyberSharedSecret>();
}

// TODO: remove this once Kyber-R3 is removed
void KDF(StrongSpan<KyberSharedSecret> out,
StrongSpan<const KyberSharedSecret> shared_secret,
Expand Down Expand Up @@ -97,6 +105,7 @@ class Kyber_Symmetric_Primitives {
protected:
virtual HashFunction& get_G() const = 0;
virtual HashFunction& get_H() const = 0;
virtual HashFunction& get_J() const = 0;
virtual HashFunction& get_KDF() const = 0;
virtual Botan::XOF& get_PRF(std::span<const uint8_t> seed, uint8_t nonce) const = 0;
virtual Botan::XOF& get_XOF(std::span<const uint8_t> seed,
Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/kyber/kyber_round3/kyber/kyber_modern.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class Kyber_Modern_Symmetric_Primitives : public Kyber_Symmetric_Primitives {

HashFunction& get_H() const override { return *m_sha3_256; }

HashFunction& get_J() const override { throw Invalid_State("Kyber-R3 does not support J()"); }

HashFunction& get_KDF() const override { return *m_shake256_256; }

Botan::XOF& get_PRF(std::span<const uint8_t> seed, const uint8_t nonce) const override {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/pubkey/kyber/kyber_round3/kyber_90s/kyber_90s.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class Kyber_90s_Symmetric_Primitives : public Kyber_Symmetric_Primitives {

HashFunction& get_H() const override { return *m_sha256; }

HashFunction& get_J() const override { throw Invalid_State("Kyber-R3 does not support J()"); }

HashFunction& get_KDF() const override { return *m_sha256; }

Botan::XOF& get_PRF(std::span<const uint8_t> seed, const uint8_t nonce) const override {
Expand Down
20 changes: 20 additions & 0 deletions src/lib/pubkey/kyber/ml_kem_ipd/info.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<defines>
ML_KEM_INITIAL_PUBLIC_DRAFT -> 20240117
</defines>

<module_info>
name -> "ML-KEM (IPD)"
brief -> "Module Lattice KEM (Initial Public Draft)"
lifecycle -> "Experimental"
</module_info>

<requires>
kyber_common
sha3
shake
shake_xof
</requires>

<header:internal>
ml_kem_ipd.h
</header:internal>
Loading

0 comments on commit f3481b2

Please sign in to comment.