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.
package/src/aegis128x.ts CHANGED
@@ -224,6 +224,35 @@ export class Aegis128XState {
224
224
  return out;
225
225
  }
226
226
 
227
+ /**
228
+ * Encrypts a plaintext block in-place.
229
+ * @param block - Buffer (plaintext in, ciphertext out), size 32*degree bytes
230
+ */
231
+ encInPlace(block: Uint8Array): void {
232
+ this.computeZ();
233
+ const z0 = this.z0;
234
+ const z1 = this.z1;
235
+
236
+ const halfRateBytes = this.rate / 16;
237
+ const rateBytes = this.rate / 8;
238
+
239
+ this.update(
240
+ block.subarray(0, halfRateBytes),
241
+ block.subarray(halfRateBytes, rateBytes),
242
+ );
243
+
244
+ for (let i = 0; i < halfRateBytes; i++) block[i] ^= z0[i]!;
245
+ for (let i = 0; i < halfRateBytes; i++) block[halfRateBytes + i] ^= z1[i]!;
246
+ }
247
+
248
+ /**
249
+ * Decrypts a ciphertext block in-place.
250
+ * @param block - Buffer (ciphertext in, plaintext out), size 32*degree bytes
251
+ */
252
+ decInPlace(block: Uint8Array): void {
253
+ this.decTo(block, block);
254
+ }
255
+
227
256
  /**
228
257
  * Decrypts a partial (final) ciphertext block.
229
258
  * @param cn - Partial ciphertext block (smaller than 32*degree bytes)
@@ -487,6 +516,145 @@ export function aegis128XDecryptDetached(
487
516
  return msg;
488
517
  }
489
518
 
519
+ /**
520
+ * Encrypts a message in-place using AEGIS-128X (detached mode).
521
+ * The input buffer is modified to contain the ciphertext.
522
+ * @param data - Buffer containing plaintext (will be overwritten with ciphertext)
523
+ * @param ad - Associated data (authenticated but not encrypted)
524
+ * @param key - 16-byte encryption key
525
+ * @param nonce - 16-byte nonce (must be unique per message with the same key)
526
+ * @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
527
+ * @param degree - Parallelism degree (default: 2)
528
+ * @returns Authentication tag
529
+ */
530
+ export function aegis128XEncryptDetachedInPlace(
531
+ data: Uint8Array,
532
+ ad: Uint8Array,
533
+ key: Uint8Array,
534
+ nonce: Uint8Array,
535
+ tagLen: 16 | 32 = 16,
536
+ degree: number = 2,
537
+ ): Uint8Array {
538
+ const state = new Aegis128XState(degree);
539
+ const rateBytes = (256 * degree) / 8;
540
+
541
+ state.init(key, nonce);
542
+
543
+ const adPadded = zeroPad(ad, rateBytes);
544
+ for (let i = 0; i + rateBytes <= adPadded.length; i += rateBytes) {
545
+ state.absorb(adPadded.subarray(i, i + rateBytes));
546
+ }
547
+
548
+ const msgLen = data.length;
549
+ const fullBlocksLen = Math.floor(msgLen / rateBytes) * rateBytes;
550
+
551
+ for (let i = 0; i < fullBlocksLen; i += rateBytes) {
552
+ state.encInPlace(data.subarray(i, i + rateBytes));
553
+ }
554
+
555
+ if (msgLen > fullBlocksLen) {
556
+ const lastPartial = data.subarray(fullBlocksLen);
557
+ const lastBlock = zeroPad(lastPartial, rateBytes);
558
+ const encBlock = state.enc(lastBlock);
559
+ lastPartial.set(encBlock.subarray(0, lastPartial.length));
560
+ }
561
+
562
+ return state.finalize(BigInt(ad.length * 8), BigInt(msgLen * 8), tagLen);
563
+ }
564
+
565
+ /**
566
+ * Decrypts a message in-place using AEGIS-128X (detached mode).
567
+ * The input buffer is modified to contain the plaintext (or zeroed on failure).
568
+ * @param data - Buffer containing ciphertext (will be overwritten with plaintext)
569
+ * @param tag - Authentication tag (16 or 32 bytes)
570
+ * @param ad - Associated data (must match what was used during encryption)
571
+ * @param key - 16-byte encryption key
572
+ * @param nonce - 16-byte nonce (must match what was used during encryption)
573
+ * @param degree - Parallelism degree (default: 2)
574
+ * @returns True if authentication succeeds, false otherwise
575
+ */
576
+ export function aegis128XDecryptDetachedInPlace(
577
+ data: Uint8Array,
578
+ tag: Uint8Array,
579
+ ad: Uint8Array,
580
+ key: Uint8Array,
581
+ nonce: Uint8Array,
582
+ degree: number = 2,
583
+ ): boolean {
584
+ const tagLen = tag.length as 16 | 32;
585
+ const state = new Aegis128XState(degree);
586
+ const rateBytes = (256 * degree) / 8;
587
+
588
+ state.init(key, nonce);
589
+
590
+ const adPadded = zeroPad(ad, rateBytes);
591
+ for (let i = 0; i + rateBytes <= adPadded.length; i += rateBytes) {
592
+ state.absorb(adPadded.subarray(i, i + rateBytes));
593
+ }
594
+
595
+ const msgLen = data.length;
596
+ const fullBlocksLen = Math.floor(msgLen / rateBytes) * rateBytes;
597
+
598
+ for (let i = 0; i < fullBlocksLen; i += rateBytes) {
599
+ state.decInPlace(data.subarray(i, i + rateBytes));
600
+ }
601
+
602
+ if (msgLen > fullBlocksLen) {
603
+ const lastPartial = data.subarray(fullBlocksLen);
604
+ const decrypted = state.decPartial(lastPartial);
605
+ lastPartial.set(decrypted);
606
+ }
607
+
608
+ const expectedTag = state.finalize(
609
+ BigInt(ad.length * 8),
610
+ BigInt(msgLen * 8),
611
+ tagLen,
612
+ );
613
+
614
+ if (!constantTimeEqual(tag, expectedTag)) {
615
+ data.fill(0);
616
+ return false;
617
+ }
618
+
619
+ return true;
620
+ }
621
+
622
+ /** AEGIS-128X2 in-place encryption - detached mode (degree=2). */
623
+ export const aegis128X2EncryptDetachedInPlace = (
624
+ data: Uint8Array,
625
+ ad: Uint8Array,
626
+ key: Uint8Array,
627
+ nonce: Uint8Array,
628
+ tagLen: 16 | 32 = 16,
629
+ ) => aegis128XEncryptDetachedInPlace(data, ad, key, nonce, tagLen, 2);
630
+
631
+ /** AEGIS-128X2 in-place decryption - detached mode (degree=2). */
632
+ export const aegis128X2DecryptDetachedInPlace = (
633
+ data: Uint8Array,
634
+ tag: Uint8Array,
635
+ ad: Uint8Array,
636
+ key: Uint8Array,
637
+ nonce: Uint8Array,
638
+ ) => aegis128XDecryptDetachedInPlace(data, tag, ad, key, nonce, 2);
639
+
640
+ /** AEGIS-128X4 in-place encryption - detached mode (degree=4). */
641
+ export const aegis128X4EncryptDetachedInPlace = (
642
+ data: Uint8Array,
643
+ ad: Uint8Array,
644
+ key: Uint8Array,
645
+ nonce: Uint8Array,
646
+ tagLen: 16 | 32 = 16,
647
+ ) => aegis128XEncryptDetachedInPlace(data, ad, key, nonce, tagLen, 4);
648
+
649
+ /** AEGIS-128X4 in-place decryption - detached mode (degree=4). */
650
+ export const aegis128X4DecryptDetachedInPlace = (
651
+ data: Uint8Array,
652
+ tag: Uint8Array,
653
+ ad: Uint8Array,
654
+ key: Uint8Array,
655
+ nonce: Uint8Array,
656
+ ) => aegis128XDecryptDetachedInPlace(data, tag, ad, key, nonce, 4);
657
+
490
658
  /** Nonce size for AEGIS-128X in bytes. */
491
659
  export const AEGIS_128X_NONCE_SIZE = 16;
492
660
 
@@ -10,12 +10,16 @@ import {
10
10
  aesRound,
11
11
  blockFromBytes,
12
12
  blocksPut,
13
+ blocksRotr6,
14
+ blocksXor,
13
15
  blockToBytes,
14
16
  blockXor,
15
17
  createAesBlock,
16
18
  createAesBlocks,
17
19
  pack,
20
+ pack04_6,
18
21
  unpack,
22
+ unpack04_6,
19
23
  wordIdx,
20
24
  } from "./aes-bs.js";
21
25
  import { randomBytes } from "./random.js";
@@ -31,58 +35,68 @@ const C1: AesBlock = new Uint32Array([
31
35
 
32
36
  /**
33
37
  * Bitsliced AEGIS-256 cipher state.
34
- * Uses 6 AES blocks (96 bytes) stored in bitsliced form.
38
+ * Uses 6 AES blocks stored in packed bitsliced form throughout the lifetime
39
+ * of the state.
35
40
  */
36
41
  export class Aegis256BsState {
37
42
  private st: AesBlocks;
38
43
  private st1: AesBlocks;
44
+ private constantInput: AesBlocks;
39
45
  private tmp: AesBlock;
46
+ private z: AesBlock;
40
47
 
41
48
  constructor() {
42
49
  this.st = createAesBlocks();
43
50
  this.st1 = createAesBlocks();
51
+ this.constantInput = createAesBlocks();
44
52
  this.tmp = createAesBlock();
53
+ this.z = createAesBlock();
45
54
  }
46
55
 
47
- /**
48
- * AEGIS round function: applies AES round to all blocks and rotates.
49
- * st[i] = AES(st[i]) ^ st[(i+5) mod 6]
50
- */
51
- private aegisRound(): void {
56
+ private aegisRoundPacked(): void {
52
57
  const st = this.st;
53
58
  const st1 = this.st1;
54
59
 
55
60
  st1.set(st);
56
- pack(st1);
57
61
  aesRound(st1);
58
- unpack(st1);
59
-
60
- for (let i = 0; i < 6; i++) {
61
- const prev = (i + 5) % 6;
62
- st[wordIdx(i, 0)] = (st[wordIdx(i, 0)]! ^ st1[wordIdx(prev, 0)]!) >>> 0;
63
- st[wordIdx(i, 1)] = (st[wordIdx(i, 1)]! ^ st1[wordIdx(prev, 1)]!) >>> 0;
64
- st[wordIdx(i, 2)] = (st[wordIdx(i, 2)]! ^ st1[wordIdx(prev, 2)]!) >>> 0;
65
- st[wordIdx(i, 3)] = (st[wordIdx(i, 3)]! ^ st1[wordIdx(prev, 3)]!) >>> 0;
66
- }
62
+ blocksRotr6(st1);
63
+ blocksXor(st, st1);
64
+ blocksXor(st, this.constantInput);
67
65
  }
68
66
 
69
- /**
70
- * Absorb rate: XOR message block into state position 0.
71
- */
72
- private absorbRate(m: AesBlock): void {
73
- const st = this.st;
74
- st[wordIdx(0, 0)] = (st[wordIdx(0, 0)]! ^ m[0]!) >>> 0;
75
- st[wordIdx(0, 1)] = (st[wordIdx(0, 1)]! ^ m[1]!) >>> 0;
76
- st[wordIdx(0, 2)] = (st[wordIdx(0, 2)]! ^ m[2]!) >>> 0;
77
- st[wordIdx(0, 3)] = (st[wordIdx(0, 3)]! ^ m[3]!) >>> 0;
67
+ private packConstantInput(m: AesBlock): void {
68
+ const out = this.constantInput;
69
+ out.fill(0);
70
+ blocksPut(out, m, 0);
71
+ pack04_6(out);
78
72
  }
79
73
 
80
74
  /**
81
- * Update state with a message block.
75
+ * Extract the keystream block z directly from the packed state.
76
+ *
77
+ * z = S1 ^ S4 ^ S5 ^ (S2 & S3)
78
+ *
79
+ * In the packed layout, logical block j lives at bit position (7 - j) of
80
+ * every byte, so S1 = bit 6, S4 = bit 3, S5 = bit 2, S2 = bit 5,
81
+ * S3 = bit 4. The combination is computed lane-wise into bit position 7
82
+ * and unpack04_6 then extracts the AES block at logical index 0.
82
83
  */
83
- private update(m: AesBlock): void {
84
- this.aegisRound();
85
- this.absorbRate(m);
84
+ private keystreamPacked(): void {
85
+ const st = this.st;
86
+ const zPacked = this.st1;
87
+ for (let i = 0; i < 32; i++) {
88
+ const x = st[i]!;
89
+ zPacked[i] =
90
+ ((x & 0x40404040) << 1) ^
91
+ ((x & 0x08080808) << 4) ^
92
+ ((x & 0x04040404) << 5) ^
93
+ ((x & (x >>> 1) & 0x10101010) << 3);
94
+ }
95
+ unpack04_6(zPacked);
96
+ const z = this.z;
97
+ for (let i = 0; i < 4; i++) {
98
+ z[i] = zPacked[wordIdx(0, i)]!;
99
+ }
86
100
  }
87
101
 
88
102
  /**
@@ -117,57 +131,45 @@ export class Aegis256BsState {
117
131
  blocksPut(this.st, k0c0, 4);
118
132
  blocksPut(this.st, k1c1, 5);
119
133
 
134
+ pack(this.st);
120
135
  for (let i = 0; i < 4; i++) {
121
- this.update(k0);
122
- this.update(k1);
123
- this.update(k0n0);
124
- this.update(k1n1);
136
+ this.packConstantInput(k0);
137
+ this.aegisRoundPacked();
138
+ this.packConstantInput(k1);
139
+ this.aegisRoundPacked();
140
+ this.packConstantInput(k0n0);
141
+ this.aegisRoundPacked();
142
+ this.packConstantInput(k1n1);
143
+ this.aegisRoundPacked();
125
144
  }
126
145
  }
127
146
 
128
147
  /**
129
148
  * Absorbs a 16-byte associated data block into the state.
130
- * @param ai - 16-byte associated data block
131
149
  */
132
150
  absorb(ai: Uint8Array): void {
133
151
  const msg = this.tmp;
134
152
  blockFromBytes(msg, ai);
135
- this.update(msg);
153
+ this.packConstantInput(msg);
154
+ this.aegisRoundPacked();
136
155
  }
137
156
 
138
157
  /**
139
158
  * Encrypts a 16-byte plaintext block and writes to output buffer.
140
- * @param xi - 16-byte plaintext block
141
- * @param out - 16-byte output buffer
142
159
  */
143
160
  encTo(xi: Uint8Array, out: Uint8Array): void {
144
- const st = this.st;
145
- const z = this.tmp;
146
- const t = createAesBlock();
161
+ const t = this.tmp;
147
162
 
148
- for (let i = 0; i < 4; i++) {
149
- z[i] =
150
- (st[wordIdx(1, i)]! ^
151
- st[wordIdx(4, i)]! ^
152
- st[wordIdx(5, i)]! ^
153
- (st[wordIdx(2, i)]! & st[wordIdx(3, i)]!)) >>>
154
- 0;
155
- }
163
+ this.keystreamPacked();
156
164
 
157
165
  blockFromBytes(t, xi);
166
+ this.packConstantInput(t);
167
+ this.aegisRoundPacked();
158
168
 
159
- const outBlock = createAesBlock();
160
- blockXor(outBlock, t, z);
161
- blockToBytes(out, outBlock);
162
-
163
- this.update(t);
169
+ blockXor(t, t, this.z);
170
+ blockToBytes(out, t);
164
171
  }
165
172
 
166
- /**
167
- * Encrypts a 16-byte plaintext block.
168
- * @param xi - 16-byte plaintext block
169
- * @returns 16-byte ciphertext block
170
- */
171
173
  enc(xi: Uint8Array): Uint8Array {
172
174
  const out = new Uint8Array(16);
173
175
  this.encTo(xi, out);
@@ -176,61 +178,42 @@ export class Aegis256BsState {
176
178
 
177
179
  /**
178
180
  * Decrypts a 16-byte ciphertext block and writes to output buffer.
179
- * @param ci - 16-byte ciphertext block
180
- * @param out - 16-byte output buffer
181
181
  */
182
182
  decTo(ci: Uint8Array, out: Uint8Array): void {
183
- const st = this.st;
184
183
  const msg = this.tmp;
185
184
 
186
185
  blockFromBytes(msg, ci);
186
+ this.keystreamPacked();
187
+ blockXor(msg, msg, this.z);
187
188
 
188
- for (let i = 0; i < 4; i++) {
189
- msg[i] =
190
- (msg[i]! ^
191
- st[wordIdx(1, i)]! ^
192
- st[wordIdx(4, i)]! ^
193
- st[wordIdx(5, i)]! ^
194
- (st[wordIdx(2, i)]! & st[wordIdx(3, i)]!)) >>>
195
- 0;
196
- }
189
+ this.packConstantInput(msg);
190
+ this.aegisRoundPacked();
197
191
 
198
- this.update(msg);
199
192
  blockToBytes(out, msg);
200
193
  }
201
194
 
202
- /**
203
- * Decrypts a 16-byte ciphertext block.
204
- * @param ci - 16-byte ciphertext block
205
- * @returns 16-byte plaintext block
206
- */
207
195
  dec(ci: Uint8Array): Uint8Array {
208
196
  const out = new Uint8Array(16);
209
197
  this.decTo(ci, out);
210
198
  return out;
211
199
  }
212
200
 
213
- /**
214
- * Decrypts a partial (final) ciphertext block smaller than 16 bytes.
215
- * @param cn - Partial ciphertext block (1-15 bytes)
216
- * @returns Decrypted plaintext of the same length
217
- */
201
+ encInPlace(block: Uint8Array): void {
202
+ this.encTo(block, block);
203
+ }
204
+
205
+ decInPlace(block: Uint8Array): void {
206
+ this.decTo(block, block);
207
+ }
208
+
218
209
  decPartial(cn: Uint8Array): Uint8Array {
219
- const st = this.st;
220
210
  const msg = this.tmp;
221
211
 
222
212
  const padded = zeroPad(cn, RATE);
223
213
  blockFromBytes(msg, padded);
224
214
 
225
- for (let i = 0; i < 4; i++) {
226
- msg[i] =
227
- (msg[i]! ^
228
- st[wordIdx(1, i)]! ^
229
- st[wordIdx(4, i)]! ^
230
- st[wordIdx(5, i)]! ^
231
- (st[wordIdx(2, i)]! & st[wordIdx(3, i)]!)) >>>
232
- 0;
233
- }
215
+ this.keystreamPacked();
216
+ blockXor(msg, msg, this.z);
234
217
 
235
218
  const pad = new Uint8Array(RATE);
236
219
  blockToBytes(pad, msg);
@@ -240,18 +223,14 @@ export class Aegis256BsState {
240
223
  pad.fill(0, cn.length);
241
224
  blockFromBytes(msg, pad);
242
225
 
243
- this.aegisRound();
244
- this.absorbRate(msg);
226
+ this.packConstantInput(msg);
227
+ this.aegisRoundPacked();
245
228
 
246
229
  return xn;
247
230
  }
248
231
 
249
232
  /**
250
233
  * Finalizes encryption/decryption and produces an authentication tag.
251
- * @param adLen - Associated data length in bytes
252
- * @param msgLen - Message length in bytes
253
- * @param tagLen - Tag length (16 or 32 bytes)
254
- * @returns Authentication tag
255
234
  */
256
235
  finalize(adLen: number, msgLen: number, tagLen: 16 | 32 = 16): Uint8Array {
257
236
  const st = this.st;
@@ -262,14 +241,20 @@ export class Aegis256BsState {
262
241
  tmp[2] = ((msgLen * 8) & 0xffffffff) >>> 0;
263
242
  tmp[3] = Math.floor((msgLen * 8) / 0x100000000) >>> 0;
264
243
 
265
- tmp[0] = (tmp[0]! ^ st[wordIdx(3, 0)]!) >>> 0;
266
- tmp[1] = (tmp[1]! ^ st[wordIdx(3, 1)]!) >>> 0;
267
- tmp[2] = (tmp[2]! ^ st[wordIdx(3, 2)]!) >>> 0;
268
- tmp[3] = (tmp[3]! ^ st[wordIdx(3, 3)]!) >>> 0;
244
+ const unpacked = this.st1;
245
+ unpacked.set(st);
246
+ unpack(unpacked);
247
+
248
+ tmp[0] = (tmp[0]! ^ unpacked[wordIdx(3, 0)]!) >>> 0;
249
+ tmp[1] = (tmp[1]! ^ unpacked[wordIdx(3, 1)]!) >>> 0;
250
+ tmp[2] = (tmp[2]! ^ unpacked[wordIdx(3, 2)]!) >>> 0;
251
+ tmp[3] = (tmp[3]! ^ unpacked[wordIdx(3, 3)]!) >>> 0;
269
252
 
253
+ this.packConstantInput(tmp);
270
254
  for (let i = 0; i < 7; i++) {
271
- this.update(tmp);
255
+ this.aegisRoundPacked();
272
256
  }
257
+ unpack(st);
273
258
 
274
259
  if (tagLen === 16) {
275
260
  const tag = new Uint8Array(16);
@@ -307,12 +292,6 @@ export class Aegis256BsState {
307
292
 
308
293
  /**
309
294
  * Encrypts a message using bitsliced AEGIS-256 (detached mode).
310
- * @param msg - Plaintext message
311
- * @param ad - Associated data (authenticated but not encrypted)
312
- * @param key - 32-byte encryption key
313
- * @param nonce - 32-byte nonce (must be unique per message with the same key)
314
- * @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
315
- * @returns Object containing ciphertext and authentication tag separately
316
295
  */
317
296
  export function aegis256BsEncryptDetached(
318
297
  msg: Uint8Array,
@@ -349,12 +328,6 @@ export function aegis256BsEncryptDetached(
349
328
 
350
329
  /**
351
330
  * Decrypts a message using bitsliced AEGIS-256 (detached mode).
352
- * @param ct - Ciphertext
353
- * @param tag - Authentication tag (16 or 32 bytes)
354
- * @param ad - Associated data (must match what was used during encryption)
355
- * @param key - 32-byte encryption key
356
- * @param nonce - 32-byte nonce (must match what was used during encryption)
357
- * @returns Decrypted plaintext, or null if authentication fails
358
331
  */
359
332
  export function aegis256BsDecryptDetached(
360
333
  ct: Uint8Array,
@@ -393,19 +366,80 @@ export function aegis256BsDecryptDetached(
393
366
  return msg;
394
367
  }
395
368
 
369
+ export function aegis256BsEncryptDetachedInPlace(
370
+ data: Uint8Array,
371
+ ad: Uint8Array,
372
+ key: Uint8Array,
373
+ nonce: Uint8Array,
374
+ tagLen: 16 | 32 = 16,
375
+ ): Uint8Array {
376
+ const state = new Aegis256BsState();
377
+ state.init(key, nonce);
378
+
379
+ const adPadded = zeroPad(ad, RATE);
380
+ for (let i = 0; i + RATE <= adPadded.length; i += RATE) {
381
+ state.absorb(adPadded.subarray(i, i + RATE));
382
+ }
383
+
384
+ const msgLen = data.length;
385
+ const fullBlocksLen = Math.floor(msgLen / RATE) * RATE;
386
+
387
+ for (let i = 0; i < fullBlocksLen; i += RATE) {
388
+ state.encInPlace(data.subarray(i, i + RATE));
389
+ }
390
+
391
+ if (msgLen > fullBlocksLen) {
392
+ const lastPartial = data.subarray(fullBlocksLen);
393
+ const lastBlock = zeroPad(lastPartial, RATE);
394
+ const encBlock = state.enc(lastBlock);
395
+ lastPartial.set(encBlock.subarray(0, lastPartial.length));
396
+ }
397
+
398
+ return state.finalize(ad.length, msgLen, tagLen);
399
+ }
400
+
401
+ export function aegis256BsDecryptDetachedInPlace(
402
+ data: Uint8Array,
403
+ tag: Uint8Array,
404
+ ad: Uint8Array,
405
+ key: Uint8Array,
406
+ nonce: Uint8Array,
407
+ ): boolean {
408
+ const tagLen = tag.length as 16 | 32;
409
+ const state = new Aegis256BsState();
410
+ state.init(key, nonce);
411
+
412
+ const adPadded = zeroPad(ad, RATE);
413
+ for (let i = 0; i + RATE <= adPadded.length; i += RATE) {
414
+ state.absorb(adPadded.subarray(i, i + RATE));
415
+ }
416
+
417
+ const msgLen = data.length;
418
+ const fullBlocksLen = Math.floor(msgLen / RATE) * RATE;
419
+
420
+ for (let i = 0; i < fullBlocksLen; i += RATE) {
421
+ state.decInPlace(data.subarray(i, i + RATE));
422
+ }
423
+
424
+ if (msgLen > fullBlocksLen) {
425
+ const lastPartial = data.subarray(fullBlocksLen);
426
+ const decrypted = state.decPartial(lastPartial);
427
+ lastPartial.set(decrypted);
428
+ }
429
+
430
+ const expectedTag = state.finalize(ad.length, msgLen, tagLen);
431
+
432
+ if (!constantTimeEqual(tag, expectedTag)) {
433
+ data.fill(0);
434
+ return false;
435
+ }
436
+
437
+ return true;
438
+ }
439
+
396
440
  export const AEGIS_256_BS_NONCE_SIZE = 32;
397
441
  export const AEGIS_256_BS_KEY_SIZE = 32;
398
442
 
399
- /**
400
- * Encrypts a message using bitsliced AEGIS-256.
401
- * Returns a single buffer containing nonce || ciphertext || tag.
402
- * @param msg - Plaintext message
403
- * @param ad - Associated data (authenticated but not encrypted)
404
- * @param key - 32-byte encryption key
405
- * @param nonce - 32-byte nonce (optional, generates random nonce if not provided)
406
- * @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
407
- * @returns Concatenated nonce || ciphertext || tag
408
- */
409
443
  export function aegis256BsEncrypt(
410
444
  msg: Uint8Array,
411
445
  ad: Uint8Array,
@@ -432,15 +466,6 @@ export function aegis256BsEncrypt(
432
466
  return result;
433
467
  }
434
468
 
435
- /**
436
- * Decrypts a message using bitsliced AEGIS-256.
437
- * Expects input as nonce || ciphertext || tag.
438
- * @param sealed - Concatenated nonce || ciphertext || tag
439
- * @param ad - Associated data (must match what was used during encryption)
440
- * @param key - 32-byte encryption key
441
- * @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
442
- * @returns Decrypted plaintext, or null if authentication fails
443
- */
444
469
  export function aegis256BsDecrypt(
445
470
  sealed: Uint8Array,
446
471
  ad: Uint8Array,
@@ -457,14 +482,6 @@ export function aegis256BsDecrypt(
457
482
  return aegis256BsDecryptDetached(ct, tag, ad, key, nonce);
458
483
  }
459
484
 
460
- /**
461
- * Computes a MAC (Message Authentication Code) using bitsliced AEGIS-256.
462
- * @param data - Data to authenticate
463
- * @param key - 32-byte key
464
- * @param nonce - 32-byte nonce (optional, uses zero nonce if null)
465
- * @param tagLen - Tag length: 16 or 32 bytes (default: 16)
466
- * @returns Authentication tag
467
- */
468
485
  export function aegis256BsMac(
469
486
  data: Uint8Array,
470
487
  key: Uint8Array,
@@ -482,14 +499,6 @@ export function aegis256BsMac(
482
499
  return state.finalize(data.length, tagLen, tagLen);
483
500
  }
484
501
 
485
- /**
486
- * Verifies a MAC computed using bitsliced AEGIS-256.
487
- * @param data - Data to verify
488
- * @param tag - Expected authentication tag (16 or 32 bytes)
489
- * @param key - 32-byte key
490
- * @param nonce - 32-byte nonce (optional, uses zero nonce if null)
491
- * @returns True if the tag is valid, false otherwise
492
- */
493
502
  export function aegis256BsMacVerify(
494
503
  data: Uint8Array,
495
504
  tag: Uint8Array,
@@ -501,18 +510,10 @@ export function aegis256BsMacVerify(
501
510
  return constantTimeEqual(tag, expectedTag);
502
511
  }
503
512
 
504
- /**
505
- * Generates a random 32-byte key for bitsliced AEGIS-256.
506
- * @returns 32-byte encryption key
507
- */
508
513
  export function aegis256BsCreateKey(): Uint8Array {
509
514
  return randomBytes(AEGIS_256_BS_KEY_SIZE);
510
515
  }
511
516
 
512
- /**
513
- * Generates a random 32-byte nonce for bitsliced AEGIS-256.
514
- * @returns 32-byte nonce
515
- */
516
517
  export function aegis256BsCreateNonce(): Uint8Array {
517
518
  return randomBytes(AEGIS_256_BS_NONCE_SIZE);
518
519
  }