aegis-aead 0.2.1 → 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,18 +224,10 @@ export class Aegis128LBsState {
270
224
  return out;
271
225
  }
272
226
 
273
- /**
274
- * Encrypts a 32-byte plaintext block in-place.
275
- * @param block - 32-byte buffer (plaintext in, ciphertext out)
276
- */
277
227
  encInPlace(block: Uint8Array): void {
278
228
  this.encTo(block, block);
279
229
  }
280
230
 
281
- /**
282
- * Decrypts a 32-byte ciphertext block in-place.
283
- * @param block - 32-byte buffer (ciphertext in, plaintext out)
284
- */
285
231
  decInPlace(block: Uint8Array): void {
286
232
  this.decTo(block, block);
287
233
  }
@@ -292,7 +238,6 @@ export class Aegis128LBsState {
292
238
  * @returns Decrypted plaintext of the same length
293
239
  */
294
240
  decPartial(cn: Uint8Array): Uint8Array {
295
- const st = this.st;
296
241
  const msg0 = this.tmp0;
297
242
  const msg1 = this.tmp1;
298
243
 
@@ -300,22 +245,9 @@ export class Aegis128LBsState {
300
245
  blockFromBytes(msg0, padded.subarray(0, 16));
301
246
  blockFromBytes(msg1, padded.subarray(16, 32));
302
247
 
303
- for (let i = 0; i < 4; i++) {
304
- msg0[i] =
305
- (msg0[i]! ^
306
- st[wordIdx(6, i)]! ^
307
- st[wordIdx(1, i)]! ^
308
- (st[wordIdx(2, i)]! & st[wordIdx(3, i)]!)) >>>
309
- 0;
310
- }
311
- for (let i = 0; i < 4; i++) {
312
- msg1[i] =
313
- (msg1[i]! ^
314
- st[wordIdx(2, i)]! ^
315
- st[wordIdx(5, i)]! ^
316
- (st[wordIdx(6, i)]! & st[wordIdx(7, i)]!)) >>>
317
- 0;
318
- }
248
+ this.keystreamPacked();
249
+ blockXor(msg0, msg0, this.z0);
250
+ blockXor(msg1, msg1, this.z1);
319
251
 
320
252
  const pad = new Uint8Array(RATE);
321
253
  blockToBytes(pad.subarray(0, 16), msg0);
@@ -327,8 +259,8 @@ export class Aegis128LBsState {
327
259
  blockFromBytes(msg0, pad.subarray(0, 16));
328
260
  blockFromBytes(msg1, pad.subarray(16, 32));
329
261
 
330
- this.aegisRound();
331
- this.absorbRate(msg0, msg1);
262
+ this.packConstantInput(msg0, msg1);
263
+ this.aegisRoundPacked();
332
264
 
333
265
  return xn;
334
266
  }
@@ -349,18 +281,20 @@ export class Aegis128LBsState {
349
281
  tmp[2] = ((msgLen * 8) & 0xffffffff) >>> 0;
350
282
  tmp[3] = Math.floor((msgLen * 8) / 0x100000000) >>> 0;
351
283
 
352
- tmp[0] = (tmp[0]! ^ st[wordIdx(2, 0)]!) >>> 0;
353
- tmp[1] = (tmp[1]! ^ st[wordIdx(2, 1)]!) >>> 0;
354
- tmp[2] = (tmp[2]! ^ st[wordIdx(2, 2)]!) >>> 0;
355
- tmp[3] = (tmp[3]! ^ st[wordIdx(2, 3)]!) >>> 0;
284
+ const unpacked = this.st1;
285
+ unpacked.set(st);
286
+ unpack(unpacked);
356
287
 
357
- const constantInput = createAesBlocks();
358
- this.packConstantInput(constantInput, tmp, tmp);
359
- 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);
360
294
  for (let i = 0; i < 7; i++) {
361
- this.aegisRoundPacked(constantInput);
295
+ this.aegisRoundPacked();
362
296
  }
363
- unpack(this.st);
297
+ unpack(st);
364
298
 
365
299
  if (tagLen === 16) {
366
300
  const tag = new Uint8Array(16);
@@ -495,13 +429,6 @@ export function aegis128LBsDecryptDetached(
495
429
 
496
430
  /**
497
431
  * Encrypts a message in-place using bitsliced AEGIS-128L (detached mode).
498
- * The input buffer is modified to contain the ciphertext.
499
- * @param data - Buffer containing plaintext (will be overwritten with ciphertext)
500
- * @param ad - Associated data (authenticated but not encrypted)
501
- * @param key - 16-byte encryption key
502
- * @param nonce - 16-byte nonce (must be unique per message with the same key)
503
- * @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
504
- * @returns Authentication tag
505
432
  */
506
433
  export function aegis128LBsEncryptDetachedInPlace(
507
434
  data: Uint8Array,
@@ -537,13 +464,6 @@ export function aegis128LBsEncryptDetachedInPlace(
537
464
 
538
465
  /**
539
466
  * Decrypts a message in-place using bitsliced AEGIS-128L (detached mode).
540
- * The input buffer is modified to contain the plaintext (or zeroed on failure).
541
- * @param data - Buffer containing ciphertext (will be overwritten with plaintext)
542
- * @param tag - Authentication tag (16 or 32 bytes)
543
- * @param ad - Associated data (must match what was used during encryption)
544
- * @param key - 16-byte encryption key
545
- * @param nonce - 16-byte nonce (must match what was used during encryption)
546
- * @returns True if authentication succeeds, false otherwise
547
467
  */
548
468
  export function aegis128LBsDecryptDetachedInPlace(
549
469
  data: Uint8Array,
@@ -590,12 +510,6 @@ export const AEGIS_128L_BS_KEY_SIZE = 16;
590
510
  /**
591
511
  * Encrypts a message using bitsliced AEGIS-128L.
592
512
  * Returns a single buffer containing nonce || ciphertext || tag.
593
- * @param msg - Plaintext message
594
- * @param ad - Associated data (authenticated but not encrypted)
595
- * @param key - 16-byte encryption key
596
- * @param nonce - 16-byte nonce (optional, generates random nonce if not provided)
597
- * @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
598
- * @returns Concatenated nonce || ciphertext || tag
599
513
  */
600
514
  export function aegis128LBsEncrypt(
601
515
  msg: Uint8Array,
@@ -626,11 +540,6 @@ export function aegis128LBsEncrypt(
626
540
  /**
627
541
  * Decrypts a message using bitsliced AEGIS-128L.
628
542
  * Expects input as nonce || ciphertext || tag.
629
- * @param sealed - Concatenated nonce || ciphertext || tag
630
- * @param ad - Associated data (must match what was used during encryption)
631
- * @param key - 16-byte encryption key
632
- * @param tagLen - Authentication tag length: 16 or 32 bytes (default: 16)
633
- * @returns Decrypted plaintext, or null if authentication fails
634
543
  */
635
544
  export function aegis128LBsDecrypt(
636
545
  sealed: Uint8Array,
@@ -649,12 +558,7 @@ export function aegis128LBsDecrypt(
649
558
  }
650
559
 
651
560
  /**
652
- * Computes a MAC (Message Authentication Code) using bitsliced AEGIS-128L.
653
- * @param data - Data to authenticate
654
- * @param key - 16-byte key
655
- * @param nonce - 16-byte nonce (optional, uses zero nonce if null)
656
- * @param tagLen - Tag length: 16 or 32 bytes (default: 16)
657
- * @returns Authentication tag
561
+ * Computes a MAC using bitsliced AEGIS-128L.
658
562
  */
659
563
  export function aegis128LBsMac(
660
564
  data: Uint8Array,
@@ -675,11 +579,6 @@ export function aegis128LBsMac(
675
579
 
676
580
  /**
677
581
  * Verifies a MAC computed using bitsliced AEGIS-128L.
678
- * @param data - Data to verify
679
- * @param tag - Expected authentication tag (16 or 32 bytes)
680
- * @param key - 16-byte key
681
- * @param nonce - 16-byte nonce (optional, uses zero nonce if null)
682
- * @returns True if the tag is valid, false otherwise
683
582
  */
684
583
  export function aegis128LBsMacVerify(
685
584
  data: Uint8Array,
@@ -692,18 +591,10 @@ export function aegis128LBsMacVerify(
692
591
  return constantTimeEqual(tag, expectedTag);
693
592
  }
694
593
 
695
- /**
696
- * Generates a random 16-byte key for bitsliced AEGIS-128L.
697
- * @returns 16-byte encryption key
698
- */
699
594
  export function aegis128LBsCreateKey(): Uint8Array {
700
595
  return randomBytes(AEGIS_128L_BS_KEY_SIZE);
701
596
  }
702
597
 
703
- /**
704
- * Generates a random 16-byte nonce for bitsliced AEGIS-128L.
705
- * @returns 16-byte nonce
706
- */
707
598
  export function aegis128LBsCreateNonce(): Uint8Array {
708
599
  return randomBytes(AEGIS_128L_BS_NONCE_SIZE);
709
600
  }