nx_connect_seal.nx source
↩ module page · 62 lines · 3294 B
1// nx_connect_seal.nx -- CONNECT message sealing on the REAL, RFC-8439-verified ChaCha20-Poly1305 AEAD,
2// built 2026-07-24 to retire the 78-line additive-cipher stand-in (nx_connect_e2ee_min) that the maturity
3// gauge honestly graded STUB. "State of the art from the first byte up": this composes the sovereign AEAD
4// whose KAT (nx_chacha20_poly1305_test) matches RFC 8439 2.8.2 byte-exact AND rejects a single-bit tamper.
5//
6// SECURITY DISCIPLINE (the parts a real messenger needs, that the stand-in never had):
7// * AUTHENTICATED: every message carries a Poly1305 tag; open() REJECTS any tampered ciphertext/tag.
8// * NONCE = per-message counter (4 zero bytes + 8-byte LE seq). Never reused for a key -> no keystream
9// reuse. Nonce reuse is the cardinal AEAD failure; the counter makes it structural.
10// * SEQUENCE-BOUND: the message counter is the AAD, so a replayed/reordered message fails to open under a
11// different expected seq -> reorder/replay is detected, not just tampering.
12// * CONTENT-BLIND BY CONSTRUCTION: the server stores only {seq, ciphertext, tag} and holds NO key, so it
13// cannot read (no key) and cannot forge (AEAD auth). Key EXCHANGE (getting the conv key to both peers
14// without the server) is a SEPARATE rung (X25519/MLS) -- not claimed here.
15// PURE CORE (no main): key/nonce/buffers are caller-supplied. Consumers: nx_connect_seal_gate, and (once the
16// AEAD chain is reachable from _hdl_build) the live serve. license_tier: ORIGINAL
17import "nx_chacha20_poly1305.nx"
18
19const CSEAL_KEY: i64 = 32
20const CSEAL_NONCE: i64 = 12
21const CSEAL_TAG: i64 = 16
22const CSEAL_OK: i64 = 1 // == NX_AEAD_VERDICT_OK
23const CSEAL_REJECT: i64 = 0
24
25// build the 12-byte nonce for message sequence `seq`: [0,0,0,0][seq as 8 LE bytes]
26func cseal_nonce(seq: i64, out: *u8) -> i64 {
27 out[0]=0 as u8; out[1]=0 as u8; out[2]=0 as u8; out[3]=0 as u8
28 var v: i64 = seq
29 var i: i64 = 4
30 while i < 12 { out[i] = (v & 0xff) as u8; v = v / 256; i = i + 1 }
31 return 0
32}
33// the AAD is the 8-byte LE sequence -> binds message order into the tag.
34func cseal_aad(seq: i64, out: *u8) -> i64 {
35 var v: i64 = seq
36 var i: i64 = 0
37 while i < 8 { out[i] = (v & 0xff) as u8; v = v / 256; i = i + 1 }
38 return 0
39}
40
41// SEAL: encrypt+authenticate plaintext at sequence seq. Writes ct[0..plen) and tag[0..16). Returns CSEAL_OK.
42func cseal_seal(key: *u8, seq: i64, pt: *u8, plen: i64, ct: *u8, tag: *u8) -> i64 {
43 let nonce: *u8 = sys_mmap(16)
44 let aad: *u8 = sys_mmap(16)
45 cseal_nonce(seq, nonce)
46 cseal_aad(seq, aad)
47 let v: i64 = nx_chacha20_poly1305_encrypt(key, nonce, aad, 8, pt, plen, ct, tag)
48 if v == NX_AEAD_VERDICT_OK { return CSEAL_OK }
49 return CSEAL_REJECT
50}
51
52// OPEN: verify+decrypt. Returns CSEAL_OK and fills pt[0..clen) ONLY if the tag verifies for THIS seq;
53// otherwise CSEAL_REJECT and pt is not trusted (tamper, wrong key, or wrong sequence).
54func cseal_open(key: *u8, seq: i64, ct: *u8, clen: i64, tag: *u8, pt: *u8) -> i64 {
55 let nonce: *u8 = sys_mmap(16)
56 let aad: *u8 = sys_mmap(16)
57 cseal_nonce(seq, nonce)
58 cseal_aad(seq, aad)
59 let v: i64 = nx_chacha20_poly1305_decrypt(key, nonce, aad, 8, ct, clen, tag, pt)
60 if v == NX_AEAD_VERDICT_OK { return CSEAL_OK }
61 return CSEAL_REJECT
62}