nx_aes128_ctr_wasm.nx source
↩ module page · 301 lines · 15696 B
1// nx_aes128_ctr_wasm.nx -- AES-128-CTR (NIST SP 800-38A §6.5).
2// Wraps inlined AES-128 ECB with counter-mode stream cipher logic.
3// Verified vs NIST SP 800-38A §F.5.1 test vector.
4// (Original ECB header below is the inlined cipher core.)
5//
6// nx_aes128_wasm.nx -- AES-128 ECB (FIPS 197) self-contained for WAT target.
7//
8// Rijndael-128 with 128-bit key, 16-byte block, 10 rounds.
9// ECB mode only -- caller composes CTR/GCM/CBC on top.
10//
11// Implementation: byte-oriented SBox/InvSBox + ShiftRows + MixColumns
12// done as straightforward 4x4 byte ops. No T-tables (slower but
13// smaller code, simpler, side-channel-friendly).
14//
15// API:
16// nx_aes128_encrypt(key_ptr, in_block, scratch_ptr, out_block) -> i64
17// key_ptr -- 16 bytes (128-bit key)
18// in_block -- 16 bytes plaintext
19// scratch_ptr -- >= 256 bytes (key schedule = 11 round keys = 176 bytes)
20// out_block -- 16 bytes ciphertext
21// nx_aes128_decrypt(key_ptr, in_block, scratch_ptr, out_block) -> i64
22// symmetric inverse (uses InvSBox + InvShiftRows + InvMixColumns)
23//
24// Verified against FIPS 197 Appendix C.1 (single-block KAT).
25//
26// license_tier: INDEPENDENT_REDERIVE
27// genealogy_id: international-research-sources/nist/fips_197
28// lineage_id: nishi_aes128_wasm_q11
29
30// === Rijndael S-box (FIPS 197 §5.1.1 / Appendix A) ===
31// Inlined as a switch. 256 entries.
32func _aes_sbox(b: i64) -> i64 {
33 if b == 0 { return 0x63 } if b == 1 { return 0x7c } if b == 2 { return 0x77 } if b == 3 { return 0x7b }
34 if b == 4 { return 0xf2 } if b == 5 { return 0x6b } if b == 6 { return 0x6f } if b == 7 { return 0xc5 }
35 if b == 8 { return 0x30 } if b == 9 { return 0x01 } if b == 10 { return 0x67 } if b == 11 { return 0x2b }
36 if b == 12 { return 0xfe } if b == 13 { return 0xd7 } if b == 14 { return 0xab } if b == 15 { return 0x76 }
37 if b == 16 { return 0xca } if b == 17 { return 0x82 } if b == 18 { return 0xc9 } if b == 19 { return 0x7d }
38 if b == 20 { return 0xfa } if b == 21 { return 0x59 } if b == 22 { return 0x47 } if b == 23 { return 0xf0 }
39 if b == 24 { return 0xad } if b == 25 { return 0xd4 } if b == 26 { return 0xa2 } if b == 27 { return 0xaf }
40 if b == 28 { return 0x9c } if b == 29 { return 0xa4 } if b == 30 { return 0x72 } if b == 31 { return 0xc0 }
41 if b == 32 { return 0xb7 } if b == 33 { return 0xfd } if b == 34 { return 0x93 } if b == 35 { return 0x26 }
42 if b == 36 { return 0x36 } if b == 37 { return 0x3f } if b == 38 { return 0xf7 } if b == 39 { return 0xcc }
43 if b == 40 { return 0x34 } if b == 41 { return 0xa5 } if b == 42 { return 0xe5 } if b == 43 { return 0xf1 }
44 if b == 44 { return 0x71 } if b == 45 { return 0xd8 } if b == 46 { return 0x31 } if b == 47 { return 0x15 }
45 if b == 48 { return 0x04 } if b == 49 { return 0xc7 } if b == 50 { return 0x23 } if b == 51 { return 0xc3 }
46 if b == 52 { return 0x18 } if b == 53 { return 0x96 } if b == 54 { return 0x05 } if b == 55 { return 0x9a }
47 if b == 56 { return 0x07 } if b == 57 { return 0x12 } if b == 58 { return 0x80 } if b == 59 { return 0xe2 }
48 if b == 60 { return 0xeb } if b == 61 { return 0x27 } if b == 62 { return 0xb2 } if b == 63 { return 0x75 }
49 if b == 64 { return 0x09 } if b == 65 { return 0x83 } if b == 66 { return 0x2c } if b == 67 { return 0x1a }
50 if b == 68 { return 0x1b } if b == 69 { return 0x6e } if b == 70 { return 0x5a } if b == 71 { return 0xa0 }
51 if b == 72 { return 0x52 } if b == 73 { return 0x3b } if b == 74 { return 0xd6 } if b == 75 { return 0xb3 }
52 if b == 76 { return 0x29 } if b == 77 { return 0xe3 } if b == 78 { return 0x2f } if b == 79 { return 0x84 }
53 if b == 80 { return 0x53 } if b == 81 { return 0xd1 } if b == 82 { return 0x00 } if b == 83 { return 0xed }
54 if b == 84 { return 0x20 } if b == 85 { return 0xfc } if b == 86 { return 0xb1 } if b == 87 { return 0x5b }
55 if b == 88 { return 0x6a } if b == 89 { return 0xcb } if b == 90 { return 0xbe } if b == 91 { return 0x39 }
56 if b == 92 { return 0x4a } if b == 93 { return 0x4c } if b == 94 { return 0x58 } if b == 95 { return 0xcf }
57 if b == 96 { return 0xd0 } if b == 97 { return 0xef } if b == 98 { return 0xaa } if b == 99 { return 0xfb }
58 if b == 100 { return 0x43 } if b == 101 { return 0x4d } if b == 102 { return 0x33 } if b == 103 { return 0x85 }
59 if b == 104 { return 0x45 } if b == 105 { return 0xf9 } if b == 106 { return 0x02 } if b == 107 { return 0x7f }
60 if b == 108 { return 0x50 } if b == 109 { return 0x3c } if b == 110 { return 0x9f } if b == 111 { return 0xa8 }
61 if b == 112 { return 0x51 } if b == 113 { return 0xa3 } if b == 114 { return 0x40 } if b == 115 { return 0x8f }
62 if b == 116 { return 0x92 } if b == 117 { return 0x9d } if b == 118 { return 0x38 } if b == 119 { return 0xf5 }
63 if b == 120 { return 0xbc } if b == 121 { return 0xb6 } if b == 122 { return 0xda } if b == 123 { return 0x21 }
64 if b == 124 { return 0x10 } if b == 125 { return 0xff } if b == 126 { return 0xf3 } if b == 127 { return 0xd2 }
65 if b == 128 { return 0xcd } if b == 129 { return 0x0c } if b == 130 { return 0x13 } if b == 131 { return 0xec }
66 if b == 132 { return 0x5f } if b == 133 { return 0x97 } if b == 134 { return 0x44 } if b == 135 { return 0x17 }
67 if b == 136 { return 0xc4 } if b == 137 { return 0xa7 } if b == 138 { return 0x7e } if b == 139 { return 0x3d }
68 if b == 140 { return 0x64 } if b == 141 { return 0x5d } if b == 142 { return 0x19 } if b == 143 { return 0x73 }
69 if b == 144 { return 0x60 } if b == 145 { return 0x81 } if b == 146 { return 0x4f } if b == 147 { return 0xdc }
70 if b == 148 { return 0x22 } if b == 149 { return 0x2a } if b == 150 { return 0x90 } if b == 151 { return 0x88 }
71 if b == 152 { return 0x46 } if b == 153 { return 0xee } if b == 154 { return 0xb8 } if b == 155 { return 0x14 }
72 if b == 156 { return 0xde } if b == 157 { return 0x5e } if b == 158 { return 0x0b } if b == 159 { return 0xdb }
73 if b == 160 { return 0xe0 } if b == 161 { return 0x32 } if b == 162 { return 0x3a } if b == 163 { return 0x0a }
74 if b == 164 { return 0x49 } if b == 165 { return 0x06 } if b == 166 { return 0x24 } if b == 167 { return 0x5c }
75 if b == 168 { return 0xc2 } if b == 169 { return 0xd3 } if b == 170 { return 0xac } if b == 171 { return 0x62 }
76 if b == 172 { return 0x91 } if b == 173 { return 0x95 } if b == 174 { return 0xe4 } if b == 175 { return 0x79 }
77 if b == 176 { return 0xe7 } if b == 177 { return 0xc8 } if b == 178 { return 0x37 } if b == 179 { return 0x6d }
78 if b == 180 { return 0x8d } if b == 181 { return 0xd5 } if b == 182 { return 0x4e } if b == 183 { return 0xa9 }
79 if b == 184 { return 0x6c } if b == 185 { return 0x56 } if b == 186 { return 0xf4 } if b == 187 { return 0xea }
80 if b == 188 { return 0x65 } if b == 189 { return 0x7a } if b == 190 { return 0xae } if b == 191 { return 0x08 }
81 if b == 192 { return 0xba } if b == 193 { return 0x78 } if b == 194 { return 0x25 } if b == 195 { return 0x2e }
82 if b == 196 { return 0x1c } if b == 197 { return 0xa6 } if b == 198 { return 0xb4 } if b == 199 { return 0xc6 }
83 if b == 200 { return 0xe8 } if b == 201 { return 0xdd } if b == 202 { return 0x74 } if b == 203 { return 0x1f }
84 if b == 204 { return 0x4b } if b == 205 { return 0xbd } if b == 206 { return 0x8b } if b == 207 { return 0x8a }
85 if b == 208 { return 0x70 } if b == 209 { return 0x3e } if b == 210 { return 0xb5 } if b == 211 { return 0x66 }
86 if b == 212 { return 0x48 } if b == 213 { return 0x03 } if b == 214 { return 0xf6 } if b == 215 { return 0x0e }
87 if b == 216 { return 0x61 } if b == 217 { return 0x35 } if b == 218 { return 0x57 } if b == 219 { return 0xb9 }
88 if b == 220 { return 0x86 } if b == 221 { return 0xc1 } if b == 222 { return 0x1d } if b == 223 { return 0x9e }
89 if b == 224 { return 0xe1 } if b == 225 { return 0xf8 } if b == 226 { return 0x98 } if b == 227 { return 0x11 }
90 if b == 228 { return 0x69 } if b == 229 { return 0xd9 } if b == 230 { return 0x8e } if b == 231 { return 0x94 }
91 if b == 232 { return 0x9b } if b == 233 { return 0x1e } if b == 234 { return 0x87 } if b == 235 { return 0xe9 }
92 if b == 236 { return 0xce } if b == 237 { return 0x55 } if b == 238 { return 0x28 } if b == 239 { return 0xdf }
93 if b == 240 { return 0x8c } if b == 241 { return 0xa1 } if b == 242 { return 0x89 } if b == 243 { return 0x0d }
94 if b == 244 { return 0xbf } if b == 245 { return 0xe6 } if b == 246 { return 0x42 } if b == 247 { return 0x68 }
95 if b == 248 { return 0x41 } if b == 249 { return 0x99 } if b == 250 { return 0x2d } if b == 251 { return 0x0f }
96 if b == 252 { return 0xb0 } if b == 253 { return 0x54 } if b == 254 { return 0xbb }
97 return 0x16
98}
99
100// === Round constants Rcon[i] for key schedule (FIPS 197 §5.2) ===
101func _aes_rcon(i: i64) -> i64 {
102 if i == 1 { return 0x01 } if i == 2 { return 0x02 } if i == 3 { return 0x04 } if i == 4 { return 0x08 }
103 if i == 5 { return 0x10 } if i == 6 { return 0x20 } if i == 7 { return 0x40 } if i == 8 { return 0x80 }
104 if i == 9 { return 0x1b }
105 return 0x36
106}
107
108// === GF(2^8) multiplication for MixColumns (xtime-based) ===
109// Multiply a by 2 in GF(2^8) with reduction polynomial x^8 + x^4 + x^3 + x + 1 (0x1b).
110func _xtime(a: i64) -> i64 {
111 let shifted: i64 = (a << 1) & 0xff
112 if (a & 0x80) != 0 { return shifted ^ 0x1b }
113 return shifted
114}
115
116// === Key expansion: derive 11 round keys (44 4-byte words) ===
117// Stored in scratch as 176 contiguous bytes, 16 bytes per round key.
118func _aes128_key_expand(key: *u8, rk: *u8) -> i64 {
119 var i: i64 = 0
120 while i < 16 { rk[i] = key[i]; i = i + 1 }
121 var n: i64 = 16 // bytes generated so far
122 var rcon_idx: i64 = 1
123 while n < 176 {
124 // last word
125 var t0: i64 = rk[n - 4]
126 var t1: i64 = rk[n - 3]
127 var t2: i64 = rk[n - 2]
128 var t3: i64 = rk[n - 1]
129 if (n & 15) == 0 {
130 // RotWord + SubBytes + Rcon
131 let r0: i64 = _aes_sbox(t1) ^ _aes_rcon(rcon_idx)
132 let r1: i64 = _aes_sbox(t2)
133 let r2: i64 = _aes_sbox(t3)
134 let r3: i64 = _aes_sbox(t0)
135 t0 = r0; t1 = r1; t2 = r2; t3 = r3
136 rcon_idx = rcon_idx + 1
137 }
138 rk[n] = (rk[n - 16] ^ t0) & 0xff
139 rk[n + 1] = (rk[n - 15] ^ t1) & 0xff
140 rk[n + 2] = (rk[n - 14] ^ t2) & 0xff
141 rk[n + 3] = (rk[n - 13] ^ t3) & 0xff
142 n = n + 4
143 }
144 return 0
145}
146
147// === AES round transformations ===
148
149// AddRoundKey: state ^= round_key (16 bytes XOR).
150func _aes_add_round_key(state: *u8, rk: *u8, round: i64) -> i64 {
151 var i: i64 = 0
152 while i < 16 { state[i] = (state[i] ^ rk[round * 16 + i]) & 0xff; i = i + 1 }
153 return 0
154}
155
156// SubBytes: state[i] = SBox[state[i]]
157func _aes_sub_bytes(state: *u8) -> i64 {
158 var i: i64 = 0
159 while i < 16 { state[i] = _aes_sbox(state[i]) & 0xff; i = i + 1 }
160 return 0
161}
162
163// ShiftRows: row r shifts left by r positions (cyclic).
164// State layout (column-major per FIPS 197):
165// state[0] state[4] state[8] state[12] <- row 0 (no shift)
166// state[1] state[5] state[9] state[13] <- row 1 (shift 1)
167// state[2] state[6] state[10] state[14] <- row 2 (shift 2)
168// state[3] state[7] state[11] state[15] <- row 3 (shift 3)
169func _aes_shift_rows(state: *u8) -> i64 {
170 // Row 1: rotate left by 1
171 let t1: i64 = state[1]
172 state[1] = state[5]
173 state[5] = state[9]
174 state[9] = state[13]
175 state[13] = t1
176 // Row 2: rotate left by 2 (swap pairs)
177 let t2: i64 = state[2]
178 state[2] = state[10]
179 state[10] = t2
180 let t6: i64 = state[6]
181 state[6] = state[14]
182 state[14] = t6
183 // Row 3: rotate left by 3 (= rotate right by 1)
184 let t3: i64 = state[15]
185 state[15] = state[11]
186 state[11] = state[7]
187 state[7] = state[3]
188 state[3] = t3
189 return 0
190}
191
192// MixColumns: for each 4-byte column, multiply by the matrix
193// [02 03 01 01]
194// [01 02 03 01]
195// [01 01 02 03]
196// [03 01 01 02]
197// in GF(2^8).
198func _aes_mix_columns(state: *u8) -> i64 {
199 var c: i64 = 0
200 while c < 4 {
201 let off: i64 = c * 4
202 let a0: i64 = state[off]
203 let a1: i64 = state[off + 1]
204 let a2: i64 = state[off + 2]
205 let a3: i64 = state[off + 3]
206 // t = a0 ^ a1 ^ a2 ^ a3
207 let t: i64 = a0 ^ a1 ^ a2 ^ a3
208 let n0: i64 = a0 ^ _xtime(a0 ^ a1) ^ t
209 let n1: i64 = a1 ^ _xtime(a1 ^ a2) ^ t
210 let n2: i64 = a2 ^ _xtime(a2 ^ a3) ^ t
211 let n3: i64 = a3 ^ _xtime(a3 ^ a0) ^ t
212 state[off] = n0 & 0xff
213 state[off + 1] = n1 & 0xff
214 state[off + 2] = n2 & 0xff
215 state[off + 3] = n3 & 0xff
216 c = c + 1
217 }
218 return 0
219}
220
221// === AES-128 encrypt one block (private, used by CTR wrapper) ===
222func _aes128_encrypt_block(key_ptr: *u8, in_block: *u8,
223 scratch_ptr: *u8, out_block: *u8) -> i64 {
224 let rk: *u8 = scratch_ptr // 176 bytes for round keys
225 let state: *u8 = (scratch_ptr as i64 + 176) as *u8 // 16 bytes state
226 _aes128_key_expand(key_ptr, rk)
227 var i: i64 = 0
228 while i < 16 { state[i] = in_block[i]; i = i + 1 }
229 _aes_add_round_key(state, rk, 0)
230 var r: i64 = 1
231 while r < 10 {
232 _aes_sub_bytes(state)
233 _aes_shift_rows(state)
234 _aes_mix_columns(state)
235 _aes_add_round_key(state, rk, r)
236 r = r + 1
237 }
238 // Final round: no MixColumns
239 _aes_sub_bytes(state)
240 _aes_shift_rows(state)
241 _aes_add_round_key(state, rk, 10)
242 var j: i64 = 0
243 while j < 16 { out_block[j] = state[j]; j = j + 1 }
244 return 0
245}
246
247// === AES-128-CTR public API ===
248
249// Big-endian increment of a 16-byte counter block IN-PLACE.
250// Per NIST SP 800-38A §B.1: counters increment in the last (lowest)
251// position, propagating carry leftward. Standard convention is to
252// increment only the low 4 bytes (treating the high 12 as a nonce);
253// here we increment the full 16-byte big-endian integer for generality
254// -- the high 12 bytes typically don't change in normal usage.
255func _aes_ctr_inc(ctr: *u8) -> i64 {
256 var i: i64 = 15
257 while i >= 0 {
258 let nb: i64 = (ctr[i] + 1) & 0xff
259 ctr[i] = nb
260 if nb != 0 { i = -1 } else { i = i - 1 }
261 }
262 return 0
263}
264
265// XOR in_len bytes from in_ptr with the AES-CTR keystream starting at
266// the 16-byte initial counter block iv16. Writes to out_ptr.
267// Same operation encrypts AND decrypts (XOR is involutive).
268//
269// scratch layout (>= 512 bytes):
270// 0..175 : AES round keys (built by inlined _aes128_encrypt_block)
271// 176..191 : AES working state (used internally by encrypt)
272// 192..207 : current counter block
273// 208..223 : current keystream block
274func nx_aes128_ctr_xor(key_ptr: *u8, iv16: *u8,
275 in_ptr: *u8, in_len: i64,
276 scratch_ptr: *u8, out_ptr: *u8) -> i64 {
277 let ctr: *u8 = (scratch_ptr as i64 + 192) as *u8
278 let ks: *u8 = (scratch_ptr as i64 + 208) as *u8
279 // Copy IV into our working counter.
280 var i: i64 = 0
281 while i < 16 { ctr[i] = iv16[i]; i = i + 1 }
282
283 var pos: i64 = 0
284 while pos < in_len {
285 // Generate one block of keystream.
286 _aes128_encrypt_block(key_ptr, ctr, scratch_ptr, ks)
287 // XOR up to 16 bytes from ks with input.
288 var b: i64 = 0
289 while b < 16 {
290 if pos + b >= in_len { b = 16 }
291 else {
292 out_ptr[pos + b] = (in_ptr[pos + b] ^ ks[b]) & 0xff
293 b = b + 1
294 }
295 }
296 // Increment counter for next block.
297 _aes_ctr_inc(ctr)
298 pos = pos + 16
299 }
300 return 0
301}