@amritk/nish-x86_64-linux 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,939 @@
1
+ /**
2
+ * `nish/crypto/aes` — AES-128 and AES-256 (FIPS 197), GCM on top of them
3
+ * (NIST SP 800-38D), and QUIC's AES header-protection mask (RFC 9001 §5.4.3).
4
+ *
5
+ * Written from the specifications and the papers below, in this module's own
6
+ * layout, with one exception: `ghashMul32` is adapted from BearSSL's
7
+ * `bmul64`, and carries BearSSL's notice. Nothing else here is ported from
8
+ * another implementation.
9
+ *
10
+ * - **Bitslicing** is the idea of E. Käsper and P. Schwabe, "Faster and
11
+ * Timing-Attack Resistant AES-GCM" (CHES 2009): hold bit `b` of many bytes
12
+ * in one word, so that SubBytes is a Boolean circuit run on whole words and
13
+ * no byte ever becomes a table index.
14
+ * - **The S-box circuit** is J. Boyar and R. Peralta's, "A depth-16 circuit
15
+ * for the AES S-box" (SEC 2012) and the 113-gate listing published with it:
16
+ * a linear layer in, 32 ANDs over GF(2^4) and GF(2^2) arithmetic, a linear
17
+ * layer out. `aesSbox` keeps their variable names so it can be read against
18
+ * the paper, gate by gate.
19
+ * - **GHASH's multiply** is carry-less multiplication done with the integer
20
+ * multiplier, by leaving three zero bits of "holes" between the bits of each
21
+ * operand so that the carries of an integer product land where nothing is
22
+ * read — T. Pornin's technique, and `ghashMul32` is his `bmul64` from
23
+ * BearSSL narrowed to 32-bit operands — with Karatsuba's three-for-four
24
+ * split on top (this module's own), and the
25
+ * reduction of S. Gueron and M. Kounavis, "Intel Carry-Less Multiplication
26
+ * Instruction and its Usage for Computing the GCM Mode" (2010), for GCM's
27
+ * bit-reflected field.
28
+ *
29
+ * **The bitsliced state.** Four blocks at a time sit in eight `u64` words,
30
+ * `q[0..7]`, word `b` holding bit `b` of all 64 bytes. Inside a word, block `k`
31
+ * owns the 16 bits from `16k`, and in those the byte in row `r` and column `c`
32
+ * of the AES state is bit `4r + c`: each row is one nibble. That makes the two
33
+ * permutations cheap. ShiftRows turns row `r` by `r` columns, which is a
34
+ * rotation of nibble `r` by `r` bits; MixColumns needs each byte's neighbour
35
+ * in the next row of its column, which is the word rotated by four bits inside
36
+ * each 16-bit lane. Getting 64 bytes in and out of that layout is an 8 × 8
37
+ * bit-matrix transpose per byte lane (`aesTranspose`, three rounds of masked
38
+ * swaps between words) after placing each byte in the word and slot the
39
+ * transpose will carry to its bit (`aesPack`, `aesUnpack`).
40
+ *
41
+ * **Constant time.** No table is read anywhere, key schedule included: the
42
+ * S-box is the circuit, and `SubWord` in the key schedule runs the same
43
+ * circuit on a four-byte state. No branch or index depends on a key, a
44
+ * plaintext, a tag or H: every `if` and every loop bound is on a length, a
45
+ * block number or the round count. GHASH multiplies with no table and no
46
+ * data-dependent shift, and `aesGcmOpen` computes the whole tag and compares
47
+ * it with `aesGcmTagMask` — a difference ORed into one word and handed to
48
+ * `ctEq` — before it decrypts or returns anything. What `tests/ct-asm.js`
49
+ * verifies by disassembly on x86-64 and aarch64 is one full round
50
+ * (`aesBitslicedRound`: S-box circuit, ShiftRows, MixColumns, AddRoundKey),
51
+ * one GHASH multiply (`ghashMultiply`) and the tag compare
52
+ * (`aesGcmTagMask`). A golden case compiles to one module and cannot import
53
+ * this one, so `tests/cases/ct_asm_aes` holds a verbatim copy of those three
54
+ * and the helpers they call, and `tests/link/crypto_aes` checks that the copy
55
+ * and this module agree on generated inputs, so neither can drift from the
56
+ * other unnoticed. Everything around them — packing bytes, the key schedule,
57
+ * the loops over blocks — is the discipline above and not a verified
58
+ * property.
59
+ *
60
+ * **What is refused.** A key of any length but 16 or 32 bytes (AES-192 is out
61
+ * of scope), a block or a sample that is not 16 bytes, an empty IV (SP 800-38D
62
+ * §5.2.1.1 asks for at least one bit), a sealed input shorter than its tag, a
63
+ * plaintext too long for its sealed form to be an array, and an `AesKey`
64
+ * `aesKey` did not make answer `null`; so does a tag that does not verify. An
65
+ * IV of any other non-zero length is hashed into the first counter block as
66
+ * §7.1 says.
67
+ *
68
+ * Private names carry the `aes` / `ghash` prefix because a `std/` module's
69
+ * private functions share the importing program's flat symbol namespace
70
+ * (`docs/wp26-stdlib.md` §3e).
71
+ */
72
+
73
+ /** The AES block, in bytes. */
74
+ export const AES_BLOCK: i32 = 16
75
+ /** GCM's tag, in bytes: this module makes and accepts only the full 128 bits. */
76
+ export const AES_GCM_TAG_SIZE: i32 = 16
77
+
78
+ /** The 64 bytes one pass of the bitsliced cipher encrypts: four blocks. */
79
+ const AES_BATCH: i32 = 64
80
+
81
+ /**
82
+ * An AES key, expanded: the round keys already bitsliced, and GCM's hash key
83
+ * H = E(K, 0^128), so a key used for many messages is expanded once.
84
+ *
85
+ * Make one with `aesKey`. The constructor and the fields are public only
86
+ * because the language has no private ones, and a key built or edited by hand
87
+ * is not trusted: one whose shape is not what `aesKey` makes answers `null`
88
+ * everywhere (`aesKeyUsable`), and one whose H is zero opens nothing
89
+ * (`aesGcmOpen`).
90
+ */
91
+ export class AesKey {
92
+ /** Nr: 10 for AES-128, 14 for AES-256. */
93
+ rounds: i32 = 0
94
+ /**
95
+ * Round key `i` in the bitsliced layout, replicated into all four block
96
+ * lanes, as eight words from `8 * i`: `8 * (rounds + 1)` words in all.
97
+ */
98
+ roundKeys: u64[]
99
+ /** H as a big-endian 128-bit number, high half. */
100
+ hHi: u64 = 0
101
+ /** H, low half. */
102
+ hLo: u64 = 0
103
+
104
+ constructor(rounds: i32, roundKeys: u64[]) {
105
+ this.rounds = rounds
106
+ this.roundKeys = roundKeys
107
+ }
108
+ }
109
+
110
+ /** A 16-bit pattern repeated into each of a word's four 16-bit lanes. */
111
+ const aesLanes = (pattern: u64): u64 => {
112
+ const two: u64 = pattern | (pattern << toU64(16))
113
+ return two | (two << toU64(32))
114
+ }
115
+
116
+ /** An 8-bit pattern repeated into each of a word's eight bytes. */
117
+ const aesBytes = (pattern: u64): u64 => aesLanes(pattern | (pattern << toU64(8)))
118
+
119
+ /**
120
+ * Swaps, in every byte lane, the bits of `q[i]` selected by `mask << shift`
121
+ * with the bits of `q[j]` selected by `mask`: one round of the transpose.
122
+ */
123
+ const aesSwapBits = (q: u64[], i: i32, j: i32, mask: u64, shift: u64): void => {
124
+ const t: u64 = ((q[i] >> shift) ^ q[j]) & mask
125
+ q[j] = q[j] ^ t
126
+ q[i] = q[i] ^ (t << shift)
127
+ }
128
+
129
+ /**
130
+ * Transposes, in each of the eight byte lanes, the 8 × 8 bit matrix whose
131
+ * row `i` is that byte of `q[i]`: afterwards bit `i` of the byte in `q[b]` is
132
+ * what bit `b` of the byte in `q[i]` was. Its own inverse, so it both enters
133
+ * and leaves the bitsliced layout. Swaps 1 × 1, then 2 × 2, then 4 × 4
134
+ * blocks across the diagonal.
135
+ */
136
+ const aesTranspose = (q: u64[]): void => {
137
+ const m1: u64 = aesBytes(toU64(0x55))
138
+ const m2: u64 = aesBytes(toU64(0x33))
139
+ const m4: u64 = aesBytes(toU64(0x0f))
140
+ aesSwapBits(q, 0, 1, m1, toU64(1))
141
+ aesSwapBits(q, 2, 3, m1, toU64(1))
142
+ aesSwapBits(q, 4, 5, m1, toU64(1))
143
+ aesSwapBits(q, 6, 7, m1, toU64(1))
144
+ aesSwapBits(q, 0, 2, m2, toU64(2))
145
+ aesSwapBits(q, 1, 3, m2, toU64(2))
146
+ aesSwapBits(q, 4, 6, m2, toU64(2))
147
+ aesSwapBits(q, 5, 7, m2, toU64(2))
148
+ aesSwapBits(q, 0, 4, m4, toU64(4))
149
+ aesSwapBits(q, 1, 5, m4, toU64(4))
150
+ aesSwapBits(q, 2, 6, m4, toU64(4))
151
+ aesSwapBits(q, 3, 7, m4, toU64(4))
152
+ }
153
+
154
+ /**
155
+ * Loads the four blocks of `buf[0 .. 64)` into the bitsliced state. After the
156
+ * transpose, bit `8p + i` of every word comes from byte slot `p` of `q[i]`,
157
+ * and that bit must be `16k + 4r + c` for block `k`'s byte in row `r` and
158
+ * column `c` (`buf[16k + 4c + r]`): so `i = 4 * (r & 1) + c` and
159
+ * `p = 2k + (r >> 1)`, which is the four lines in the loop.
160
+ */
161
+ const aesPack = (q: u64[], buf: u8[]): void => {
162
+ if (toI32(q.length) < 8 || toI32(buf.length) < 64) {
163
+ return
164
+ }
165
+ for (let i: i32 = 0; i < 8; i++) {
166
+ q[i] = 0
167
+ }
168
+ for (let k: i32 = 0; k < 4; k++) {
169
+ const low: u64 = toU64(16 * k)
170
+ const high: u64 = toU64(16 * k + 8)
171
+ for (let c: i32 = 0; c < 4; c++) {
172
+ q[c] = q[c] | (toU64(buf[16 * k + 4 * c]) << low) | (toU64(buf[16 * k + 4 * c + 2]) << high)
173
+ q[c + 4] = q[c + 4] | (toU64(buf[16 * k + 4 * c + 1]) << low) | (toU64(buf[16 * k + 4 * c + 3]) << high)
174
+ }
175
+ }
176
+ aesTranspose(q)
177
+ }
178
+
179
+ /** The inverse of `aesPack`: the four blocks of the state into `buf[0 .. 64)`. Spends `q`. */
180
+ const aesUnpack = (buf: u8[], q: u64[]): void => {
181
+ if (toI32(q.length) < 8 || toI32(buf.length) < 64) {
182
+ return
183
+ }
184
+ aesTranspose(q)
185
+ // `toU8` keeps the low eight bits, so each byte needs no mask.
186
+ for (let k: i32 = 0; k < 4; k++) {
187
+ const low: u64 = toU64(16 * k)
188
+ const high: u64 = toU64(16 * k + 8)
189
+ for (let c: i32 = 0; c < 4; c++) {
190
+ buf[16 * k + 4 * c] = toU8(q[c] >> low)
191
+ buf[16 * k + 4 * c + 2] = toU8(q[c] >> high)
192
+ buf[16 * k + 4 * c + 1] = toU8(q[c + 4] >> low)
193
+ buf[16 * k + 4 * c + 3] = toU8(q[c + 4] >> high)
194
+ }
195
+ }
196
+ }
197
+
198
+ /**
199
+ * SubBytes on every byte of the state at once: Boyar and Peralta's circuit,
200
+ * with their names. `x0` is the most significant bit of a byte, so it is
201
+ * `q[7]`, and the answer `s0` goes back to `q[7]` too. The three `^ ~` terms
202
+ * are the circuit's XNOR gates, the affine constant 0x63.
203
+ */
204
+ const aesSbox = (q: u64[]): void => {
205
+ const x0: u64 = q[7]
206
+ const x1: u64 = q[6]
207
+ const x2: u64 = q[5]
208
+ const x3: u64 = q[4]
209
+ const x4: u64 = q[3]
210
+ const x5: u64 = q[2]
211
+ const x6: u64 = q[1]
212
+ const x7: u64 = q[0]
213
+
214
+ // The top linear layer.
215
+ const y14: u64 = x3 ^ x5
216
+ const y13: u64 = x0 ^ x6
217
+ const y9: u64 = x0 ^ x3
218
+ const y8: u64 = x0 ^ x5
219
+ const t0: u64 = x1 ^ x2
220
+ const y1: u64 = t0 ^ x7
221
+ const y4: u64 = y1 ^ x3
222
+ const y12: u64 = y13 ^ y14
223
+ const y2: u64 = y1 ^ x0
224
+ const y5: u64 = y1 ^ x6
225
+ const y3: u64 = y5 ^ y8
226
+ const t1: u64 = x4 ^ y12
227
+ const y15: u64 = t1 ^ x5
228
+ const y20: u64 = t1 ^ x1
229
+ const y6: u64 = y15 ^ x7
230
+ const y10: u64 = y15 ^ t0
231
+ const y11: u64 = y20 ^ y9
232
+ const y7: u64 = x7 ^ y11
233
+ const y17: u64 = y10 ^ y11
234
+ const y19: u64 = y10 ^ y8
235
+ const y16: u64 = t0 ^ y11
236
+ const y21: u64 = y13 ^ y16
237
+ const y18: u64 = x0 ^ y16
238
+
239
+ // The non-linear middle: inversion in GF(2^8) through GF(2^4).
240
+ const t2: u64 = y12 & y15
241
+ const t3: u64 = y3 & y6
242
+ const t4: u64 = t3 ^ t2
243
+ const t5: u64 = y4 & x7
244
+ const t6: u64 = t5 ^ t2
245
+ const t7: u64 = y13 & y16
246
+ const t8: u64 = y5 & y1
247
+ const t9: u64 = t8 ^ t7
248
+ const t10: u64 = y2 & y7
249
+ const t11: u64 = t10 ^ t7
250
+ const t12: u64 = y9 & y11
251
+ const t13: u64 = y14 & y17
252
+ const t14: u64 = t13 ^ t12
253
+ const t15: u64 = y8 & y10
254
+ const t16: u64 = t15 ^ t12
255
+ const t17: u64 = t4 ^ t14
256
+ const t18: u64 = t6 ^ t16
257
+ const t19: u64 = t9 ^ t14
258
+ const t20: u64 = t11 ^ t16
259
+ const t21: u64 = t17 ^ y20
260
+ const t22: u64 = t18 ^ y19
261
+ const t23: u64 = t19 ^ y21
262
+ const t24: u64 = t20 ^ y18
263
+
264
+ const t25: u64 = t21 ^ t22
265
+ const t26: u64 = t21 & t23
266
+ const t27: u64 = t24 ^ t26
267
+ const t28: u64 = t25 & t27
268
+ const t29: u64 = t28 ^ t22
269
+ const t30: u64 = t23 ^ t24
270
+ const t31: u64 = t22 ^ t26
271
+ const t32: u64 = t31 & t30
272
+ const t33: u64 = t32 ^ t24
273
+ const t34: u64 = t23 ^ t33
274
+ const t35: u64 = t27 ^ t33
275
+ const t36: u64 = t24 & t35
276
+ const t37: u64 = t36 ^ t34
277
+ const t38: u64 = t27 ^ t36
278
+ const t39: u64 = t29 & t38
279
+ const t40: u64 = t25 ^ t39
280
+
281
+ const t41: u64 = t40 ^ t37
282
+ const t42: u64 = t29 ^ t33
283
+ const t43: u64 = t29 ^ t40
284
+ const t44: u64 = t33 ^ t37
285
+ const t45: u64 = t42 ^ t41
286
+ const z0: u64 = t44 & y15
287
+ const z1: u64 = t37 & y6
288
+ const z2: u64 = t33 & x7
289
+ const z3: u64 = t43 & y16
290
+ const z4: u64 = t40 & y1
291
+ const z5: u64 = t29 & y7
292
+ const z6: u64 = t42 & y11
293
+ const z7: u64 = t45 & y17
294
+ const z8: u64 = t41 & y10
295
+ const z9: u64 = t44 & y12
296
+ const z10: u64 = t37 & y3
297
+ const z11: u64 = t33 & y4
298
+ const z12: u64 = t43 & y13
299
+ const z13: u64 = t40 & y5
300
+ const z14: u64 = t29 & y2
301
+ const z15: u64 = t42 & y9
302
+ const z16: u64 = t45 & y14
303
+ const z17: u64 = t41 & y8
304
+
305
+ // The bottom linear layer.
306
+ const t46: u64 = z15 ^ z16
307
+ const t47: u64 = z10 ^ z11
308
+ const t48: u64 = z5 ^ z13
309
+ const t49: u64 = z9 ^ z10
310
+ const t50: u64 = z2 ^ z12
311
+ const t51: u64 = z2 ^ z5
312
+ const t52: u64 = z7 ^ z8
313
+ const t53: u64 = z0 ^ z3
314
+ const t54: u64 = z6 ^ z7
315
+ const t55: u64 = z16 ^ z17
316
+ const t56: u64 = z12 ^ t48
317
+ const t57: u64 = t50 ^ t53
318
+ const t58: u64 = z4 ^ t46
319
+ const t59: u64 = z3 ^ t54
320
+ const t60: u64 = t46 ^ t57
321
+ const t61: u64 = z14 ^ t57
322
+ const t62: u64 = t52 ^ t58
323
+ const t63: u64 = t49 ^ t58
324
+ const t64: u64 = z4 ^ t59
325
+ const t65: u64 = t61 ^ t62
326
+ const t66: u64 = z1 ^ t63
327
+ const s0: u64 = t59 ^ t63
328
+ const s6: u64 = t56 ^ ~t62
329
+ const s7: u64 = t48 ^ ~t60
330
+ const t67: u64 = t64 ^ t65
331
+ const s3: u64 = t53 ^ t66
332
+ const s4: u64 = t51 ^ t66
333
+ const s5: u64 = t47 ^ t65
334
+ const s1: u64 = t64 ^ ~s3
335
+ const s2: u64 = t55 ^ ~t67
336
+
337
+ q[7] = s0
338
+ q[6] = s1
339
+ q[5] = s2
340
+ q[4] = s3
341
+ q[3] = s4
342
+ q[2] = s5
343
+ q[1] = s6
344
+ q[0] = s7
345
+ }
346
+
347
+ /**
348
+ * ShiftRows on one bit plane: row `r` is nibble `r` of each 16-bit lane, and
349
+ * its column `c` takes column `c + r` (mod 4), so the nibble turns right by `r`.
350
+ */
351
+ const aesShiftPlane = (x: u64): u64 =>
352
+ (x & aesLanes(toU64(0x000f))) |
353
+ ((x >> toU64(1)) & aesLanes(toU64(0x0070))) |
354
+ ((x << toU64(3)) & aesLanes(toU64(0x0080))) |
355
+ ((x >> toU64(2)) & aesLanes(toU64(0x0300))) |
356
+ ((x << toU64(2)) & aesLanes(toU64(0x0c00))) |
357
+ ((x >> toU64(3)) & aesLanes(toU64(0x1000))) |
358
+ ((x << toU64(1)) & aesLanes(toU64(0xe000)))
359
+
360
+ /** ShiftRows (FIPS 197 §5.1.2) on the whole state. */
361
+ const aesShiftRows = (q: u64[]): void => {
362
+ q[0] = aesShiftPlane(q[0])
363
+ q[1] = aesShiftPlane(q[1])
364
+ q[2] = aesShiftPlane(q[2])
365
+ q[3] = aesShiftPlane(q[3])
366
+ q[4] = aesShiftPlane(q[4])
367
+ q[5] = aesShiftPlane(q[5])
368
+ q[6] = aesShiftPlane(q[6])
369
+ q[7] = aesShiftPlane(q[7])
370
+ }
371
+
372
+ /** Each byte replaced by the byte one row down its column (row 3 by row 0): nibbles turned by one. */
373
+ const aesNextRow = (x: u64): u64 =>
374
+ ((x >> toU64(4)) & aesLanes(toU64(0x0fff))) | ((x << toU64(12)) & aesLanes(toU64(0xf000)))
375
+
376
+ /** Each byte replaced by the byte two rows down its column. */
377
+ const aesRowAfterNext = (x: u64): u64 =>
378
+ ((x >> toU64(8)) & aesLanes(toU64(0x00ff))) | ((x << toU64(8)) & aesLanes(toU64(0xff00)))
379
+
380
+ /**
381
+ * MixColumns (FIPS 197 §5.1.3): s'_r = 2·s_r ⊕ 3·s_{r+1} ⊕ s_{r+2} ⊕ s_{r+3},
382
+ * rewritten as 2·t ⊕ s_{r+1} ⊕ (t two rows down) with t = s_r ⊕ s_{r+1}, so
383
+ * the one multiplication by 2 is a shift of the planes: plane `b` of 2·t is
384
+ * plane `b - 1` of t, and t's top plane folds back into planes 0, 1, 3 and 4
385
+ * for the reduction by x^8 + x^4 + x^3 + x + 1.
386
+ */
387
+ const aesMixColumns = (q: u64[]): void => {
388
+ const n0: u64 = aesNextRow(q[0])
389
+ const n1: u64 = aesNextRow(q[1])
390
+ const n2: u64 = aesNextRow(q[2])
391
+ const n3: u64 = aesNextRow(q[3])
392
+ const n4: u64 = aesNextRow(q[4])
393
+ const n5: u64 = aesNextRow(q[5])
394
+ const n6: u64 = aesNextRow(q[6])
395
+ const n7: u64 = aesNextRow(q[7])
396
+ const t0: u64 = q[0] ^ n0
397
+ const t1: u64 = q[1] ^ n1
398
+ const t2: u64 = q[2] ^ n2
399
+ const t3: u64 = q[3] ^ n3
400
+ const t4: u64 = q[4] ^ n4
401
+ const t5: u64 = q[5] ^ n5
402
+ const t6: u64 = q[6] ^ n6
403
+ const t7: u64 = q[7] ^ n7
404
+ q[0] = t7 ^ n0 ^ aesRowAfterNext(t0)
405
+ q[1] = t0 ^ t7 ^ n1 ^ aesRowAfterNext(t1)
406
+ q[2] = t1 ^ n2 ^ aesRowAfterNext(t2)
407
+ q[3] = t2 ^ t7 ^ n3 ^ aesRowAfterNext(t3)
408
+ q[4] = t3 ^ t7 ^ n4 ^ aesRowAfterNext(t4)
409
+ q[5] = t4 ^ n5 ^ aesRowAfterNext(t5)
410
+ q[6] = t5 ^ n6 ^ aesRowAfterNext(t6)
411
+ q[7] = t6 ^ n7 ^ aesRowAfterNext(t7)
412
+ }
413
+
414
+ /** AddRoundKey (FIPS 197 §5.1.4): the eight words of `rk` from `at` into the state. */
415
+ const aesAddRoundKey = (q: u64[], rk: u64[], at: i32): void => {
416
+ q[0] = q[0] ^ rk[at]
417
+ q[1] = q[1] ^ rk[at + 1]
418
+ q[2] = q[2] ^ rk[at + 2]
419
+ q[3] = q[3] ^ rk[at + 3]
420
+ q[4] = q[4] ^ rk[at + 4]
421
+ q[5] = q[5] ^ rk[at + 5]
422
+ q[6] = q[6] ^ rk[at + 6]
423
+ q[7] = q[7] ^ rk[at + 7]
424
+ }
425
+
426
+ /**
427
+ * One full middle round of the cipher on bitsliced state — SubBytes,
428
+ * ShiftRows, MixColumns, then the round key of eight words from `at` — for
429
+ * four blocks at once. Straight-line code with no branch and no index but
430
+ * `at`, which is a round number times eight. Exported so that
431
+ * `tests/link/crypto_aes` can hold the copy the disassembly check reads to
432
+ * this one; a caller has no other use for it.
433
+ */
434
+ export const aesBitslicedRound = (q: u64[], rk: u64[], at: i32): void => {
435
+ aesSbox(q)
436
+ aesShiftRows(q)
437
+ aesMixColumns(q)
438
+ aesAddRoundKey(q, rk, at)
439
+ }
440
+
441
+ /** The cipher (FIPS 197 §5.1) on the four blocks of `buf[0 .. 64)`, in place. `q` is scratch. */
442
+ const aesEncryptBatch = (key: AesKey, buf: u8[], q: u64[]): void => {
443
+ const rk: u64[] = key.roundKeys
444
+ aesPack(q, buf)
445
+ aesAddRoundKey(q, rk, 0)
446
+ for (let round: i32 = 1; round < key.rounds; round++) {
447
+ aesBitslicedRound(q, rk, 8 * round)
448
+ }
449
+ aesSbox(q)
450
+ aesShiftRows(q)
451
+ aesAddRoundKey(q, rk, 8 * key.rounds)
452
+ aesUnpack(buf, q)
453
+ }
454
+
455
+ /**
456
+ * SubWord (FIPS 197 §5.2) through the same circuit: the four bytes of `w`
457
+ * become bits 0 to 3 of eight planes, the circuit runs, and the bits come
458
+ * back. The unused bits of the planes are zero going in and are ignored
459
+ * coming out. No table, so the key schedule is as constant-time as a round.
460
+ */
461
+ const aesSubWord = (w: u32, q: u64[]): u32 => {
462
+ if (toI32(q.length) < 8) {
463
+ return 0
464
+ }
465
+ for (let b: i32 = 0; b < 8; b++) {
466
+ let plane: u64 = 0
467
+ for (let m: i32 = 0; m < 4; m++) {
468
+ plane = plane | (toU64((w >> toU32(8 * m + b)) & toU32(1)) << toU64(m))
469
+ }
470
+ q[b] = plane
471
+ }
472
+ aesSbox(q)
473
+ let out: u32 = 0
474
+ for (let b: i32 = 0; b < 8; b++) {
475
+ for (let m: i32 = 0; m < 4; m++) {
476
+ out = out | (toU32((q[b] >> toU64(m)) & toU64(1)) << toU32(8 * m + b))
477
+ }
478
+ }
479
+ return out
480
+ }
481
+
482
+ /** Four bytes of `data` from `at` as a big-endian word; the caller has checked the window. */
483
+ const aesLoad32 = (data: u8[], at: i32): u32 =>
484
+ (toU32(data[at]) << toU32(24)) |
485
+ (toU32(data[at + 1]) << toU32(16)) |
486
+ (toU32(data[at + 2]) << toU32(8)) |
487
+ toU32(data[at + 3])
488
+
489
+ /** `w` big-endian into `out[at .. at + 4)`; the caller has checked the window. */
490
+ const aesStore32 = (out: u8[], at: i32, w: u32): void => {
491
+ out[at] = toU8(w >> toU32(24))
492
+ out[at + 1] = toU8(w >> toU32(16))
493
+ out[at + 2] = toU8(w >> toU32(8))
494
+ out[at + 3] = toU8(w)
495
+ }
496
+
497
+ /**
498
+ * KeyExpansion (FIPS 197 §5.2) for Nk = 4 or 8, then every round key
499
+ * bitsliced and replicated into the four block lanes. `w` holds the words
500
+ * big-endian, byte 0 of the key in the high byte of `w[0]`, as the standard
501
+ * writes them. Branches only on the word number.
502
+ */
503
+ const aesExpand = (key: u8[], rounds: i32): u64[] => {
504
+ const nk: i32 = rounds - 6
505
+ const total: i32 = (rounds + 1) * 4
506
+ const w: u32[] = new Array<u32>(total)
507
+ const q: u64[] = new Array<u64>(8)
508
+ const keyLength: i32 = toI32(key.length)
509
+ const wLength: i32 = toI32(w.length)
510
+ // Rcon[i/Nk] = x^(i/Nk - 1) in GF(2^8), doubled once per Nk words.
511
+ let rcon: u32 = 1
512
+ for (let i: i32 = 0; i < wLength; i++) {
513
+ if (i < nk) {
514
+ const at: i32 = 4 * i
515
+ if (at >= 0 && at + 3 < keyLength) {
516
+ w[i] = aesLoad32(key, at)
517
+ }
518
+ } else {
519
+ let temp: u32 = w[i - 1]
520
+ if (i % nk === 0) {
521
+ temp = aesSubWord((temp << toU32(8)) | (temp >> toU32(24)), q) ^ (rcon << toU32(24))
522
+ rcon = ((rcon << toU32(1)) ^ (toU32(0x1b) & (toU32(0) - (rcon >> toU32(7))))) & toU32(0xff)
523
+ } else if (nk > 6 && i % nk === 4) {
524
+ temp = aesSubWord(temp, q)
525
+ }
526
+ w[i] = w[i - nk] ^ temp
527
+ }
528
+ }
529
+ // Each round key, written into all four blocks of a batch and packed.
530
+ const rk: u64[] = new Array<u64>((rounds + 1) * 8)
531
+ const buf: u8[] = new Array<u8>(AES_BATCH)
532
+ for (let round: i32 = 0; round <= rounds; round++) {
533
+ for (let k: i32 = 0; k < 4; k++) {
534
+ for (let j: i32 = 0; j < 4; j++) {
535
+ aesStore32(buf, 16 * k + 4 * j, w[4 * round + j])
536
+ }
537
+ }
538
+ aesPack(q, buf)
539
+ for (let b: i32 = 0; b < 8; b++) {
540
+ rk[8 * round + b] = q[b]
541
+ }
542
+ }
543
+ return rk
544
+ }
545
+
546
+ /**
547
+ * Eight bytes of `data` from `at`, big-endian, reading only below `end` and
548
+ * zero past it: how GHASH pads a partial last block (SP 800-38D §6.4).
549
+ */
550
+ const aesLoad64 = (data: u8[], at: i32, end: i32): u64 => {
551
+ const length: i32 = toI32(data.length)
552
+ let v: u64 = 0
553
+ for (let i: i32 = 0; i < 8; i++) {
554
+ const from: i32 = at + i
555
+ let byte: u64 = 0
556
+ if (from >= 0 && from < end && from < length) {
557
+ byte = toU64(data[from])
558
+ }
559
+ v = v | (byte << toU64((7 - i) * 8))
560
+ }
561
+ return v
562
+ }
563
+
564
+ /** `v` big-endian into `out[at .. at + 8)`; a byte outside `out` is dropped. */
565
+ const aesStore64 = (out: u8[], at: i32, v: u64): void => {
566
+ const length: i32 = toI32(out.length)
567
+ for (let i: i32 = 0; i < 8; i++) {
568
+ const to: i32 = at + i
569
+ if (to >= 0 && to < length) {
570
+ out[to] = toU8(v >> toU64((7 - i) * 8))
571
+ }
572
+ }
573
+ }
574
+
575
+ /** `dst[i] = src[i]` for `i` below `count` and inside both arrays. */
576
+ const aesCopyPrefix = (dst: u8[], src: u8[], count: i32): void => {
577
+ const dstLength: i32 = toI32(dst.length)
578
+ const srcLength: i32 = toI32(src.length)
579
+ for (let i: i32 = 0; i < count && i < dstLength && i < srcLength; i++) {
580
+ dst[i] = src[i]
581
+ }
582
+ }
583
+
584
+ /**
585
+ * Whether `key` has the shape `aesKey` makes: 10 or 14 rounds and the
586
+ * `(rounds + 1) * 8` round-key words the cipher reads. A key of another shape
587
+ * could only have been built or edited by hand, and would index past its round
588
+ * keys and panic, so every entry point answers `null` for it instead. The
589
+ * branch is on the shape, which is not secret.
590
+ */
591
+ const aesKeyUsable = (key: AesKey): boolean => {
592
+ const rounds: i32 = key.rounds
593
+ const words: i32 = (rounds + 1) * 8
594
+ return (rounds === 10 || rounds === 14) && toI32(key.roundKeys.length) === words
595
+ }
596
+
597
+ /**
598
+ * The first 16 bytes of `block` encrypted, in the first 16 bytes of a fresh
599
+ * batch; the rest of the batch is the cipher of zeros and is not read.
600
+ */
601
+ const aesEncryptOne = (key: AesKey, block: u8[]): u8[] => {
602
+ const buf: u8[] = new Array<u8>(AES_BATCH)
603
+ aesCopyPrefix(buf, block, AES_BLOCK)
604
+ aesEncryptBatch(key, buf, new Array<u64>(8))
605
+ return buf
606
+ }
607
+
608
+ /**
609
+ * Makes an `AesKey` from 16 bytes (AES-128) or 32 (AES-256): the key
610
+ * schedule, bitsliced, and H = E(K, 0^128) for GCM. Any other length — 24
611
+ * bytes included, since AES-192 is not offered — answers `null`.
612
+ */
613
+ export const aesKey = (key: u8[]): AesKey | null => {
614
+ const length: i32 = toI32(key.length)
615
+ if (length !== 16 && length !== 32) {
616
+ return null
617
+ }
618
+ const rounds: i32 = length === 16 ? 10 : 14
619
+ const expanded: AesKey = new AesKey(rounds, aesExpand(key, rounds))
620
+ const buf: u8[] = aesEncryptOne(expanded, [])
621
+ expanded.hHi = aesLoad64(buf, 0, 8)
622
+ expanded.hLo = aesLoad64(buf, 8, 16)
623
+ return expanded
624
+ }
625
+
626
+ /** One block encrypted (FIPS 197 §5.1, ECB): 16 bytes in, 16 fresh bytes out, or `null` for another length. */
627
+ export const aesEncryptBlock = (key: AesKey, block: u8[]): u8[] | null => {
628
+ if (!aesKeyUsable(key) || toI32(block.length) !== AES_BLOCK) {
629
+ return null
630
+ }
631
+ const out: u8[] = new Array<u8>(AES_BLOCK)
632
+ aesCopyPrefix(out, aesEncryptOne(key, block), AES_BLOCK)
633
+ return out
634
+ }
635
+
636
+ /**
637
+ * The AES header-protection mask of RFC 9001 §5.4.3: the first five bytes of
638
+ * the 16-byte `sample` encrypted under the header-protection key. `null` for
639
+ * a sample of another length.
640
+ */
641
+ export const aesHeaderMask = (key: AesKey, sample: u8[]): u8[] | null => {
642
+ if (!aesKeyUsable(key) || toI32(sample.length) !== AES_BLOCK) {
643
+ return null
644
+ }
645
+ const mask: u8[] = new Array<u8>(5)
646
+ aesCopyPrefix(mask, aesEncryptOne(key, sample), 5)
647
+ return mask
648
+ }
649
+
650
+ /*
651
+ * Adapted from BearSSL (https://www.bearssl.org/), src/hash/ghash_ctmul64.c
652
+ * bmul64(). Copyright (c) 2016 Thomas Pornin. Used under the MIT licence;
653
+ * see std/crypto/LICENSE-bearssl.
654
+ */
655
+ /**
656
+ * The carry-less product of two 32-bit polynomials, as a 64-bit one. Each
657
+ * operand is split into four with one bit in every four kept, so that an
658
+ * integer product of two parts has at most eight terms at any bit — below 16,
659
+ * so its carries stay inside the three zero bits above it — and the bits of
660
+ * the true product are the low bit of each four, read back through the same
661
+ * masks. Sixteen integer multiplications and no branch.
662
+ */
663
+ const ghashMul32 = (x: u64, y: u64): u64 => {
664
+ const m0: u64 = toU64(0x11111111)
665
+ const m1: u64 = toU64(0x22222222)
666
+ const m2: u64 = toU64(0x44444444)
667
+ const m3: u64 = m2 << toU64(1)
668
+ const x0: u64 = x & m0
669
+ const x1: u64 = x & m1
670
+ const x2: u64 = x & m2
671
+ const x3: u64 = x & m3
672
+ const y0: u64 = y & m0
673
+ const y1: u64 = y & m1
674
+ const y2: u64 = y & m2
675
+ const y3: u64 = y & m3
676
+ const z0: u64 = (x0 * y0) ^ (x1 * y3) ^ (x2 * y2) ^ (x3 * y1)
677
+ const z1: u64 = (x0 * y1) ^ (x1 * y0) ^ (x2 * y3) ^ (x3 * y2)
678
+ const z2: u64 = (x0 * y2) ^ (x1 * y1) ^ (x2 * y0) ^ (x3 * y3)
679
+ const z3: u64 = (x0 * y3) ^ (x1 * y2) ^ (x2 * y1) ^ (x3 * y0)
680
+ const w0: u64 = m0 | (m0 << toU64(32))
681
+ return (z0 & w0) | (z1 & (w0 << toU64(1))) | (z2 & (w0 << toU64(2))) | (z3 & (w0 << toU64(3)))
682
+ }
683
+
684
+ /**
685
+ * GHASH's multiply (SP 800-38D §6.3), `y = y · H` in GF(2^128), with `y` as
686
+ * two words `y[0]` (high) and `y[1]` (low) of the block read big-endian and
687
+ * H likewise in `hHi` and `hLo`.
688
+ *
689
+ * GCM numbers a block's bits from the first, so the block read big-endian is
690
+ * the polynomial with its coefficients reversed. The carry-less product of
691
+ * two reversed polynomials is the reversed product one bit short, so the
692
+ * 256-bit product is shifted left once, and then its low half — the
693
+ * coefficients of x^128 and up — is folded into the high half by
694
+ * x^128 = x^7 + x^2 + x + 1, in two steps because the fold of the lowest
695
+ * seven bits lands back in the low half (Gueron and Kounavis, §4 of the
696
+ * paper cited in the header).
697
+ *
698
+ * The 128 × 128 product is Karatsuba twice over: three 64 × 64 products, each
699
+ * of three 32 × 32 ones from `ghashMul32`. Straight-line code; exported, like
700
+ * `aesBitslicedRound`, so the disassembly check's copy can be held to it.
701
+ */
702
+ export const ghashMultiply = (y: u64[], hHi: u64, hLo: u64): void => {
703
+ const low32: u64 = (toU64(1) << toU64(32)) - toU64(1)
704
+ const thirtyTwo: u64 = toU64(32)
705
+ const a1: u64 = y[0]
706
+ const a0: u64 = y[1]
707
+ const a2: u64 = a1 ^ a0
708
+ const b2: u64 = hHi ^ hLo
709
+
710
+ // A0 · B0, as `lHi:lLo`.
711
+ const l0: u64 = ghashMul32(a0 & low32, hLo & low32)
712
+ const l1: u64 = ghashMul32(a0 >> thirtyTwo, hLo >> thirtyTwo)
713
+ const l2: u64 = ghashMul32((a0 ^ (a0 >> thirtyTwo)) & low32, (hLo ^ (hLo >> thirtyTwo)) & low32) ^ l0 ^ l1
714
+ const lHi: u64 = l1 ^ (l2 >> thirtyTwo)
715
+ const lLo: u64 = l0 ^ (l2 << thirtyTwo)
716
+
717
+ // A1 · B1, as `hHi2:hLo2`.
718
+ const h0: u64 = ghashMul32(a1 & low32, hHi & low32)
719
+ const h1: u64 = ghashMul32(a1 >> thirtyTwo, hHi >> thirtyTwo)
720
+ const h2: u64 = ghashMul32((a1 ^ (a1 >> thirtyTwo)) & low32, (hHi ^ (hHi >> thirtyTwo)) & low32) ^ h0 ^ h1
721
+ const hiHi: u64 = h1 ^ (h2 >> thirtyTwo)
722
+ const hiLo: u64 = h0 ^ (h2 << thirtyTwo)
723
+
724
+ // (A0 ⊕ A1) · (B0 ⊕ B1), less the other two, as `mHi:mLo`.
725
+ const m0: u64 = ghashMul32(a2 & low32, b2 & low32)
726
+ const m1: u64 = ghashMul32(a2 >> thirtyTwo, b2 >> thirtyTwo)
727
+ const m2: u64 = ghashMul32((a2 ^ (a2 >> thirtyTwo)) & low32, (b2 ^ (b2 >> thirtyTwo)) & low32) ^ m0 ^ m1
728
+ const mHi: u64 = m1 ^ (m2 >> thirtyTwo) ^ lHi ^ hiHi
729
+ const mLo: u64 = m0 ^ (m2 << thirtyTwo) ^ lLo ^ hiLo
730
+
731
+ // The 256-bit product p3:p2:p1:p0, shifted left once.
732
+ const r3: u64 = hiHi
733
+ const r2: u64 = hiLo ^ mHi
734
+ const r1: u64 = lHi ^ mLo
735
+ const r0: u64 = lLo
736
+ const one: u64 = toU64(1)
737
+ const top: u64 = toU64(63)
738
+ const p3: u64 = (r3 << one) | (r2 >> top)
739
+ const p2: u64 = (r2 << one) | (r1 >> top)
740
+ const p1: u64 = (r1 << one) | (r0 >> top)
741
+ const p0: u64 = r0 << one
742
+
743
+ // Reduce: fold the lowest bits of p1:p0 into p1 first, then all of it up.
744
+ const d1: u64 = p1 ^ (p0 << toU64(63)) ^ (p0 << toU64(62)) ^ (p0 << toU64(57))
745
+ y[0] = p3 ^ d1 ^ (d1 >> one) ^ (d1 >> toU64(2)) ^ (d1 >> toU64(7))
746
+ y[1] =
747
+ p2 ^
748
+ p0 ^
749
+ (p0 >> one) ^
750
+ (d1 << top) ^
751
+ (p0 >> toU64(2)) ^
752
+ (d1 << toU64(62)) ^
753
+ (p0 >> toU64(7)) ^
754
+ (d1 << toU64(57))
755
+ }
756
+
757
+ /**
758
+ * Absorbs `data[0 .. length)` into the GHASH state `y`, 16 bytes at a time,
759
+ * the last block padded with zeros (SP 800-38D §6.4, §7.1 step 5).
760
+ */
761
+ const ghashUpdate = (y: u64[], key: AesKey, data: u8[], length: i32): void => {
762
+ if (toI32(y.length) < 2) {
763
+ return
764
+ }
765
+ // Counted in blocks rather than stepped by 16, so no offset is ever formed
766
+ // past `length`: an AAD or IV within 16 bytes of 2^31 would overflow `at + 16`.
767
+ const blocks: i32 = (length >> 4) + ((length & 15) !== 0 ? 1 : 0)
768
+ for (let block: i32 = 0; block < blocks; block++) {
769
+ const at: i32 = block * 16
770
+ y[0] = y[0] ^ aesLoad64(data, at, length)
771
+ y[1] = y[1] ^ aesLoad64(data, at + 8, length)
772
+ ghashMultiply(y, key.hHi, key.hLo)
773
+ }
774
+ }
775
+
776
+ /** The last GHASH block: the AAD's and the ciphertext's lengths in bits, 64 bits each. */
777
+ const ghashLengths = (y: u64[], key: AesKey, aadLength: i32, textLength: i32): void => {
778
+ y[0] = y[0] ^ (toU64(aadLength) << toU64(3))
779
+ y[1] = y[1] ^ (toU64(textLength) << toU64(3))
780
+ ghashMultiply(y, key.hHi, key.hLo)
781
+ }
782
+
783
+ /**
784
+ * The pre-counter block J0 (SP 800-38D §7.1 step 2): a 12-byte IV followed by
785
+ * the 32-bit counter 1, or for any other length GHASH of the IV padded to a
786
+ * block and followed by its length in bits.
787
+ */
788
+ const aesGcmJ0 = (key: AesKey, iv: u8[]): u8[] => {
789
+ const length: i32 = toI32(iv.length)
790
+ const j0: u8[] = new Array<u8>(AES_BLOCK)
791
+ if (length === 12) {
792
+ aesCopyPrefix(j0, iv, 12)
793
+ j0[15] = 1
794
+ return j0
795
+ }
796
+ const y: u64[] = new Array<u64>(2)
797
+ ghashUpdate(y, key, iv, length)
798
+ ghashLengths(y, key, 0, length)
799
+ aesStore64(j0, 0, y[0])
800
+ aesStore64(j0, 8, y[1])
801
+ return j0
802
+ }
803
+
804
+ /**
805
+ * Whether GCTR can encrypt `length` bytes from inc32(J0) without its 32-bit
806
+ * counter coming back round to J0's, whose block masks the tag: SP 800-38D
807
+ * §5.2.1.1 caps a plaintext at 2^32 - 2 blocks (2^39 - 256 bits). An array
808
+ * length is below 2^31, so this never refuses today; it is here so that the
809
+ * bound is stated where the counter is, rather than left to the width of an
810
+ * array length.
811
+ */
812
+ const aesGcmLengthFits = (length: i32): boolean =>
813
+ (toI64(length) + toI64(15)) >> toI64(4) <= (toI64(1) << toI64(32)) - toI64(2)
814
+
815
+ /**
816
+ * GCTR (SP 800-38D §6.5) from inc32(J0): `dst[i] = src[i] ⊕ keystream[i]` for
817
+ * `i` below `length`, four blocks per pass of the cipher. The counter is the
818
+ * last four bytes of J0 as a big-endian `u32` and wraps modulo 2^32, as
819
+ * inc32 does, while the first twelve bytes stay as they are.
820
+ */
821
+ const aesGcmCtr = (key: AesKey, j0: u8[], src: u8[], dst: u8[], length: i32): void => {
822
+ if (toI32(j0.length) < 16) {
823
+ return
824
+ }
825
+ const counter: u32 = aesLoad32(j0, 12)
826
+ const buf: u8[] = new Array<u8>(AES_BATCH)
827
+ const q: u64[] = new Array<u64>(8)
828
+ const srcLength: i32 = toI32(src.length)
829
+ const dstLength: i32 = toI32(dst.length)
830
+ // Counted in batches, like `ghashUpdate`'s blocks, so `base` never passes `length`.
831
+ const batches: i32 = (length >> 6) + ((length & 63) !== 0 ? 1 : 0)
832
+ for (let batch: i32 = 0; batch < batches; batch++) {
833
+ const base: i32 = batch * AES_BATCH
834
+ for (let k: i32 = 0; k < 4; k++) {
835
+ for (let i: i32 = 0; i < 12; i++) {
836
+ buf[16 * k + i] = j0[i]
837
+ }
838
+ aesStore32(buf, 16 * k + 12, counter + toU32((base >> 4) + k + 1))
839
+ }
840
+ aesEncryptBatch(key, buf, q)
841
+ for (let j: i32 = 0; j < AES_BATCH && j < toI32(buf.length); j++) {
842
+ const at: i32 = base + j
843
+ if (at >= 0 && at < length && at < srcLength && at < dstLength) {
844
+ dst[at] = src[at] ^ buf[j]
845
+ }
846
+ }
847
+ }
848
+ }
849
+
850
+ /**
851
+ * E(K, J0) ⊕ S, GCM's tag (SP 800-38D §7.1 step 6), into `y` (zero, two
852
+ * words) as two big-endian words: S is the GHASH of the AAD and the
853
+ * ciphertext `text[0 .. textLength)`.
854
+ */
855
+ const aesGcmTag = (y: u64[], key: AesKey, j0: u8[], aad: u8[], text: u8[], textLength: i32): void => {
856
+ const aadLength: i32 = toI32(aad.length)
857
+ ghashUpdate(y, key, aad, aadLength)
858
+ ghashUpdate(y, key, text, textLength)
859
+ ghashLengths(y, key, aadLength, textLength)
860
+ const mask: u8[] = aesEncryptOne(key, j0)
861
+ y[0] = y[0] ^ aesLoad64(mask, 0, 16)
862
+ y[1] = y[1] ^ aesLoad64(mask, 8, 16)
863
+ }
864
+
865
+ /**
866
+ * All-ones when the tag `tagHi:tagLo` equals `gotHi:gotLo`, zero otherwise:
867
+ * the difference of both halves ORed into one word and handed to `ctEq`, so
868
+ * every bit of both tags is read and nothing is branched on until the caller
869
+ * tests the one answer. Exported, like `aesBitslicedRound`, so the
870
+ * disassembly check's copy can be held to it.
871
+ */
872
+ export const aesGcmTagMask = (tagHi: u64, tagLo: u64, gotHi: u64, gotLo: u64): u64 =>
873
+ ctEq((tagHi ^ gotHi) | (tagLo ^ gotLo), toU64(0))
874
+
875
+ /**
876
+ * GCM authenticated encryption (SP 800-38D §7.1): the ciphertext followed by
877
+ * the 16-byte tag, in one fresh array. The IV may be any non-zero length; 12
878
+ * bytes is the fast and usual one. `null` for an empty IV, for a key
879
+ * `aesKey` did not make, and for a plaintext longer than 2^31 - 17 bytes,
880
+ * whose sealed form would not fit in an array.
881
+ */
882
+ export const aesGcmSeal = (key: AesKey, iv: u8[], aad: u8[], plaintext: u8[]): u8[] | null => {
883
+ const length: i32 = toI32(plaintext.length)
884
+ // 2^31 - 17: the longest plaintext whose sealed form, sixteen bytes longer,
885
+ // is still an array length.
886
+ const longest: i32 = 0x7fffffef
887
+ if (!aesKeyUsable(key) || toI32(iv.length) === 0 || length > longest || !aesGcmLengthFits(length)) {
888
+ return null
889
+ }
890
+ const j0: u8[] = aesGcmJ0(key, iv)
891
+ const out: u8[] = new Array<u8>(length + AES_GCM_TAG_SIZE)
892
+ aesGcmCtr(key, j0, plaintext, out, length)
893
+ const tag: u64[] = new Array<u64>(2)
894
+ aesGcmTag(tag, key, j0, aad, out, length)
895
+ aesStore64(out, length, tag[0])
896
+ aesStore64(out, length + 8, tag[1])
897
+ return out
898
+ }
899
+
900
+ /**
901
+ * GCM authenticated decryption (SP 800-38D §7.2): `sealed` is the ciphertext
902
+ * followed by its 16-byte tag, and the answer is the plaintext, or `null` when
903
+ * the tag does not verify, the IV is empty, `sealed` is shorter than a tag or
904
+ * the key is not one `aesKey` made.
905
+ * The tag is computed over the received ciphertext and compared in constant
906
+ * time before anything is decrypted, so a forgery yields no plaintext at all.
907
+ */
908
+ export const aesGcmOpen = (key: AesKey, iv: u8[], aad: u8[], sealed: u8[]): u8[] | null => {
909
+ const total: i32 = toI32(sealed.length)
910
+ if (!aesKeyUsable(key) || toI32(iv.length) === 0 || total < AES_GCM_TAG_SIZE) {
911
+ return null
912
+ }
913
+ // `total` is an array length and at least a tag, so neither this nor the
914
+ // tag's second word at `length + 8` can leave the range of an `i32`.
915
+ const length: i32 = total - AES_GCM_TAG_SIZE
916
+ if (!aesGcmLengthFits(length)) {
917
+ return null
918
+ }
919
+ const j0: u8[] = aesGcmJ0(key, iv)
920
+ const tag: u64[] = new Array<u64>(2)
921
+ aesGcmTag(tag, key, j0, aad, sealed, length)
922
+ const same: u64 = aesGcmTagMask(
923
+ tag[0],
924
+ tag[1],
925
+ aesLoad64(sealed, length, total),
926
+ aesLoad64(sealed, length + 8, total)
927
+ )
928
+ // Under H = 0 GHASH is zero for every input, so the tag E(K, J0) would
929
+ // authenticate any ciphertext and AAD. Only a key built or edited by hand
930
+ // has it (for one from `aesKey` the chance is 2^-128), and such a key opens
931
+ // nothing. H is secret, so this is folded into the mask, not branched on.
932
+ const hashKeySet: u64 = ~ctEq(key.hHi | key.hLo, toU64(0))
933
+ if ((same & hashKeySet) === toU64(0)) {
934
+ return null
935
+ }
936
+ const out: u8[] = new Array<u8>(length)
937
+ aesGcmCtr(key, j0, sealed, out, length)
938
+ return out
939
+ }