nx_pq_hybrid.nx
buildroot/runtime/nx_pq_hybrid.nx
about
nx_pq_hybrid.nx -- C8 of the comms lane: POST-QUANTUM HYBRID KEY TRANSPORT for the C7 ratchet.
Contract symbol pqh_kex == the /compare/comms C8 watch. REFEREE: nx_pq_hybrid_gate.
DONE-RULE (comms.plan): hybrid classical+KEM ratcheting in the C7 tree (Signal SPQR / triple-ratchet
class): the gate proves a classical-only decryption fails AND a KEM-only decryption fails -- both
families must break.
WHAT IT DOES: C7 distributes each epoch's commit_secret to the remaining members over X25519-only
ECDH. C8 replaces that transport with the X25519MLKEM768 HYBRID KEM (TLS 1.3 named group 0x11EC,
draft-ietf-tls-ecdhe-mlkem): the wrap key is derived from a 64-byte secret that is
ml_kem_768_shared(32) || x25519_shared(32), so recovering it needs BOTH the elliptic-curve secret
AND the lattice KEM secret. An adversary who breaks X25519 (a future quantum computer solving ECDH)
still cannot read the commit secret without also breaking ML-KEM-768 -- and vice versa. Because the
commit secret drives every subsequent epoch, this defeats harvest-now-decrypt-later on the whole
forward chain, which is exactly the property Signal's SPQR triple ratchet added in Oct-2025.
PURE COMPOSITION, no new crypto: it forks NOTHING and imports the sovereign hybrid KEM
(nx_x25519mlkem768, itself x25519 + ml_kem_768) plus sha256 (HMAC/HKDF) and chacha20 (RFC 8439).
No WebRTC, no OpenSSL, no liboqs -- the ML-KEM here is the estate's own FIPS 203 implementation.
THE PROOF, mechanical: unwrap takes a `corrupt` argument that damages ONE half of the recovered
64-byte hybrid secret before deriving the wrap key -- corrupt=pq models an attacker who has the
classical half but not the lattice half (broke ECDH, not ML-KEM), corrupt=classical the reverse.
Either damaged half fails the HMAC and decrypts NOTHING; only corrupt=none succeeds. That is the
two-independent-families claim made falsifiable.
DEBT NAMED, not hidden: mg_hmac/mg_derive/hex/file helpers here are the same shapes as nx_mls_group.
Extracting them to a shared nx_kdf_lib that BOTH C7 and C8 import is the correct DRY move and is
filed (comms.plan debt kdf-lib-extract); shipping C8 independent now lands the verified exceed
without re-opening the just-gated C7 binary in the same breath.
license_tier: ORIGINAL No hw writes (Rule 26).
dependencies 5 imports · 0 importers
imports: nx_syscalls.nxnx_itoa_lib.nxnx_x25519mlkem768.nxnx_sha256.nxnx_chacha20.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 37 | const PQH_EXIT_OK: i64 = 0 |
| 38 | const PQH_EXIT_USAGE: i64 = 2 |
| 39 | const PQH_EXIT_REFUSED: i64 = 3 |
| 40 | const PQH_EXIT_CORRUPT: i64 = 5 |
| 42 | const PQH_SHARE: i64 = 1216 // ek(1184) || x_pub(32) -- a member's hybrid public share |
| 43 | const PQH_SECRET: i64 = 2432 // x_priv(32) || dk(2400) -- a member's hybrid secret |
| 44 | const PQH_SRVSHARE: i64 = 1120 // ct(1088) || x_pub_s(32) -- the encapsulation to a member |
| 45 | const PQH_SS: i64 = 64 // mlkem_ss(32) || x_ss(32) -- the hybrid shared secret |
| 46 | const PQH_RAND96: i64 = 96 |
| 47 | const PQH_RAND64: i64 = 64 |
| 48 | const PQH_KEY: i64 = 32 |
| 49 | const PQH_MAC: i64 = 32 |
| 50 | const PQH_NONCE: i64 = 12 |
| 51 | const PQH_BLOCK: i64 = 64 |
| 52 | const PQH_IPAD: i64 = 54 |
| 53 | const PQH_OPAD: i64 = 92 |
| 54 | const PQH_NL: i64 = 10 |
| 55 | const PQH_PIPE: i64 = 124 |
| 56 | const PQH_FILECAP: i64 = 262144 |
functions
| 58 | func pqh_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } |
| 59 | func pqh_num(v: i64) -> i64 { nxi_out(v); return 0 } calls 1: nxi_out |
| 60 | func pqh_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 61 | func pqh_eq(a: *u8, b: *u8) -> i64 |
| 67 | func pqh_memeq(a: *u8, b: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { if a[i] != b[i] { return 0 } i = i + 1 } return 1 } called by 1: pqh_unwrap |
| 68 | func pqh_hexe(src: *u8, n: i64, dst: *u8) -> i64 |
| 81 | func pqh_hexv(c: i64) -> i64 called by 1: pqh_hexd |
| 87 | func pqh_hexd(src: *u8, n: i64, dst: *u8) -> i64 |
| 100 | func pqh_read(path: *u8, buf: *u8, cap: i64) -> i64 |
| 108 | func pqh_write(path: *u8, buf: *u8, n: i64) -> i64 |
| 117 | func pqh_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o] = s[i]; o = o + 1; i = i + 1 } return o } |
| 119 | func pqh_field(line: *u8, n: i64, idx: i64, out: *u8) -> i64 called by 1: pqh_unwrap |
| 133 | func pqh_hmac(key: *u8, klen: i64, msg: *u8, mlen: i64, out: *u8) -> i64 |
| 160 | func pqh_derive(secret: *u8, slen: i64, label: *u8, out: *u8) -> i64 |
| 171 | func pqh_kex(hybrid_ss: *u8, corrupt: i64, wrapkey: *u8) -> i64 |
| 182 | func pqh_keygen(share_out: *u8, secret_out: *u8, rand96_hex: *u8) -> i64 |
| 202 | func pqh_wrap(share_file: *u8, rand64_hex: *u8, payload_hex: *u8, wrap_out: *u8) -> i64 |
| 248 | func pqh_unwrap(secret_file: *u8, wrap_file: *u8, corrupt_s: *u8) -> i64 |
| 299 | func pqh_usage() -> i64 |
| 303 | func main(argc: i64, argv: *i64) -> i64 |