@didcid/gatekeeper 0.3.7 → 0.4.1

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.
@@ -30534,6 +30534,11 @@ function requireBn () {
30534
30534
  this.words[this.length - 1] &= mask;
30535
30535
  }
30536
30536
 
30537
+ if (this.length === 0) {
30538
+ this.words[0] = 0;
30539
+ this.length = 1;
30540
+ }
30541
+
30537
30542
  return this.strip();
30538
30543
  };
30539
30544
 
@@ -38283,7 +38288,7 @@ const toU8 = (a, len) => abytes$1(isStr(a) ? hexToBytes(a) : u8fr(abytes$1(a)),
38283
38288
  const cr = () => globalThis?.crypto; // WebCrypto is available in all modern environments
38284
38289
  const subtle = () => cr()?.subtle ?? err('crypto.subtle must be defined');
38285
38290
  // prettier-ignore
38286
- const concatBytes$1 = (...arrs) => {
38291
+ const concatBytes$2 = (...arrs) => {
38287
38292
  const r = u8n(arrs.reduce((sum, a) => sum + abytes$1(a).length, 0)); // create u8a of summed length
38288
38293
  let pad = 0; // walk through each array,
38289
38294
  arrs.forEach(a => { r.set(a, pad); pad += a.length; }); // ensure they have proper type
@@ -38524,8 +38529,8 @@ class Point {
38524
38529
  const { x, y } = this.assertValidity().toAffine();
38525
38530
  const x32b = numTo32b(x);
38526
38531
  if (isCompressed)
38527
- return concatBytes$1(getPrefix(y), x32b);
38528
- return concatBytes$1(u8of(0x04), x32b, numTo32b(y));
38532
+ return concatBytes$2(getPrefix(y), x32b);
38533
+ return concatBytes$2(u8of(0x04), x32b, numTo32b(y));
38529
38534
  }
38530
38535
  /** Create 3d xyz point from 2d xy. (0, 0) => (0, 1, 0), not (0, 0, 1) */
38531
38536
  static fromAffine(ap) {
@@ -38599,7 +38604,7 @@ class Signature {
38599
38604
  }
38600
38605
  toBytes() {
38601
38606
  const { r, s } = this;
38602
- return concatBytes$1(numTo32b(r), numTo32b(s));
38607
+ return concatBytes$2(numTo32b(r), numTo32b(s));
38603
38608
  }
38604
38609
  /** Copy signature, with newly added recovery bit. */
38605
38610
  addRecoveryBit(bit) {
@@ -38691,7 +38696,7 @@ const prepSig = (msgh, priv, opts = signOpts) => {
38691
38696
  }
38692
38697
  return new Signature(r, normS, recovery); // use normS, not s
38693
38698
  };
38694
- return { seed: concatBytes$1(...seed), k2sig };
38699
+ return { seed: concatBytes$2(...seed), k2sig };
38695
38700
  };
38696
38701
  // HMAC-DRBG from NIST 800-90. Minimal, non-full-spec - used for RFC6979 signatures.
38697
38702
  const hmacDrbg = (asynchronous) => {
@@ -38809,7 +38814,7 @@ const recoverPublicKey = (sig, msgh) => {
38809
38814
  const radj = recovery === 2 || recovery === 3 ? r + N : r;
38810
38815
  afield(radj); // ensure q.x is still a field element
38811
38816
  const head = getPrefix(big(recovery)); // head is 0x02 or 0x03
38812
- const Rb = concatBytes$1(head, numTo32b(radj)); // concat head + r
38817
+ const Rb = concatBytes$2(head, numTo32b(radj)); // concat head + r
38813
38818
  const R = Point.fromBytes(Rb);
38814
38819
  const ir = invert(radj, N); // r^-1
38815
38820
  const u1 = modN(-h * ir); // -hr^-1
@@ -38842,7 +38847,7 @@ const _sha = 'SHA-256';
38842
38847
  const etc = {
38843
38848
  hexToBytes: hexToBytes,
38844
38849
  bytesToHex: bytesToHex,
38845
- concatBytes: concatBytes$1,
38850
+ concatBytes: concatBytes$2,
38846
38851
  bytesToNumberBE: bytesToNumBE,
38847
38852
  numberToBytesBE: numTo32b,
38848
38853
  mod: M,
@@ -38851,7 +38856,7 @@ const etc = {
38851
38856
  const s = subtle();
38852
38857
  const name = 'HMAC';
38853
38858
  const k = await s.importKey('raw', key, { name, hash: { name: _sha } }, false, ['sign']);
38854
- return u8n(await s.sign(name, k, concatBytes$1(...msgs)));
38859
+ return u8n(await s.sign(name, k, concatBytes$2(...msgs)));
38855
38860
  },
38856
38861
  hmacSha256Sync: undefined, // For TypeScript. Actual logic is below
38857
38862
  hashToPrivateKey: hashToPrivateKey,
@@ -39387,6 +39392,7 @@ const sha256$1 = sha256$2;
39387
39392
 
39388
39393
  /*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
39389
39394
  // Cast array to different type
39395
+ const u8 = (arr) => new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
39390
39396
  const u32 = (arr) => new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
39391
39397
  function isBytes$1(a) {
39392
39398
  return (a instanceof Uint8Array ||
@@ -39427,7 +39433,7 @@ function toBytes(data) {
39427
39433
  /**
39428
39434
  * Copies several Uint8Arrays into one.
39429
39435
  */
39430
- function concatBytes(...arrays) {
39436
+ function concatBytes$1(...arrays) {
39431
39437
  let sum = 0;
39432
39438
  for (let i = 0; i < arrays.length; i++) {
39433
39439
  const a = arrays[i];
@@ -39479,8 +39485,8 @@ function setBigUint64(view, byteOffset, value, isLE) {
39479
39485
  const _u32_max = BigInt(0xffffffff);
39480
39486
  const wh = Number((value >> _32n) & _u32_max);
39481
39487
  const wl = Number(value & _u32_max);
39482
- const h = 4 ;
39483
- const l = 0 ;
39488
+ const h = isLE ? 4 : 0;
39489
+ const l = isLE ? 0 : 4;
39484
39490
  view.setUint32(byteOffset + h, wh, isLE);
39485
39491
  view.setUint32(byteOffset + l, wl, isLE);
39486
39492
  }
@@ -39771,7 +39777,7 @@ class Poly1305 {
39771
39777
  return res;
39772
39778
  }
39773
39779
  }
39774
- function wrapConstructorWithKey(hashCons) {
39780
+ function wrapConstructorWithKey$1(hashCons) {
39775
39781
  const hashC = (msg, key) => hashCons(key).update(toBytes(msg)).digest();
39776
39782
  const tmp = hashCons(new Uint8Array(32));
39777
39783
  hashC.outputLen = tmp.outputLen;
@@ -39779,7 +39785,7 @@ function wrapConstructorWithKey(hashCons) {
39779
39785
  hashC.create = (key) => hashCons(key);
39780
39786
  return hashC;
39781
39787
  }
39782
- const poly1305 = wrapConstructorWithKey((key) => new Poly1305(key));
39788
+ const poly1305 = wrapConstructorWithKey$1((key) => new Poly1305(key));
39783
39789
 
39784
39790
  // Basic utils for ARX (add-rotate-xor) salsa and chacha ciphers.
39785
39791
  /*
@@ -40140,17 +40146,17 @@ const xchacha20 = /* @__PURE__ */ createCipher(chachaCore, {
40140
40146
  extendNonceFn: hchacha,
40141
40147
  allowShortKeys: false,
40142
40148
  });
40143
- const ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
40149
+ const ZEROS16$1 = /* @__PURE__ */ new Uint8Array(16);
40144
40150
  // Pad to digest size with zeros
40145
40151
  const updatePadded = (h, msg) => {
40146
40152
  h.update(msg);
40147
40153
  const left = msg.length % 16;
40148
40154
  if (left)
40149
- h.update(ZEROS16.subarray(left));
40155
+ h.update(ZEROS16$1.subarray(left));
40150
40156
  };
40151
- const ZEROS32 = /* @__PURE__ */ new Uint8Array(32);
40152
- function computeTag(fn, key, nonce, data, AAD) {
40153
- const authKey = fn(key, nonce, ZEROS32);
40157
+ const ZEROS32$1 = /* @__PURE__ */ new Uint8Array(32);
40158
+ function computeTag$1(fn, key, nonce, data, AAD) {
40159
+ const authKey = fn(key, nonce, ZEROS32$1);
40154
40160
  const h = poly1305.create(authKey);
40155
40161
  if (AAD)
40156
40162
  updatePadded(h, AAD);
@@ -40188,7 +40194,7 @@ const _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
40188
40194
  output = new Uint8Array(clength);
40189
40195
  }
40190
40196
  xorStream(key, nonce, plaintext, output, 1);
40191
- const tag = computeTag(xorStream, key, nonce, output.subarray(0, -tagLength), AAD);
40197
+ const tag = computeTag$1(xorStream, key, nonce, output.subarray(0, -tagLength), AAD);
40192
40198
  output.set(tag, plength); // append tag
40193
40199
  return output;
40194
40200
  },
@@ -40205,7 +40211,7 @@ const _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
40205
40211
  }
40206
40212
  const data = ciphertext.subarray(0, -tagLength);
40207
40213
  const passedTag = ciphertext.subarray(-tagLength);
40208
- const tag = computeTag(xorStream, key, nonce, data, AAD);
40214
+ const tag = computeTag$1(xorStream, key, nonce, data, AAD);
40209
40215
  if (!equalBytes(passedTag, tag))
40210
40216
  throw new Error('invalid tag');
40211
40217
  xorStream(key, nonce, data, output, 1);
@@ -40245,7 +40251,7 @@ function managedNonce(fn) {
40245
40251
  const { nonceLength } = fn;
40246
40252
  const nonce = randomBytes(nonceLength);
40247
40253
  const ciphertext = fn(key, nonce, ...args).encrypt(plaintext, ...argsEnc);
40248
- const out = concatBytes(nonce, ciphertext);
40254
+ const out = concatBytes$1(nonce, ciphertext);
40249
40255
  ciphertext.fill(0);
40250
40256
  return out;
40251
40257
  },
@@ -40272,6 +40278,979 @@ function managedNonce(fn) {
40272
40278
  // const wcbc2 = managedNonce(managedNonce(cbc));
40273
40279
  // const wecb = managedNonce(ecb);
40274
40280
 
40281
+ // GHash from AES-GCM and its little-endian "mirror image" Polyval from AES-SIV.
40282
+ // Implemented in terms of GHash with conversion function for keys
40283
+ // GCM GHASH from NIST SP800-38d, SIV from RFC 8452.
40284
+ // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
40285
+ // GHASH modulo: x^128 + x^7 + x^2 + x + 1
40286
+ // POLYVAL modulo: x^128 + x^127 + x^126 + x^121 + 1
40287
+ const BLOCK_SIZE$1 = 16;
40288
+ // TODO: rewrite
40289
+ // temporary padding buffer
40290
+ const ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
40291
+ const ZEROS32 = u32(ZEROS16);
40292
+ const POLY$1 = 0xe1; // v = 2*v % POLY
40293
+ // v = 2*v % POLY
40294
+ // NOTE: because x + x = 0 (add/sub is same), mul2(x) != x+x
40295
+ // We can multiply any number using montgomery ladder and this function (works as double, add is simple xor)
40296
+ const mul2$1 = (s0, s1, s2, s3) => {
40297
+ const hiBit = s3 & 1;
40298
+ return {
40299
+ s3: (s2 << 31) | (s3 >>> 1),
40300
+ s2: (s1 << 31) | (s2 >>> 1),
40301
+ s1: (s0 << 31) | (s1 >>> 1),
40302
+ s0: (s0 >>> 1) ^ ((POLY$1 << 24) & -(hiBit & 1)), // reduce % poly
40303
+ };
40304
+ };
40305
+ const swapLE = (n) => (((n >>> 0) & 0xff) << 24) |
40306
+ (((n >>> 8) & 0xff) << 16) |
40307
+ (((n >>> 16) & 0xff) << 8) |
40308
+ ((n >>> 24) & 0xff) |
40309
+ 0;
40310
+ /**
40311
+ * `mulX_POLYVAL(ByteReverse(H))` from spec
40312
+ * @param k mutated in place
40313
+ */
40314
+ function _toGHASHKey(k) {
40315
+ k.reverse();
40316
+ const hiBit = k[15] & 1;
40317
+ // k >>= 1
40318
+ let carry = 0;
40319
+ for (let i = 0; i < k.length; i++) {
40320
+ const t = k[i];
40321
+ k[i] = (t >>> 1) | carry;
40322
+ carry = (t & 1) << 7;
40323
+ }
40324
+ k[0] ^= -hiBit & 0xe1; // if (hiBit) n ^= 0xe1000000000000000000000000000000;
40325
+ return k;
40326
+ }
40327
+ const estimateWindow = (bytes) => {
40328
+ if (bytes > 64 * 1024)
40329
+ return 8;
40330
+ if (bytes > 1024)
40331
+ return 4;
40332
+ return 2;
40333
+ };
40334
+ class GHASH {
40335
+ // We select bits per window adaptively based on expectedLength
40336
+ constructor(key, expectedLength) {
40337
+ this.blockLen = BLOCK_SIZE$1;
40338
+ this.outputLen = BLOCK_SIZE$1;
40339
+ this.s0 = 0;
40340
+ this.s1 = 0;
40341
+ this.s2 = 0;
40342
+ this.s3 = 0;
40343
+ this.finished = false;
40344
+ key = toBytes(key);
40345
+ ensureBytes(key, 16);
40346
+ const kView = createView(key);
40347
+ let k0 = kView.getUint32(0, false);
40348
+ let k1 = kView.getUint32(4, false);
40349
+ let k2 = kView.getUint32(8, false);
40350
+ let k3 = kView.getUint32(12, false);
40351
+ // generate table of doubled keys (half of montgomery ladder)
40352
+ const doubles = [];
40353
+ for (let i = 0; i < 128; i++) {
40354
+ doubles.push({ s0: swapLE(k0), s1: swapLE(k1), s2: swapLE(k2), s3: swapLE(k3) });
40355
+ ({ s0: k0, s1: k1, s2: k2, s3: k3 } = mul2$1(k0, k1, k2, k3));
40356
+ }
40357
+ const W = estimateWindow(expectedLength || 1024);
40358
+ if (![1, 2, 4, 8].includes(W))
40359
+ throw new Error(`ghash: wrong window size=${W}, should be 2, 4 or 8`);
40360
+ this.W = W;
40361
+ const bits = 128; // always 128 bits;
40362
+ const windows = bits / W;
40363
+ const windowSize = (this.windowSize = 2 ** W);
40364
+ const items = [];
40365
+ // Create precompute table for window of W bits
40366
+ for (let w = 0; w < windows; w++) {
40367
+ // truth table: 00, 01, 10, 11
40368
+ for (let byte = 0; byte < windowSize; byte++) {
40369
+ // prettier-ignore
40370
+ let s0 = 0, s1 = 0, s2 = 0, s3 = 0;
40371
+ for (let j = 0; j < W; j++) {
40372
+ const bit = (byte >>> (W - j - 1)) & 1;
40373
+ if (!bit)
40374
+ continue;
40375
+ const { s0: d0, s1: d1, s2: d2, s3: d3 } = doubles[W * w + j];
40376
+ (s0 ^= d0), (s1 ^= d1), (s2 ^= d2), (s3 ^= d3);
40377
+ }
40378
+ items.push({ s0, s1, s2, s3 });
40379
+ }
40380
+ }
40381
+ this.t = items;
40382
+ }
40383
+ _updateBlock(s0, s1, s2, s3) {
40384
+ (s0 ^= this.s0), (s1 ^= this.s1), (s2 ^= this.s2), (s3 ^= this.s3);
40385
+ const { W, t, windowSize } = this;
40386
+ // prettier-ignore
40387
+ let o0 = 0, o1 = 0, o2 = 0, o3 = 0;
40388
+ const mask = (1 << W) - 1; // 2**W will kill performance.
40389
+ let w = 0;
40390
+ for (const num of [s0, s1, s2, s3]) {
40391
+ for (let bytePos = 0; bytePos < 4; bytePos++) {
40392
+ const byte = (num >>> (8 * bytePos)) & 0xff;
40393
+ for (let bitPos = 8 / W - 1; bitPos >= 0; bitPos--) {
40394
+ const bit = (byte >>> (W * bitPos)) & mask;
40395
+ const { s0: e0, s1: e1, s2: e2, s3: e3 } = t[w * windowSize + bit];
40396
+ (o0 ^= e0), (o1 ^= e1), (o2 ^= e2), (o3 ^= e3);
40397
+ w += 1;
40398
+ }
40399
+ }
40400
+ }
40401
+ this.s0 = o0;
40402
+ this.s1 = o1;
40403
+ this.s2 = o2;
40404
+ this.s3 = o3;
40405
+ }
40406
+ update(data) {
40407
+ data = toBytes(data);
40408
+ exists(this);
40409
+ const b32 = u32(data);
40410
+ const blocks = Math.floor(data.length / BLOCK_SIZE$1);
40411
+ const left = data.length % BLOCK_SIZE$1;
40412
+ for (let i = 0; i < blocks; i++) {
40413
+ this._updateBlock(b32[i * 4 + 0], b32[i * 4 + 1], b32[i * 4 + 2], b32[i * 4 + 3]);
40414
+ }
40415
+ if (left) {
40416
+ ZEROS16.set(data.subarray(blocks * BLOCK_SIZE$1));
40417
+ this._updateBlock(ZEROS32[0], ZEROS32[1], ZEROS32[2], ZEROS32[3]);
40418
+ ZEROS32.fill(0); // clean tmp buffer
40419
+ }
40420
+ return this;
40421
+ }
40422
+ destroy() {
40423
+ const { t } = this;
40424
+ // clean precompute table
40425
+ for (const elm of t) {
40426
+ (elm.s0 = 0), (elm.s1 = 0), (elm.s2 = 0), (elm.s3 = 0);
40427
+ }
40428
+ }
40429
+ digestInto(out) {
40430
+ exists(this);
40431
+ output(out, this);
40432
+ this.finished = true;
40433
+ const { s0, s1, s2, s3 } = this;
40434
+ const o32 = u32(out);
40435
+ o32[0] = s0;
40436
+ o32[1] = s1;
40437
+ o32[2] = s2;
40438
+ o32[3] = s3;
40439
+ return out;
40440
+ }
40441
+ digest() {
40442
+ const res = new Uint8Array(BLOCK_SIZE$1);
40443
+ this.digestInto(res);
40444
+ this.destroy();
40445
+ return res;
40446
+ }
40447
+ }
40448
+ class Polyval extends GHASH {
40449
+ constructor(key, expectedLength) {
40450
+ key = toBytes(key);
40451
+ const ghKey = _toGHASHKey(key.slice());
40452
+ super(ghKey, expectedLength);
40453
+ ghKey.fill(0);
40454
+ }
40455
+ update(data) {
40456
+ data = toBytes(data);
40457
+ exists(this);
40458
+ const b32 = u32(data);
40459
+ const left = data.length % BLOCK_SIZE$1;
40460
+ const blocks = Math.floor(data.length / BLOCK_SIZE$1);
40461
+ for (let i = 0; i < blocks; i++) {
40462
+ this._updateBlock(swapLE(b32[i * 4 + 3]), swapLE(b32[i * 4 + 2]), swapLE(b32[i * 4 + 1]), swapLE(b32[i * 4 + 0]));
40463
+ }
40464
+ if (left) {
40465
+ ZEROS16.set(data.subarray(blocks * BLOCK_SIZE$1));
40466
+ this._updateBlock(swapLE(ZEROS32[3]), swapLE(ZEROS32[2]), swapLE(ZEROS32[1]), swapLE(ZEROS32[0]));
40467
+ ZEROS32.fill(0); // clean tmp buffer
40468
+ }
40469
+ return this;
40470
+ }
40471
+ digestInto(out) {
40472
+ exists(this);
40473
+ output(out, this);
40474
+ this.finished = true;
40475
+ // tmp ugly hack
40476
+ const { s0, s1, s2, s3 } = this;
40477
+ const o32 = u32(out);
40478
+ o32[0] = s0;
40479
+ o32[1] = s1;
40480
+ o32[2] = s2;
40481
+ o32[3] = s3;
40482
+ return out.reverse();
40483
+ }
40484
+ }
40485
+ function wrapConstructorWithKey(hashCons) {
40486
+ const hashC = (msg, key) => hashCons(key, msg.length).update(toBytes(msg)).digest();
40487
+ const tmp = hashCons(new Uint8Array(16), 0);
40488
+ hashC.outputLen = tmp.outputLen;
40489
+ hashC.blockLen = tmp.blockLen;
40490
+ hashC.create = (key, expectedLength) => hashCons(key, expectedLength);
40491
+ return hashC;
40492
+ }
40493
+ const ghash = wrapConstructorWithKey((key, expectedLength) => new GHASH(key, expectedLength));
40494
+ const polyval = wrapConstructorWithKey((key, expectedLength) => new Polyval(key, expectedLength));
40495
+
40496
+ // AES (Advanced Encryption Standard) aka Rijndael block cipher.
40497
+ //
40498
+ // Data is split into 128-bit blocks. Encrypted in 10/12/14 rounds (128/192/256bit). Every round:
40499
+ // 1. **S-box**, table substitution
40500
+ // 2. **Shift rows**, cyclic shift left of all rows of data array
40501
+ // 3. **Mix columns**, multiplying every column by fixed polynomial
40502
+ // 4. **Add round key**, round_key xor i-th column of array
40503
+ //
40504
+ // Resources:
40505
+ // - FIPS-197 https://csrc.nist.gov/files/pubs/fips/197/final/docs/fips-197.pdf
40506
+ // - Original proposal: https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/aes-development/rijndael-ammended.pdf
40507
+ const BLOCK_SIZE = 16;
40508
+ const BLOCK_SIZE32 = 4;
40509
+ const EMPTY_BLOCK = new Uint8Array(BLOCK_SIZE);
40510
+ const POLY = 0x11b; // 1 + x + x**3 + x**4 + x**8
40511
+ // TODO: remove multiplication, binary ops only
40512
+ function mul2(n) {
40513
+ return (n << 1) ^ (POLY & -(n >> 7));
40514
+ }
40515
+ function mul(a, b) {
40516
+ let res = 0;
40517
+ for (; b > 0; b >>= 1) {
40518
+ // Montgomery ladder
40519
+ res ^= a & -(b & 1); // if (b&1) res ^=a (but const-time).
40520
+ a = mul2(a); // a = 2*a
40521
+ }
40522
+ return res;
40523
+ }
40524
+ // AES S-box is generated using finite field inversion,
40525
+ // an affine transform, and xor of a constant 0x63.
40526
+ const _sbox = /* @__PURE__ */ (() => {
40527
+ let t = new Uint8Array(256);
40528
+ for (let i = 0, x = 1; i < 256; i++, x ^= mul2(x))
40529
+ t[i] = x;
40530
+ const sbox = new Uint8Array(256);
40531
+ sbox[0] = 0x63; // first elm
40532
+ for (let i = 0; i < 255; i++) {
40533
+ let x = t[255 - i];
40534
+ x |= x << 8;
40535
+ sbox[t[i]] = (x ^ (x >> 4) ^ (x >> 5) ^ (x >> 6) ^ (x >> 7) ^ 0x63) & 0xff;
40536
+ }
40537
+ return sbox;
40538
+ })();
40539
+ // Inverted S-box
40540
+ const _inv_sbox = /* @__PURE__ */ _sbox.map((_, j) => _sbox.indexOf(j));
40541
+ // Rotate u32 by 8
40542
+ const rotr32_8 = (n) => (n << 24) | (n >>> 8);
40543
+ const rotl32_8 = (n) => (n << 8) | (n >>> 24);
40544
+ // T-table is optimization suggested in 5.2 of original proposal (missed from FIPS-197). Changes:
40545
+ // - LE instead of BE
40546
+ // - bigger tables: T0 and T1 are merged into T01 table and T2 & T3 into T23;
40547
+ // so index is u16, instead of u8. This speeds up things, unexpectedly
40548
+ function genTtable(sbox, fn) {
40549
+ if (sbox.length !== 256)
40550
+ throw new Error('Wrong sbox length');
40551
+ const T0 = new Uint32Array(256).map((_, j) => fn(sbox[j]));
40552
+ const T1 = T0.map(rotl32_8);
40553
+ const T2 = T1.map(rotl32_8);
40554
+ const T3 = T2.map(rotl32_8);
40555
+ const T01 = new Uint32Array(256 * 256);
40556
+ const T23 = new Uint32Array(256 * 256);
40557
+ const sbox2 = new Uint16Array(256 * 256);
40558
+ for (let i = 0; i < 256; i++) {
40559
+ for (let j = 0; j < 256; j++) {
40560
+ const idx = i * 256 + j;
40561
+ T01[idx] = T0[i] ^ T1[j];
40562
+ T23[idx] = T2[i] ^ T3[j];
40563
+ sbox2[idx] = (sbox[i] << 8) | sbox[j];
40564
+ }
40565
+ }
40566
+ return { sbox, sbox2, T0, T1, T2, T3, T01, T23 };
40567
+ }
40568
+ const TABLE_ENC = /* @__PURE__ */ genTtable(_sbox, (s) => (mul(s, 3) << 24) | (s << 16) | (s << 8) | mul(s, 2));
40569
+ const TABLE_DEC = /* @__PURE__ */ genTtable(_inv_sbox, (s) => (mul(s, 11) << 24) | (mul(s, 13) << 16) | (mul(s, 9) << 8) | mul(s, 14));
40570
+ const POWX = /* @__PURE__ */ (() => {
40571
+ const p = new Uint8Array(16);
40572
+ for (let i = 0, x = 1; i < 16; i++, x = mul2(x))
40573
+ p[i] = x;
40574
+ return p;
40575
+ })();
40576
+ function expandKeyLE(key) {
40577
+ ensureBytes(key);
40578
+ const len = key.length;
40579
+ if (![16, 24, 32].includes(len))
40580
+ throw new Error(`aes: wrong key size: should be 16, 24 or 32, got: ${len}`);
40581
+ const { sbox2 } = TABLE_ENC;
40582
+ const k32 = u32(key);
40583
+ const Nk = k32.length;
40584
+ const subByte = (n) => applySbox(sbox2, n, n, n, n);
40585
+ const xk = new Uint32Array(len + 28); // expanded key
40586
+ xk.set(k32);
40587
+ // 4.3.1 Key expansion
40588
+ for (let i = Nk; i < xk.length; i++) {
40589
+ let t = xk[i - 1];
40590
+ if (i % Nk === 0)
40591
+ t = subByte(rotr32_8(t)) ^ POWX[i / Nk - 1];
40592
+ else if (Nk > 6 && i % Nk === 4)
40593
+ t = subByte(t);
40594
+ xk[i] = xk[i - Nk] ^ t;
40595
+ }
40596
+ return xk;
40597
+ }
40598
+ function expandKeyDecLE(key) {
40599
+ const encKey = expandKeyLE(key);
40600
+ const xk = encKey.slice();
40601
+ const Nk = encKey.length;
40602
+ const { sbox2 } = TABLE_ENC;
40603
+ const { T0, T1, T2, T3 } = TABLE_DEC;
40604
+ // Inverse key by chunks of 4 (rounds)
40605
+ for (let i = 0; i < Nk; i += 4) {
40606
+ for (let j = 0; j < 4; j++)
40607
+ xk[i + j] = encKey[Nk - i - 4 + j];
40608
+ }
40609
+ encKey.fill(0);
40610
+ // apply InvMixColumn except first & last round
40611
+ for (let i = 4; i < Nk - 4; i++) {
40612
+ const x = xk[i];
40613
+ const w = applySbox(sbox2, x, x, x, x);
40614
+ xk[i] = T0[w & 0xff] ^ T1[(w >>> 8) & 0xff] ^ T2[(w >>> 16) & 0xff] ^ T3[w >>> 24];
40615
+ }
40616
+ return xk;
40617
+ }
40618
+ // Apply tables
40619
+ function apply0123(T01, T23, s0, s1, s2, s3) {
40620
+ return (T01[((s0 << 8) & 0xff00) | ((s1 >>> 8) & 0xff)] ^
40621
+ T23[((s2 >>> 8) & 0xff00) | ((s3 >>> 24) & 0xff)]);
40622
+ }
40623
+ function applySbox(sbox2, s0, s1, s2, s3) {
40624
+ return (sbox2[(s0 & 0xff) | (s1 & 0xff00)] |
40625
+ (sbox2[((s2 >>> 16) & 0xff) | ((s3 >>> 16) & 0xff00)] << 16));
40626
+ }
40627
+ function encrypt(xk, s0, s1, s2, s3) {
40628
+ const { sbox2, T01, T23 } = TABLE_ENC;
40629
+ let k = 0;
40630
+ (s0 ^= xk[k++]), (s1 ^= xk[k++]), (s2 ^= xk[k++]), (s3 ^= xk[k++]);
40631
+ const rounds = xk.length / 4 - 2;
40632
+ for (let i = 0; i < rounds; i++) {
40633
+ const t0 = xk[k++] ^ apply0123(T01, T23, s0, s1, s2, s3);
40634
+ const t1 = xk[k++] ^ apply0123(T01, T23, s1, s2, s3, s0);
40635
+ const t2 = xk[k++] ^ apply0123(T01, T23, s2, s3, s0, s1);
40636
+ const t3 = xk[k++] ^ apply0123(T01, T23, s3, s0, s1, s2);
40637
+ (s0 = t0), (s1 = t1), (s2 = t2), (s3 = t3);
40638
+ }
40639
+ // last round (without mixcolumns, so using SBOX2 table)
40640
+ const t0 = xk[k++] ^ applySbox(sbox2, s0, s1, s2, s3);
40641
+ const t1 = xk[k++] ^ applySbox(sbox2, s1, s2, s3, s0);
40642
+ const t2 = xk[k++] ^ applySbox(sbox2, s2, s3, s0, s1);
40643
+ const t3 = xk[k++] ^ applySbox(sbox2, s3, s0, s1, s2);
40644
+ return { s0: t0, s1: t1, s2: t2, s3: t3 };
40645
+ }
40646
+ function decrypt(xk, s0, s1, s2, s3) {
40647
+ const { sbox2, T01, T23 } = TABLE_DEC;
40648
+ let k = 0;
40649
+ (s0 ^= xk[k++]), (s1 ^= xk[k++]), (s2 ^= xk[k++]), (s3 ^= xk[k++]);
40650
+ const rounds = xk.length / 4 - 2;
40651
+ for (let i = 0; i < rounds; i++) {
40652
+ const t0 = xk[k++] ^ apply0123(T01, T23, s0, s3, s2, s1);
40653
+ const t1 = xk[k++] ^ apply0123(T01, T23, s1, s0, s3, s2);
40654
+ const t2 = xk[k++] ^ apply0123(T01, T23, s2, s1, s0, s3);
40655
+ const t3 = xk[k++] ^ apply0123(T01, T23, s3, s2, s1, s0);
40656
+ (s0 = t0), (s1 = t1), (s2 = t2), (s3 = t3);
40657
+ }
40658
+ // Last round
40659
+ const t0 = xk[k++] ^ applySbox(sbox2, s0, s3, s2, s1);
40660
+ const t1 = xk[k++] ^ applySbox(sbox2, s1, s0, s3, s2);
40661
+ const t2 = xk[k++] ^ applySbox(sbox2, s2, s1, s0, s3);
40662
+ const t3 = xk[k++] ^ applySbox(sbox2, s3, s2, s1, s0);
40663
+ return { s0: t0, s1: t1, s2: t2, s3: t3 };
40664
+ }
40665
+ function getDst(len, dst) {
40666
+ if (!dst)
40667
+ return new Uint8Array(len);
40668
+ ensureBytes(dst);
40669
+ if (dst.length < len)
40670
+ throw new Error(`aes: wrong destination length, expected at least ${len}, got: ${dst.length}`);
40671
+ return dst;
40672
+ }
40673
+ // TODO: investigate merging with ctr32
40674
+ function ctrCounter(xk, nonce, src, dst) {
40675
+ ensureBytes(nonce, BLOCK_SIZE);
40676
+ ensureBytes(src);
40677
+ const srcLen = src.length;
40678
+ dst = getDst(srcLen, dst);
40679
+ const ctr = nonce;
40680
+ const c32 = u32(ctr);
40681
+ // Fill block (empty, ctr=0)
40682
+ let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]);
40683
+ const src32 = u32(src);
40684
+ const dst32 = u32(dst);
40685
+ // process blocks
40686
+ for (let i = 0; i + 4 <= src32.length; i += 4) {
40687
+ dst32[i + 0] = src32[i + 0] ^ s0;
40688
+ dst32[i + 1] = src32[i + 1] ^ s1;
40689
+ dst32[i + 2] = src32[i + 2] ^ s2;
40690
+ dst32[i + 3] = src32[i + 3] ^ s3;
40691
+ // Full 128 bit counter with wrap around
40692
+ let carry = 1;
40693
+ for (let i = ctr.length - 1; i >= 0; i--) {
40694
+ carry = (carry + (ctr[i] & 0xff)) | 0;
40695
+ ctr[i] = carry & 0xff;
40696
+ carry >>>= 8;
40697
+ }
40698
+ ({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]));
40699
+ }
40700
+ // leftovers (less than block)
40701
+ // It's possible to handle > u32 fast, but is it worth it?
40702
+ const start = BLOCK_SIZE * Math.floor(src32.length / BLOCK_SIZE32);
40703
+ if (start < srcLen) {
40704
+ const b32 = new Uint32Array([s0, s1, s2, s3]);
40705
+ const buf = u8(b32);
40706
+ for (let i = start, pos = 0; i < srcLen; i++, pos++)
40707
+ dst[i] = src[i] ^ buf[pos];
40708
+ }
40709
+ return dst;
40710
+ }
40711
+ // AES CTR with overflowing 32 bit counter
40712
+ // It's possible to do 32le significantly simpler (and probably faster) by using u32.
40713
+ // But, we need both, and perf bottleneck is in ghash anyway.
40714
+ function ctr32(xk, isLE, nonce, src, dst) {
40715
+ ensureBytes(nonce, BLOCK_SIZE);
40716
+ ensureBytes(src);
40717
+ dst = getDst(src.length, dst);
40718
+ const ctr = nonce; // write new value to nonce, so it can be re-used
40719
+ const c32 = u32(ctr);
40720
+ const view = createView(ctr);
40721
+ const src32 = u32(src);
40722
+ const dst32 = u32(dst);
40723
+ const ctrPos = isLE ? 0 : 12;
40724
+ const srcLen = src.length;
40725
+ // Fill block (empty, ctr=0)
40726
+ let ctrNum = view.getUint32(ctrPos, isLE); // read current counter value
40727
+ let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]);
40728
+ // process blocks
40729
+ for (let i = 0; i + 4 <= src32.length; i += 4) {
40730
+ dst32[i + 0] = src32[i + 0] ^ s0;
40731
+ dst32[i + 1] = src32[i + 1] ^ s1;
40732
+ dst32[i + 2] = src32[i + 2] ^ s2;
40733
+ dst32[i + 3] = src32[i + 3] ^ s3;
40734
+ ctrNum = (ctrNum + 1) >>> 0; // u32 wrap
40735
+ view.setUint32(ctrPos, ctrNum, isLE);
40736
+ ({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]));
40737
+ }
40738
+ // leftovers (less than a block)
40739
+ const start = BLOCK_SIZE * Math.floor(src32.length / BLOCK_SIZE32);
40740
+ if (start < srcLen) {
40741
+ const b32 = new Uint32Array([s0, s1, s2, s3]);
40742
+ const buf = u8(b32);
40743
+ for (let i = start, pos = 0; i < srcLen; i++, pos++)
40744
+ dst[i] = src[i] ^ buf[pos];
40745
+ }
40746
+ return dst;
40747
+ }
40748
+ /**
40749
+ * CTR: counter mode. Creates stream cipher.
40750
+ * Requires good IV. Parallelizable. OK, but no MAC.
40751
+ */
40752
+ wrapCipher({ blockSize: 16, nonceLength: 16 }, function ctr(key, nonce) {
40753
+ ensureBytes(key);
40754
+ ensureBytes(nonce, BLOCK_SIZE);
40755
+ function processCtr(buf, dst) {
40756
+ const xk = expandKeyLE(key);
40757
+ const n = nonce.slice();
40758
+ const out = ctrCounter(xk, n, buf, dst);
40759
+ xk.fill(0);
40760
+ n.fill(0);
40761
+ return out;
40762
+ }
40763
+ return {
40764
+ encrypt: (plaintext, dst) => processCtr(plaintext, dst),
40765
+ decrypt: (ciphertext, dst) => processCtr(ciphertext, dst),
40766
+ };
40767
+ });
40768
+ function validateBlockDecrypt(data) {
40769
+ ensureBytes(data);
40770
+ if (data.length % BLOCK_SIZE !== 0) {
40771
+ throw new Error(`aes/(cbc-ecb).decrypt ciphertext should consist of blocks with size ${BLOCK_SIZE}`);
40772
+ }
40773
+ }
40774
+ function validateBlockEncrypt(plaintext, pcks5, dst) {
40775
+ let outLen = plaintext.length;
40776
+ const remaining = outLen % BLOCK_SIZE;
40777
+ if (!pcks5 && remaining !== 0)
40778
+ throw new Error('aec/(cbc-ecb): unpadded plaintext with disabled padding');
40779
+ const b = u32(plaintext);
40780
+ if (pcks5) {
40781
+ let left = BLOCK_SIZE - remaining;
40782
+ if (!left)
40783
+ left = BLOCK_SIZE; // if no bytes left, create empty padding block
40784
+ outLen = outLen + left;
40785
+ }
40786
+ const out = getDst(outLen, dst);
40787
+ const o = u32(out);
40788
+ return { b, o, out };
40789
+ }
40790
+ function validatePCKS(data, pcks5) {
40791
+ if (!pcks5)
40792
+ return data;
40793
+ const len = data.length;
40794
+ if (!len)
40795
+ throw new Error(`aes/pcks5: empty ciphertext not allowed`);
40796
+ const lastByte = data[len - 1];
40797
+ if (lastByte <= 0 || lastByte > 16)
40798
+ throw new Error(`aes/pcks5: wrong padding byte: ${lastByte}`);
40799
+ const out = data.subarray(0, -lastByte);
40800
+ for (let i = 0; i < lastByte; i++)
40801
+ if (data[len - i - 1] !== lastByte)
40802
+ throw new Error(`aes/pcks5: wrong padding`);
40803
+ return out;
40804
+ }
40805
+ function padPCKS(left) {
40806
+ const tmp = new Uint8Array(16);
40807
+ const tmp32 = u32(tmp);
40808
+ tmp.set(left);
40809
+ const paddingByte = BLOCK_SIZE - left.length;
40810
+ for (let i = BLOCK_SIZE - paddingByte; i < BLOCK_SIZE; i++)
40811
+ tmp[i] = paddingByte;
40812
+ return tmp32;
40813
+ }
40814
+ /**
40815
+ * ECB: Electronic CodeBook. Simple deterministic replacement.
40816
+ * Dangerous: always map x to y. See [AES Penguin](https://words.filippo.io/the-ecb-penguin/).
40817
+ */
40818
+ wrapCipher({ blockSize: 16 }, function ecb(key, opts = {}) {
40819
+ ensureBytes(key);
40820
+ const pcks5 = !opts.disablePadding;
40821
+ return {
40822
+ encrypt: (plaintext, dst) => {
40823
+ ensureBytes(plaintext);
40824
+ const { b, o, out: _out } = validateBlockEncrypt(plaintext, pcks5, dst);
40825
+ const xk = expandKeyLE(key);
40826
+ let i = 0;
40827
+ for (; i + 4 <= b.length;) {
40828
+ const { s0, s1, s2, s3 } = encrypt(xk, b[i + 0], b[i + 1], b[i + 2], b[i + 3]);
40829
+ (o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
40830
+ }
40831
+ if (pcks5) {
40832
+ const tmp32 = padPCKS(plaintext.subarray(i * 4));
40833
+ const { s0, s1, s2, s3 } = encrypt(xk, tmp32[0], tmp32[1], tmp32[2], tmp32[3]);
40834
+ (o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
40835
+ }
40836
+ xk.fill(0);
40837
+ return _out;
40838
+ },
40839
+ decrypt: (ciphertext, dst) => {
40840
+ validateBlockDecrypt(ciphertext);
40841
+ const xk = expandKeyDecLE(key);
40842
+ const out = getDst(ciphertext.length, dst);
40843
+ const b = u32(ciphertext);
40844
+ const o = u32(out);
40845
+ for (let i = 0; i + 4 <= b.length;) {
40846
+ const { s0, s1, s2, s3 } = decrypt(xk, b[i + 0], b[i + 1], b[i + 2], b[i + 3]);
40847
+ (o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
40848
+ }
40849
+ xk.fill(0);
40850
+ return validatePCKS(out, pcks5);
40851
+ },
40852
+ };
40853
+ });
40854
+ /**
40855
+ * CBC: Cipher-Block-Chaining. Key is previous round’s block.
40856
+ * Fragile: needs proper padding. Unauthenticated: needs MAC.
40857
+ */
40858
+ wrapCipher({ blockSize: 16, nonceLength: 16 }, function cbc(key, iv, opts = {}) {
40859
+ ensureBytes(key);
40860
+ ensureBytes(iv, 16);
40861
+ const pcks5 = !opts.disablePadding;
40862
+ return {
40863
+ encrypt: (plaintext, dst) => {
40864
+ const xk = expandKeyLE(key);
40865
+ const { b, o, out: _out } = validateBlockEncrypt(plaintext, pcks5, dst);
40866
+ const n32 = u32(iv);
40867
+ // prettier-ignore
40868
+ let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3];
40869
+ let i = 0;
40870
+ for (; i + 4 <= b.length;) {
40871
+ (s0 ^= b[i + 0]), (s1 ^= b[i + 1]), (s2 ^= b[i + 2]), (s3 ^= b[i + 3]);
40872
+ ({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3));
40873
+ (o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
40874
+ }
40875
+ if (pcks5) {
40876
+ const tmp32 = padPCKS(plaintext.subarray(i * 4));
40877
+ (s0 ^= tmp32[0]), (s1 ^= tmp32[1]), (s2 ^= tmp32[2]), (s3 ^= tmp32[3]);
40878
+ ({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3));
40879
+ (o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
40880
+ }
40881
+ xk.fill(0);
40882
+ return _out;
40883
+ },
40884
+ decrypt: (ciphertext, dst) => {
40885
+ validateBlockDecrypt(ciphertext);
40886
+ const xk = expandKeyDecLE(key);
40887
+ const n32 = u32(iv);
40888
+ const out = getDst(ciphertext.length, dst);
40889
+ const b = u32(ciphertext);
40890
+ const o = u32(out);
40891
+ // prettier-ignore
40892
+ let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3];
40893
+ for (let i = 0; i + 4 <= b.length;) {
40894
+ // prettier-ignore
40895
+ const ps0 = s0, ps1 = s1, ps2 = s2, ps3 = s3;
40896
+ (s0 = b[i + 0]), (s1 = b[i + 1]), (s2 = b[i + 2]), (s3 = b[i + 3]);
40897
+ const { s0: o0, s1: o1, s2: o2, s3: o3 } = decrypt(xk, s0, s1, s2, s3);
40898
+ (o[i++] = o0 ^ ps0), (o[i++] = o1 ^ ps1), (o[i++] = o2 ^ ps2), (o[i++] = o3 ^ ps3);
40899
+ }
40900
+ xk.fill(0);
40901
+ return validatePCKS(out, pcks5);
40902
+ },
40903
+ };
40904
+ });
40905
+ // TODO: merge with chacha, however gcm has bitLen while chacha has byteLen
40906
+ function computeTag(fn, isLE, key, data, AAD) {
40907
+ const h = fn.create(key, data.length + (AAD?.length || 0));
40908
+ if (AAD)
40909
+ h.update(AAD);
40910
+ h.update(data);
40911
+ const num = new Uint8Array(16);
40912
+ const view = createView(num);
40913
+ if (AAD)
40914
+ setBigUint64(view, 0, BigInt(AAD.length * 8), isLE);
40915
+ setBigUint64(view, 8, BigInt(data.length * 8), isLE);
40916
+ h.update(num);
40917
+ return h.digest();
40918
+ }
40919
+ /**
40920
+ * GCM: Galois/Counter Mode.
40921
+ * Good, modern version of CTR, parallel, with MAC.
40922
+ * Be careful: MACs can be forged.
40923
+ */
40924
+ const gcm = wrapCipher({ blockSize: 16, nonceLength: 12, tagLength: 16 }, function gcm(key, nonce, AAD) {
40925
+ ensureBytes(nonce);
40926
+ // Nonce can be pretty much anything (even 1 byte). But smaller nonces less secure.
40927
+ if (nonce.length === 0)
40928
+ throw new Error('aes/gcm: empty nonce');
40929
+ const tagLength = 16;
40930
+ function _computeTag(authKey, tagMask, data) {
40931
+ const tag = computeTag(ghash, false, authKey, data, AAD);
40932
+ for (let i = 0; i < tagMask.length; i++)
40933
+ tag[i] ^= tagMask[i];
40934
+ return tag;
40935
+ }
40936
+ function deriveKeys() {
40937
+ const xk = expandKeyLE(key);
40938
+ const authKey = EMPTY_BLOCK.slice();
40939
+ const counter = EMPTY_BLOCK.slice();
40940
+ ctr32(xk, false, counter, counter, authKey);
40941
+ if (nonce.length === 12) {
40942
+ counter.set(nonce);
40943
+ }
40944
+ else {
40945
+ // Spec (NIST 800-38d) supports variable size nonce.
40946
+ // Not supported for now, but can be useful.
40947
+ const nonceLen = EMPTY_BLOCK.slice();
40948
+ const view = createView(nonceLen);
40949
+ setBigUint64(view, 8, BigInt(nonce.length * 8), false);
40950
+ // ghash(nonce || u64be(0) || u64be(nonceLen*8))
40951
+ ghash.create(authKey).update(nonce).update(nonceLen).digestInto(counter);
40952
+ }
40953
+ const tagMask = ctr32(xk, false, counter, EMPTY_BLOCK);
40954
+ return { xk, authKey, counter, tagMask };
40955
+ }
40956
+ return {
40957
+ encrypt: (plaintext) => {
40958
+ ensureBytes(plaintext);
40959
+ const { xk, authKey, counter, tagMask } = deriveKeys();
40960
+ const out = new Uint8Array(plaintext.length + tagLength);
40961
+ ctr32(xk, false, counter, plaintext, out);
40962
+ const tag = _computeTag(authKey, tagMask, out.subarray(0, out.length - tagLength));
40963
+ out.set(tag, plaintext.length);
40964
+ xk.fill(0);
40965
+ return out;
40966
+ },
40967
+ decrypt: (ciphertext) => {
40968
+ ensureBytes(ciphertext);
40969
+ if (ciphertext.length < tagLength)
40970
+ throw new Error(`aes/gcm: ciphertext less than tagLen (${tagLength})`);
40971
+ const { xk, authKey, counter, tagMask } = deriveKeys();
40972
+ const data = ciphertext.subarray(0, -tagLength);
40973
+ const passedTag = ciphertext.subarray(-tagLength);
40974
+ const tag = _computeTag(authKey, tagMask, data);
40975
+ if (!equalBytes(tag, passedTag))
40976
+ throw new Error('aes/gcm: invalid ghash tag');
40977
+ const out = ctr32(xk, false, counter, data);
40978
+ authKey.fill(0);
40979
+ tagMask.fill(0);
40980
+ xk.fill(0);
40981
+ return out;
40982
+ },
40983
+ };
40984
+ });
40985
+ const limit = (name, min, max) => (value) => {
40986
+ if (!Number.isSafeInteger(value) || min > value || value > max)
40987
+ throw new Error(`${name}: invalid value=${value}, must be [${min}..${max}]`);
40988
+ };
40989
+ /**
40990
+ * AES-GCM-SIV: classic AES-GCM with nonce-misuse resistance.
40991
+ * Guarantees that, when a nonce is repeated, the only security loss is that identical
40992
+ * plaintexts will produce identical ciphertexts.
40993
+ * RFC 8452, https://datatracker.ietf.org/doc/html/rfc8452
40994
+ */
40995
+ wrapCipher({ blockSize: 16, nonceLength: 12, tagLength: 16 }, function siv(key, nonce, AAD) {
40996
+ const tagLength = 16;
40997
+ // From RFC 8452: Section 6
40998
+ const AAD_LIMIT = limit('AAD', 0, 2 ** 36);
40999
+ const PLAIN_LIMIT = limit('plaintext', 0, 2 ** 36);
41000
+ const NONCE_LIMIT = limit('nonce', 12, 12);
41001
+ const CIPHER_LIMIT = limit('ciphertext', 16, 2 ** 36 + 16);
41002
+ ensureBytes(nonce);
41003
+ NONCE_LIMIT(nonce.length);
41004
+ if (AAD) {
41005
+ ensureBytes(AAD);
41006
+ AAD_LIMIT(AAD.length);
41007
+ }
41008
+ function deriveKeys() {
41009
+ const len = key.length;
41010
+ if (len !== 16 && len !== 24 && len !== 32)
41011
+ throw new Error(`key length must be 16, 24 or 32 bytes, got: ${len} bytes`);
41012
+ const xk = expandKeyLE(key);
41013
+ const encKey = new Uint8Array(len);
41014
+ const authKey = new Uint8Array(16);
41015
+ const n32 = u32(nonce);
41016
+ // prettier-ignore
41017
+ let s0 = 0, s1 = n32[0], s2 = n32[1], s3 = n32[2];
41018
+ let counter = 0;
41019
+ for (const derivedKey of [authKey, encKey].map(u32)) {
41020
+ const d32 = u32(derivedKey);
41021
+ for (let i = 0; i < d32.length; i += 2) {
41022
+ // aes(u32le(0) || nonce)[:8] || aes(u32le(1) || nonce)[:8] ...
41023
+ const { s0: o0, s1: o1 } = encrypt(xk, s0, s1, s2, s3);
41024
+ d32[i + 0] = o0;
41025
+ d32[i + 1] = o1;
41026
+ s0 = ++counter; // increment counter inside state
41027
+ }
41028
+ }
41029
+ xk.fill(0);
41030
+ return { authKey, encKey: expandKeyLE(encKey) };
41031
+ }
41032
+ function _computeTag(encKey, authKey, data) {
41033
+ const tag = computeTag(polyval, true, authKey, data, AAD);
41034
+ // Compute the expected tag by XORing S_s and the nonce, clearing the
41035
+ // most significant bit of the last byte and encrypting with the
41036
+ // message-encryption key.
41037
+ for (let i = 0; i < 12; i++)
41038
+ tag[i] ^= nonce[i];
41039
+ tag[15] &= 0x7f; // Clear the highest bit
41040
+ // encrypt tag as block
41041
+ const t32 = u32(tag);
41042
+ // prettier-ignore
41043
+ let s0 = t32[0], s1 = t32[1], s2 = t32[2], s3 = t32[3];
41044
+ ({ s0, s1, s2, s3 } = encrypt(encKey, s0, s1, s2, s3));
41045
+ (t32[0] = s0), (t32[1] = s1), (t32[2] = s2), (t32[3] = s3);
41046
+ return tag;
41047
+ }
41048
+ // actual decrypt/encrypt of message.
41049
+ function processSiv(encKey, tag, input) {
41050
+ let block = tag.slice();
41051
+ block[15] |= 0x80; // Force highest bit
41052
+ return ctr32(encKey, true, block, input);
41053
+ }
41054
+ return {
41055
+ encrypt: (plaintext) => {
41056
+ ensureBytes(plaintext);
41057
+ PLAIN_LIMIT(plaintext.length);
41058
+ const { encKey, authKey } = deriveKeys();
41059
+ const tag = _computeTag(encKey, authKey, plaintext);
41060
+ const out = new Uint8Array(plaintext.length + tagLength);
41061
+ out.set(tag, plaintext.length);
41062
+ out.set(processSiv(encKey, tag, plaintext));
41063
+ encKey.fill(0);
41064
+ authKey.fill(0);
41065
+ return out;
41066
+ },
41067
+ decrypt: (ciphertext) => {
41068
+ ensureBytes(ciphertext);
41069
+ CIPHER_LIMIT(ciphertext.length);
41070
+ const tag = ciphertext.subarray(-tagLength);
41071
+ const { encKey, authKey } = deriveKeys();
41072
+ const plaintext = processSiv(encKey, tag, ciphertext.subarray(0, -tagLength));
41073
+ const expectedTag = _computeTag(encKey, authKey, plaintext);
41074
+ encKey.fill(0);
41075
+ authKey.fill(0);
41076
+ if (!equalBytes(tag, expectedTag))
41077
+ throw new Error('invalid polyval tag');
41078
+ return plaintext;
41079
+ },
41080
+ };
41081
+ });
41082
+
41083
+ /**
41084
+ * Concat KDF (Single Step KDF) per NIST SP 800-56A and RFC 7518 §4.6.2.
41085
+ *
41086
+ * Derives a symmetric key from an ECDH shared secret for use in JWE.
41087
+ *
41088
+ * @param sharedSecret - The raw ECDH shared secret (Z)
41089
+ * @param keyBitLength - Desired key length in bits (e.g. 128, 256)
41090
+ * @param algorithmId - The "enc" value for ECDH-ES or "alg" for ECDH-ES+A*KW
41091
+ * @param apu - Agreement PartyUInfo (typically empty)
41092
+ * @param apv - Agreement PartyVInfo (typically empty)
41093
+ * @returns Derived key as Uint8Array
41094
+ */
41095
+ function concatKdf(sharedSecret, keyBitLength, algorithmId, apu = new Uint8Array(0), apv = new Uint8Array(0)) {
41096
+ const algIdBytes = new TextEncoder().encode(algorithmId);
41097
+ // Build otherInfo per RFC 7518 §4.6.2:
41098
+ // AlgorithmID = len(algId) || algId
41099
+ // PartyUInfo = len(apu) || apu
41100
+ // PartyVInfo = len(apv) || apv
41101
+ // SuppPubInfo = keydatalen (32-bit BE, in bits)
41102
+ const otherInfo = concatBytes(uint32BE(algIdBytes.length), algIdBytes, uint32BE(apu.length), apu, uint32BE(apv.length), apv, uint32BE(keyBitLength));
41103
+ const hashLength = 256; // SHA-256 output in bits
41104
+ const reps = Math.ceil(keyBitLength / hashLength);
41105
+ const result = new Uint8Array(reps * 32);
41106
+ for (let counter = 1; counter <= reps; counter++) {
41107
+ const input = concatBytes(uint32BE(counter), sharedSecret, otherInfo);
41108
+ const digest = sha256$1(input);
41109
+ result.set(digest, (counter - 1) * 32);
41110
+ }
41111
+ return result.slice(0, keyBitLength / 8);
41112
+ }
41113
+ function uint32BE(value) {
41114
+ const buf = new Uint8Array(4);
41115
+ buf[0] = (value >>> 24) & 0xff;
41116
+ buf[1] = (value >>> 16) & 0xff;
41117
+ buf[2] = (value >>> 8) & 0xff;
41118
+ buf[3] = value & 0xff;
41119
+ return buf;
41120
+ }
41121
+ function concatBytes(...arrays) {
41122
+ let totalLength = 0;
41123
+ for (const arr of arrays)
41124
+ totalLength += arr.length;
41125
+ const result = new Uint8Array(totalLength);
41126
+ let offset = 0;
41127
+ for (const arr of arrays) {
41128
+ result.set(arr, offset);
41129
+ offset += arr.length;
41130
+ }
41131
+ return result;
41132
+ }
41133
+
41134
+ const ENCODER = new TextEncoder();
41135
+ const DECODER = new TextDecoder();
41136
+ /**
41137
+ * Build a JWE Compact Serialization string using ECDH-ES + A256GCM.
41138
+ *
41139
+ * Uses an ephemeral secp256k1 keypair for key agreement.
41140
+ * The sender's identity key is NOT involved — only the recipient's public key.
41141
+ *
41142
+ * @param recipientPubKey - Recipient's public key (secp256k1 JWK)
41143
+ * @param plaintext - Data to encrypt
41144
+ * @returns JWE Compact string: header.encryptedKey.iv.ciphertext.tag
41145
+ */
41146
+ function buildJweCompact(recipientPubKey, plaintext) {
41147
+ // 1. Generate ephemeral keypair
41148
+ const ephemeralPrivKey = utils.randomPrivateKey();
41149
+ const ephemeralPubKeyBytes = getPublicKey(ephemeralPrivKey);
41150
+ // Get uncompressed public key coordinates for the JWE header
41151
+ const ephemeralPubHex = etc.bytesToHex(ephemeralPubKeyBytes);
41152
+ const curvePoints = Point.fromHex(ephemeralPubHex);
41153
+ const uncompressed = curvePoints.toRawBytes(false);
41154
+ const epkX = base64url.baseEncode(uncompressed.subarray(1, 33));
41155
+ const epkY = base64url.baseEncode(uncompressed.subarray(33, 65));
41156
+ // 2. Build protected header
41157
+ const header = {
41158
+ alg: 'ECDH-ES',
41159
+ enc: 'A256GCM',
41160
+ epk: {
41161
+ kty: 'EC',
41162
+ crv: 'secp256k1',
41163
+ x: epkX,
41164
+ y: epkY,
41165
+ },
41166
+ };
41167
+ const headerJson = JSON.stringify(header);
41168
+ const headerB64 = base64url.baseEncode(ENCODER.encode(headerJson));
41169
+ // 3. ECDH key agreement: ephemeral private + recipient public
41170
+ const recipientPubBytes = jwkToCompressedBytes(recipientPubKey);
41171
+ const sharedSecret = getSharedSecret(ephemeralPrivKey, recipientPubBytes);
41172
+ // 4. Derive CEK via Concat KDF (RFC 7518 §4.6.2)
41173
+ // For ECDH-ES (direct), algorithmId = enc value
41174
+ const cek = concatKdf(sharedSecret.slice(1), 256, 'A256GCM');
41175
+ // 5. Generate random 96-bit IV
41176
+ const iv = utils.randomPrivateKey().slice(0, 12);
41177
+ // 6. Encrypt with AES-256-GCM
41178
+ // AAD = ASCII bytes of the base64url-encoded protected header
41179
+ const aad = ENCODER.encode(headerB64);
41180
+ const cipher = gcm(cek, iv, aad);
41181
+ const encrypted = cipher.encrypt(plaintext); // returns ciphertext || tag
41182
+ // 7. Split ciphertext and tag (tag is last 16 bytes)
41183
+ const ciphertext = encrypted.slice(0, encrypted.length - 16);
41184
+ const tag = encrypted.slice(encrypted.length - 16);
41185
+ // 8. Assemble JWE Compact: header.encryptedKey.iv.ciphertext.tag
41186
+ // For ECDH-ES (direct), encrypted key is empty
41187
+ return [
41188
+ headerB64,
41189
+ '', // empty encrypted key
41190
+ base64url.baseEncode(iv),
41191
+ base64url.baseEncode(ciphertext),
41192
+ base64url.baseEncode(tag),
41193
+ ].join('.');
41194
+ }
41195
+ /**
41196
+ * Parse and decrypt a JWE Compact Serialization string.
41197
+ *
41198
+ * @param recipientPrivKey - Recipient's private key (secp256k1 JWK)
41199
+ * @param jweCompact - The JWE Compact string to decrypt
41200
+ * @returns Decrypted plaintext
41201
+ */
41202
+ function parseJweCompact(recipientPrivKey, jweCompact) {
41203
+ // 1. Split into 5 parts
41204
+ const parts = jweCompact.split('.');
41205
+ if (parts.length !== 5) {
41206
+ throw new Error('Invalid JWE Compact: expected 5 segments');
41207
+ }
41208
+ const [headerB64, , ivB64, ciphertextB64, tagB64] = parts;
41209
+ // 2. Parse protected header
41210
+ const headerJson = DECODER.decode(base64url.baseDecode(headerB64));
41211
+ const header = JSON.parse(headerJson);
41212
+ if (header.alg !== 'ECDH-ES') {
41213
+ throw new Error(`Unsupported JWE alg: ${header.alg}`);
41214
+ }
41215
+ if (header.enc !== 'A256GCM') {
41216
+ throw new Error(`Unsupported JWE enc: ${header.enc}`);
41217
+ }
41218
+ // 3. Reconstruct ephemeral public key from header
41219
+ const epk = header.epk;
41220
+ const ephemeralPubBytes = jwkToCompressedBytes(epk);
41221
+ // 4. ECDH key agreement: recipient private + ephemeral public
41222
+ const recipientPrivBytes = base64url.baseDecode(recipientPrivKey.d);
41223
+ const sharedSecret = getSharedSecret(recipientPrivBytes, ephemeralPubBytes);
41224
+ // 5. Derive CEK via Concat KDF
41225
+ const cek = concatKdf(sharedSecret.slice(1), 256, 'A256GCM');
41226
+ // 6. Decrypt with AES-256-GCM
41227
+ const iv = base64url.baseDecode(ivB64);
41228
+ const ciphertext = base64url.baseDecode(ciphertextB64);
41229
+ const tag = base64url.baseDecode(tagB64);
41230
+ // Reassemble ciphertext || tag for @noble/ciphers GCM
41231
+ const encrypted = new Uint8Array(ciphertext.length + tag.length);
41232
+ encrypted.set(ciphertext, 0);
41233
+ encrypted.set(tag, ciphertext.length);
41234
+ const aad = ENCODER.encode(headerB64);
41235
+ const cipher = gcm(cek, iv, aad);
41236
+ return cipher.decrypt(encrypted);
41237
+ }
41238
+ /**
41239
+ * Detect whether a string is a JWE Compact Serialization.
41240
+ */
41241
+ function isJweCompact(ciphertext) {
41242
+ return ciphertext.startsWith('eyJ') && ciphertext.split('.').length === 5;
41243
+ }
41244
+ /**
41245
+ * Convert a JWK public key to compressed secp256k1 bytes.
41246
+ */
41247
+ function jwkToCompressedBytes(jwk) {
41248
+ const xBytes = base64url.baseDecode(jwk.x);
41249
+ const yBytes = base64url.baseDecode(jwk.y);
41250
+ const prefix = yBytes[yBytes.length - 1] % 2 === 0 ? 0x02 : 0x03;
41251
+ return new Uint8Array([prefix, ...xBytes]);
41252
+ }
41253
+
40275
41254
  const canonicalize = canonicalizeModule;
40276
41255
  // Polyfill for synchronous signatures
40277
41256
  etc.hmacSha256Sync = (k, ...m) => hmac(sha256$1, k, etc.concatBytes(...m));
@@ -40326,16 +41305,27 @@ class CipherBase {
40326
41305
  const signature = Signature.fromCompact(sigHex);
40327
41306
  return verify(signature, msgHash, compressedPublicKeyBytes);
40328
41307
  }
40329
- encryptBytes(pubKey, privKey, data) {
40330
- const priv = base64url.baseDecode(privKey.d);
40331
- const pub = this.convertJwkToCompressedBytes(pubKey);
40332
- const ss = getSharedSecret(priv, pub);
40333
- const key = ss.slice(0, 32);
40334
- const chacha = managedNonce(xchacha20poly1305)(key);
40335
- const ciphertext = chacha.encrypt(data);
40336
- return base64url.baseEncode(ciphertext);
41308
+ encryptBytes(recipientPubKey, data) {
41309
+ return buildJweCompact(recipientPubKey, data);
41310
+ }
41311
+ decryptBytes(recipientPrivKey, ciphertext, legacyPubKey) {
41312
+ if (isJweCompact(ciphertext)) {
41313
+ return parseJweCompact(recipientPrivKey, ciphertext);
41314
+ }
41315
+ if (legacyPubKey) {
41316
+ return this.decryptBytesLegacy(legacyPubKey, recipientPrivKey, ciphertext);
41317
+ }
41318
+ throw new Error('Cannot decrypt: not a JWE and no legacy public key provided. Pass legacyPubKey as the third argument for old ciphertext.');
41319
+ }
41320
+ encryptMessage(recipientPubKey, message) {
41321
+ const data = utf8ToBytes(message);
41322
+ return this.encryptBytes(recipientPubKey, data);
40337
41323
  }
40338
- decryptBytes(pubKey, privKey, ciphertext) {
41324
+ decryptMessage(recipientPrivKey, ciphertext, legacyPubKey) {
41325
+ const data = this.decryptBytes(recipientPrivKey, ciphertext, legacyPubKey);
41326
+ return bytesToUtf8(data);
41327
+ }
41328
+ decryptBytesLegacy(pubKey, privKey, ciphertext) {
40339
41329
  const priv = base64url.baseDecode(privKey.d);
40340
41330
  const pub = this.convertJwkToCompressedBytes(pubKey);
40341
41331
  const ss = getSharedSecret(priv, pub);
@@ -40344,12 +41334,8 @@ class CipherBase {
40344
41334
  const cipherdata = base64url.baseDecode(ciphertext);
40345
41335
  return chacha.decrypt(cipherdata);
40346
41336
  }
40347
- encryptMessage(pubKey, privKey, message) {
40348
- const data = utf8ToBytes(message);
40349
- return this.encryptBytes(pubKey, privKey, data);
40350
- }
40351
- decryptMessage(pubKey, privKey, ciphertext) {
40352
- const data = this.decryptBytes(pubKey, privKey, ciphertext);
41337
+ decryptMessageLegacy(pubKey, privKey, ciphertext) {
41338
+ const data = this.decryptBytesLegacy(pubKey, privKey, ciphertext);
40353
41339
  return bytesToUtf8(data);
40354
41340
  }
40355
41341
  hasLeadingZeroBits(hexHash, bits) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@didcid/gatekeeper",
3
- "version": "0.3.7",
3
+ "version": "0.4.1",
4
4
  "description": "Archon Gatekeeper",
5
5
  "type": "module",
6
6
  "module": "./dist/esm/index.js",
@@ -106,7 +106,7 @@
106
106
  "author": "David McFadzean <davidmc@gmail.com>",
107
107
  "license": "MIT",
108
108
  "dependencies": {
109
- "@didcid/cipher": "^0.1.3",
109
+ "@didcid/cipher": "^0.2.1",
110
110
  "@didcid/common": "^0.1.3",
111
111
  "@didcid/ipfs": "^0.1.3",
112
112
  "axios": "^1.7.7",
@@ -124,5 +124,5 @@
124
124
  "devDependencies": {
125
125
  "@rollup/plugin-json": "^6.1.0"
126
126
  },
127
- "gitHead": "bd4d25151dfe8e2b940e0691d01a34b2efdc5530"
127
+ "gitHead": "40bac9cb578004fc32d9c937c68da9179210ec23"
128
128
  }