code wiki / _hdl_build / nx_extvec_authentic_gate.nx

nx_extvec_authentic_gate.nx source

↩ module page · 351 lines · 16626 B

1// nx_extvec_authentic_gate.nx -- THE GUARD THAT WOULD HAVE CAUGHT IT ON DAY ONE, PLUS THE LEDGER. 2// 3// ★★★★★ THE LAW THIS GATE ENFORCES: 4// A DIGEST PIN PROVES INTEGRITY-SINCE-FETCH, NOT AUTHENTICITY-AT-SOURCE. 5// Every external-vector gate pins its source document to a SHA-256 computed in-process at the socket and 6// treats "PIN OK" as if it established that the bytes ARE the authority's document. It does not. A pin 7// proves only that the bytes have not changed since we hashed them. A corrupted, truncated, or -- as 8// actually happened on 2026-07-31 -- a fetch that stored the raw HTTP WIRE TRANSCRIPT instead of the 9// document, all pin exactly as cleanly as a good one. The pin is self-consistent BY CONSTRUCTION, so no 10// amount of re-checking the pin can ever reveal the fault. ★INDEPENDENCE NEEDS A SECOND STACK, NOT A 11// SECOND HASH. 12// 13// ⚠THE DEFECT THIS EXISTS TO PREVENT: knowledge/extvec/rfc2202.txt began 14// HTTP/1.1 200 OK\r\nDate: ...\r\nTransfer-Encoding: chunked\r\n ... \r\n\r\n2ea6\r\n 15// -- 707 bytes of response headers plus chunk framing -- because nx_vecfetch called the raw nx_https_get, 16// which BY CONTRACT returns the entire response, and never stripped headers or de-chunked. All 14 pinned 17// "authority documents" were transcripts. The shared decoder (nx_fetch_staged / fu_dechunk in 18// nx_fetch_unit.nx) already existed and was not adopted. ★THE ADOPTION GAP, NOT A MISSING PRIMITIVE. 19// 20// TWO INDEPENDENT CHECKS, AND THEY FAIL FOR DIFFERENT REASONS -- THAT IS THE POINT: 21// A. SHAPE -- is this file a document rather than a transcript? (content predicate, no external input) 22// B. LEDGER -- does an independent stack agree on this exact file's digest? (corroboration, external) 23// A catches the accident. B catches the substitution. A file can pass A and fail B (a DIFFERENT but 24// well-formed document swapped in), or pass B and fail A (both stacks fetched the same transcript). 25// ★A SINGLE CHECK THAT COULD HAVE CAUGHT BOTH WOULD HAVE CAUGHT NEITHER CLEANLY. 26// 27// ⚠HONEST LIMIT OF THE LEDGER, STATED SO NOBODY OVER-TRUSTS IT: CORROBORATION.tsv is a plain file this 28// agent can write. It raises the bar -- swapping a document now requires forging a matching "independent" 29// column, at which point that column is no longer independent and the forgery is recorded in-tree rather 30// than invisible -- but it is NOT a transparency log. The July-2026 answer to that is Sigstore/Fulcio/Rekor 31// or in-toto/DSSE: an append-only log held by a party we cannot edit. Nishi already has nx_coe_dsse / 32// nx_coe_intoto / nx_coe_witness and this lane has NOT adopted them. That is the next rung, not this one. 33// license_tier: ORIGINAL expect_exit: 0 34import "nx_syscalls.nx" 35import "nx_sha256_wasm.nx" 36import "nx_gate_verdict.nx" // D001 base class: gv_check/gv_verdict -- canonical verdict LAST + journal frame 37 38func w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 39func wb(b: *u8, n: i64) -> i64 { sys_write(1, b, n); return 0 } 40 41func nn(v: i64) -> i64 { 42 var m: i64 = v 43 if m < 0 { w("-" as *u8); m = 0 - m } 44 let t: *u8 = sys_mmap(32) 45 var k: i64 = 0 46 if m == 0 { t[0] = 48 as u8; k = 1 } 47 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 48 let b: *u8 = sys_mmap(32) 49 var j: i64 = 0 50 while j < k { b[j] = t[k - 1 - j]; j = j + 1 } 51 sys_write(1, b, k) 52 return 0 53} 54 55func hexnib(v: i64) -> i64 { if v < 10 { return 48 + v } return 87 + v } 56 57func starts(b: *u8, n: i64, at: i64, s: *u8) -> i64 { 58 var i: i64 = 0 59 while s[i] != (0 as u8) { 60 if at + i >= n { return 0 } 61 if b[at + i] != s[i] { return 0 } 62 i = i + 1 63 } 64 return 1 65} 66 67func findfrom(b: *u8, n: i64, s: *u8, from: i64) -> i64 { 68 var p: i64 = from 69 while p < n { if starts(b, n, p, s) == 1 { return p } p = p + 1 } 70 return 0 - 1 71} 72 73// Case-insensitive search bounded to the first `lim` bytes. Header names are case-insensitive per RFC 9110, 74// so a case-sensitive probe for "Transfer-Encoding" would miss "transfer-encoding" and pass a contaminated 75// file. ★A CHECK THAT ONLY CATCHES THE SPELLING YOU SAW IS NOT A CHECK. 76func lc(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c } 77func find_ci(b: *u8, n: i64, lim: i64, s: *u8) -> i64 { 78 var sl: i64 = 0 79 while s[sl] != (0 as u8) { sl = sl + 1 } 80 if sl == 0 { return 0 - 1 } 81 var top: i64 = lim 82 if top > n { top = n } 83 var i: i64 = 0 84 while i + sl <= top { 85 var j: i64 = 0 86 var ok: i64 = 1 87 while j < sl { if lc(b[i + j] as i64) != lc(s[j] as i64) { ok = 0; j = sl } else { j = j + 1 } } 88 if ok == 1 { return i } 89 i = i + 1 90 } 91 return 0 - 1 92} 93 94func count_byte(b: *u8, n: i64, v: i64) -> i64 { 95 var i: i64 = 0 96 var c: i64 = 0 97 while i < n { if (b[i] as i64) == v { c = c + 1 } i = i + 1 } 98 return c 99} 100 101func mkpath(name: *u8, out: *u8) -> i64 { 102 let pre: *u8 = "knowledge/extvec/" as *u8 103 var o: i64 = 0 104 var i: i64 = 0 105 while pre[i] != (0 as u8) { out[o] = pre[i]; o = o + 1; i = i + 1 } 106 i = 0 107 while name[i] != (0 as u8) { out[o] = name[i]; o = o + 1; i = i + 1 } 108 let suf: *u8 = ".txt" as *u8 109 i = 0 110 while suf[i] != (0 as u8) { out[o] = suf[i]; o = o + 1; i = i + 1 } 111 out[o] = 0 as u8 112 return o 113} 114 115// ---- CHECK A: SHAPE ---- 116const F_OK: i64 = 0 117const F_STATUSLINE: i64 = 1 118const F_HDR_TE: i64 = 2 119const F_HDR_CL: i64 = 3 120const F_HDR_BREAK: i64 = 4 121const F_CR: i64 = 5 122 123func transcript_fault(b: *u8, n: i64) -> i64 { 124 if n <= 0 { return F_STATUSLINE } 125 if starts(b, n, 0, "HTTP/" as *u8) == 1 { return F_STATUSLINE } 126 // Bounded to 2048 bytes: these words legitimately appear in the BODY of HTTP-related RFCs (RFC 9112 127 // defines chunked encoding and quotes these very strings), so an unbounded search would convict correct 128 // documents. ★BOUND THE PROBE TO THE REGION WHERE THE SIGNATURE IS DIAGNOSTIC, OR IT CONVICTS THE INNOCENT. 129 if find_ci(b, n, 2048, "transfer-encoding:" as *u8) >= 0 { return F_HDR_TE } 130 if find_ci(b, n, 2048, "content-length:" as *u8) >= 0 { return F_HDR_CL } 131 if find_ci(b, n, 2048, "\r\n\r\n" as *u8) >= 0 { return F_HDR_BREAK } 132 if count_byte(b, n, 13) > 0 { return F_CR } 133 return F_OK 134} 135 136func faultname(f: i64) -> i64 { 137 if f == F_STATUSLINE { w("HTTP status line at offset 0 -- STORED TRANSCRIPT, not document" as *u8) } 138 if f == F_HDR_TE { w("Transfer-Encoding header in preamble -- chunk framing interleaved" as *u8) } 139 if f == F_HDR_CL { w("Content-Length header in preamble -- response headers retained" as *u8) } 140 if f == F_HDR_BREAK { w("CRLFCRLF header/body break in preamble" as *u8) } 141 if f == F_CR { w("carriage returns present -- rfc-editor serves LF-only; bytes were re-framed" as *u8) } 142 if f == F_OK { w("clean" as *u8) } 143 return 0 144} 145 146// ---- CHECK B: LEDGER ---- 147const L_OK: i64 = 0 148const L_NOROW: i64 = 1 149const L_SOVMISS: i64 = 2 150const L_INDEPMISS: i64 = 3 151const L_NOTAGREED: i64 = 4 152const L_SHORT: i64 = 5 153 154// Row shape: name \t bytes \t sovereign_sha256 \t independent_sha256 \t verdict \t date 155// The row is located by "\n<name>\t" -- the TAB matters. Anchoring on the bare name would let "rfc321" 156// match inside "rfc3210". ★A PREFIX MATCH IS NOT A TOKEN MATCH (third time this workstream). 157func ledger_check(led: *u8, ln: i64, name: *u8, filedig: *u8) -> i64 { 158 if ln <= 0 { return L_NOROW } 159 let key: *u8 = sys_mmap(64) 160 var o: i64 = 0 161 key[o] = 10 as u8 162 o = 1 163 var i: i64 = 0 164 while name[i] != (0 as u8) { key[o] = name[i]; o = o + 1; i = i + 1 } 165 key[o] = 9 as u8 166 key[o + 1] = 0 as u8 167 let at: i64 = findfrom(led, ln, key, 0) 168 if at < 0 { return L_NOROW } 169 // advance past name and TAB, then past the bytes field and its TAB 170 var p: i64 = at + o + 1 171 var tabs: i64 = 0 172 while tabs < 1 { 173 if p >= ln { return L_SHORT } 174 if (led[p] as i64) == 9 { tabs = tabs + 1 } 175 p = p + 1 176 } 177 // p now at sovereign digest 178 if p + 64 > ln { return L_SHORT } 179 var k: i64 = 0 180 while k < 64 { if lc(led[p + k] as i64) != (filedig[k] as i64) { return L_SOVMISS } k = k + 1 } 181 p = p + 64 182 if p >= ln { return L_SHORT } 183 if (led[p] as i64) != 9 { return L_SHORT } 184 p = p + 1 185 // independent digest -- THE WHOLE POINT: it must equal the bytes we actually hold. 186 if p + 64 > ln { return L_SHORT } 187 k = 0 188 while k < 64 { if lc(led[p + k] as i64) != (filedig[k] as i64) { return L_INDEPMISS } k = k + 1 } 189 p = p + 64 190 if p >= ln { return L_SHORT } 191 if (led[p] as i64) != 9 { return L_SHORT } 192 p = p + 1 193 if starts(led, ln, p, "AGREED" as *u8) == 0 { return L_NOTAGREED } 194 return L_OK 195} 196 197func ledgername(f: i64) -> i64 { 198 if f == L_NOROW { w("NO LEDGER ROW -- document is UNCORROBORATED" as *u8) } 199 if f == L_SOVMISS { w("ledger sovereign digest does not match these bytes -- file changed since acquisition" as *u8) } 200 if f == L_INDEPMISS { w("INDEPENDENT digest does not match these bytes -- the second stack saw something else" as *u8) } 201 if f == L_NOTAGREED { w("ledger verdict is not AGREED" as *u8) } 202 if f == L_SHORT { w("ledger row malformed/truncated" as *u8) } 203 if f == L_OK { w("corroborated" as *u8) } 204 return 0 205} 206 207func hexdig(b: *u8, n: i64, out: *u8) -> i64 { 208 let ctx: *u8 = sys_mmap(1024) 209 let dg: *u8 = sys_mmap(64) 210 nx_sha256_one_shot(b, n, ctx, dg) 211 var i: i64 = 0 212 while i < 32 { 213 out[i * 2] = hexnib(((dg[i] as i64) / 16) & 15) as u8 214 out[i * 2 + 1] = hexnib((dg[i] as i64) & 15) as u8 215 i = i + 1 216 } 217 out[64] = 0 as u8 218 return 0 219} 220 221func main() -> i64 { 222 w("nx_extvec_authentic_gate -- are the pinned 'authority documents' actually documents, and corroborated?\n" as *u8) 223 w(" law: A DIGEST PIN PROVES INTEGRITY-SINCE-FETCH, NOT AUTHENTICITY-AT-SOURCE.\n\n" as *u8) 224 225 let pathbuf: *u8 = sys_mmap(512) 226 let lp: *i64 = sys_mmap(16) as *i64 227 let dhex: *u8 = sys_mmap(80) 228 229 // load the corroboration ledger once 230 let ctr: *i64 = gv_ctr() 231 let llp: *i64 = sys_mmap(16) as *i64 232 llp[0] = 0 233 let led: *u8 = sys_read_file("knowledge/extvec/CORROBORATION.tsv\x00" as *u8, llp) 234 // Ledger absence is a FAILING state, not a SKIP: the property under test IS "corroborated", and a 235 // missing ledger makes every document uncorroborated by definition. 236 if gv_check("T-LEDGER corroboration ledger present (knowledge/extvec/CORROBORATION.tsv)" as *u8, llp[0] > 0, ctr) == 0 { 237 return gv_verdict("EXTVEC-AUTHENTIC-GATE" as *u8, ctr, "" as *u8) 238 } 239 w(" ledger loaded: " as *u8); nn(llp[0]); w(" bytes\n\n" as *u8) 240 241 var clean: i64 = 0 242 var contam: i64 = 0 243 var missing: i64 = 0 244 var uncorro: i64 = 0 245 246 // Declared explicitly: a gate that discovers its own subjects can be silently starved to zero and still 247 // print GREEN. ★A GATE WITH NO DECLARED SUBJECT COUNT CANNOT TELL "ALL PASSED" FROM "NOTHING RAN". 248 let names: *i64 = sys_mmap(256) as *i64 249 names[0] = "rfc1321" as i64 250 names[1] = "rfc2202" as i64 251 names[2] = "rfc3174" as i64 252 names[3] = "rfc3602" as i64 253 names[4] = "rfc4231" as i64 254 names[5] = "rfc4648" as i64 255 names[6] = "rfc5869" as i64 256 names[7] = "rfc6234" as i64 257 names[8] = "rfc7693" as i64 258 names[9] = "rfc7748" as i64 259 names[10] = "rfc7914" as i64 260 names[11] = "rfc8032" as i64 261 names[12] = "rfc8439" as i64 262 names[13] = "rfc9106" as i64 263 // rfc6070 (PKCS#5 PBKDF2 test vectors) -- the FIRST document acquired through the REPAIRED pipeline: 264 // status-checked, envelope stripped (720B), de-chunked, hashed as a document, and corroborated against 265 // an independent stack BEFORE being written. Added here the moment it landed: ★A DOCUMENT THAT IS NOT 266 // IN THE SUBJECT LIST IS NOT GUARDED, AND AN UNGUARDED DOCUMENT SILENTLY DRIFTS. 267 names[14] = "rfc6070" as i64 268 // rfc4226 (HOTP) + rfc6238 (TOTP): present in knowledge/extvec/ since the 2FA sweep but never 269 // DECLARED here -- and an unguarded document silently drifts (this gate's own law above). Added 270 // 2026-08-17 alongside rfc6234/rfc9106 finally being fetched (declared but absent on disk). 271 names[15] = "rfc4226" as i64 272 names[16] = "rfc6238" as i64 273 let NDOC: i64 = 17 274 275 var i: i64 = 0 276 while i < NDOC { 277 let nm: *u8 = names[i] as *u8 278 mkpath(nm, pathbuf) 279 lp[0] = 0 280 let b: *u8 = sys_read_file(pathbuf, lp) 281 if lp[0] <= 0 { 282 w(" MISSING " as *u8); w(nm); w(" (file absent -- cannot grade)\n" as *u8) 283 missing = missing + 1 284 } else { 285 let f: i64 = transcript_fault(b, lp[0]) 286 hexdig(b, lp[0], dhex) 287 let lf: i64 = ledger_check(led, llp[0], nm, dhex) 288 if f != F_OK { 289 w(" CONTAM " as *u8); w(nm); w(" fault=" as *u8); faultname(f); w("\n" as *u8) 290 contam = contam + 1 291 } else { 292 if lf != L_OK { 293 w(" UNCORRO " as *u8); w(nm); w(" " as *u8); ledgername(lf); w("\n" as *u8) 294 uncorro = uncorro + 1 295 } else { 296 w(" OK " as *u8); w(nm); w(" bytes=" as *u8); nn(lp[0]) 297 w(" shape=document corroboration=AGREED\n" as *u8) 298 clean = clean + 1 299 } 300 } 301 } 302 i = i + 1 303 } 304 305 // ---- NON-VACUITY: BOTH checks must be shown able to FAIL, and able NOT to false-positive. ---- 306 // A guard never shown to reject anything is indistinguishable from a guard that rejects nothing. 307 w("\n -- non-vacuity: each check must discriminate, in both directions --\n" as *u8) 308 309 let bad: *u8 = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n2ea6\r\nNetwork Working Group\x00" as *u8 310 var bl: i64 = 0 311 while bad[bl] != (0 as u8) { bl = bl + 1 } 312 gv_check("neg-control-shape: synthetic wire transcript REJECTED (a blind predicate would accept it)" as *u8, transcript_fault(bad, bl) != F_OK, ctr) 313 314 let good: *u8 = "Network Working Group P. Cheng\nRequest for Comments: 2202\n\n test_case = 1\n\x00" as *u8 315 var gl: i64 = 0 316 while good[gl] != (0 as u8) { gl = gl + 1 } 317 gv_check("shape positive-control: synthetic document ACCEPTED, no false positive" as *u8, transcript_fault(good, gl) == F_OK, ctr) 318 319 // prose mention past the bound must not trip the preamble probes 320 let pad: *u8 = sys_mmap(8192) 321 var p2: i64 = 0 322 while p2 < 3000 { pad[p2] = 32 as u8; p2 = p2 + 1 } 323 let tail: *u8 = "Transfer-Encoding: chunked\x00" as *u8 324 var q: i64 = 0 325 while tail[q] != (0 as u8) { pad[3000 + q] = tail[q]; q = q + 1 } 326 gv_check("shape bound: body-legitimate 'Transfer-Encoding:' past the preamble NOT convicted" as *u8, transcript_fault(pad, 3000 + q) == F_OK, ctr) 327 328 // LEDGER must reject an unknown name, and must reject a KNOWN name whose bytes changed. 329 gv_check("neg-control-ledger: unknown document name REJECTED, no row" as *u8, ledger_check(led, llp[0], "rfc0000" as *u8, dhex) == L_NOROW, ctr) 330 331 let fake: *u8 = sys_mmap(80) 332 var z: i64 = 0 333 while z < 64 { fake[z] = 97 as u8; z = z + 1 } 334 fake[64] = 0 as u8 335 gv_check("neg-control-ledger: known name with WRONG bytes REJECTED (a decorative ledger accepts)" as *u8, ledger_check(led, llp[0], "rfc2202" as *u8, fake) == L_SOVMISS, ctr) 336 337 w("\n ref=CABF-MPIC+SIGSTORE-REKOR gate=nx_extvec_authentic_gate\n" as *u8) 338 w(" SCOPE: shape-check proves absence of ONE observed contamination class; ledger proves a second\n" as *u8) 339 w(" stack agreed on THESE EXACT BYTES. The ledger is agent-writable and is NOT a transparency\n" as *u8) 340 w(" log -- Sigstore/Rekor or in-toto/DSSE is the next rung, and is NOT claimed here.\n" as *u8) 341 w("nx_extvec_authentic_gate: docs=" as *u8); nn(NDOC) 342 w(" ok=" as *u8); nn(clean) 343 w(" CONTAMINATED=" as *u8); nn(contam) 344 w(" UNCORROBORATED=" as *u8); nn(uncorro) 345 w(" missing=" as *u8); nn(missing) 346 w("\n" as *u8) 347 gv_check("T-DOCS every declared subject document present on disk" as *u8, missing == 0, ctr) 348 gv_check("T-SHAPE zero pinned documents carrying wire-transcript contamination" as *u8, contam == 0, ctr) 349 gv_check("T-CORRO zero documents lacking two-stack corroboration" as *u8, uncorro == 0, ctr) 350 return gv_verdict("EXTVEC-AUTHENTIC-GATE" as *u8, ctr, "" as *u8) 351}