code wiki / _hdl_build / nx_aescbc_extvec_gate.nx

nx_aescbc_extvec_gate.nx source

↩ module page · 201 lines · 8844 B

1// nx_aescbc_extvec_gate.nx -- ELEVENTH provably third-party-validated claim: AES-128 vs RFC 3602 Case #1. 2// 3// ★SEVENTH VECTOR DIALECT: `0x`-prefixed hex plus a QUOTED ASCII plaintext, with the three hex values 4// distinguished only by ORDER on the page: 5// Key : 0x06a9214036b8a15b512e03d534120006 6// IV : 0x3dafba429d9eb430b422da802c9fac41 7// Plaintext : "Single block msg" 8// Ciphertext: 0xe353779c1079aeb82708942dbe77181a 9// The labels are padded to different widths, so this reads them POSITIONALLY: after "Case #1", the 1st 10// `0x` run is the key, the 2nd is the IV, the 3rd is the ciphertext, and the quoted run is the plaintext. 11// ★Position, not label-matching, for the same reason the hexdump reader uses position: label spacing is 12// cosmetic and drifts, ordering is structural. 13// 14// ⚠SCOPE, STATED HONESTLY: this gate composes CBC's FIRST-BLOCK RULE ITSELF (C = E(P xor IV)) because the 15// primitives available are aes128_expand_key + aes128_encrypt_block. So what is externally validated is the 16// AES-128 BLOCK CIPHER and the key schedule -- the published ciphertext cannot be produced without both 17// being exactly right. It is NOT a validation of a full CBC mode implementation over multiple blocks or of 18// padding; RFC 3602 Case #2+ would be needed for that, and this gate does not claim it. 19// license_tier: ORIGINAL expect_exit: 0 20import "nx_syscalls.nx" 21import "nx_sha256_wasm.nx" 22import "nx_aes.nx" 23import "nx_gate_verdict.nx" 24 25func w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 26func wb(b: *u8, n: i64) -> i64 { sys_write(1, b, n); return 0 } 27 28func nn(v: i64) -> i64 { 29 var m: i64 = v 30 if m < 0 { w("-" as *u8); m = 0 - m } 31 let t: *u8 = sys_mmap(32) 32 var k: i64 = 0 33 if m == 0 { t[0] = 48 as u8; k = 1 } 34 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 35 let b: *u8 = sys_mmap(32) 36 var j: i64 = 0 37 while j < k { b[j] = t[k - 1 - j]; j = j + 1 } 38 sys_write(1, b, k) 39 return 0 40} 41 42func hexnib(v: i64) -> i64 { if v < 10 { return 48 + v } return 87 + v } 43 44func hexval(c: i64) -> i64 { 45 if c >= 48 { if c <= 57 { return c - 48 } } 46 if c >= 97 { if c <= 102 { return c - 87 } } 47 if c >= 65 { if c <= 70 { return c - 55 } } 48 return 0 - 1 49} 50 51func starts(b: *u8, n: i64, at: i64, s: *u8) -> i64 { 52 var i: i64 = 0 53 while s[i] != (0 as u8) { 54 if at + i >= n { return 0 } 55 if b[at + i] != s[i] { return 0 } 56 i = i + 1 57 } 58 return 1 59} 60 61func findfrom(b: *u8, n: i64, s: *u8, from: i64) -> i64 { 62 var p: i64 = from 63 while p < n { 64 if starts(b, n, p, s) == 1 { return p } 65 p = p + 1 66 } 67 return 0 - 1 68} 69 70// Read exactly `want` bytes of contiguous hex starting at `from`. STRICT: no whitespace tolerance here, 71// because these values are on one line and a run that stops short must fail loudly rather than drift into 72// the next field. 73func parsehex(b: *u8, n: i64, from: i64, out: *u8, want: i64) -> i64 { 74 var p: i64 = from 75 var got: i64 = 0 76 while got < want { 77 if p + 1 >= n { return 0 - 1 } 78 let h1: i64 = hexval(b[p] as i64) 79 let h2: i64 = hexval(b[p + 1] as i64) 80 if h1 < 0 { return 0 - 1 } 81 if h2 < 0 { return 0 - 1 } 82 out[got] = ((h1 * 16) + h2) as u8 83 got = got + 1 84 p = p + 2 85 } 86 return p 87} 88 89func parseq(b: *u8, n: i64, from: i64, out: *u8, cap: i64) -> i64 { 90 var p: i64 = from 91 var k: i64 = 0 92 while p < n { 93 if b[p] == (34 as u8) { return k } 94 if k >= cap { return 0 - 1 } 95 out[k] = b[p] 96 k = k + 1 97 p = p + 1 98 } 99 return 0 - 1 100} 101 102func main() -> i64 { 103 w("nx_aescbc_extvec_gate -- AES-128 vs RFC 3602 Case #1, READ FROM THE FETCHED DOCUMENT\n" as *u8) 104 105 let lp: *i64 = sys_mmap(16) as *i64 106 lp[0] = 0 107 let b: *u8 = sys_read_file("knowledge/extvec/rfc3602.txt\x00" as *u8, lp) 108 if lp[0] <= 0 { w("RED: fetched vector file absent -- run nx_vecfetch.\n" as *u8); return 1 } 109 110 let ctx: *u8 = sys_mmap(1024) 111 let dg: *u8 = sys_mmap(64) 112 nx_sha256_one_shot(b, lp[0], ctx, dg) 113 let hx: *u8 = sys_mmap(80) 114 var i: i64 = 0 115 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 } 116 let wnt: *u8 = "1a95ec00c065983ffa3f253d70eb4a07530d9e1829f27cdb522bdbb7343da3cb\x00" as *u8 117 var pin: i64 = 1 118 i = 0 119 while i < 64 { if hx[i] != wnt[i] { pin = 0 } i = i + 1 } 120 w(" acquisition digest: " as *u8); wb(hx, 64); w("\n" as *u8) 121 if pin == 0 { w("RED: PIN FAILED -- not the file nx_vecfetch acquired.\n" as *u8); return 1 } 122 w(" PIN OK -- bytes match the digest computed in-process at the socket\n" as *u8) 123 124 let at: i64 = findfrom(b, lp[0], "Case #1:" as *u8, 0) 125 if at < 0 { w("RED: no 'Case #1:'\n" as *u8); return 1 } 126 127 // positional: 1st 0x = Key, 2nd = IV, 3rd = Ciphertext 128 let k1: i64 = findfrom(b, lp[0], "0x" as *u8, at) 129 if k1 < 0 { w("RED: no key hex\n" as *u8); return 1 } 130 let key: *u8 = sys_mmap(32) 131 let e1: i64 = parsehex(b, lp[0], k1 + 2, key, 16) 132 if e1 < 0 { w("RED: key short\n" as *u8); return 1 } 133 134 let k2: i64 = findfrom(b, lp[0], "0x" as *u8, e1) 135 if k2 < 0 { w("RED: no IV hex\n" as *u8); return 1 } 136 let iv: *u8 = sys_mmap(32) 137 let e2: i64 = parsehex(b, lp[0], k2 + 2, iv, 16) 138 if e2 < 0 { w("RED: iv short\n" as *u8); return 1 } 139 140 let pq: i64 = findfrom(b, lp[0], "\"" as *u8, e2) 141 if pq < 0 { w("RED: no quoted plaintext\n" as *u8); return 1 } 142 let pt: *u8 = sys_mmap(64) 143 let ptlen: i64 = parseq(b, lp[0], pq + 1, pt, 60) 144 if ptlen != 16 { w("RED: plaintext is not 16 bytes (got " as *u8); nn(ptlen); w(")\n" as *u8); return 1 } 145 146 let k3: i64 = findfrom(b, lp[0], "0x" as *u8, pq) 147 if k3 < 0 { w("RED: no ciphertext hex\n" as *u8); return 1 } 148 let ct: *u8 = sys_mmap(32) 149 if parsehex(b, lp[0], k3 + 2, ct, 16) < 0 { w("RED: ciphertext short\n" as *u8); return 1 } 150 151 // PARSE SELF-CHECK: the document's plaintext is the ASCII "Single block msg" -- assert its first bytes 152 // so a positional mis-read is named as a READER fault before AES is ever blamed. 153 var ok: i64 = 0 154 if pt[0] == (83 as u8) { if pt[1] == (105 as u8) { ok = 1 } } 155 if ok == 0 { w("RED: PARSE SELF-CHECK FAILED -- plaintext does not begin 'Si'; the reader, not AES.\n" as *u8); return 1 } 156 w(" parse self-check OK -- plaintext begins 'Si', 16 bytes\n" as *u8) 157 158 // CBC first block: C = E(P xor IV). The externally validated components are the AES-128 block cipher 159 // and its key schedule; the xor is composed here and stated in the header. 160 let sched: *u8 = sys_mmap(256) 161 aes128_expand_key(key, sched) 162 let blk: *u8 = sys_mmap(32) 163 i = 0 164 while i < 16 { blk[i] = ((pt[i] as i64) ^ (iv[i] as i64)) as u8; i = i + 1 } 165 let out: *u8 = sys_mmap(32) 166 aes128_encrypt_block(blk, sched, out) 167 168 var pass: i64 = 0 169 var fail: i64 = 0 170 var same: i64 = 1 171 i = 0 172 while i < 16 { if out[i] != ct[i] { same = 0 } i = i + 1 } 173 if same == 1 { pass = pass + 1; w(" PASS T1: AES-128 CBC first block == the document's Ciphertext\n" as *u8) } 174 else { fail = fail + 1; w(" FAIL T1: ciphertext mismatch\n" as *u8) } 175 176 // NEGATIVE CONTROL: flip one key bit; the block must change. 177 key[0] = (((key[0] as i64) ^ 1) & 255) as u8 178 aes128_expand_key(key, sched) 179 aes128_encrypt_block(blk, sched, out) 180 var same2: i64 = 1 181 i = 0 182 while i < 16 { if out[i] != ct[i] { same2 = 0 } i = i + 1 } 183 if same2 == 0 { pass = pass + 1; w(" PASS NEG: one-bit key change alters the block (the check can fail)\n" as *u8) } 184 else { fail = fail + 1; w(" FAIL NEG: key change did not alter the block\n" as *u8) } 185 186 w("\n refsrc=https://www.rfc-editor.org/rfc/rfc3602.txt\n" as *u8) 187 w(" refsrcdig=" as *u8); wb(hx, 64); w("\n" as *u8) 188 w(" ref=RFC3602-Case1 gate=nx_aescbc_extvec_gate\n" as *u8) 189 w(" SCOPE: validates the AES-128 BLOCK CIPHER + key schedule; the CBC xor is composed here, and\n" as *u8) 190 w(" multi-block CBC and padding are NOT claimed (that needs Case #2+).\n" as *u8) 191 w("nx_aescbc_extvec_gate: pass=" as *u8); nn(pass); w(" fail=" as *u8); nn(fail) 192 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 193 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 194 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 195 let ctr__dry: *i64 = gv_ctr() 196 ctr__dry[0] = pass 197 ctr__dry[1] = pass + fail 198 let rc__dry: i64 = gv_verdict("AESCBC-EXTVEC-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 199 sys_exit(rc__dry) 200 return rc__dry 201}