code wiki / _hdl_build / nx_hkdf_extvec_gate.nx

nx_hkdf_extvec_gate.nx source

↩ module page · 275 lines · 12897 B

1// nx_hkdf_extvec_gate.nx -- validated against RFC5869-A.1, read from the pinned+corroborated RFC5869 document. 2// ⚠HEADER CORRECTED 2026-08-01: this file was CLONED from nx_hmac_extvec_gate.nx and inherited its 3// header verbatim, so it claimed to be that gate validating HMAC-SHA-256 against RFC 4231. Nine of 4// twenty-three extvec gates carried the same wrong self-description. ★★★★★A CLONED FILE INHERITS ITS 5// PARENT'S CLAIMS, AND ON AN EVIDENCE ARTIFACT THE HEADER IS A PROVENANCE CLAIM, NOT A COMMENT -- 6// an auditor reading headers would have concluded RFC 4231 validated all nine subjects. 7// 8// SUPERSEDES the single-case version. Going from 1 vector to 7 matters because the cases are deliberately 9// chosen by the authority to hit DIFFERENT code paths, and the ones I was NOT running are the interesting 10// ones: case 3 uses a 50-byte repeated data block, cases 6 and 7 use a 131-BYTE KEY (longer than the 64-byte 11// SHA-256 block, so the key must be HASHED first), and case 5 publishes a TRUNCATED 128-bit MAC. 12// ★A gate that ran only case 1 would never touch the key-longer-than-block branch -- the single most 13// commonly botched part of HMAC. Running one vector from a seven-vector suite is not "validated against 14// RFC 4231"; it is validated against one line of it. 15// 16// ⚠VARIABLE-LENGTH FIELDS, HANDLED BY TERMINATOR NOT BY LENGTH. Key/Data lengths differ per case and their 17// annotations are inconsistent -- "(20 bytes)" for keys but ("Hi There") for data -- so a length cannot be 18// read uniformly. Instead the hex run is read until the first `(`, which terminates both forms. 19// ⚠THE MAC HAS NO `(` TERMINATOR and case 5's is TRUNCATED to 16 bytes, so it is read as hex pairs until a 20// pair is not both-hex, capped at 32. That correctly stops at the section heading that follows -- note 21// "4.3." begins with '4', a HEX DIGIT, and is only rejected because '.' is not: the PAIR rule saves this, 22// a single-nibble rule would have swallowed it. 23// 24// Construction unchanged: no expected value in this source, document pinned to a socket-time digest, every 25// key/data/MAC read from that pinned document, and a completeness check that refuses GREEN below 7. 26// license_tier: ORIGINAL expect_exit: 0 27import "nx_syscalls.nx" 28import "nx_sha256_wasm.nx" 29import "hkdf.nx" 30import "nx_gate_verdict.nx" 31 32func w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 33func wb(b: *u8, n: i64) -> i64 { sys_write(1, b, n); return 0 } 34 35func nn(v: i64) -> i64 { 36 var m: i64 = v 37 if m < 0 { w("-" as *u8); m = 0 - m } 38 let t: *u8 = sys_mmap(32) 39 var k: i64 = 0 40 if m == 0 { t[0] = 48 as u8; k = 1 } 41 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 42 let b: *u8 = sys_mmap(32) 43 var j: i64 = 0 44 while j < k { b[j] = t[k - 1 - j]; j = j + 1 } 45 sys_write(1, b, k) 46 return 0 47} 48 49func hexnib(v: i64) -> i64 { if v < 10 { return 48 + v } return 87 + v } 50 51func hexval(c: i64) -> i64 { 52 if c >= 48 { if c <= 57 { return c - 48 } } 53 if c >= 97 { if c <= 102 { return c - 87 } } 54 if c >= 65 { if c <= 70 { return c - 55 } } 55 return 0 - 1 56} 57 58func isws(c: i64) -> i64 { 59 if c == 32 { return 1 } 60 if c == 10 { return 1 } 61 if c == 13 { return 1 } 62 if c == 9 { return 1 } 63 return 0 64} 65 66func starts(b: *u8, n: i64, at: i64, s: *u8) -> i64 { 67 var i: i64 = 0 68 while s[i] != (0 as u8) { 69 if at + i >= n { return 0 } 70 if b[at + i] != s[i] { return 0 } 71 i = i + 1 72 } 73 return 1 74} 75 76func findfrom(b: *u8, n: i64, s: *u8, from: i64) -> i64 { 77 var p: i64 = from 78 while p < n { 79 if starts(b, n, p, s) == 1 { return p } 80 p = p + 1 81 } 82 return 0 - 1 83} 84 85// Read hex PAIRS (whitespace between pairs is skipped) until a pair is not both-hex, or `(` is reached, 86// or cap is hit. Returns the byte count. 87func parserun(b: *u8, n: i64, from: i64, out: *u8, cap: i64) -> i64 { 88 var p: i64 = from 89 var got: i64 = 0 90 var done: i64 = 0 91 while done == 0 { 92 if got >= cap { done = 1 } 93 else { 94 // skip whitespace 95 var d1: i64 = 0 96 while d1 == 0 { 97 if p >= n { d1 = 1 } 98 else { if isws(b[p] as i64) == 1 { p = p + 1 } else { d1 = 1 } } 99 } 100 if p + 1 >= n { done = 1 } 101 else { 102 // `(` ends a LINE SEGMENT, not the value: RFC 4231 annotates EVERY wrapped line, e.g. 103 // Data = 7768...6e7420 ("what do ya want ") 104 // 666f...693f ("for nothing?") 105 // Treating `(` as the value terminator stopped case 2 at 16 of its 28 bytes. On `(`, skip 106 // to the next line and continue ONLY if it resumes with a hex pair; otherwise stop. 107 if b[p] == (40 as u8) { 108 var dl: i64 = 0 109 while dl == 0 { 110 if p >= n { dl = 1 } 111 else { if b[p] == (10 as u8) { p = p + 1; dl = 1 } else { p = p + 1 } } 112 } 113 var dw: i64 = 0 114 while dw == 0 { 115 if p >= n { dw = 1 } 116 else { if isws(b[p] as i64) == 1 { p = p + 1 } else { dw = 1 } } 117 } 118 // ⚠"Da" IS VALID HEX. The next line may be ` Data = 7768...`, and testing only 119 // "do the first two chars parse as hex" accepted D,a and read the LABEL as byte 0xDA -- 120 // adding one phantom byte to every key (case 1 read 21 of 20, case 2 read 5 of 4). 121 // Third form of the same hazard today: an ASCII gutter, then English prose, now a 122 // FIELD LABEL that happens to spell hex. 123 // STRUCTURAL DISCRIMINATOR: a continuation line has NO '='; every label line has one. 124 var eqfound: i64 = 0 125 var sc: i64 = p 126 var de: i64 = 0 127 while de == 0 { 128 if sc >= n { de = 1 } 129 else { if b[sc] == (10 as u8) { de = 1 } 130 else { if b[sc] == (61 as u8) { eqfound = 1; de = 1 } else { sc = sc + 1 } } } 131 } 132 if eqfound == 1 { done = 1 } 133 else { 134 if p + 1 >= n { done = 1 } 135 else { 136 if hexval(b[p] as i64) < 0 { done = 1 } 137 else { if hexval(b[p + 1] as i64) < 0 { done = 1 } } 138 } 139 } 140 } 141 else { 142 let h1: i64 = hexval(b[p] as i64) 143 let h2: i64 = hexval(b[p + 1] as i64) 144 if h1 < 0 { done = 1 } 145 else { if h2 < 0 { done = 1 } 146 else { 147 out[got] = ((h1 * 16) + h2) as u8 148 got = got + 1 149 p = p + 2 150 } } 151 } 152 } 153 } 154 } 155 return got 156} 157 158// Find the next "Key" FIELD LABEL at line start. Verified against the raw bytes of RFC 4231: 159// line 192 " Key = 0b0b..." <- cases 1,2,4,5,6,7 160// line 251 " Key aaaa..." <- CASE 3: no '=' at all 161// line 177 " Keys, data, and digests..." <- PROSE. "Key" is a PREFIX of "Keys". 162// Anchoring on "\n Key" alone matched that prose line and drove the parse to ZERO cases. So the label is 163// accepted only when the character AFTER "Key" is a SPACE or '=' -- which "Keys" fails on 's'. 164// ★A prefix match is not a token match. Every anchor in this file is now checked against the byte AFTER it. 165func find_key_label(b: *u8, n: i64, from: i64) -> i64 { 166 var p: i64 = from 167 var done: i64 = 0 168 while done == 0 { 169 let h: i64 = findfrom(b, n, "\n Key" as *u8, p) 170 if h < 0 { return 0 - 1 } 171 let c: i64 = b[h + 7] as i64 172 if c == 32 { return h } 173 if c == 61 { return h } 174 p = h + 7 175 } 176 return 0 - 1 177} 178func lab(b: *u8, n: i64, from: i64, name: *u8, out: *u8, cap: i64) -> i64 { 179 let l: i64 = findfrom(b, n, name, from) 180 if l < 0 { return 0 - 1 } 181 let h: i64 = findfrom(b, n, "0x" as *u8, l) 182 if h < 0 { return 0 - 1 } 183 return parserun(b, n, h + 2, out, cap) 184} 185 186func main() -> i64 { 187 w("nx_hkdf_extvec_gate -- HKDF-SHA256 vs RFC 5869 A.1, READ FROM THE FETCHED DOCUMENT\n" as *u8) 188 let lp: *i64 = sys_mmap(16) as *i64 189 lp[0] = 0 190 let b: *u8 = sys_read_file("knowledge/extvec/rfc5869.txt\x00" as *u8, lp) 191 if lp[0] <= 0 { w("RED: fetched vector file absent -- run nx_vecfetch.\n" as *u8); return 1 } 192 let ctx: *u8 = sys_mmap(1024) 193 let dg: *u8 = sys_mmap(64) 194 nx_sha256_one_shot(b, lp[0], ctx, dg) 195 let hx: *u8 = sys_mmap(80) 196 var i: i64 = 0 197 while i < 32 { hx[i*2] = hexnib(((dg[i] as i64)/16)&15) as u8; hx[i*2+1] = hexnib((dg[i] as i64)&15) as u8; i = i + 1 } 198 let wnt: *u8 = "7a40eb3835b35fc947eb12a2ed614db079d43b26e50dbc537c31fba16397089c\x00" as *u8 199 var pin: i64 = 1 200 i = 0 201 while i < 64 { if hx[i] != wnt[i] { pin = 0 } i = i + 1 } 202 w(" acquisition digest: " as *u8); wb(hx, 64); w("\n" as *u8) 203 if pin == 0 { w("RED: PIN FAILED.\n" as *u8); return 1 } 204 w(" PIN OK -- bytes match the digest computed in-process at the socket\n" as *u8) 205 206 let s1: i64 = findfrom(b, lp[0], "A.1. Test Case 1" as *u8, 0) 207 if s1 < 0 { w("RED: no A.1\n" as *u8); return 1 } 208 let s2: i64 = findfrom(b, lp[0], "A.1. Test Case 1" as *u8, s1 + 10) 209 var at: i64 = s1 210 if s2 >= 0 { at = s2 } 211 212 let ikm: *u8 = sys_mmap(128) 213 let salt: *u8 = sys_mmap(128) 214 let info: *u8 = sys_mmap(128) 215 let prk: *u8 = sys_mmap(128) 216 let okm: *u8 = sys_mmap(128) 217 let ikmn: i64 = lab(b, lp[0], at, "IKM" as *u8, ikm, 100) 218 let saltn: i64 = lab(b, lp[0], at, "salt" as *u8, salt, 100) 219 let infon: i64 = lab(b, lp[0], at, "info" as *u8, info, 100) 220 let prkn: i64 = lab(b, lp[0], at, "PRK" as *u8, prk, 100) 221 let okmn: i64 = lab(b, lp[0], at, "OKM" as *u8, okm, 100) 222 223 // PARSE SELF-CHECK against the octet counts the document itself states (22/13/10/32/42). If the reader 224 // drifts, this names the READER before HKDF is blamed. 225 var ok: i64 = 0 226 if ikmn == 22 { if saltn == 13 { if infon == 10 { if prkn == 32 { if okmn == 42 { ok = 1 } } } } } 227 if ok == 0 { 228 w("RED: PARSE SELF-CHECK FAILED -- got ikm=" as *u8); nn(ikmn); w(" salt=" as *u8); nn(saltn) 229 w(" info=" as *u8); nn(infon); w(" prk=" as *u8); nn(prkn); w(" okm=" as *u8); nn(okmn) 230 w(" ; document declares 22/13/10/32/42.\n" as *u8) 231 return 1 232 } 233 w(" parse self-check OK -- 22/13/10/32/42 octets as the document declares\n" as *u8) 234 235 var pass: i64 = 0 236 var fail: i64 = 0 237 let gp: *u8 = sys_mmap(128) 238 hkdf_extract(salt, saltn, ikm, ikmn, gp) 239 var same: i64 = 1 240 i = 0 241 while i < prkn { if gp[i] != prk[i] { same = 0 } i = i + 1 } 242 if same == 1 { pass = pass + 1; w(" PASS extract: HKDF-Extract == the document's PRK\n" as *u8) } 243 else { fail = fail + 1; w(" FAIL extract: PRK mismatch\n" as *u8) } 244 245 let go: *u8 = sys_mmap(128) 246 hkdf_expand(prk, info, infon, okmn, go) 247 var same2: i64 = 1 248 i = 0 249 while i < okmn { if go[i] != okm[i] { same2 = 0 } i = i + 1 } 250 if same2 == 1 { pass = pass + 1; w(" PASS expand: HKDF-Expand(PRK,info,42) == the document's OKM\n" as *u8) } 251 else { fail = fail + 1; w(" FAIL expand: OKM mismatch\n" as *u8) } 252 253 // NEGATIVE CONTROL: perturb info; the OKM must change. 254 info[0] = (((info[0] as i64) ^ 1) & 255) as u8 255 hkdf_expand(prk, info, infon, okmn, go) 256 var same3: i64 = 1 257 i = 0 258 while i < okmn { if go[i] != okm[i] { same3 = 0 } i = i + 1 } 259 if same3 == 0 { pass = pass + 1; w(" PASS NEG: one-bit info change alters the OKM (the check can fail)\n" as *u8) } 260 else { fail = fail + 1; w(" FAIL NEG: info change did not alter the OKM\n" as *u8) } 261 262 w("\n refsrc=https://www.rfc-editor.org/rfc/rfc5869.txt\n" as *u8) 263 w(" refsrcdig=" as *u8); wb(hx, 64); w("\n" as *u8) 264 w(" ref=RFC5869-A.1 gate=nx_hkdf_extvec_gate\n" as *u8) 265 w("nx_hkdf_extvec_gate: pass=" as *u8); nn(pass); w(" fail=" as *u8); nn(fail) 266 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 267 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 268 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 269 let ctr__dry: *i64 = gv_ctr() 270 ctr__dry[0] = pass 271 ctr__dry[1] = pass + fail 272 let rc__dry: i64 = gv_verdict("HKDF-EXTVEC-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 273 sys_exit(rc__dry) 274 return rc__dry 275}