aegis-aead 0.1.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,605 @@
1
+ import {
2
+ aesRoundTo,
3
+ andBlocksTo,
4
+ C0,
5
+ C1,
6
+ concatBytes,
7
+ constantTimeEqual,
8
+ le64,
9
+ xorBlocks,
10
+ xorBlocksTo,
11
+ zeroPad,
12
+ } from "./aes.js";
13
+
14
+ /**
15
+ * AEGIS-128X cipher state with configurable parallelism degree.
16
+ * Extends AEGIS-128L with parallel AES rounds for improved performance on wide SIMD architectures.
17
+ */
18
+ export class Aegis128XState {
19
+ private v: Uint8Array[][];
20
+ private d: number;
21
+ private rate: number;
22
+ private newV: Uint8Array[][];
23
+ private tmp: Uint8Array;
24
+ private z0: Uint8Array;
25
+ private z1: Uint8Array;
26
+ private ctxBufs: Uint8Array[];
27
+
28
+ /**
29
+ * Creates a new AEGIS-128X state.
30
+ * @param degree - Parallelism degree (default: 2). Use 2 for AEGIS-128X2, 4 for AEGIS-128X4.
31
+ */
32
+ constructor(degree: number = 2) {
33
+ this.d = degree;
34
+ this.rate = 256 * degree;
35
+ this.v = Array.from({ length: 8 }, () =>
36
+ Array.from({ length: degree }, () => new Uint8Array(16)),
37
+ );
38
+ this.newV = Array.from({ length: 8 }, () =>
39
+ Array.from({ length: degree }, () => new Uint8Array(16)),
40
+ );
41
+ this.tmp = new Uint8Array(16);
42
+ this.z0 = new Uint8Array(16 * degree);
43
+ this.z1 = new Uint8Array(16 * degree);
44
+ this.ctxBufs = Array.from({ length: degree }, (_, i) => {
45
+ const buf = new Uint8Array(16);
46
+ buf[0] = i;
47
+ buf[1] = degree - 1;
48
+ return buf;
49
+ });
50
+ }
51
+
52
+ /**
53
+ * Initializes the state with a key and nonce.
54
+ * @param key - 16-byte encryption key
55
+ * @param nonce - 16-byte nonce (must be unique per message)
56
+ */
57
+ init(key: Uint8Array, nonce: Uint8Array): void {
58
+ const keyXorNonce = new Uint8Array(16);
59
+ const keyXorC0 = new Uint8Array(16);
60
+ const keyXorC1 = new Uint8Array(16);
61
+ xorBlocksTo(key, nonce, keyXorNonce);
62
+ xorBlocksTo(key, C0, keyXorC0);
63
+ xorBlocksTo(key, C1, keyXorC1);
64
+
65
+ for (let i = 0; i < this.d; i++) {
66
+ this.v[0]![i]!.set(keyXorNonce);
67
+ this.v[1]![i]!.set(C1);
68
+ this.v[2]![i]!.set(C0);
69
+ this.v[3]![i]!.set(C1);
70
+ this.v[4]![i]!.set(keyXorNonce);
71
+ this.v[5]![i]!.set(keyXorC0);
72
+ this.v[6]![i]!.set(keyXorC1);
73
+ this.v[7]![i]!.set(keyXorC0);
74
+ }
75
+
76
+ const nonceV = new Uint8Array(16 * this.d);
77
+ const keyV = new Uint8Array(16 * this.d);
78
+ for (let i = 0; i < this.d; i++) {
79
+ nonceV.set(nonce, i * 16);
80
+ keyV.set(key, i * 16);
81
+ }
82
+
83
+ for (let round = 0; round < 10; round++) {
84
+ for (let i = 0; i < this.d; i++) {
85
+ const ctx = this.ctxBufs[i]!;
86
+ for (let j = 0; j < 16; j++) this.v[3]![i]![j] ^= ctx[j]!;
87
+ for (let j = 0; j < 16; j++) this.v[7]![i]![j] ^= ctx[j]!;
88
+ }
89
+ this.update(nonceV, keyV);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Updates the state with two message vectors.
95
+ * @param m0 - First message vector (16*degree bytes)
96
+ * @param m1 - Second message vector (16*degree bytes)
97
+ */
98
+ update(m0: Uint8Array, m1: Uint8Array): void {
99
+ const newV = this.newV;
100
+ const tmp = this.tmp;
101
+
102
+ for (let i = 0; i < this.d; i++) {
103
+ const m0i = m0.subarray(i * 16, (i + 1) * 16);
104
+ const m1i = m1.subarray(i * 16, (i + 1) * 16);
105
+
106
+ xorBlocksTo(this.v[0]![i]!, m0i, tmp);
107
+ aesRoundTo(this.v[7]![i]!, tmp, newV[0]![i]!);
108
+ aesRoundTo(this.v[0]![i]!, this.v[1]![i]!, newV[1]![i]!);
109
+ aesRoundTo(this.v[1]![i]!, this.v[2]![i]!, newV[2]![i]!);
110
+ aesRoundTo(this.v[2]![i]!, this.v[3]![i]!, newV[3]![i]!);
111
+ xorBlocksTo(this.v[4]![i]!, m1i, tmp);
112
+ aesRoundTo(this.v[3]![i]!, tmp, newV[4]![i]!);
113
+ aesRoundTo(this.v[4]![i]!, this.v[5]![i]!, newV[5]![i]!);
114
+ aesRoundTo(this.v[5]![i]!, this.v[6]![i]!, newV[6]![i]!);
115
+ aesRoundTo(this.v[6]![i]!, this.v[7]![i]!, newV[7]![i]!);
116
+ }
117
+
118
+ for (let j = 0; j < 8; j++) {
119
+ for (let i = 0; i < this.d; i++) {
120
+ this.v[j]![i]!.set(newV[j]![i]!);
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Absorbs an associated data block into the state.
127
+ * @param ai - Associated data block (32*degree bytes)
128
+ */
129
+ absorb(ai: Uint8Array): void {
130
+ const halfRateBytes = this.rate / 16;
131
+ const rateBytes = this.rate / 8;
132
+ this.update(
133
+ ai.subarray(0, halfRateBytes),
134
+ ai.subarray(halfRateBytes, rateBytes),
135
+ );
136
+ }
137
+
138
+ private computeZ(): void {
139
+ const z0 = this.z0;
140
+ const z1 = this.z1;
141
+ const tmp = this.tmp;
142
+
143
+ for (let i = 0; i < this.d; i++) {
144
+ const off = i * 16;
145
+ xorBlocksTo(this.v[6]![i]!, this.v[1]![i]!, z0.subarray(off, off + 16));
146
+ andBlocksTo(this.v[2]![i]!, this.v[3]![i]!, tmp);
147
+ for (let j = 0; j < 16; j++) z0[off + j] ^= tmp[j]!;
148
+
149
+ xorBlocksTo(this.v[2]![i]!, this.v[5]![i]!, z1.subarray(off, off + 16));
150
+ andBlocksTo(this.v[6]![i]!, this.v[7]![i]!, tmp);
151
+ for (let j = 0; j < 16; j++) z1[off + j] ^= tmp[j]!;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Encrypts a plaintext block and writes to output buffer.
157
+ * @param xi - Plaintext block (32*degree bytes)
158
+ * @param out - Output buffer (32*degree bytes)
159
+ */
160
+ encTo(xi: Uint8Array, out: Uint8Array): void {
161
+ this.computeZ();
162
+ const z0 = this.z0;
163
+ const z1 = this.z1;
164
+
165
+ const halfRateBytes = this.rate / 16;
166
+ const rateBytes = this.rate / 8;
167
+ const t0 = xi.subarray(0, halfRateBytes);
168
+ const t1 = xi.subarray(halfRateBytes, rateBytes);
169
+
170
+ for (let i = 0; i < halfRateBytes; i++) out[i] = t0[i]! ^ z0[i]!;
171
+ for (let i = 0; i < halfRateBytes; i++)
172
+ out[halfRateBytes + i] = t1[i]! ^ z1[i]!;
173
+
174
+ this.update(t0, t1);
175
+ }
176
+
177
+ /**
178
+ * Encrypts a plaintext block.
179
+ * @param xi - Plaintext block (32*degree bytes)
180
+ * @returns Ciphertext block of the same size
181
+ */
182
+ enc(xi: Uint8Array): Uint8Array {
183
+ const rateBytes = this.rate / 8;
184
+ const out = new Uint8Array(rateBytes);
185
+ this.encTo(xi, out);
186
+ return out;
187
+ }
188
+
189
+ /**
190
+ * Decrypts a ciphertext block and writes to output buffer.
191
+ * @param ci - Ciphertext block (32*degree bytes)
192
+ * @param out - Output buffer (32*degree bytes)
193
+ */
194
+ decTo(ci: Uint8Array, out: Uint8Array): void {
195
+ this.computeZ();
196
+ const z0 = this.z0;
197
+ const z1 = this.z1;
198
+
199
+ const halfRateBytes = this.rate / 16;
200
+ const rateBytes = this.rate / 8;
201
+ const t0 = ci.subarray(0, halfRateBytes);
202
+ const t1 = ci.subarray(halfRateBytes, rateBytes);
203
+
204
+ for (let i = 0; i < halfRateBytes; i++) out[i] = t0[i]! ^ z0[i]!;
205
+ for (let i = 0; i < halfRateBytes; i++)
206
+ out[halfRateBytes + i] = t1[i]! ^ z1[i]!;
207
+
208
+ this.update(
209
+ out.subarray(0, halfRateBytes),
210
+ out.subarray(halfRateBytes, rateBytes),
211
+ );
212
+ }
213
+
214
+ /**
215
+ * Decrypts a ciphertext block.
216
+ * @param ci - Ciphertext block (32*degree bytes)
217
+ * @returns Plaintext block of the same size
218
+ */
219
+ dec(ci: Uint8Array): Uint8Array {
220
+ const rateBytes = this.rate / 8;
221
+ const out = new Uint8Array(rateBytes);
222
+ this.decTo(ci, out);
223
+ return out;
224
+ }
225
+
226
+ /**
227
+ * Decrypts a partial (final) ciphertext block.
228
+ * @param cn - Partial ciphertext block (smaller than 32*degree bytes)
229
+ * @returns Decrypted plaintext of the same length
230
+ */
231
+ decPartial(cn: Uint8Array): Uint8Array {
232
+ this.computeZ();
233
+ const z0 = this.z0;
234
+ const z1 = this.z1;
235
+
236
+ const rateBytes = this.rate / 8;
237
+ const halfRateBytes = rateBytes / 2;
238
+ const padded = zeroPad(cn, rateBytes);
239
+ const t0 = padded.subarray(0, halfRateBytes);
240
+ const t1 = padded.subarray(halfRateBytes, rateBytes);
241
+
242
+ const out = new Uint8Array(rateBytes);
243
+ for (let i = 0; i < halfRateBytes; i++) out[i] = t0[i]! ^ z0[i]!;
244
+ for (let i = 0; i < halfRateBytes; i++)
245
+ out[halfRateBytes + i] = t1[i]! ^ z1[i]!;
246
+
247
+ const xn = new Uint8Array(out.subarray(0, cn.length));
248
+
249
+ const v = zeroPad(xn, rateBytes);
250
+ this.update(
251
+ v.subarray(0, halfRateBytes),
252
+ v.subarray(halfRateBytes, rateBytes),
253
+ );
254
+
255
+ return xn;
256
+ }
257
+
258
+ /**
259
+ * Finalizes encryption/decryption and produces an authentication tag.
260
+ * @param adLenBits - Associated data length in bits
261
+ * @param msgLenBits - Message length in bits
262
+ * @param tagLen - Tag length (16 or 32 bytes)
263
+ * @returns Authentication tag
264
+ */
265
+ finalize(
266
+ adLenBits: bigint,
267
+ msgLenBits: bigint,
268
+ tagLen: 16 | 32 = 16,
269
+ ): Uint8Array {
270
+ let t = new Uint8Array(0);
271
+ const u = concatBytes(le64(adLenBits), le64(msgLenBits));
272
+
273
+ for (let i = 0; i < this.d; i++) {
274
+ t = concatBytes(t, xorBlocks(this.v[2]![i]!, u));
275
+ }
276
+
277
+ for (let round = 0; round < 7; round++) {
278
+ this.update(t, t);
279
+ }
280
+
281
+ if (tagLen === 16) {
282
+ let tag = new Uint8Array(16);
283
+ for (let i = 0; i < this.d; i++) {
284
+ let ti = xorBlocks(this.v[0]![i]!, this.v[1]![i]!);
285
+ ti = xorBlocks(ti, this.v[2]![i]!);
286
+ ti = xorBlocks(ti, this.v[3]![i]!);
287
+ ti = xorBlocks(ti, this.v[4]![i]!);
288
+ ti = xorBlocks(ti, this.v[5]![i]!);
289
+ ti = xorBlocks(ti, this.v[6]![i]!);
290
+ tag = xorBlocks(tag, ti);
291
+ }
292
+ return tag;
293
+ } else {
294
+ let ti0 = new Uint8Array(16);
295
+ let ti1 = new Uint8Array(16);
296
+ for (let i = 0; i < this.d; i++) {
297
+ ti0 = xorBlocks(ti0, this.v[0]![i]!);
298
+ ti0 = xorBlocks(ti0, this.v[1]![i]!);
299
+ ti0 = xorBlocks(ti0, this.v[2]![i]!);
300
+ ti0 = xorBlocks(ti0, this.v[3]![i]!);
301
+ ti1 = xorBlocks(ti1, this.v[4]![i]!);
302
+ ti1 = xorBlocks(ti1, this.v[5]![i]!);
303
+ ti1 = xorBlocks(ti1, this.v[6]![i]!);
304
+ ti1 = xorBlocks(ti1, this.v[7]![i]!);
305
+ }
306
+ return concatBytes(ti0, ti1);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Finalizes MAC computation and produces an authentication tag.
312
+ * Uses a different finalization procedure than encryption/decryption.
313
+ * @param dataLenBits - Data length in bits
314
+ * @param tagLen - Tag length (16 or 32 bytes)
315
+ * @returns Authentication tag
316
+ */
317
+ finalizeMac(dataLenBits: bigint, tagLen: 16 | 32 = 16): Uint8Array {
318
+ let t = new Uint8Array(0);
319
+ const u = concatBytes(le64(dataLenBits), le64(BigInt(tagLen * 8)));
320
+
321
+ for (let i = 0; i < this.d; i++) {
322
+ t = concatBytes(t, xorBlocks(this.v[2]![i]!, u));
323
+ }
324
+
325
+ for (let round = 0; round < 7; round++) {
326
+ this.update(t, t);
327
+ }
328
+
329
+ let tags = new Uint8Array(0);
330
+ if (tagLen === 16) {
331
+ for (let i = 0; i < this.d; i++) {
332
+ let ti = xorBlocks(this.v[0]![i]!, this.v[1]![i]!);
333
+ ti = xorBlocks(ti, this.v[2]![i]!);
334
+ ti = xorBlocks(ti, this.v[3]![i]!);
335
+ ti = xorBlocks(ti, this.v[4]![i]!);
336
+ ti = xorBlocks(ti, this.v[5]![i]!);
337
+ ti = xorBlocks(ti, this.v[6]![i]!);
338
+ tags = concatBytes(tags, ti);
339
+ }
340
+ } else {
341
+ for (let i = 1; i < this.d; i++) {
342
+ let ti0 = xorBlocks(this.v[0]![i]!, this.v[1]![i]!);
343
+ ti0 = xorBlocks(ti0, this.v[2]![i]!);
344
+ ti0 = xorBlocks(ti0, this.v[3]![i]!);
345
+ let ti1 = xorBlocks(this.v[4]![i]!, this.v[5]![i]!);
346
+ ti1 = xorBlocks(ti1, this.v[6]![i]!);
347
+ ti1 = xorBlocks(ti1, this.v[7]![i]!);
348
+ tags = concatBytes(tags, ti0, ti1);
349
+ }
350
+ }
351
+
352
+ if (this.d > 1) {
353
+ for (let i = 0; i + 32 <= tags.length; i += 32) {
354
+ const tb = tags.subarray(i, i + 32);
355
+ const x0 = zeroPad(tb.subarray(0, 16), 16 * this.d);
356
+ const x1 = zeroPad(tb.subarray(16, 32), 16 * this.d);
357
+ this.absorb(concatBytes(x0, x1));
358
+ }
359
+
360
+ const u2 = concatBytes(le64(BigInt(this.d)), le64(BigInt(tagLen * 8)));
361
+ const t2 = zeroPad(xorBlocks(this.v[2]![0]!, u2), this.rate / 8);
362
+ for (let round = 0; round < 7; round++) {
363
+ this.update(t2, t2);
364
+ }
365
+ }
366
+
367
+ if (tagLen === 16) {
368
+ let tag = xorBlocks(this.v[0]![0]!, this.v[1]![0]!);
369
+ tag = xorBlocks(tag, this.v[2]![0]!);
370
+ tag = xorBlocks(tag, this.v[3]![0]!);
371
+ tag = xorBlocks(tag, this.v[4]![0]!);
372
+ tag = xorBlocks(tag, this.v[5]![0]!);
373
+ tag = xorBlocks(tag, this.v[6]![0]!);
374
+ return tag;
375
+ } else {
376
+ let t0 = xorBlocks(this.v[0]![0]!, this.v[1]![0]!);
377
+ t0 = xorBlocks(t0, this.v[2]![0]!);
378
+ t0 = xorBlocks(t0, this.v[3]![0]!);
379
+ let t1 = xorBlocks(this.v[4]![0]!, this.v[5]![0]!);
380
+ t1 = xorBlocks(t1, this.v[6]![0]!);
381
+ t1 = xorBlocks(t1, this.v[7]![0]!);
382
+ return concatBytes(t0, t1);
383
+ }
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Encrypts a message using AEGIS-128X.
389
+ * @param msg - Plaintext message
390
+ * @param ad - Associated data (authenticated but not encrypted)
391
+ * @param key - 16-byte encryption key
392
+ * @param nonce - 16-byte nonce (must be unique per message with the same key)
393
+ * @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
394
+ * @param degree - Parallelism degree (default: 2)
395
+ * @returns Object containing ciphertext and authentication tag
396
+ */
397
+ export function aegis128XEncrypt(
398
+ msg: Uint8Array,
399
+ ad: Uint8Array,
400
+ key: Uint8Array,
401
+ nonce: Uint8Array,
402
+ tagLen: 16 | 32 = 16,
403
+ degree: number = 2,
404
+ ): { ciphertext: Uint8Array; tag: Uint8Array } {
405
+ const state = new Aegis128XState(degree);
406
+ const rateBytes = (256 * degree) / 8;
407
+
408
+ state.init(key, nonce);
409
+
410
+ const adPadded = zeroPad(ad, rateBytes);
411
+ for (let i = 0; i + rateBytes <= adPadded.length; i += rateBytes) {
412
+ state.absorb(adPadded.subarray(i, i + rateBytes));
413
+ }
414
+
415
+ const msgPadded = zeroPad(msg, rateBytes);
416
+ const ct = new Uint8Array(msgPadded.length);
417
+ for (let i = 0; i + rateBytes <= msgPadded.length; i += rateBytes) {
418
+ state.encTo(
419
+ msgPadded.subarray(i, i + rateBytes),
420
+ ct.subarray(i, i + rateBytes),
421
+ );
422
+ }
423
+
424
+ const tag = state.finalize(
425
+ BigInt(ad.length * 8),
426
+ BigInt(msg.length * 8),
427
+ tagLen,
428
+ );
429
+ const ciphertext = new Uint8Array(ct.subarray(0, msg.length));
430
+
431
+ return { ciphertext, tag };
432
+ }
433
+
434
+ /**
435
+ * Decrypts a message using AEGIS-128X.
436
+ * @param ct - Ciphertext
437
+ * @param tag - Authentication tag (16 or 32 bytes)
438
+ * @param ad - Associated data (must match what was used during encryption)
439
+ * @param key - 16-byte encryption key
440
+ * @param nonce - 16-byte nonce (must match what was used during encryption)
441
+ * @param degree - Parallelism degree (default: 2)
442
+ * @returns Decrypted plaintext, or null if authentication fails
443
+ */
444
+ export function aegis128XDecrypt(
445
+ ct: Uint8Array,
446
+ tag: Uint8Array,
447
+ ad: Uint8Array,
448
+ key: Uint8Array,
449
+ nonce: Uint8Array,
450
+ degree: number = 2,
451
+ ): Uint8Array | null {
452
+ const tagLen = tag.length as 16 | 32;
453
+ const state = new Aegis128XState(degree);
454
+ const rateBytes = (256 * degree) / 8;
455
+
456
+ state.init(key, nonce);
457
+
458
+ const adPadded = zeroPad(ad, rateBytes);
459
+ for (let i = 0; i + rateBytes <= adPadded.length; i += rateBytes) {
460
+ state.absorb(adPadded.subarray(i, i + rateBytes));
461
+ }
462
+
463
+ const fullBlocksLen = Math.floor(ct.length / rateBytes) * rateBytes;
464
+ const cn = ct.subarray(fullBlocksLen);
465
+
466
+ const msg = new Uint8Array(fullBlocksLen + (cn.length > 0 ? cn.length : 0));
467
+ for (let i = 0; i + rateBytes <= ct.length; i += rateBytes) {
468
+ state.decTo(ct.subarray(i, i + rateBytes), msg.subarray(i, i + rateBytes));
469
+ }
470
+
471
+ if (cn.length > 0) {
472
+ msg.set(state.decPartial(cn), fullBlocksLen);
473
+ }
474
+
475
+ const expectedTag = state.finalize(
476
+ BigInt(ad.length * 8),
477
+ BigInt(msg.length * 8),
478
+ tagLen,
479
+ );
480
+
481
+ if (!constantTimeEqual(tag, expectedTag)) {
482
+ msg.fill(0);
483
+ return null;
484
+ }
485
+
486
+ return msg;
487
+ }
488
+
489
+ /** AEGIS-128X2 encryption (degree=2). */
490
+ export const aegis128X2Encrypt = (
491
+ msg: Uint8Array,
492
+ ad: Uint8Array,
493
+ key: Uint8Array,
494
+ nonce: Uint8Array,
495
+ tagLen: 16 | 32 = 16,
496
+ ) => aegis128XEncrypt(msg, ad, key, nonce, tagLen, 2);
497
+
498
+ /** AEGIS-128X2 decryption (degree=2). */
499
+ export const aegis128X2Decrypt = (
500
+ ct: Uint8Array,
501
+ tag: Uint8Array,
502
+ ad: Uint8Array,
503
+ key: Uint8Array,
504
+ nonce: Uint8Array,
505
+ ) => aegis128XDecrypt(ct, tag, ad, key, nonce, 2);
506
+
507
+ /** AEGIS-128X4 encryption (degree=4). */
508
+ export const aegis128X4Encrypt = (
509
+ msg: Uint8Array,
510
+ ad: Uint8Array,
511
+ key: Uint8Array,
512
+ nonce: Uint8Array,
513
+ tagLen: 16 | 32 = 16,
514
+ ) => aegis128XEncrypt(msg, ad, key, nonce, tagLen, 4);
515
+
516
+ /** AEGIS-128X4 decryption (degree=4). */
517
+ export const aegis128X4Decrypt = (
518
+ ct: Uint8Array,
519
+ tag: Uint8Array,
520
+ ad: Uint8Array,
521
+ key: Uint8Array,
522
+ nonce: Uint8Array,
523
+ ) => aegis128XDecrypt(ct, tag, ad, key, nonce, 4);
524
+
525
+ /**
526
+ * Computes a MAC (Message Authentication Code) using AEGIS-128X.
527
+ * @param data - Data to authenticate
528
+ * @param key - 16-byte key
529
+ * @param nonce - 16-byte nonce (optional, uses zero nonce if null)
530
+ * @param tagLen - Tag length: 16 or 32 bytes (default: 16)
531
+ * @param degree - Parallelism degree (default: 2)
532
+ * @returns Authentication tag
533
+ */
534
+ export function aegis128XMac(
535
+ data: Uint8Array,
536
+ key: Uint8Array,
537
+ nonce: Uint8Array | null = null,
538
+ tagLen: 16 | 32 = 16,
539
+ degree: number = 2,
540
+ ): Uint8Array {
541
+ const state = new Aegis128XState(degree);
542
+ const rateBytes = (256 * degree) / 8;
543
+
544
+ state.init(key, nonce ?? new Uint8Array(16));
545
+
546
+ const dataPadded = zeroPad(data, rateBytes);
547
+ for (let i = 0; i + rateBytes <= dataPadded.length; i += rateBytes) {
548
+ state.absorb(dataPadded.subarray(i, i + rateBytes));
549
+ }
550
+
551
+ return state.finalizeMac(BigInt(data.length * 8), tagLen);
552
+ }
553
+
554
+ /**
555
+ * Verifies a MAC computed using AEGIS-128X.
556
+ * @param data - Data to verify
557
+ * @param tag - Expected authentication tag (16 or 32 bytes)
558
+ * @param key - 16-byte key
559
+ * @param nonce - 16-byte nonce (optional, uses zero nonce if null)
560
+ * @param degree - Parallelism degree (default: 2)
561
+ * @returns True if the tag is valid, false otherwise
562
+ */
563
+ export function aegis128XMacVerify(
564
+ data: Uint8Array,
565
+ tag: Uint8Array,
566
+ key: Uint8Array,
567
+ nonce: Uint8Array | null = null,
568
+ degree: number = 2,
569
+ ): boolean {
570
+ const tagLen = tag.length as 16 | 32;
571
+ const expectedTag = aegis128XMac(data, key, nonce, tagLen, degree);
572
+ return constantTimeEqual(tag, expectedTag);
573
+ }
574
+
575
+ /** AEGIS-128X2 MAC computation (degree=2). */
576
+ export const aegis128X2Mac = (
577
+ data: Uint8Array,
578
+ key: Uint8Array,
579
+ nonce: Uint8Array | null = null,
580
+ tagLen: 16 | 32 = 16,
581
+ ) => aegis128XMac(data, key, nonce, tagLen, 2);
582
+
583
+ /** AEGIS-128X2 MAC verification (degree=2). */
584
+ export const aegis128X2MacVerify = (
585
+ data: Uint8Array,
586
+ tag: Uint8Array,
587
+ key: Uint8Array,
588
+ nonce: Uint8Array | null = null,
589
+ ) => aegis128XMacVerify(data, tag, key, nonce, 2);
590
+
591
+ /** AEGIS-128X4 MAC computation (degree=4). */
592
+ export const aegis128X4Mac = (
593
+ data: Uint8Array,
594
+ key: Uint8Array,
595
+ nonce: Uint8Array | null = null,
596
+ tagLen: 16 | 32 = 16,
597
+ ) => aegis128XMac(data, key, nonce, tagLen, 4);
598
+
599
+ /** AEGIS-128X4 MAC verification (degree=4). */
600
+ export const aegis128X4MacVerify = (
601
+ data: Uint8Array,
602
+ tag: Uint8Array,
603
+ key: Uint8Array,
604
+ nonce: Uint8Array | null = null,
605
+ ) => aegis128XMacVerify(data, tag, key, nonce, 4);