ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
mlkem.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file mlkem.h
6 * @brief ML-KEM-768 (FIPS 203): Encaps (responder) + KeyGen and Decaps (initiator).
7 *
8 * The post-quantum half of the mlkem768x25519-sha256 (SSH) and X25519MLKEM768 (TLS 1.3) hybrid key
9 * exchanges. Both KEM roles are present:
10 * - responder (server terminating an inbound handshake): Encaps takes the peer's encapsulation key
11 * and produces (ciphertext, shared secret);
12 * - initiator (the device dialling out as an SSH/TLS *client*): KeyGen produces (ek, dk), the peer
13 * Encaps against ek, and Decaps recovers the shared secret from the returned ciphertext.
14 *
15 * Decaps carries the full constant-time Fujisaki-Okamoto transform (re-encrypt m' under the embedded
16 * ek and select the real key vs the implicit-reject key J(z || ct) under a constant-time ciphertext
17 * compare), so a malformed or tampered ciphertext yields a pseudorandom secret rather than leaking a
18 * decryption failure - FIPS 203 §6.3.
19 *
20 * KeyGen, Encaps and Decaps are the FIPS 203 "internal" (derandomized) forms: the caller supplies the
21 * randomness (KeyGen's (d, z), Encaps's message @p m), drawn from the platform RNG in production and
22 * fixed in known-answer tests. Deterministic given their inputs, which is exactly what the ACVP
23 * keyGen / encapDecap vectors pin.
24 *
25 * Arithmetic is a software NTT over q=3329 with Montgomery reduction (the twiddle factors are fixed
26 * constants premultiplied into Montgomery form, so each butterfly is two int16 multiplies and a
27 * shift - no division, and the hardware MPI, which targets RSA/DH-sized operands, would only add
28 * marshaling overhead). Zero heap; peak stack ~9 KB (Decaps, which re-encrypts).
29 *
30 * @author Douglas Quigg (dstroy0)
31 * @date 2026
32 */
33
34#ifndef PROTOCORE_MLKEM_H
35#define PROTOCORE_MLKEM_H
36
37#include "protocore_config.h"
38
39#if PC_ENABLE_PQC_KEX
40
41#include <stddef.h>
42#include <stdint.h>
43
44#define MLKEM768_EK_BYTES 1184 ///< encapsulation key (public key): 384*k + 32
45#define MLKEM768_DK_BYTES 2400 ///< decapsulation key (private): 768*k + 96
46#define MLKEM768_CT_BYTES 1088 ///< ciphertext: 32*(du*k + dv) = 32*(30+4)
47#define MLKEM768_SS_BYTES 32 ///< shared secret
48#define MLKEM768_MSG_BYTES 32 ///< the random message m fed to Encaps
49#define MLKEM768_D_BYTES 32 ///< KeyGen seed d (K-PKE key material)
50#define MLKEM768_Z_BYTES 32 ///< KeyGen seed z (implicit-reject value)
51
52/**
53 * @brief ML-KEM-768 KeyGen (FIPS 203, derandomized): (ek, dk) from the two 32-octet seeds.
54 *
55 * The initiator's step: publish @p ek (the peer Encaps against it) and keep @p dk for Decaps. @p dk
56 * embeds @p ek, H(ek) and @p z, so Decaps needs no other state. Deterministic given (@p d, @p z).
57 *
58 * @param[in] d 32-octet key-material seed.
59 * @param[in] z 32-octet implicit-reject seed.
60 * @param[out] ek encapsulation key (MLKEM768_EK_BYTES).
61 * @param[out] dk decapsulation key (MLKEM768_DK_BYTES).
62 */
63void pc_mlkem768_keygen(const uint8_t d[MLKEM768_D_BYTES], const uint8_t z[MLKEM768_Z_BYTES],
64 uint8_t ek[MLKEM768_EK_BYTES], uint8_t dk[MLKEM768_DK_BYTES]);
65
66/**
67 * @brief ML-KEM-768 Encaps (FIPS 203, derandomized): (ct, ss) from an encapsulation key and message.
68 *
69 * Validates @p ek (FIPS 203 modulus check: every decoded coefficient must be < q); on a malformed key
70 * it writes nothing and returns false. Otherwise derives K = ss and encrypts @p m under @p ek.
71 *
72 * @param[in] ek peer encapsulation key (MLKEM768_EK_BYTES).
73 * @param[in] m 32-octet message (the encapsulation randomness).
74 * @param[out] ct ciphertext (MLKEM768_CT_BYTES).
75 * @param[out] ss 32-octet shared secret.
76 * @return true on success, false if @p ek fails the modulus check.
77 */
78bool pc_mlkem768_encaps(const uint8_t ek[MLKEM768_EK_BYTES], const uint8_t m[MLKEM768_MSG_BYTES],
79 uint8_t ct[MLKEM768_CT_BYTES], uint8_t ss[MLKEM768_SS_BYTES]);
80
81/**
82 * @brief ML-KEM-768 Decaps (FIPS 203, §6.3): recover the shared secret from a ciphertext.
83 *
84 * Runs the full constant-time Fujisaki-Okamoto transform: decrypt @p ct to m', re-derive (K', r') and
85 * re-encrypt under the ek embedded in @p dk, then in constant time return K' if the recomputed
86 * ciphertext matches @p ct or the implicit-reject key J(z || ct) if it does not. Never fails - a bad
87 * ciphertext yields a pseudorandom secret, not an error - so there is no boolean return.
88 *
89 * @param[in] dk decapsulation key (MLKEM768_DK_BYTES) from pc_mlkem768_keygen().
90 * @param[in] ct ciphertext (MLKEM768_CT_BYTES) from the peer's Encaps.
91 * @param[out] ss 32-octet shared secret.
92 */
93void pc_mlkem768_decaps(const uint8_t dk[MLKEM768_DK_BYTES], const uint8_t ct[MLKEM768_CT_BYTES],
94 uint8_t ss[MLKEM768_SS_BYTES]);
95
96#endif // PC_ENABLE_PQC_KEX
97
98#endif // PROTOCORE_MLKEM_H
User-facing configuration for ProtoCore.