nx_ml_kem_768.nx source
↩ module page · 138 lines · 5515 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//
42// license_tier: INDEPENDENT_REDERIVE
43// genealogy_id: international-research-sources/ietf/rfc_7748
44//
45// nx_safety_envelope:
46// intended_use: "ML-KEM-768 (FIPS 203 / Kyber) -- post-
47// quantum KEM for substrate's TLS 1.3 + key
48// transport. Hybrid with X25519 recommended."
49// sil_target: SIL3 (KEM correctness gates entire
50// session confidentiality)
51// asil_target: QM
52// dal_target: DAL B
53// evidence: [FIPS_203_canonical_basis, no_FP,
54// sealed_verdict_enum,
55// NTT_polynomial_arithmetic_classical,
56// NIST_PQ_round3_winner]
57// hazard_register: [bug-tape-private-key-leakage-via-NTT-side-channel,
58// bug-tape-decapsulation-failure-not-handled,
59// bug-tape-non-hybrid-deployment-risk-q-day]
60// residual_risk: "Post-quantum primitives are CRYPTANALYSIS-
61// ACTIVE; recommend HYBRID with X25519 until
62// ML-KEM has decade-scale review. Substrate
63// provides hybrid path via composition."
64// verdict: NOT_YET_EVALUATED
65
66import "nx_syscalls.nx"
67
68const MLKEM_N: i64 = 256
69const MLKEM_Q: i64 = 3329
70const MLKEM_K: i64 = 3
71const MLKEM_ETA1: i64 = 2
72const MLKEM_ETA2: i64 = 2
73const MLKEM_DU: i64 = 10
74const MLKEM_DV: i64 = 4
75
76const MLKEM_PK_BYTES: i64 = 1184
77const MLKEM_SK_BYTES: i64 = 2400
78const MLKEM_CT_BYTES: i64 = 1088
79const MLKEM_SS_BYTES: i64 = 32
80
81const MLKEM_ERR_PENDING: i64 = -1
82const MLKEM_ERR_RANDOM: i64 = -2
83const MLKEM_ERR_DECAP: i64 = -3
84
85// ===== keygen ========================================================
86//
87// KeyGen() -> (pk, sk)
88// Internal: pick 32 bytes of randomness, call K-PKE.KeyGen + hash
89// pk into sk so decapsulation can re-verify the implicit reject
90// path.
91func ml_kem_768_keygen(pk_out: *u8, sk_out: *u8) -> i64 {
92 // Placeholder: zero-fill to keep callers from reading uninit
93 // bytes during the skeleton phase.
94 var i: i64 = 0
95 while i < MLKEM_PK_BYTES { pk_out[i] = 0; i = i + 1 }
96 i = 0
97 while i < MLKEM_SK_BYTES { sk_out[i] = 0; i = i + 1 }
98 return MLKEM_ERR_PENDING
99}
100
101// Encaps(pk) -> (ct, ss)
102// Caller-visible: given Alice's public key, produce a ciphertext
103// to send her + the 32-byte shared secret.
104func ml_kem_768_encaps(pk_in: *u8,
105 ct_out: *u8, ss_out: *u8) -> i64 {
106 var i: i64 = 0
107 while i < MLKEM_CT_BYTES { ct_out[i] = 0; i = i + 1 }
108 i = 0
109 while i < MLKEM_SS_BYTES { ss_out[i] = 0; i = i + 1 }
110 return MLKEM_ERR_PENDING
111}
112
113// Decaps(sk, ct) -> ss
114// Caller-visible: given Alice's secret key + Bob's ciphertext,
115// recover the shared secret. Constant-time reject on
116// tampered ct (implicit rejection via rand hash of sk || ct).
117func ml_kem_768_decaps(sk_in: *u8, ct_in: *u8,
118 ss_out: *u8) -> i64 {
119 var i: i64 = 0
120 while i < MLKEM_SS_BYTES { ss_out[i] = 0; i = i + 1 }
121 return MLKEM_ERR_PENDING
122}
123
124// Compile-only smoke: confirm the API compiles + returns the
125// PENDING sentinel. Full test vector check lands with the
126// implementation.
127func main() -> i64 {
128 let pk: *u8 = sys_mmap(MLKEM_PK_BYTES)
129 let sk: *u8 = sys_mmap(MLKEM_SK_BYTES)
130 let ct: *u8 = sys_mmap(MLKEM_CT_BYTES)
131 let ss: *u8 = sys_mmap(MLKEM_SS_BYTES)
132 let ss2: *u8 = sys_mmap(MLKEM_SS_BYTES)
133
134 if ml_kem_768_keygen(pk, sk) != MLKEM_ERR_PENDING { return 1 }
135 if ml_kem_768_encaps(pk, ct, ss) != MLKEM_ERR_PENDING { return 2 }
136 if ml_kem_768_decaps(sk, ct, ss2) != MLKEM_ERR_PENDING { return 3 }
137 return 0
138}