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}