ML-KEM – Post-Quantum Key Encapsulation (Kyber)
Overview
ML-KEM (Module-Lattice Key Encapsulation Mechanism) – is a lattice-based key-encapsulation protocol selected by NIST for post-quantum cryptography (FIPS 203).
ML-KEM provides three security categories that correspond to the Kyber512 (Level 1), Kyber768 (Level 3) and Kyber1024 (Level 5) parameter sets.
Byte sizes
| Parameter set | Public key | Secret key | Cipher-text | Shared secret | Entropy bytes | Message bytes | Security category |
|---|---|---|---|---|---|---|---|
| Kyber512 | 800 B | 1632 B | 768 B | 32 B | 64 B | 32 B | Level 1 |
| Kyber768 | 1184 B | 2400 B | 1088 B | 32 B | 64 B | 32 B | Level 3 |
| Kyber1024 | 1568 B | 3168 B | 1568 B | 32 B | 64 B | 32 B | Level 5 |
C++ API
All three operations are templated by the Kyber parameter set (Kyber512Params, Kyber768Params, Kyber1024Params). The templates are explicitly instantiated by Icicle, so you only have to include the header and link against icicle_pqc.
MlKemConfig struct
struct MlKemConfig {
icicleStreamHandle stream = nullptr; // Optional async stream
bool is_async = false; // If true – return immediately and synchronize later
// Location hints – set to `true` if the corresponding buffer already resides on the device
bool messages_on_device = false;
bool entropy_on_device = false;
bool public_keys_on_device = false;
bool secret_keys_on_device = false;
bool ciphertexts_on_device = false;
bool shared_secrets_on_device = false;
int batch_size = 1; // Number of independent KEMs processed in parallel
ConfigExtension* ext = nullptr; // Backend-specific tuning knobs (optional)
};
Key pair generation
template <typename Params /* Kyber512Params | Kyber768Params | Kyber1024Params */>
eIcicleError keygen(
const std::byte* entropy, // [batch_size × ENTROPY_BYTES]
MlKemConfig config,
std::byte* public_keys, // [batch_size × Params::PUBLIC_KEY_BYTES]
std::byte* secret_keys // [batch_size × Params::SECRET_KEY_BYTES]
);
Encapsulation
template <typename Params>
eIcicleError encapsulate(
const std::byte* message, // [batch_size × MESSAGE_BYTES] arbitrary plaintext
const std::byte* public_keys, // [batch_size × Params::PUBLIC_KEY_BYTES]
MlKemConfig config,
std::byte* ciphertexts, // [batch_size × Params::CIPHERTEXT_BYTES]
std::byte* shared_secrets // [batch_size × Params::SHARED_SECRET_BYTES]
);
Decapsulation
template <typename Params>
eIcicleError decapsulate(
const std::byte* secret_keys, // [batch_size × Params::SECRET_KEY_BYTES]
const std::byte* ciphertexts, // [batch_size × Params::CIPHERTEXT_BYTES]
MlKemConfig config,
std::byte* shared_secrets // [batch_size × Params::SHARED_SECRET_BYTES]
);
Example: generate & encapsulate (Kyber768)
#include "icicle/pqc/ml_kem.h"
using namespace icicle::pqc::ml_kem;
int main() {
const int batch_size = 1 << 12;
// Config
MlKemConfig config;
config.batch_size = batch_size;
// Allocate buffers
auto entropy = this->random_entropy(batch_size * ENTROPY_BYTES);
std::vector<std::byte> public_key(batch_size * TypeParam::PUBLIC_KEY_BYTES);
std::vector<std::byte> secret_key(batch_size * TypeParam::SECRET_KEY_BYTES);
std::vector<std::byte> ciphertext(batch_size * TypeParam::CIPHERTEXT_BYTES);
std::vector<std::byte> shared_secret_enc(batch_size * TypeParam::SHARED_SECRET_BYTES);
std::vector<std::byte> shared_secret_dec(batch_size * TypeParam::SHARED_SECRET_BYTES);
auto message = this->random_entropy(batch_size * MESSAGE_BYTES);
// Key generation
auto err = keygen<TypeParam>(entropy.data(), config, public_key.data(), secret_key.data());
// Encapsulation
err = encapsulate<TypeParam>(message.data(), public_key.data(), config, ciphertext.data(), shared_secret_enc.data());
// Decapsulation
err = decapsulate<TypeParam>(secret_key.data(), ciphertext.data(), config, shared_secret_dec.data());
}
References
• NIST FIPS 203 – Module-Lattice-Based Key-Encapsulation Mechanism Standard.