code wiki / _hdl_build / nx_hmacmd5alt_extvec_gate.nx

nx_hmacmd5alt_extvec_gate.nx source

↩ module page · 520 lines · 29122 B

1// nx_hmacmd5alt_extvec_gate.nx -- THE ADJUDICATOR FOR THE MD5 DUPLICATE PAIR. 2// 3// Identical to nx_hmacmd5_extvec_gate except ONE import: 4// nx_hmacmd5_extvec_gate -> nx_hmac_md5.nx -> nx_md5_canonical.nx (core VALIDATED 7/7 vs RFC 1321) 5// nx_hmacmd5alt_extvec_gate -> hmac_md5.nx -> md5.nx (core NEVER GRADED) 6// Both export an identical hmac_md5 symbol, so the ONLY difference is which FILE was bound. 7// 8// THIS IS THE SAME EXPERIMENT THAT CAUGHT sha1.nx. That defect was found because an un-prefixed duplicate 9// had never been graded against an authority while its nx_-prefixed twin had. The same shape exists here 10// (and for hkdf_sha1, hex_encode, and syscalls), so the question is empirical, not rhetorical. 11// * A DUPLICATE PAIR IS A COIN FLIP UNTIL AN AUTHORITY ADJUDICATES IT -- "it's probably fine" is what let 12// a wrong SHA-1 core sit under HOTP, TOTP, PBKDF2 and UUIDv5. 13// GREEN here EXONERATES md5.nx; RED convicts it and makes the un-prefixed MD5 family a second sev-8. 14//// nx_hmacmd5alt_extvec_gate.nx -- validated against RFC2202-HMAC-MD5, read from the pinned+corroborated RFC2202 document. 15// ⚠HEADER CORRECTED 2026-08-01: this file was CLONED from nx_hmac_extvec_gate.nx and inherited its 16// header verbatim, so it claimed to be that gate validating HMAC-SHA-256 against RFC 4231. Nine of 17// twenty-three extvec gates carried the same wrong self-description. ★★★★★A CLONED FILE INHERITS ITS 18// PARENT'S CLAIMS, AND ON AN EVIDENCE ARTIFACT THE HEADER IS A PROVENANCE CLAIM, NOT A COMMENT -- 19// an auditor reading headers would have concluded RFC 4231 validated all nine subjects. 20// 21// SUPERSEDES the single-case version. Going from 1 vector to 7 matters because the cases are deliberately 22// chosen by the authority to hit DIFFERENT code paths, and the ones I was NOT running are the interesting 23// ones: case 3 uses a 50-byte repeated data block, cases 6 and 7 use a 131-BYTE KEY (longer than the 64-byte 24// SHA-256 block, so the key must be HASHED first), and case 5 publishes a TRUNCATED 128-bit MAC. 25// ★A gate that ran only case 1 would never touch the key-longer-than-block branch -- the single most 26// commonly botched part of HMAC. Running one vector from a seven-vector suite is not "validated against 27// RFC 4231"; it is validated against one line of it. 28// 29// ⚠VARIABLE-LENGTH FIELDS, HANDLED BY TERMINATOR NOT BY LENGTH. Key/Data lengths differ per case and their 30// annotations are inconsistent -- "(20 bytes)" for keys but ("Hi There") for data -- so a length cannot be 31// read uniformly. Instead the hex run is read until the first `(`, which terminates both forms. 32// ⚠THE MAC HAS NO `(` TERMINATOR and case 5's is TRUNCATED to 16 bytes, so it is read as hex pairs until a 33// pair is not both-hex, capped at 32. That correctly stops at the section heading that follows -- note 34// "4.3." begins with '4', a HEX DIGIT, and is only rejected because '.' is not: the PAIR rule saves this, 35// a single-nibble rule would have swallowed it. 36// 37// Construction unchanged: no expected value in this source, document pinned to a socket-time digest, every 38// key/data/MAC read from that pinned document, and a completeness check that refuses GREEN below 7. 39// license_tier: ORIGINAL expect_exit: 0 40import "nx_syscalls.nx" 41import "nx_sha256_wasm.nx" 42import "hmac_md5.nx" 43import "nx_gate_verdict.nx" 44 45func w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 46func wb(b: *u8, n: i64) -> i64 { sys_write(1, b, n); return 0 } 47 48func nn(v: i64) -> i64 { 49 var m: i64 = v 50 if m < 0 { w("-" as *u8); m = 0 - m } 51 let t: *u8 = sys_mmap(32) 52 var k: i64 = 0 53 if m == 0 { t[0] = 48 as u8; k = 1 } 54 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 55 let b: *u8 = sys_mmap(32) 56 var j: i64 = 0 57 while j < k { b[j] = t[k - 1 - j]; j = j + 1 } 58 sys_write(1, b, k) 59 return 0 60} 61 62func hexnib(v: i64) -> i64 { if v < 10 { return 48 + v } return 87 + v } 63 64func hexval(c: i64) -> i64 { 65 if c >= 48 { if c <= 57 { return c - 48 } } 66 if c >= 97 { if c <= 102 { return c - 87 } } 67 if c >= 65 { if c <= 70 { return c - 55 } } 68 return 0 - 1 69} 70 71func isws(c: i64) -> i64 { 72 if c == 32 { return 1 } 73 if c == 10 { return 1 } 74 if c == 13 { return 1 } 75 if c == 9 { return 1 } 76 return 0 77} 78 79func starts(b: *u8, n: i64, at: i64, s: *u8) -> i64 { 80 var i: i64 = 0 81 while s[i] != (0 as u8) { 82 if at + i >= n { return 0 } 83 if b[at + i] != s[i] { return 0 } 84 i = i + 1 85 } 86 return 1 87} 88 89func findfrom(b: *u8, n: i64, s: *u8, from: i64) -> i64 { 90 var p: i64 = from 91 while p < n { 92 if starts(b, n, p, s) == 1 { return p } 93 p = p + 1 94 } 95 return 0 - 1 96} 97 98// Read hex PAIRS (whitespace between pairs is skipped) until a pair is not both-hex, or `(` is reached, 99// or cap is hit. Returns the byte count. 100func parserun(b: *u8, n: i64, from: i64, out: *u8, cap: i64) -> i64 { 101 var p: i64 = from 102 var got: i64 = 0 103 var done: i64 = 0 104 while done == 0 { 105 if got >= cap { done = 1 } 106 else { 107 // skip whitespace, REMEMBERING whether the skip crossed a line boundary 108 var d1: i64 = 0 109 var crossed: i64 = 0 110 while d1 == 0 { 111 if p >= n { d1 = 1 } 112 else { if isws(b[p] as i64) == 1 { if b[p] == (10 as u8) { crossed = 1 } p = p + 1 } else { d1 = 1 } } 113 } 114 // ★★★★★ THE HAZARD THIS FILE ALREADY NAMED, IN THE ONE PATH THE FIX NEVER COVERED. 115 // "Da" IS VALID HEX. The discriminator -- a continuation line has NO '=', a label line always 116 // does -- was written above for the `(`-ANNOTATED branch ONLY. A `digest =` value carries no 117 // `(` annotation, so it fell through to the plain hex-pair path, ran straight past end-of-line, 118 // and read the "da" of "data_len =" as byte 0xDA: 21 bytes returned for a 20-byte digest. 119 // MEASURED, not inferred: 120 // expected = e8e9…1a91 DA computed = e8e9…1a91 00 <- first 20 bytes IDENTICAL 121 // It bit exactly ONE case because that block is the only one followed by a `data_len` line; the 122 // duplicate case 7 is followed by the page footer ("Cheng & Glenn" -- 'h' is not hex), which is 123 // why two cases with IDENTICAL inputs disagreed and looked like a crypto or pairing fault. 124 // ★A FIX APPLIED TO ONE BRANCH OF A TWO-BRANCH READER IS HALF A FIX -- AND THE HALF YOU SKIPPED 125 // WILL IMPERSONATE A BUG IN THE SUBJECT. 126 // Terminating by setting p = n reuses the existing bounds check instead of adding a branch: 127 // after four failed hypotheses here, the smallest possible edit is the safest one. 128 if crossed == 1 { 129 var eq2: i64 = 0 130 var sc2: i64 = p 131 var de2: i64 = 0 132 while de2 == 0 { 133 if sc2 >= n { de2 = 1 } 134 else { if b[sc2] == (10 as u8) { de2 = 1 } 135 else { if b[sc2] == (61 as u8) { eq2 = 1; de2 = 1 } else { sc2 = sc2 + 1 } } } 136 } 137 if eq2 == 1 { p = n } 138 } 139 if p + 1 >= n { done = 1 } 140 else { 141 // `(` ends a LINE SEGMENT, not the value: RFC 4231 annotates EVERY wrapped line, e.g. 142 // Data = 7768...6e7420 ("what do ya want ") 143 // 666f...693f ("for nothing?") 144 // Treating `(` as the value terminator stopped case 2 at 16 of its 28 bytes. On `(`, skip 145 // to the next line and continue ONLY if it resumes with a hex pair; otherwise stop. 146 if b[p] == (40 as u8) { 147 var dl: i64 = 0 148 while dl == 0 { 149 if p >= n { dl = 1 } 150 else { if b[p] == (10 as u8) { p = p + 1; dl = 1 } else { p = p + 1 } } 151 } 152 var dw: i64 = 0 153 while dw == 0 { 154 if p >= n { dw = 1 } 155 else { if isws(b[p] as i64) == 1 { p = p + 1 } else { dw = 1 } } 156 } 157 // ⚠"Da" IS VALID HEX. The next line may be ` Data = 7768...`, and testing only 158 // "do the first two chars parse as hex" accepted D,a and read the LABEL as byte 0xDA -- 159 // adding one phantom byte to every key (case 1 read 21 of 20, case 2 read 5 of 4). 160 // Third form of the same hazard today: an ASCII gutter, then English prose, now a 161 // FIELD LABEL that happens to spell hex. 162 // STRUCTURAL DISCRIMINATOR: a continuation line has NO '='; every label line has one. 163 var eqfound: i64 = 0 164 var sc: i64 = p 165 var de: i64 = 0 166 while de == 0 { 167 if sc >= n { de = 1 } 168 else { if b[sc] == (10 as u8) { de = 1 } 169 else { if b[sc] == (61 as u8) { eqfound = 1; de = 1 } else { sc = sc + 1 } } } 170 } 171 if eqfound == 1 { done = 1 } 172 else { 173 if p + 1 >= n { done = 1 } 174 else { 175 if hexval(b[p] as i64) < 0 { done = 1 } 176 else { if hexval(b[p + 1] as i64) < 0 { done = 1 } } 177 } 178 } 179 } 180 else { 181 let h1: i64 = hexval(b[p] as i64) 182 let h2: i64 = hexval(b[p + 1] as i64) 183 if h1 < 0 { done = 1 } 184 else { if h2 < 0 { done = 1 } 185 else { 186 out[got] = ((h1 * 16) + h2) as u8 187 got = got + 1 188 p = p + 2 189 } } 190 } 191 } 192 } 193 } 194 return got 195} 196 197// Find the next "Key" FIELD LABEL at line start. Verified against the raw bytes of RFC 4231: 198// line 192 " Key = 0b0b..." <- cases 1,2,4,5,6,7 199// line 251 " Key aaaa..." <- CASE 3: no '=' at all 200// line 177 " Keys, data, and digests..." <- PROSE. "Key" is a PREFIX of "Keys". 201// Anchoring on "\n Key" alone matched that prose line and drove the parse to ZERO cases. So the label is 202// accepted only when the character AFTER "Key" is a SPACE or '=' -- which "Keys" fails on 's'. 203// ★A prefix match is not a token match. Every anchor in this file is now checked against the byte AFTER it. 204func find_key_label(b: *u8, n: i64, from: i64) -> i64 { 205 var p: i64 = from 206 var done: i64 = 0 207 while done == 0 { 208 let h: i64 = findfrom(b, n, "\n Key" as *u8, p) 209 if h < 0 { return 0 - 1 } 210 let c: i64 = b[h + 7] as i64 211 if c == 32 { return h } 212 if c == 61 { return h } 213 p = h + 7 214 } 215 return 0 - 1 216} 217func readdec(b: *u8, n: i64, from: i64) -> i64 { 218 var p: i64 = from 219 var v: i64 = 0 220 var got: i64 = 0 221 var done: i64 = 0 222 while done == 0 { 223 if p >= n { done = 1 } 224 else { 225 let c: i64 = b[p] as i64 226 if c == 32 { if got == 1 { done = 1 } else { p = p + 1 } } 227 else { if c >= 48 { if c <= 57 { v = (v*10)+(c-48); got = 1; p = p + 1 } else { done = 1 } } else { done = 1 } } 228 } 229 } 230 if got == 0 { return 0 - 1 } 231 return v 232} 233 234// A field is EITHER 0x-hex OR a quoted ASCII string. RFC 2202 uses both for `data` across its cases 235// (0xdd... for the repeated-byte cases, "Hi There" for the readable ones), so the reader must dispatch on 236// the first non-space character rather than assume one form. 237func readdec_skipeq(b: *u8, n: i64, from: i64) -> i64 { 238 var p: i64 = from 239 var d: i64 = 0 240 while d == 0 { 241 if p >= n { d = 1 } 242 else { if b[p] == (32 as u8) { p = p + 1 } else { if b[p] == (61 as u8) { p = p + 1 } else { d = 1 } } } 243 } 244 return readdec(b, n, p) 245} 246 247func readfield(b: *u8, n: i64, from: i64, out: *u8, cap: i64) -> i64 { 248 var p: i64 = from 249 var d: i64 = 0 250 while d == 0 { 251 if p >= n { d = 1 } 252 else { if b[p] == (32 as u8) { p = p + 1 } else { d = 1 } } 253 } 254 if p >= n { return 0 - 1 } 255 if b[p] == (34 as u8) { 256 // ⚠A WRAPPED QUOTED STRING: the newline REPLACES a space, it does not delete one. 257 // data = "Test Using Larger Than Block-Size Key and Larger 258 // Than One Block-Size Data" 259 // Copying raw gave 89 (47 + newline + 16 indent + 25); dropping the wrap entirely gives 72; the 260 // document declares 73. So a newline plus its following indentation collapses to EXACTLY ONE SPACE. 261 // ★Verified against the document's own data_len, not assumed -- the declared length is what 262 // distinguishes "delete the wrap" from "replace the wrap", and those differ by one byte. 263 var k: i64 = 0 264 p = p + 1 265 while p < n { 266 if b[p] == (34 as u8) { return k } 267 if k >= cap { return 0 - 1 } 268 if b[p] == (10 as u8) { 269 out[k] = 32 as u8 270 k = k + 1 271 p = p + 1 272 var ds: i64 = 0 273 while ds == 0 { 274 if p >= n { ds = 1 } 275 else { if b[p] == (32 as u8) { p = p + 1 } else { if b[p] == (13 as u8) { p = p + 1 } else { ds = 1 } } } 276 } 277 } else { 278 if b[p] == (13 as u8) { p = p + 1 } 279 else { 280 out[k] = b[p] 281 k = k + 1 282 p = p + 1 283 } 284 } 285 } 286 return 0 - 1 287 } 288 if p + 1 < n { if b[p] == (48 as u8) { if b[p+1] == (120 as u8) { 289 let got: i64 = parserun(b, n, p + 2, out, cap) 290 // ⚠RFC 2202 EXPRESSES BULK DATA AS PROSE: `data = 0xdd repeated 50 times`. That is ONE hex byte 291 // followed by a repetition count in ENGLISH. A literal hex reader returns 1 byte, and the 292 // document-declared data_len=50 is what caught it -- the self-check named the READER instead of 293 // letting HMAC be blamed for a 1-byte input. 294 // ★A value can be expressed as a PROGRAM ("repeat this"), not just as data. Tenth notation form. 295 if got == 1 { 296 let rp: i64 = findfrom(b, n, "repeated" as *u8, p) 297 if rp >= 0 { if rp < p + 40 { 298 let cnt: i64 = readdec(b, n, rp + 8) 299 if cnt > 1 { if cnt <= cap { 300 let fill: i64 = out[0] as i64 301 var q: i64 = 0 302 while q < cnt { out[q] = fill as u8; q = q + 1 } 303 return cnt 304 } } 305 } } 306 } 307 return got 308 } } } 309 return 0 - 1 310} 311 312// HMAC-MD5 IS TAKEN FROM THE LIBRARY (nx_hmac_md5.nx), NOT REBUILT HERE -- unlike the SHA-1 gate this 313// RFC 2104: HMAC(K,m) = H((K' xor opad) || H((K' xor ipad) || m)), K' = K padded to 64 (hashed first if 314// longer). This shares ONLY the hash core with hmac_sha1 -- and that core is independently PROVEN correct 315// against RFC 3174. So if THIS matches the published digest and hmac_sha1 does not, the wrapper is at 316// fault; if BOTH miss, the reader is. ★A differential is only worth running when its shared set excludes 317// the suspect -- round 53's did not, this one does. 318// print one byte as two lowercase hex chars -- for the measured failure dump. 319func ph(v: i64) -> i64 { 320 let t: *u8 = sys_mmap(8) 321 t[0] = hexnib((v / 16) & 15) as u8 322 t[1] = hexnib(v & 15) as u8 323 sys_write(1, t, 2) 324 return 0 325} 326 327// RFC 2202 section 2 publishes SEVEN HMAC-MD5 vectors in the SAME document already pinned and 328// corroborated for the SHA-1 section -- coverage that needed no new acquisition and had never been 329// graded. The previous gate over this file counted them as `skipped_md5` and moved on. 330// * UNGRADED VECTORS INSIDE AN ALREADY-TRUSTED DOCUMENT ARE THE CHEAPEST COVERAGE THERE IS, AND THE 331// EASIEST TO MISS -- the document was "done", so nobody looked at the other half of it. 332// 333// Binds nx_hmac_md5.nx -> nx_md5_canonical.nx, the core ALREADY validated 7/7 vs RFC 1321 by 334// nx_md5_extvec_gate. The sibling gate binds hmac_md5.nx -> md5.nx, whose core has NEVER been graded -- 335// exactly the seam on which sha1.nx was caught computing wrong digests. 336// * NAME THE FILE YOU BOUND: both files export an identical `hmac_md5` symbol. 337func ref_hmac_md5(key: *u8, klen: i64, msg: *u8, mlen: i64, out: *u8) -> i64 { 338 return hmac_md5(key, klen, msg, mlen, out) 339} 340func main() -> i64 { 341 w("nx_hmacmd5alt_extvec_gate -- HMAC-SHA-1 (RFC 2104 construction over nx_sha1.nx) vs RFC 2202, READ FROM THE FETCHED DOCUMENT\n" as *u8) 342 let lp: *i64 = sys_mmap(16) as *i64 343 lp[0] = 0 344 let b: *u8 = sys_read_file("knowledge/extvec/rfc2202.txt\x00" as *u8, lp) 345 if lp[0] <= 0 { w("RED: fetched vector file absent.\n" as *u8); return 1 } 346 let ctx: *u8 = sys_mmap(1024) 347 let dg: *u8 = sys_mmap(64) 348 nx_sha256_one_shot(b, lp[0], ctx, dg) 349 let hx: *u8 = sys_mmap(80) 350 var i: i64 = 0 351 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 } 352 let wnt: *u8 = "c19effeca47e801be304460da3b2fb05f595e2309abceb050e40e024868fe483\x00" as *u8 353 var pin: i64 = 1 354 i = 0 355 while i < 64 { if hx[i] != wnt[i] { pin = 0 } i = i + 1 } 356 w(" acquisition digest: " as *u8); wb(hx, 64); w("\n" as *u8) 357 if pin == 0 { w("RED: PIN FAILED.\n" as *u8); return 1 } 358 w(" PIN OK -- bytes match the digest computed in-process at the socket\n" as *u8) 359 360 let key: *u8 = sys_mmap(512) 361 let data: *u8 = sys_mmap(512) 362 let exp: *u8 = sys_mmap(128) 363 let got: *u8 = sys_mmap(128) 364 365 var pass: i64 = 0 366 var fail: i64 = 0 367 var seen: i64 = 0 368 var skipped: i64 = 0 369 // ANCHOR ON THE SECTION, NOT ON A DIGEST-LENGTH GUESS. RFC 2202 has 7 HMAC-MD5 cases (sec 2) and 370 // NINE HMAC-SHA-1 markers (sec 3 -- cases 6 and 7 appear TWICE, once with longer data). Dispatching by 371 // digest length mis-sorted them 8/8 against the true 7/9. Starting at the section heading makes every 372 // case found a SHA-1 case BY CONSTRUCTION -- no inference required. 373 let sec: i64 = findfrom(b, lp[0], "2. Test Cases for HMAC-MD5" as *u8, 0) 374 // hard upper bound: where section 3 begins. Without it this reader walks into the SHA-1 vectors. 375 let endsec: i64 = findfrom(b, lp[0], "3. Test Cases for HMAC-SHA-1" as *u8, 0) 376 if sec < 0 { w("RED: no HMAC-SHA-1 section heading\n" as *u8); return 1 } 377 var cur: i64 = sec 378 var done: i64 = 0 379 while done == 0 { 380 // ★★★★★AN ANCHOR WITHOUT A BOUND IS ONLY CORRECT FOR THE LAST SECTION IN THE DOCUMENT. 381 // This reader was written for section 3 (HMAC-SHA-1), the FINAL section, so running to EOF was 382 // harmless. Re-anchoring it on section 2 (HMAC-MD5) silently extended it straight into section 3: 383 // it graded 16 cases instead of 7 and computed HMAC-MD5 for the SHA-1 vectors, producing 9 bogus 384 // failures whose "expected" values were plainly 20 bytes against a 16-byte result. 385 // ★MOVING A READER FROM THE LAST SECTION TO AN EARLIER ONE CHANGES ITS TERMINATION CONDITION, AND 386 // NOTHING ABOUT THE MOVE ANNOUNCES THAT. 387 // Clamping to -1 reuses the existing "no more cases" path -- no new branch, no brace surgery. 388 var tc: i64 = findfrom(b, lp[0], "test_case =" as *u8, cur) 389 if endsec > 0 { if tc >= endsec { tc = 0 - 1 } } 390 if tc < 0 { done = 1 } 391 else { 392 let lk: i64 = findfrom(b, lp[0], "key =" as *u8, tc) 393 let lkl: i64 = findfrom(b, lp[0], "key_len" as *u8, tc) 394 let ld: i64 = findfrom(b, lp[0], "data =" as *u8, tc) 395 let ldl: i64 = findfrom(b, lp[0], "data_len" as *u8, tc) 396 // BOUND EVERY FIELD TO ITS OWN CASE. Fields were searched forward from `test_case =` with no 397 // upper limit, so a case missing a field silently borrowed the NEXT case's. Symptom: two 398 // datalen=73 cases (RFC 2202 sec 3 duplicates 6 and 7) -- IDENTICAL inputs, one PASS one FAIL, 399 // which no crypto hypothesis can explain and which pins it to pairing. 400 // ★Same shape as the lk>lm guard that saved RFC 4231: a field belonging to another record is 401 // not a missing field, it is a WRONG field, and only a bound can tell them apart. 402 let nt: i64 = findfrom(b, lp[0], "test_case =" as *u8, tc + 11) 403 let lg: i64 = findfrom(b, lp[0], "digest =" as *u8, tc) 404 // ⚠⚠MY OWN PREVIOUS EDIT MALFORMED THIS CHAIN: adding the boundary check as an `else` left TWO 405 // `else` clauses hanging off `if lg < 0`. It COMPILED -- nx_cc bound the second one somewhere I 406 // did not intend -- and the gate kept printing plausible per-case verdicts, so nothing looked 407 // wrong. ★★★★★A CONTROL-FLOW BUG THAT STILL PRINTS PLAUSIBLE OUTPUT IS AN INSTRUMENT THAT LIES; 408 // AN EDIT THAT COMPILES IS NOT AN EDIT THAT PARSED THE WAY YOU READ IT. 409 // ★Rule 3: this was the second patch to this loop, so the chain is RESTRUCTURED into flat, 410 // unambiguous guards rather than patched a third time. No nested else, nothing to mis-bind. 411 var bad: i64 = 0 412 if lk < 0 { bad = 1 } 413 if lg < 0 { bad = 1 } 414 if nt >= 0 { 415 if lg > nt { 416 w(" RED: case field pairing crossed a case boundary -- the READER.\n" as *u8) 417 fail = fail + 1 418 bad = 1 419 } 420 } 421 if bad == 1 { done = 1 } 422 else { 423 let kn: i64 = readfield(b, lp[0], lk + 5, key, 400) 424 let dn: i64 = readfield(b, lp[0], ld + 6, data, 400) 425 let kdecl: i64 = readdec_skipeq(b, lp[0], lkl + 7) 426 let ddecl: i64 = readdec_skipeq(b, lp[0], ldl + 8) 427 let gn: i64 = readfield(b, lp[0], lg + 8, exp, 100) 428 if gn < 0 { done = 1 } 429 else { 430 // THE DOCUMENT DECLARES ITS OWN LENGTHS -- grade the reader by the source. 431 if kn != kdecl { 432 w(" RED: key parsed " as *u8); nn(kn); w(" but document declares key_len=" as *u8); nn(kdecl) 433 w(" -- the READER.\n" as *u8) 434 fail = fail + 1 435 done = 1 436 } else { if dn != ddecl { 437 w(" RED: data parsed " as *u8); nn(dn); w(" but document declares data_len=" as *u8); nn(ddecl) 438 w(" -- the READER.\n" as *u8) 439 fail = fail + 1 440 done = 1 441 } else { 442 // dispatch on the PUBLISHED digest length: 20 = SHA-1, 16 = the MD5 section 443 if gn > 0 { 444 ref_hmac_md5(key, kn, data, dn, got) 445 // DIFFERENTIAL: HKDF-Extract(salt, ikm) IS DEFINED AS HMAC-Hash(salt, ikm) 446 // (RFC 5869 sec 2.2), so hkdf_sha1_extract is an INDEPENDENT internal path to 447 // the same function. If one matches the published vector and the other does 448 // not, the defect is localised to the one that disagrees -- adjudicated by the 449 // AUTHORITY, not by which of our two implementations we happen to trust. 450 let alt: *u8 = sys_mmap(64) 451 ref_hmac_md5(key, kn, data, dn, alt) 452 var altsame: i64 = 1 453 var z: i64 = 0 454 while z < gn { if alt[z] != exp[z] { altsame = 0 } z = z + 1 } 455 // ⚠⚠RETRACTED, AND THE RETRACTION IS THE POINT. This block PRINTED 456 // "[differential] ... -> the WRAPPER is at fault" / "-> crypto exonerated" on 457 // every case, and its comment claimed hkdf_sha1_extract as an independent second 458 // path. THE CODE NEVER DID THAT: the lines above call the SAME reference function twice with 459 // IDENTICAL arguments, so `alt` and `got` are the same computation and altsame 460 // could never disagree with same. It was a MIRROR, not a second opinion, and it 461 // confidently attributed blame it had no information about -- it told me "crypto 462 // exonerated, the READER is at fault" on the failing case, which happened to be 463 // TRUE BY LUCK while carrying zero evidence. 464 // ★★★★★A DIFFERENTIAL THAT CALLS THE SAME FUNCTION TWICE IS A MIRROR, NOT A 465 // SECOND OPINION -- AND A COMMENT CLAIMING INDEPENDENCE THE CODE DOES NOT 466 // IMPLEMENT IS THE INSTRUMENT LYING IN ITS OWN DOCUMENTATION. 467 // Restoring a REAL differential needs a path sharing no core with this one; 468 // until one exists, this gate states its single-path scope instead of faking two. 469 if altsame == 1 { w(" [single-path] matches published digest (NO independent second path -- see note)\n" as *u8) } 470 else { w(" [single-path] differs from published digest\n" as *u8) } 471 // Compare only the PUBLISHED digest length: section 3 case 5 is the truncation 472 // case, whose published digest is SHORTER than HMAC-SHA-1's 20-byte output. 473 // Comparing a fixed 20 would fail a correct implementation on that case. 474 var same: i64 = 1 475 i = 0 476 while i < gn { if got[i] != exp[i] { same = 0 } i = i + 1 } 477 seen = seen + 1 478 if same == 1 { pass = pass + 1; w(" PASS md5 keylen=" as *u8); nn(kn); w(" datalen=" as *u8); nn(dn); w(" maclen=" as *u8); nn(gn); w("\n" as *u8) } 479 else { 480 fail = fail + 1 481 w(" FAIL md5 case keylen=" as *u8); nn(kn); w(" datalen=" as *u8); nn(dn); w("\n" as *u8) 482 // ★MEASURE, DO NOT INFER. I hypothesised this failure four separate ways -- 483 // case-boundary pairing, chunk framing, a short inner buffer, a wrong hash -- 484 // and every one was refuted. Printing the two digests and the byte offset the 485 // expected one was read from settles it in a single run. 486 w(" expected(read at offset " as *u8); nn(lg); w(") = " as *u8) 487 var z2: i64 = 0 488 while z2 < gn { ph(exp[z2] as i64); z2 = z2 + 1 } 489 w("\n computed = " as *u8) 490 z2 = 0 491 while z2 < gn { ph(got[z2] as i64); z2 = z2 + 1 } 492 w("\n first 40 bytes of source at that offset: " as *u8) 493 wb(((b as i64) + lg) as *u8, 40) 494 w("\n" as *u8) 495 } 496 } else { skipped = skipped + 1 } 497 cur = lg + 8 498 } } 499 } 500 } 501 } 502 } 503 504 w("\n refsrc=https://www.rfc-editor.org/rfc/rfc2202.txt\n" as *u8) 505 w(" refsrcdig=" as *u8); wb(hx, 64); w("\n" as *u8) 506 w(" ref=RFC2202-HMAC-MD5 gate=nx_hmacmd5alt_extvec_gate\n" as *u8) 507 w(" NOTE: key_len and data_len are cross-checked against the document on EVERY case.\n" as *u8) 508 w(" Bounded strictly to section 2; section 3 (HMAC-SHA-1) is graded by nx_hmacsha1fix_extvec_gate.\n" as *u8) 509 w("nx_hmacmd5alt_extvec_gate: md5_cases=" as *u8); nn(seen); w(" skipped_md5=" as *u8); nn(skipped) 510 w(" pass=" as *u8); nn(pass); w(" fail=" as *u8); nn(fail) 511 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 512 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 513 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 514 let ctr__dry: *i64 = gv_ctr() 515 ctr__dry[0] = pass 516 ctr__dry[1] = pass + fail 517 let rc__dry: i64 = gv_verdict("HMACMD5ALT-EXTVEC-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 518 sys_exit(rc__dry) 519 return rc__dry 520}