aegis-aead 0.2.0 → 0.2.2

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.
@@ -19,6 +19,7 @@ import {
19
19
  pack,
20
20
  pack04,
21
21
  unpack,
22
+ unpack04,
22
23
  wordIdx,
23
24
  } from "./aes-bs.js";
24
25
  import { randomBytes } from "./random.js";
@@ -34,11 +35,13 @@ const C1: AesBlock = new Uint32Array([
34
35
 
35
36
  /**
36
37
  * Bitsliced AEGIS-128L cipher state.
37
- * Uses 8 AES blocks (128 bytes) stored in bitsliced form (32 uint32 words).
38
+ * The state is kept in packed bitsliced form between operations so that
39
+ * pack/unpack does not need to be applied at every round.
38
40
  */
39
41
  export class Aegis128LBsState {
40
42
  private st: AesBlocks;
41
43
  private st1: AesBlocks;
44
+ private constantInput: AesBlocks;
42
45
  private tmp0: AesBlock;
43
46
  private tmp1: AesBlock;
44
47
  private z0: AesBlock;
@@ -47,38 +50,14 @@ export class Aegis128LBsState {
47
50
  constructor() {
48
51
  this.st = createAesBlocks();
49
52
  this.st1 = createAesBlocks();
53
+ this.constantInput = createAesBlocks();
50
54
  this.tmp0 = createAesBlock();
51
55
  this.tmp1 = createAesBlock();
52
56
  this.z0 = createAesBlock();
53
57
  this.z1 = createAesBlock();
54
58
  }
55
59
 
56
- /**
57
- * AEGIS round function: applies AES round to all blocks and rotates.
58
- * st[i] = AES(st[i]) ^ st[(i-1) mod 8]
59
- */
60
- private aegisRound(): void {
61
- const st = this.st;
62
- const st1 = this.st1;
63
-
64
- st1.set(st);
65
- pack(st1);
66
- aesRound(st1);
67
- unpack(st1);
68
-
69
- for (let i = 0; i < 8; i++) {
70
- const prev = (i + 7) % 8;
71
- st[wordIdx(i, 0)] = (st[wordIdx(i, 0)]! ^ st1[wordIdx(prev, 0)]!) >>> 0;
72
- st[wordIdx(i, 1)] = (st[wordIdx(i, 1)]! ^ st1[wordIdx(prev, 1)]!) >>> 0;
73
- st[wordIdx(i, 2)] = (st[wordIdx(i, 2)]! ^ st1[wordIdx(prev, 2)]!) >>> 0;
74
- st[wordIdx(i, 3)] = (st[wordIdx(i, 3)]! ^ st1[wordIdx(prev, 3)]!) >>> 0;
75
- }
76
- }
77
-
78
- /**
79
- * AEGIS round function with constant input (used in packed mode).
80
- */
81
- private aegisRoundPacked(constantInput: AesBlocks): void {
60
+ private aegisRoundPacked(): void {
82
61
  const st = this.st;
83
62
  const st1 = this.st1;
84
63
 
@@ -86,13 +65,11 @@ export class Aegis128LBsState {
86
65
  aesRound(st1);
87
66
  blocksRotr(st1);
88
67
  blocksXor(st, st1);
89
- blocksXor(st, constantInput);
68
+ blocksXor(st, this.constantInput);
90
69
  }
91
70
 
92
- /**
93
- * Pack constant input for blocks 0 and 4.
94
- */
95
- private packConstantInput(out: AesBlocks, m0: AesBlock, m1: AesBlock): void {
71
+ private packConstantInput(m0: AesBlock, m1: AesBlock): void {
72
+ const out = this.constantInput;
96
73
  out.fill(0);
97
74
  blocksPut(out, m0, 0);
98
75
  blocksPut(out, m1, 4);
@@ -100,27 +77,35 @@ export class Aegis128LBsState {
100
77
  }
101
78
 
102
79
  /**
103
- * Absorb rate: XOR message blocks into state positions 0 and 4.
80
+ * Extract the keystream blocks z0 and z1 directly from the packed state.
81
+ *
82
+ * z0 = S6 ^ S1 ^ (S2 & S3)
83
+ * z1 = S2 ^ S5 ^ (S6 & S7)
84
+ *
85
+ * The formula is evaluated lane-wise on the packed state, the result is
86
+ * placed in positions 0 (z0) and 4 (z1), then unpack04 extracts the two
87
+ * AES blocks.
104
88
  */
105
- private absorbRate(m0: AesBlock, m1: AesBlock): void {
89
+ private keystreamPacked(): void {
106
90
  const st = this.st;
107
- st[wordIdx(0, 0)] = (st[wordIdx(0, 0)]! ^ m0[0]!) >>> 0;
108
- st[wordIdx(0, 1)] = (st[wordIdx(0, 1)]! ^ m0[1]!) >>> 0;
109
- st[wordIdx(0, 2)] = (st[wordIdx(0, 2)]! ^ m0[2]!) >>> 0;
110
- st[wordIdx(0, 3)] = (st[wordIdx(0, 3)]! ^ m0[3]!) >>> 0;
111
-
112
- st[wordIdx(4, 0)] = (st[wordIdx(4, 0)]! ^ m1[0]!) >>> 0;
113
- st[wordIdx(4, 1)] = (st[wordIdx(4, 1)]! ^ m1[1]!) >>> 0;
114
- st[wordIdx(4, 2)] = (st[wordIdx(4, 2)]! ^ m1[2]!) >>> 0;
115
- st[wordIdx(4, 3)] = (st[wordIdx(4, 3)]! ^ m1[3]!) >>> 0;
116
- }
117
-
118
- /**
119
- * Update state with two message blocks.
120
- */
121
- private update(m0: AesBlock, m1: AesBlock): void {
122
- this.aegisRound();
123
- this.absorbRate(m0, m1);
91
+ const z = this.st1;
92
+ for (let i = 0; i < 32; i++) {
93
+ const x = st[i]!;
94
+ z[i] =
95
+ ((x & 0x02020202) << 6) ^
96
+ ((x & 0x40404040) << 1) ^
97
+ ((x & (x >>> 1) & 0x10101010) << 3) ^
98
+ ((x & 0x20202020) >>> 2) ^
99
+ ((x & 0x04040404) << 1) ^
100
+ ((x & (x >>> 1) & 0x01010101) << 3);
101
+ }
102
+ unpack04(z);
103
+ const z0 = this.z0;
104
+ const z1 = this.z1;
105
+ for (let i = 0; i < 4; i++) {
106
+ z0[i] = z[wordIdx(0, i)]!;
107
+ z1[i] = z[wordIdx(4, i)]!;
108
+ }
124
109
  }
125
110
 
126
111
  /**
@@ -150,13 +135,11 @@ export class Aegis128LBsState {
150
135
  blocksPut(this.st, kc1, 6);
151
136
  blocksPut(this.st, kc0, 7);
152
137
 
153
- const constantInput = createAesBlocks();
154
- this.packConstantInput(constantInput, n, k);
138
+ this.packConstantInput(n, k);
155
139
  pack(this.st);
156
140
  for (let i = 0; i < 10; i++) {
157
- this.aegisRoundPacked(constantInput);
141
+ this.aegisRoundPacked();
158
142
  }
159
- unpack(this.st);
160
143
  }
161
144
 
162
145
  /**
@@ -168,7 +151,8 @@ export class Aegis128LBsState {
168
151
  const msg1 = this.tmp1;
169
152
  blockFromBytes(msg0, ai.subarray(0, 16));
170
153
  blockFromBytes(msg1, ai.subarray(16, 32));
171
- this.update(msg0, msg1);
154
+ this.packConstantInput(msg0, msg1);
155
+ this.aegisRoundPacked();
172
156
  }
173
157
 
174
158
  /**
@@ -177,39 +161,22 @@ export class Aegis128LBsState {
177
161
  * @param out - 32-byte output buffer
178
162
  */
179
163
  encTo(xi: Uint8Array, out: Uint8Array): void {
180
- const st = this.st;
181
- const z0 = this.z0;
182
- const z1 = this.z1;
183
164
  const t0 = this.tmp0;
184
165
  const t1 = this.tmp1;
185
166
 
186
- for (let i = 0; i < 4; i++) {
187
- z0[i] =
188
- (st[wordIdx(6, i)]! ^
189
- st[wordIdx(1, i)]! ^
190
- (st[wordIdx(2, i)]! & st[wordIdx(3, i)]!)) >>>
191
- 0;
192
- }
193
- for (let i = 0; i < 4; i++) {
194
- z1[i] =
195
- (st[wordIdx(2, i)]! ^
196
- st[wordIdx(5, i)]! ^
197
- (st[wordIdx(6, i)]! & st[wordIdx(7, i)]!)) >>>
198
- 0;
199
- }
167
+ this.keystreamPacked();
200
168
 
201
169
  blockFromBytes(t0, xi.subarray(0, 16));
202
170
  blockFromBytes(t1, xi.subarray(16, 32));
203
171
 
204
- const out0 = createAesBlock();
205
- const out1 = createAesBlock();
206
- blockXor(out0, t0, z0);
207
- blockXor(out1, t1, z1);
172
+ this.packConstantInput(t0, t1);
173
+ this.aegisRoundPacked();
208
174
 
209
- blockToBytes(out.subarray(0, 16), out0);
210
- blockToBytes(out.subarray(16, 32), out1);
175
+ blockXor(t0, t0, this.z0);
176
+ blockXor(t1, t1, this.z1);
211
177
 
212
- this.update(t0, t1);
178
+ blockToBytes(out.subarray(0, 16), t0);
179
+ blockToBytes(out.subarray(16, 32), t1);
213
180
  }
214
181
 
215
182
  /**
@@ -229,31 +196,18 @@ export class Aegis128LBsState {
229
196
  * @param out - 32-byte output buffer
230
197
  */
231
198
  decTo(ci: Uint8Array, out: Uint8Array): void {
232
- const st = this.st;
233
199
  const msg0 = this.tmp0;
234
200
  const msg1 = this.tmp1;
235
201
 
236
202
  blockFromBytes(msg0, ci.subarray(0, 16));
237
203
  blockFromBytes(msg1, ci.subarray(16, 32));
238
204
 
239
- for (let i = 0; i < 4; i++) {
240
- msg0[i] =
241
- (msg0[i]! ^
242
- st[wordIdx(6, i)]! ^
243
- st[wordIdx(1, i)]! ^
244
- (st[wordIdx(2, i)]! & st[wordIdx(3, i)]!)) >>>
245
- 0;
246
- }
247
- for (let i = 0; i < 4; i++) {
248
- msg1[i] =
249
- (msg1[i]! ^
250
- st[wordIdx(2, i)]! ^
251
- st[wordIdx(5, i)]! ^
252
- (st[wordIdx(6, i)]! & st[wordIdx(7, i)]!)) >>>
253
- 0;
254
- }
205
+ this.keystreamPacked();
206
+ blockXor(msg0, msg0, this.z0);
207
+ blockXor(msg1, msg1, this.z1);
255
208
 
256
- this.update(msg0, msg1);
209
+ this.packConstantInput(msg0, msg1);
210
+ this.aegisRoundPacked();
257
211
 
258
212
  blockToBytes(out.subarray(0, 16), msg0);
259
213
  blockToBytes(out.subarray(16, 32), msg1);
@@ -270,13 +224,20 @@ export class Aegis128LBsState {
270
224
  return out;
271
225
  }
272
226
 
227
+ encInPlace(block: Uint8Array): void {
228
+ this.encTo(block, block);
229
+ }
230
+
231
+ decInPlace(block: Uint8Array): void {
232
+ this.decTo(block, block);
233
+ }
234
+
273
235
  /**
274
236
  * Decrypts a partial (final) ciphertext block smaller than 32 bytes.
275
237
  * @param cn - Partial ciphertext block (1-31 bytes)
276
238
  * @returns Decrypted plaintext of the same length
277
239
  */
278
240
  decPartial(cn: Uint8Array): Uint8Array {
279
- const st = this.st;
280
241
  const msg0 = this.tmp0;
281
242
  const msg1 = this.tmp1;
282
243
 
@@ -284,22 +245,9 @@ export class Aegis128LBsState {
284
245
  blockFromBytes(msg0, padded.subarray(0, 16));
285
246
  blockFromBytes(msg1, padded.subarray(16, 32));
286
247
 
287
- for (let i = 0; i < 4; i++) {
288
- msg0[i] =
289
- (msg0[i]! ^
290
- st[wordIdx(6, i)]! ^
291
- st[wordIdx(1, i)]! ^
292
- (st[wordIdx(2, i)]! & st[wordIdx(3, i)]!)) >>>
293
- 0;
294
- }
295
- for (let i = 0; i < 4; i++) {
296
- msg1[i] =
297
- (msg1[i]! ^
298
- st[wordIdx(2, i)]! ^
299
- st[wordIdx(5, i)]! ^
300
- (st[wordIdx(6, i)]! & st[wordIdx(7, i)]!)) >>>
301
- 0;
302
- }
248
+ this.keystreamPacked();
249
+ blockXor(msg0, msg0, this.z0);
250
+ blockXor(msg1, msg1, this.z1);
303
251
 
304
252
  const pad = new Uint8Array(RATE);
305
253
  blockToBytes(pad.subarray(0, 16), msg0);
@@ -311,8 +259,8 @@ export class Aegis128LBsState {
311
259
  blockFromBytes(msg0, pad.subarray(0, 16));
312
260
  blockFromBytes(msg1, pad.subarray(16, 32));
313
261
 
314
- this.aegisRound();
315
- this.absorbRate(msg0, msg1);
262
+ this.packConstantInput(msg0, msg1);
263
+ this.aegisRoundPacked();
316
264
 
317
265
  return xn;
318
266
  }
@@ -333,18 +281,20 @@ export class Aegis128LBsState {
333
281
  tmp[2] = ((msgLen * 8) & 0xffffffff) >>> 0;
334
282
  tmp[3] = Math.floor((msgLen * 8) / 0x100000000) >>> 0;
335
283
 
336
- tmp[0] = (tmp[0]! ^ st[wordIdx(2, 0)]!) >>> 0;
337
- tmp[1] = (tmp[1]! ^ st[wordIdx(2, 1)]!) >>> 0;
338
- tmp[2] = (tmp[2]! ^ st[wordIdx(2, 2)]!) >>> 0;
339
- tmp[3] = (tmp[3]! ^ st[wordIdx(2, 3)]!) >>> 0;
284
+ const unpacked = this.st1;
285
+ unpacked.set(st);
286
+ unpack(unpacked);
340
287
 
341
- const constantInput = createAesBlocks();
342
- this.packConstantInput(constantInput, tmp, tmp);
343
- pack(this.st);
288
+ tmp[0] = (tmp[0]! ^ unpacked[wordIdx(2, 0)]!) >>> 0;
289
+ tmp[1] = (tmp[1]! ^ unpacked[wordIdx(2, 1)]!) >>> 0;
290
+ tmp[2] = (tmp[2]! ^ unpacked[wordIdx(2, 2)]!) >>> 0;
291
+ tmp[3] = (tmp[3]! ^ unpacked[wordIdx(2, 3)]!) >>> 0;
292
+
293
+ this.packConstantInput(tmp, tmp);
344
294
  for (let i = 0; i < 7; i++) {
345
- this.aegisRoundPacked(constantInput);
295
+ this.aegisRoundPacked();
346
296
  }
347
- unpack(this.st);
297
+ unpack(st);
348
298
 
349
299
  if (tagLen === 16) {
350
300
  const tag = new Uint8Array(16);
@@ -477,18 +427,89 @@ export function aegis128LBsDecryptDetached(
477
427
  return msg;
478
428
  }
479
429
 
430
+ /**
431
+ * Encrypts a message in-place using bitsliced AEGIS-128L (detached mode).
432
+ */
433
+ export function aegis128LBsEncryptDetachedInPlace(
434
+ data: Uint8Array,
435
+ ad: Uint8Array,
436
+ key: Uint8Array,
437
+ nonce: Uint8Array,
438
+ tagLen: 16 | 32 = 16,
439
+ ): Uint8Array {
440
+ const state = new Aegis128LBsState();
441
+ state.init(key, nonce);
442
+
443
+ const adPadded = zeroPad(ad, RATE);
444
+ for (let i = 0; i + RATE <= adPadded.length; i += RATE) {
445
+ state.absorb(adPadded.subarray(i, i + RATE));
446
+ }
447
+
448
+ const msgLen = data.length;
449
+ const fullBlocksLen = Math.floor(msgLen / RATE) * RATE;
450
+
451
+ for (let i = 0; i < fullBlocksLen; i += RATE) {
452
+ state.encInPlace(data.subarray(i, i + RATE));
453
+ }
454
+
455
+ if (msgLen > fullBlocksLen) {
456
+ const lastPartial = data.subarray(fullBlocksLen);
457
+ const lastBlock = zeroPad(lastPartial, RATE);
458
+ const encBlock = state.enc(lastBlock);
459
+ lastPartial.set(encBlock.subarray(0, lastPartial.length));
460
+ }
461
+
462
+ return state.finalize(ad.length, msgLen, tagLen);
463
+ }
464
+
465
+ /**
466
+ * Decrypts a message in-place using bitsliced AEGIS-128L (detached mode).
467
+ */
468
+ export function aegis128LBsDecryptDetachedInPlace(
469
+ data: Uint8Array,
470
+ tag: Uint8Array,
471
+ ad: Uint8Array,
472
+ key: Uint8Array,
473
+ nonce: Uint8Array,
474
+ ): boolean {
475
+ const tagLen = tag.length as 16 | 32;
476
+ const state = new Aegis128LBsState();
477
+ state.init(key, nonce);
478
+
479
+ const adPadded = zeroPad(ad, RATE);
480
+ for (let i = 0; i + RATE <= adPadded.length; i += RATE) {
481
+ state.absorb(adPadded.subarray(i, i + RATE));
482
+ }
483
+
484
+ const msgLen = data.length;
485
+ const fullBlocksLen = Math.floor(msgLen / RATE) * RATE;
486
+
487
+ for (let i = 0; i < fullBlocksLen; i += RATE) {
488
+ state.decInPlace(data.subarray(i, i + RATE));
489
+ }
490
+
491
+ if (msgLen > fullBlocksLen) {
492
+ const lastPartial = data.subarray(fullBlocksLen);
493
+ const decrypted = state.decPartial(lastPartial);
494
+ lastPartial.set(decrypted);
495
+ }
496
+
497
+ const expectedTag = state.finalize(ad.length, msgLen, tagLen);
498
+
499
+ if (!constantTimeEqual(tag, expectedTag)) {
500
+ data.fill(0);
501
+ return false;
502
+ }
503
+
504
+ return true;
505
+ }
506
+
480
507
  export const AEGIS_128L_BS_NONCE_SIZE = 16;
481
508
  export const AEGIS_128L_BS_KEY_SIZE = 16;
482
509
 
483
510
  /**
484
511
  * Encrypts a message using bitsliced AEGIS-128L.
485
512
  * Returns a single buffer containing nonce || ciphertext || tag.
486
- * @param msg - Plaintext message
487
- * @param ad - Associated data (authenticated but not encrypted)
488
- * @param key - 16-byte encryption key
489
- * @param nonce - 16-byte nonce (optional, generates random nonce if not provided)
490
- * @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
491
- * @returns Concatenated nonce || ciphertext || tag
492
513
  */
493
514
  export function aegis128LBsEncrypt(
494
515
  msg: Uint8Array,
@@ -519,11 +540,6 @@ export function aegis128LBsEncrypt(
519
540
  /**
520
541
  * Decrypts a message using bitsliced AEGIS-128L.
521
542
  * Expects input as nonce || ciphertext || tag.
522
- * @param sealed - Concatenated nonce || ciphertext || tag
523
- * @param ad - Associated data (must match what was used during encryption)
524
- * @param key - 16-byte encryption key
525
- * @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
526
- * @returns Decrypted plaintext, or null if authentication fails
527
543
  */
528
544
  export function aegis128LBsDecrypt(
529
545
  sealed: Uint8Array,
@@ -542,12 +558,7 @@ export function aegis128LBsDecrypt(
542
558
  }
543
559
 
544
560
  /**
545
- * Computes a MAC (Message Authentication Code) using bitsliced AEGIS-128L.
546
- * @param data - Data to authenticate
547
- * @param key - 16-byte key
548
- * @param nonce - 16-byte nonce (optional, uses zero nonce if null)
549
- * @param tagLen - Tag length: 16 or 32 bytes (default: 16)
550
- * @returns Authentication tag
561
+ * Computes a MAC using bitsliced AEGIS-128L.
551
562
  */
552
563
  export function aegis128LBsMac(
553
564
  data: Uint8Array,
@@ -568,11 +579,6 @@ export function aegis128LBsMac(
568
579
 
569
580
  /**
570
581
  * Verifies a MAC computed using bitsliced AEGIS-128L.
571
- * @param data - Data to verify
572
- * @param tag - Expected authentication tag (16 or 32 bytes)
573
- * @param key - 16-byte key
574
- * @param nonce - 16-byte nonce (optional, uses zero nonce if null)
575
- * @returns True if the tag is valid, false otherwise
576
582
  */
577
583
  export function aegis128LBsMacVerify(
578
584
  data: Uint8Array,
@@ -585,18 +591,10 @@ export function aegis128LBsMacVerify(
585
591
  return constantTimeEqual(tag, expectedTag);
586
592
  }
587
593
 
588
- /**
589
- * Generates a random 16-byte key for bitsliced AEGIS-128L.
590
- * @returns 16-byte encryption key
591
- */
592
594
  export function aegis128LBsCreateKey(): Uint8Array {
593
595
  return randomBytes(AEGIS_128L_BS_KEY_SIZE);
594
596
  }
595
597
 
596
- /**
597
- * Generates a random 16-byte nonce for bitsliced AEGIS-128L.
598
- * @returns 16-byte nonce
599
- */
600
598
  export function aegis128LBsCreateNonce(): Uint8Array {
601
599
  return randomBytes(AEGIS_128L_BS_NONCE_SIZE);
602
600
  }
package/src/aegis128l.ts CHANGED
@@ -204,6 +204,37 @@ export class Aegis128LState {
204
204
  return out;
205
205
  }
206
206
 
207
+ /**
208
+ * Encrypts a 32-byte plaintext block in-place.
209
+ * @param block - 32-byte buffer (plaintext in, ciphertext out)
210
+ */
211
+ encInPlace(block: Uint8Array): void {
212
+ const z0 = this.z0;
213
+ const z1 = this.z1;
214
+ const tmp = this.tmp0;
215
+
216
+ xorBlocksTo(this.s1, this.s6, z0);
217
+ andBlocksTo(this.s2, this.s3, tmp);
218
+ for (let i = 0; i < 16; i++) z0[i] ^= tmp[i]!;
219
+
220
+ xorBlocksTo(this.s2, this.s5, z1);
221
+ andBlocksTo(this.s6, this.s7, tmp);
222
+ for (let i = 0; i < 16; i++) z1[i] ^= tmp[i]!;
223
+
224
+ this.update(block.subarray(0, 16), block.subarray(16, 32));
225
+
226
+ for (let i = 0; i < 16; i++) block[i] ^= z0[i]!;
227
+ for (let i = 0; i < 16; i++) block[16 + i] ^= z1[i]!;
228
+ }
229
+
230
+ /**
231
+ * Decrypts a 32-byte ciphertext block in-place.
232
+ * @param block - 32-byte buffer (ciphertext in, plaintext out)
233
+ */
234
+ decInPlace(block: Uint8Array): void {
235
+ this.decTo(block, block);
236
+ }
237
+
207
238
  /**
208
239
  * Decrypts a partial (final) ciphertext block smaller than 32 bytes.
209
240
  * @param cn - Partial ciphertext block (1-31 bytes)
@@ -376,6 +407,101 @@ export function aegis128LDecryptDetached(
376
407
  return msg;
377
408
  }
378
409
 
410
+ /**
411
+ * Encrypts a message in-place using AEGIS-128L (detached mode).
412
+ * The input buffer is modified to contain the ciphertext.
413
+ * @param data - Buffer containing plaintext (will be overwritten with ciphertext)
414
+ * @param ad - Associated data (authenticated but not encrypted)
415
+ * @param key - 16-byte encryption key
416
+ * @param nonce - 16-byte nonce (must be unique per message with the same key)
417
+ * @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
418
+ * @returns Authentication tag
419
+ */
420
+ export function aegis128LEncryptDetachedInPlace(
421
+ data: Uint8Array,
422
+ ad: Uint8Array,
423
+ key: Uint8Array,
424
+ nonce: Uint8Array,
425
+ tagLen: 16 | 32 = 16,
426
+ ): Uint8Array {
427
+ const state = new Aegis128LState();
428
+ state.init(key, nonce);
429
+
430
+ const adPadded = zeroPad(ad, 32);
431
+ for (let i = 0; i + 32 <= adPadded.length; i += 32) {
432
+ state.absorb(adPadded.subarray(i, i + 32));
433
+ }
434
+
435
+ const msgLen = data.length;
436
+ const fullBlocksLen = Math.floor(msgLen / 32) * 32;
437
+
438
+ for (let i = 0; i < fullBlocksLen; i += 32) {
439
+ state.encInPlace(data.subarray(i, i + 32));
440
+ }
441
+
442
+ if (msgLen > fullBlocksLen) {
443
+ const lastPartial = data.subarray(fullBlocksLen);
444
+ const lastBlock = zeroPad(lastPartial, 32);
445
+ const encBlock = state.enc(lastBlock);
446
+ lastPartial.set(encBlock.subarray(0, lastPartial.length));
447
+ }
448
+
449
+ return state.finalize(BigInt(ad.length * 8), BigInt(msgLen * 8), tagLen);
450
+ }
451
+
452
+ /**
453
+ * Decrypts a message in-place using AEGIS-128L (detached mode).
454
+ * The input buffer is modified to contain the plaintext (or zeroed on failure).
455
+ * @param data - Buffer containing ciphertext (will be overwritten with plaintext)
456
+ * @param tag - Authentication tag (16 or 32 bytes)
457
+ * @param ad - Associated data (must match what was used during encryption)
458
+ * @param key - 16-byte encryption key
459
+ * @param nonce - 16-byte nonce (must match what was used during encryption)
460
+ * @returns True if authentication succeeds, false otherwise
461
+ */
462
+ export function aegis128LDecryptDetachedInPlace(
463
+ data: Uint8Array,
464
+ tag: Uint8Array,
465
+ ad: Uint8Array,
466
+ key: Uint8Array,
467
+ nonce: Uint8Array,
468
+ ): boolean {
469
+ const tagLen = tag.length as 16 | 32;
470
+ const state = new Aegis128LState();
471
+ state.init(key, nonce);
472
+
473
+ const adPadded = zeroPad(ad, 32);
474
+ for (let i = 0; i + 32 <= adPadded.length; i += 32) {
475
+ state.absorb(adPadded.subarray(i, i + 32));
476
+ }
477
+
478
+ const msgLen = data.length;
479
+ const fullBlocksLen = Math.floor(msgLen / 32) * 32;
480
+
481
+ for (let i = 0; i < fullBlocksLen; i += 32) {
482
+ state.decInPlace(data.subarray(i, i + 32));
483
+ }
484
+
485
+ if (msgLen > fullBlocksLen) {
486
+ const lastPartial = data.subarray(fullBlocksLen);
487
+ const decrypted = state.decPartial(lastPartial);
488
+ lastPartial.set(decrypted);
489
+ }
490
+
491
+ const expectedTag = state.finalize(
492
+ BigInt(ad.length * 8),
493
+ BigInt(msgLen * 8),
494
+ tagLen,
495
+ );
496
+
497
+ if (!constantTimeEqual(tag, expectedTag)) {
498
+ data.fill(0);
499
+ return false;
500
+ }
501
+
502
+ return true;
503
+ }
504
+
379
505
  /** Nonce size for AEGIS-128L in bytes. */
380
506
  export const AEGIS_128L_NONCE_SIZE = 16;
381
507