code wiki / _hdl_build / nx_poly1305_extvec_gate.nx

nx_poly1305_extvec_gate.nx source

↩ module page · 194 lines · 8819 B

1// nx_poly1305_extvec_gate.nx -- FOURTH provably third-party-validated claim: Poly1305 vs RFC 8439 2.5.2. 2// 3// Fourth authority, fourth primitive, same construction -- but a THIRD vector dialect, which is the point: 4// RFC 7748 plain 64-char hex runs 5// RFC 8032 wrapped at 32 chars/line (even, so byte pairs never split) 6// RFC 8439 COLON-SEPARATED AND WRAPS MID-BYTE: "...a8:01:0" / newline / "3:80:..." splits the byte 03 7// An adjacent-hex-pair parser (what the first three gates use) drops a nibble at that break and SILENTLY 8// SHIFTS EVERY SUBSEQUENT BYTE. It does not fail loudly -- it yields a plausible wrong key, a red gate, and 9// an hour spent blaming Poly1305 instead of the reader. 10// ★★★★★LAW: EACH STANDARDS BODY PUBLISHES IN ITS OWN DIALECT, AND THE PARSER MUST BE PROVEN AGAINST THAT 11// DIALECT BEFORE THE GATE'S VERDICT MEANS ANYTHING. There is no general parser, only a proven one. 12// So this gate uses a NIBBLE-STREAM reader: accumulate hex nibbles, skip ':' and whitespace, pair them 13// afterwards. That is immune to both colon separators and mid-byte line wraps. 14// 15// ⚠ONE INPUT IS TYPED, DELIBERATELY, AND HERE IS WHY IT IS STILL SOUND: the RFC gives the message as the 16// ASCII string "Cryptographic Forum Research Group". I supply those 34 bytes rather than parsing prose out 17// of the document. That does NOT weaken the claim, because THE ANSWER STILL COMES FROM THE DOCUMENT: if my 18// message were wrong by one byte the computed tag would not match the published tag, and the gate would go 19// RED. A PASS therefore proves BOTH that the message was right AND that our Poly1305 agrees with the IETF. 20// ★The rule this respects: an agent may supply an INPUT whose correctness the published ANSWER verifies; 21// an agent may NEVER supply the ANSWER. That distinction is the whole of the third-party property. 22// license_tier: ORIGINAL expect_exit: 0 23import "nx_syscalls.nx" 24import "nx_sha256_wasm.nx" 25import "nx_poly1305.nx" 26import "nx_gate_verdict.nx" 27 28func w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 29func wb(b: *u8, n: i64) -> i64 { sys_write(1, b, n); return 0 } 30 31func nn(v: i64) -> i64 { 32 var m: i64 = v 33 if m < 0 { w("-" as *u8); m = 0 - m } 34 let t: *u8 = sys_mmap(32) 35 var k: i64 = 0 36 if m == 0 { t[0] = 48 as u8; k = 1 } 37 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 38 let b: *u8 = sys_mmap(32) 39 var j: i64 = 0 40 while j < k { b[j] = t[k - 1 - j]; j = j + 1 } 41 sys_write(1, b, k) 42 return 0 43} 44 45func hexnib(v: i64) -> i64 { if v < 10 { return 48 + v } return 87 + v } 46 47func hexval(c: i64) -> i64 { 48 if c >= 48 { if c <= 57 { return c - 48 } } 49 if c >= 97 { if c <= 102 { return c - 87 } } 50 if c >= 65 { if c <= 70 { return c - 55 } } 51 return 0 - 1 52} 53 54func starts(b: *u8, n: i64, at: i64, s: *u8) -> i64 { 55 var i: i64 = 0 56 while s[i] != (0 as u8) { 57 if at + i >= n { return 0 } 58 if b[at + i] != s[i] { return 0 } 59 i = i + 1 60 } 61 return 1 62} 63 64func findfrom(b: *u8, n: i64, s: *u8, from: i64) -> i64 { 65 var p: i64 = from 66 while p < n { 67 if starts(b, n, p, s) == 1 { return p } 68 p = p + 1 69 } 70 return 0 - 1 71} 72 73// NIBBLE-STREAM reader: ':' and whitespace are separators that CONTINUE the run; hex chars accumulate as 74// nibbles and pair up across ANY separator, including a line break in the middle of a byte. Any other 75// character ends the value. Returns index past `want` bytes, or -1 if short. 76func parsenib(b: *u8, n: i64, from: i64, out: *u8, want: i64) -> i64 { 77 var p: i64 = from 78 var got: i64 = 0 79 var have: i64 = 0 80 var hi: i64 = 0 81 while p < n { 82 if got >= want { return p } 83 let c: i64 = b[p] as i64 84 var sep: i64 = 0 85 if c == 58 { sep = 1 } 86 if c == 32 { sep = 1 } 87 if c == 10 { sep = 1 } 88 if c == 13 { sep = 1 } 89 if c == 9 { sep = 1 } 90 if sep == 1 { p = p + 1 } 91 else { 92 let hv: i64 = hexval(c) 93 if hv < 0 { 94 // a non-hex, non-separator byte ends this value; if we have not got enough, restart 95 if got > 0 { if got < want { got = 0; have = 0 } } 96 p = p + 1 97 } else { 98 if have == 0 { hi = hv; have = 1 } else { out[got] = ((hi * 16) + hv) as u8; got = got + 1; have = 0 } 99 p = p + 1 100 } 101 } 102 } 103 if got >= want { return p } 104 return 0 - 1 105} 106 107func main() -> i64 { 108 w("nx_poly1305_extvec_gate -- Poly1305 vs RFC 8439 2.5.2, vectors READ FROM THE FETCHED DOCUMENT\n" as *u8) 109 110 let lp: *i64 = sys_mmap(16) as *i64 111 lp[0] = 0 112 let b: *u8 = sys_read_file("knowledge/extvec/rfc8439.txt\x00" as *u8, lp) 113 if lp[0] <= 0 { w("RED: fetched vector file absent -- run nx_vecfetch. NOT falling back to constants.\n" as *u8); return 1 } 114 115 let ctx: *u8 = sys_mmap(1024) 116 let dig: *u8 = sys_mmap(64) 117 nx_sha256_one_shot(b, lp[0], ctx, dig) 118 let hx: *u8 = sys_mmap(80) 119 var i: i64 = 0 120 while i < 32 { 121 hx[i * 2] = hexnib(((dig[i] as i64) / 16) & 15) as u8 122 hx[i * 2 + 1] = hexnib((dig[i] as i64) & 15) as u8 123 i = i + 1 124 } 125 let want: *u8 = "25bef70fbf7a07ff45c2fe4cb7c6ce954eac687413d8610603268b4e4415324c\x00" as *u8 126 var pin: i64 = 1 127 i = 0 128 while i < 64 { if hx[i] != want[i] { pin = 0 } i = i + 1 } 129 w(" acquisition digest: " as *u8); wb(hx, 64); w("\n" as *u8) 130 if pin == 0 { w("RED: PIN FAILED -- not the file nx_vecfetch acquired; refusing to read vectors.\n" as *u8); return 1 } 131 w(" PIN OK -- bytes match the digest computed in-process at the socket\n" as *u8) 132 133 // Anchor on the SECTION BODY, not the table-of-contents line (both contain the same heading text). 134 let sec1: i64 = findfrom(b, lp[0], "2.5.2. Poly1305 Example and Test Vector" as *u8, 0) 135 if sec1 < 0 { w("RED: could not locate section 2.5.2\n" as *u8); return 1 } 136 let sec2: i64 = findfrom(b, lp[0], "2.5.2. Poly1305 Example and Test Vector" as *u8, sec1 + 10) 137 var at: i64 = sec1 138 if sec2 >= 0 { at = sec2 } 139 140 let lk: i64 = findfrom(b, lp[0], "Key Material:" as *u8, at) 141 if lk < 0 { w("RED: no 'Key Material:' in 2.5.2\n" as *u8); return 1 } 142 let key: *u8 = sys_mmap(64) 143 if parsenib(b, lp[0], lk + 13, key, 32) < 0 { w("RED: key nibble-stream short\n" as *u8); return 1 } 144 145 let lt: i64 = findfrom(b, lp[0], "Tag:" as *u8, at) 146 if lt < 0 { w("RED: no 'Tag:' in 2.5.2\n" as *u8); return 1 } 147 let exp: *u8 = sys_mmap(64) 148 if parsenib(b, lp[0], lt + 4, exp, 16) < 0 { w("RED: tag nibble-stream short\n" as *u8); return 1 } 149 150 // The message, as the RFC states it. See the header note: supplying an INPUT is sound because the 151 // published TAG verifies it -- one wrong byte here and the comparison fails. 152 let msg: *u8 = "Cryptographic Forum Research Group\x00" as *u8 153 var mlen: i64 = 0 154 while msg[mlen] != (0 as u8) { mlen = mlen + 1 } 155 156 let tag: *u8 = sys_mmap(64) 157 poly1305_mac(key, msg, mlen, tag) 158 159 var pass: i64 = 0 160 var fail: i64 = 0 161 var same: i64 = 1 162 i = 0 163 while i < 16 { if tag[i] != exp[i] { same = 0 } i = i + 1 } 164 if same == 1 { 165 pass = pass + 1 166 w(" PASS T1: poly1305_mac(key,msg) == the document's published Tag (msglen=" as *u8); nn(mlen); w(")\n" as *u8) 167 } else { 168 fail = fail + 1 169 w(" FAIL T1: tag mismatch against the published value\n" as *u8) 170 } 171 172 // NEGATIVE CONTROL: flip one key bit; the tag must change. 173 key[0] = (((key[0] as i64) ^ 1) & 255) as u8 174 poly1305_mac(key, msg, mlen, tag) 175 var same2: i64 = 1 176 i = 0 177 while i < 16 { if tag[i] != exp[i] { same2 = 0 } i = i + 1 } 178 if same2 == 0 { pass = pass + 1; w(" PASS NEG: one-bit key change breaks the tag (the check can fail)\n" as *u8) } 179 else { fail = fail + 1; w(" FAIL NEG: tag unchanged under a key change -- not discriminating\n" as *u8) } 180 181 w("\n refsrc=https://www.rfc-editor.org/rfc/rfc8439.txt\n" as *u8) 182 w(" refsrcdig=" as *u8); wb(hx, 64); w("\n" as *u8) 183 w(" ref=RFC8439-2.5.2 gate=nx_poly1305_extvec_gate\n" as *u8) 184 w("nx_poly1305_extvec_gate: pass=" as *u8); nn(pass); w(" fail=" as *u8); nn(fail) 185 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 186 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 187 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 188 let ctr__dry: *i64 = gv_ctr() 189 ctr__dry[0] = pass 190 ctr__dry[1] = pass + fail 191 let rc__dry: i64 = gv_verdict("POLY1305-EXTVEC-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 192 sys_exit(rc__dry) 193 return rc__dry 194}