code wiki / (root) / nx_x25519mlkem768.nx

nx_x25519mlkem768.nx source

↩ module page · 140 lines · 7180 B

1// nx_x25519mlkem768.nx -- the MODERN post-quantum hybrid KEM X25519MLKEM768 (TLS 1.3 named group 2// 0x11EC) per draft-ietf-tls-ecdhe-mlkem-05 (banked knowledge/fetched/pq_hybrid_ietf_draft.raw). 3// 4// COMPLETES the post-quantum story: retires the UNWIRED constant-only NG_X25519_KYBER768_DRAFT00 5// (0x6399) "draft" by composing two COMPLETE primitives into the FINAL standardized hybrid -- 6// * FIPS-203 ML-KEM-768 (nx_ml_kem_768_wasm: nx_mlkem_keygen/encaps/decaps) 7// * X25519 ECDH (nx_x25519: x25519 Montgomery ladder) 8// by CONCATENATION with the ML-KEM part FIRST (the spec's deliberately-reversed order, sec 3+4.3): 9// client key_exchange = ML-KEM-768 ek (1184) || X25519 pubkey (32) = 1216 bytes 10// server key_exchange = ML-KEM ciphertext (1088) || X25519 pubkey (32) = 1120 bytes 11// shared secret = ML-KEM shared secret (32) || X25519 shared secret (32) = 64 bytes 12// The 64-byte hybrid secret is quantum-safe iff EITHER component is (harvest-now-decrypt-later proof). 13// 14// This is the KEM engine; the TLS wiring (offer 0x11EC in supported_groups + carry the shares in 15// key_share + feed the 64B secret to the key schedule) composes these three functions. Proven END-TO- 16// END by the round-trip KAT in main (client_keygen -> server -> client_finish => identical 64B secret). 17// 18// license_tier: INDEPENDENT_REDERIVE 19// genealogy_id: international-research-sources/ietf/draft-ietf-tls-ecdhe-mlkem + nist/fips_203 + rfc_7748 20// lineage_id: nishi_x25519mlkem768_q10 21import "nx_syscalls.nx" 22import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 23import "nx_x25519.nx" 24import "nx_ml_kem_768_wasm.nx" 25const XM_MAGIC_65536: i64 = 65536 26 27const XM_X_PUB: i64 = 32 28const XM_X_PRIV: i64 = 32 29const XM_X_SS: i64 = 32 30const XM_EK: i64 = 1184 31const XM_CT: i64 = 1088 32const XM_SS_K: i64 = 32 33const XM_DK: i64 = 2400 34const XM_CLIENT_SHARE: i64 = 1216 // ek(1184) || x_pub(32) 35const XM_SERVER_SHARE: i64 = 1120 // ct(1088) || x_pub(32) 36const XM_CLIENT_SECRET: i64 = 2432 // x_priv(32) || dk(2400) 37const XM_SHARED: i64 = 64 // mlkem_ss(32) || x_ss(32) 38 39func xm_cpy(dst: *u8, src: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { dst[i] = src[i]; i = i + 1 } return 0 } 40func xm_off(p: *u8, k: i64) -> *u8 { return (p as i64 + k) as *u8 } 41// X25519 basepoint u=9 (32 bytes little-endian: 0x09 then zeros). 42func xm_basepoint(bp: *u8) -> i64 { bp[0] = 9 as u8; var i: i64 = 1; while i < 32 { bp[i] = 0 as u8; i = i + 1 } return 0 } 43 44func xm_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 45// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 46// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 47// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 48// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 49func xm_putn(v: i64) -> i64 { nxi_out(v); return 0 } 50 51// CLIENT keygen: rand96 = x_priv(32) || mlkem_seed(64). Emits client_share(1216) + client_secret(2432). 52func x25519mlkem768_client_keygen(rand96: *u8, client_share: *u8, client_secret: *u8) -> i64 { 53 let scratch: *u8 = sys_mmap(XM_MAGIC_65536) 54 let bp: *u8 = sys_mmap(32); xm_basepoint(bp) 55 let x_priv: *u8 = rand96 56 let x_pub: *u8 = sys_mmap(32) 57 x25519(x_priv, bp, x_pub) 58 let mlkem_seed: *u8 = xm_off(rand96, 32) 59 let ek: *u8 = sys_mmap(XM_EK) 60 let dk: *u8 = sys_mmap(XM_DK) 61 nx_mlkem_keygen(mlkem_seed, scratch, ek, dk) 62 xm_cpy(client_share, ek, XM_EK) // ML-KEM first 63 xm_cpy(xm_off(client_share, XM_EK), x_pub, XM_X_PUB) 64 xm_cpy(client_secret, x_priv, XM_X_PRIV) 65 xm_cpy(xm_off(client_secret, XM_X_PRIV), dk, XM_DK) 66 return 0 67} 68 69// SERVER: parse client_share(1216); rand64 = x_priv_s(32) || mlkem_msg(32). Emits server_share(1120) + shared(64). 70func x25519mlkem768_server(client_share: *u8, rand64: *u8, server_share: *u8, shared: *u8) -> i64 { 71 let scratch: *u8 = sys_mmap(XM_MAGIC_65536) 72 let bp: *u8 = sys_mmap(32); xm_basepoint(bp) 73 let peer_ek: *u8 = client_share 74 let peer_x_pub: *u8 = xm_off(client_share, XM_EK) 75 let x_priv_s: *u8 = rand64 76 let x_pub_s: *u8 = sys_mmap(32) 77 x25519(x_priv_s, bp, x_pub_s) 78 let x_ss: *u8 = sys_mmap(32) 79 x25519(x_priv_s, peer_x_pub, x_ss) 80 let mlkem_msg: *u8 = xm_off(rand64, 32) 81 let ct: *u8 = sys_mmap(XM_CT) 82 let mlkem_ss: *u8 = sys_mmap(32) 83 nx_mlkem_encaps(peer_ek, mlkem_msg, scratch, ct, mlkem_ss) 84 xm_cpy(server_share, ct, XM_CT) // ML-KEM first 85 xm_cpy(xm_off(server_share, XM_CT), x_pub_s, XM_X_PUB) 86 xm_cpy(shared, mlkem_ss, XM_SS_K) // ML-KEM first 87 xm_cpy(xm_off(shared, XM_SS_K), x_ss, XM_X_SS) 88 return 0 89} 90 91// CLIENT finish: parse server_share(1120) + client_secret(2432). Emits shared(64). 92func x25519mlkem768_client_finish(server_share: *u8, client_secret: *u8, shared: *u8) -> i64 { 93 let scratch: *u8 = sys_mmap(XM_MAGIC_65536) 94 let ct: *u8 = server_share 95 let peer_x_pub_s: *u8 = xm_off(server_share, XM_CT) 96 let x_priv: *u8 = client_secret 97 let dk: *u8 = xm_off(client_secret, XM_X_PRIV) 98 let x_ss: *u8 = sys_mmap(32) 99 x25519(x_priv, peer_x_pub_s, x_ss) 100 let mlkem_ss: *u8 = sys_mmap(32) 101 nx_mlkem_decaps(dk, ct, scratch, mlkem_ss) 102 xm_cpy(shared, mlkem_ss, XM_SS_K) // ML-KEM first 103 xm_cpy(xm_off(shared, XM_SS_K), x_ss, XM_X_SS) 104 return 0 105} 106 107// Round-trip KAT: client_keygen -> server -> client_finish; assert the two 64B secrets are IDENTICAL. 108func main() -> i64 { 109 let rand96: *u8 = sys_mmap(96) 110 var i: i64 = 0; while i < 96 { rand96[i] = ((i * 7 + 3) & 0xff) as u8; i = i + 1 } 111 let rand64: *u8 = sys_mmap(64) 112 i = 0; while i < 64 { rand64[i] = ((i * 5 + 11) & 0xff) as u8; i = i + 1 } 113 114 let client_share: *u8 = sys_mmap(XM_CLIENT_SHARE) 115 let client_secret: *u8 = sys_mmap(XM_CLIENT_SECRET) 116 x25519mlkem768_client_keygen(rand96, client_share, client_secret) 117 118 let server_share: *u8 = sys_mmap(XM_SERVER_SHARE) 119 let ss_server: *u8 = sys_mmap(XM_SHARED) 120 x25519mlkem768_server(client_share, rand64, server_share, ss_server) 121 122 let ss_client: *u8 = sys_mmap(XM_SHARED) 123 x25519mlkem768_client_finish(server_share, client_secret, ss_client) 124 125 var eq: i64 = 1 126 i = 0; while i < XM_SHARED { if ss_server[i] != ss_client[i] { eq = 0 } i = i + 1 } 127 var nz: i64 = 0 128 i = 0; while i < XM_SHARED { if ss_server[i] != (0 as u8) { nz = 1 } i = i + 1 } 129 130 xm_puts("=== nx_x25519mlkem768 round-trip KAT (TLS group 0x11EC, draft-ietf-tls-ecdhe-mlkem) ===\n" as *u8) 131 xm_puts("client_share=" as *u8); xm_putn(XM_CLIENT_SHARE) 132 xm_puts(" server_share=" as *u8); xm_putn(XM_SERVER_SHARE) 133 xm_puts(" hybrid_secret=" as *u8); xm_putn(XM_SHARED); xm_puts(" bytes\n" as *u8) 134 if eq == 1 { if nz == 1 { 135 xm_puts("PASS: client + server derived the IDENTICAL 64B hybrid secret (ML-KEM-768 || X25519)\n" as *u8) 136 return 0 137 } } 138 xm_puts("FAIL: hybrid secrets differ or all-zero\n" as *u8) 139 return 1 140}