ml_kem_768.nx source
↩ module page · 114 lines · 4361 B
1// ml_kem_768.nx -- ML-KEM-768 post-quantum key encapsulation.
2//
3// NIST FIPS 203 (August 2024). Lattice-based (Module-LWE),
4// security category 3 (AES-192 equivalent against both classical
5// and quantum adversaries). Drop-in replacement for the X25519
6// key-exchange primitive we ship today -- X25519 is broken by
7// Shor's algorithm once a sufficiently large fault-tolerant
8// quantum computer exists. ML-KEM stays secure even then.
9//
10// Parameters (FIPS 203 Table 2, ML-KEM-768):
11// n = 256 polynomial degree
12// q = 3329 modulus
13// k = 3 module rank
14// eta1 = 2 secret / error distribution parameter
15// eta2 = 2 noise distribution parameter
16// d_u = 10 ciphertext compression (u part)
17// d_v = 4 ciphertext compression (v part)
18//
19// Key / ciphertext sizes:
20// public key = 1184 bytes
21// secret key = 2400 bytes
22// ciphertext = 1088 bytes
23// shared secret = 32 bytes
24//
25// Status (2026-04-23): SKELETON. Structures + API signatures +
26// test vectors scaffolded. Heart of the impl -- NTT over
27// Z_3329[x]/(x^256+1), sample_poly, compress/decompress, K-PKE
28// wrapping -- is bounded ~600 LoC port from the NIST reference
29// and will ship next.
30//
31// Dependencies:
32// - sha3.nx (shipped) for SHAKE-128/256 + SHA3-256/512
33// - rand.nx (shipped) for 32-byte randomness at keygen + encap
34//
35// Invariants:
36// ML1 Output bytes match FIPS 203 Appendix A test vectors.
37// ML2 Constant-time critical paths (sample_ntt / compress /
38// decapsulate re-encrypt) -- no secret-dependent branches.
39// ML3 Zero dynamic allocation in decap hot path (fixed-size
40// buffers) for embedded deployability.
41
42import "syscalls.nx"
43
44const MLKEM_N: i64 = 256
45const MLKEM_Q: i64 = 3329
46const MLKEM_K: i64 = 3
47const MLKEM_ETA1: i64 = 2
48const MLKEM_ETA2: i64 = 2
49const MLKEM_DU: i64 = 10
50const MLKEM_DV: i64 = 4
51
52const MLKEM_PK_BYTES: i64 = 1184
53const MLKEM_SK_BYTES: i64 = 2400
54const MLKEM_CT_BYTES: i64 = 1088
55const MLKEM_SS_BYTES: i64 = 32
56
57const MLKEM_ERR_PENDING: i64 = -1
58const MLKEM_ERR_RANDOM: i64 = -2
59const MLKEM_ERR_DECAP: i64 = -3
60
61// ===== keygen ========================================================
62//
63// KeyGen() -> (pk, sk)
64// Internal: pick 32 bytes of randomness, call K-PKE.KeyGen + hash
65// pk into sk so decapsulation can re-verify the implicit reject
66// path.
67func ml_kem_768_keygen(pk_out: *u8, sk_out: *u8) -> i64 {
68 // Placeholder: zero-fill to keep callers from reading uninit
69 // bytes during the skeleton phase.
70 var i: i64 = 0
71 while i < MLKEM_PK_BYTES { pk_out[i] = 0; i = i + 1 }
72 i = 0
73 while i < MLKEM_SK_BYTES { sk_out[i] = 0; i = i + 1 }
74 return MLKEM_ERR_PENDING
75}
76
77// Encaps(pk) -> (ct, ss)
78// Caller-visible: given Alice's public key, produce a ciphertext
79// to send her + the 32-byte shared secret.
80func ml_kem_768_encaps(pk_in: *u8,
81 ct_out: *u8, ss_out: *u8) -> i64 {
82 var i: i64 = 0
83 while i < MLKEM_CT_BYTES { ct_out[i] = 0; i = i + 1 }
84 i = 0
85 while i < MLKEM_SS_BYTES { ss_out[i] = 0; i = i + 1 }
86 return MLKEM_ERR_PENDING
87}
88
89// Decaps(sk, ct) -> ss
90// Caller-visible: given Alice's secret key + Bob's ciphertext,
91// recover the shared secret. Constant-time reject on
92// tampered ct (implicit rejection via rand hash of sk || ct).
93func ml_kem_768_decaps(sk_in: *u8, ct_in: *u8,
94 ss_out: *u8) -> i64 {
95 var i: i64 = 0
96 while i < MLKEM_SS_BYTES { ss_out[i] = 0; i = i + 1 }
97 return MLKEM_ERR_PENDING
98}
99
100// Compile-only smoke: confirm the API compiles + returns the
101// PENDING sentinel. Full test vector check lands with the
102// implementation.
103func main() -> i64 {
104 let pk: *u8 = sys_mmap(MLKEM_PK_BYTES)
105 let sk: *u8 = sys_mmap(MLKEM_SK_BYTES)
106 let ct: *u8 = sys_mmap(MLKEM_CT_BYTES)
107 let ss: *u8 = sys_mmap(MLKEM_SS_BYTES)
108 let ss2: *u8 = sys_mmap(MLKEM_SS_BYTES)
109
110 if ml_kem_768_keygen(pk, sk) != MLKEM_ERR_PENDING { return 1 }
111 if ml_kem_768_encaps(pk, ct, ss) != MLKEM_ERR_PENDING { return 2 }
112 if ml_kem_768_decaps(sk, ct, ss2) != MLKEM_ERR_PENDING { return 3 }
113 return 0
114}