code wiki / (root) / nx_ml_kem_768.nx

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}