argon2.mjs raw

   1  // Argon2id from RFC 9106 — self-contained ES module
   2  // Ported from @noble/hashes (MIT License, Paul Miller)
   3  // All dependencies (blake2b, u64 helpers, utils) inlined.
   4  
   5  import { Slice } from './builtin.mjs';
   6  
   7  // ============================================================
   8  // Go slice conversion helpers (same pattern as subtle.mjs)
   9  // ============================================================
  10  
  11  function sliceToU8(s) {
  12    if (s instanceof Uint8Array) return s;
  13    if (typeof s === 'string') return new TextEncoder().encode(s);
  14    const u = new Uint8Array(s.$length);
  15    for (let i = 0; i < s.$length; i++) u[i] = s.$array[s.$offset + i];
  16    return u;
  17  }
  18  
  19  function u8ToSlice(u8arr) {
  20    const arr = new Array(u8arr.length);
  21    for (let i = 0; i < u8arr.length; i++) arr[i] = u8arr[i];
  22    return new Slice(arr, 0, u8arr.length, u8arr.length);
  23  }
  24  
  25  // ============================================================
  26  // utils.ts — only functions used by argon2 + blake2b
  27  // ============================================================
  28  
  29  function isBytes(a) {
  30    return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');
  31  }
  32  
  33  function anumber(n) {
  34    if (!Number.isSafeInteger(n) || n < 0) throw new Error('positive integer expected, got ' + n);
  35  }
  36  
  37  function abytes(b, ...lengths) {
  38    if (!isBytes(b)) throw new Error('Uint8Array expected');
  39    if (lengths.length > 0 && !lengths.includes(b.length))
  40      throw new Error('Uint8Array expected of length ' + lengths + ', got length=' + b.length);
  41  }
  42  
  43  function aexists(instance, checkFinished = true) {
  44    if (instance.destroyed) throw new Error('Hash instance has been destroyed');
  45    if (checkFinished && instance.finished) throw new Error('Hash#digest() has already been called');
  46  }
  47  
  48  function aoutput(out, instance) {
  49    abytes(out);
  50    const min = instance.outputLen;
  51    if (out.length < min) {
  52      throw new Error('digestInto() expects output buffer of length at least ' + min);
  53    }
  54  }
  55  
  56  function u8(arr) {
  57    return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
  58  }
  59  
  60  function u32(arr) {
  61    return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
  62  }
  63  
  64  function clean(...arrays) {
  65    for (let i = 0; i < arrays.length; i++) {
  66      arrays[i].fill(0);
  67    }
  68  }
  69  
  70  const isLE = (() =>
  71    new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44)();
  72  
  73  function byteSwap(word) {
  74    return (
  75      ((word << 24) & 0xff000000) |
  76      ((word << 8) & 0xff0000) |
  77      ((word >>> 8) & 0xff00) |
  78      ((word >>> 24) & 0xff)
  79    );
  80  }
  81  
  82  const swap8IfBE = isLE ? (n) => n : (n) => byteSwap(n);
  83  
  84  function byteSwap32(arr) {
  85    for (let i = 0; i < arr.length; i++) {
  86      arr[i] = byteSwap(arr[i]);
  87    }
  88    return arr;
  89  }
  90  
  91  const swap32IfBE = isLE ? (u) => u : byteSwap32;
  92  
  93  function utf8ToBytes(str) {
  94    if (typeof str !== 'string') throw new Error('string expected');
  95    return new Uint8Array(new TextEncoder().encode(str));
  96  }
  97  
  98  function toBytes(data) {
  99    if (typeof data === 'string') data = utf8ToBytes(data);
 100    abytes(data);
 101    return data;
 102  }
 103  
 104  function kdfInputToBytes(data) {
 105    if (typeof data === 'string') data = utf8ToBytes(data);
 106    abytes(data);
 107    return data;
 108  }
 109  
 110  // ============================================================
 111  // _u64.ts — 64-bit helpers
 112  // ============================================================
 113  
 114  const U32_MASK64 = BigInt(2 ** 32 - 1);
 115  const _32n = BigInt(32);
 116  
 117  function fromBig(n, le = false) {
 118    if (le) return { h: Number(n & U32_MASK64), l: Number((n >> _32n) & U32_MASK64) };
 119    return { h: Number((n >> _32n) & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 };
 120  }
 121  
 122  const rotrSH = (h, l, s) => (h >>> s) | (l << (32 - s));
 123  const rotrSL = (h, l, s) => (h << (32 - s)) | (l >>> s);
 124  const rotrBH = (h, l, s) => (h << (64 - s)) | (l >>> (s - 32));
 125  const rotrBL = (h, l, s) => (h >>> (s - 32)) | (l << (64 - s));
 126  const rotr32H = (_h, l) => l;
 127  const rotr32L = (h, _l) => h;
 128  
 129  function u64add(Ah, Al, Bh, Bl) {
 130    const l = (Al >>> 0) + (Bl >>> 0);
 131    return { h: (Ah + Bh + ((l / 2 ** 32) | 0)) | 0, l: l | 0 };
 132  }
 133  
 134  const add3L = (Al, Bl, Cl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0);
 135  const add3H = (low, Ah, Bh, Ch) => (Ah + Bh + Ch + ((low / 2 ** 32) | 0)) | 0;
 136  
 137  // ============================================================
 138  // _blake.ts — BSIGMA constant
 139  // ============================================================
 140  
 141  const BSIGMA = Uint8Array.from([
 142    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
 143    14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3,
 144    11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4,
 145    7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8,
 146    9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13,
 147    2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9,
 148    12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11,
 149    13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10,
 150    6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5,
 151    10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0,
 152    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
 153    14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3,
 154  ]);
 155  
 156  // ============================================================
 157  // blake2.ts — BLAKE2b only
 158  // ============================================================
 159  
 160  const B2B_IV = Uint32Array.from([
 161    0xf3bcc908, 0x6a09e667, 0x84caa73b, 0xbb67ae85, 0xfe94f82b, 0x3c6ef372, 0x5f1d36f1, 0xa54ff53a,
 162    0xade682d1, 0x510e527f, 0x2b3e6c1f, 0x9b05688c, 0xfb41bd6b, 0x1f83d9ab, 0x137e2179, 0x5be0cd19,
 163  ]);
 164  
 165  const BBUF = new Uint32Array(32);
 166  
 167  function G1b(a, b, c, d, msg, x) {
 168    const Xl = msg[x], Xh = msg[x + 1];
 169    let Al = BBUF[2 * a], Ah = BBUF[2 * a + 1];
 170    let Bl = BBUF[2 * b], Bh = BBUF[2 * b + 1];
 171    let Cl = BBUF[2 * c], Ch = BBUF[2 * c + 1];
 172    let Dl = BBUF[2 * d], Dh = BBUF[2 * d + 1];
 173    let ll = add3L(Al, Bl, Xl);
 174    Ah = add3H(ll, Ah, Bh, Xh);
 175    Al = ll | 0;
 176    ({ Dh, Dl } = { Dh: Dh ^ Ah, Dl: Dl ^ Al });
 177    ({ Dh, Dl } = { Dh: rotr32H(Dh, Dl), Dl: rotr32L(Dh, Dl) });
 178    ({ h: Ch, l: Cl } = u64add(Ch, Cl, Dh, Dl));
 179    ({ Bh, Bl } = { Bh: Bh ^ Ch, Bl: Bl ^ Cl });
 180    ({ Bh, Bl } = { Bh: rotrSH(Bh, Bl, 24), Bl: rotrSL(Bh, Bl, 24) });
 181    (BBUF[2 * a] = Al), (BBUF[2 * a + 1] = Ah);
 182    (BBUF[2 * b] = Bl), (BBUF[2 * b + 1] = Bh);
 183    (BBUF[2 * c] = Cl), (BBUF[2 * c + 1] = Ch);
 184    (BBUF[2 * d] = Dl), (BBUF[2 * d + 1] = Dh);
 185  }
 186  
 187  function G2b(a, b, c, d, msg, x) {
 188    const Xl = msg[x], Xh = msg[x + 1];
 189    let Al = BBUF[2 * a], Ah = BBUF[2 * a + 1];
 190    let Bl = BBUF[2 * b], Bh = BBUF[2 * b + 1];
 191    let Cl = BBUF[2 * c], Ch = BBUF[2 * c + 1];
 192    let Dl = BBUF[2 * d], Dh = BBUF[2 * d + 1];
 193    let ll = add3L(Al, Bl, Xl);
 194    Ah = add3H(ll, Ah, Bh, Xh);
 195    Al = ll | 0;
 196    ({ Dh, Dl } = { Dh: Dh ^ Ah, Dl: Dl ^ Al });
 197    ({ Dh, Dl } = { Dh: rotrSH(Dh, Dl, 16), Dl: rotrSL(Dh, Dl, 16) });
 198    ({ h: Ch, l: Cl } = u64add(Ch, Cl, Dh, Dl));
 199    ({ Bh, Bl } = { Bh: Bh ^ Ch, Bl: Bl ^ Cl });
 200    ({ Bh, Bl } = { Bh: rotrBH(Bh, Bl, 63), Bl: rotrBL(Bh, Bl, 63) });
 201    (BBUF[2 * a] = Al), (BBUF[2 * a + 1] = Ah);
 202    (BBUF[2 * b] = Bl), (BBUF[2 * b + 1] = Bh);
 203    (BBUF[2 * c] = Cl), (BBUF[2 * c + 1] = Ch);
 204    (BBUF[2 * d] = Dl), (BBUF[2 * d + 1] = Dh);
 205  }
 206  
 207  function checkBlake2Opts(outputLen, opts = {}, keyLen, saltLen, persLen) {
 208    anumber(keyLen);
 209    if (outputLen < 0 || outputLen > keyLen) throw new Error('outputLen bigger than keyLen');
 210    const { key, salt, personalization } = opts;
 211    if (key !== undefined && (key.length < 1 || key.length > keyLen))
 212      throw new Error('key length must be undefined or 1..' + keyLen);
 213    if (salt !== undefined && salt.length !== saltLen)
 214      throw new Error('salt must be undefined or ' + saltLen);
 215    if (personalization !== undefined && personalization.length !== persLen)
 216      throw new Error('personalization must be undefined or ' + persLen);
 217  }
 218  
 219  class BLAKE2b {
 220    constructor(opts = {}) {
 221      const olen = opts.dkLen === undefined ? 64 : opts.dkLen;
 222      this.blockLen = 128;
 223      this.outputLen = olen;
 224      this.buffer = new Uint8Array(128);
 225      this.buffer32 = u32(this.buffer);
 226      this.finished = false;
 227      this.destroyed = false;
 228      this.length = 0;
 229      this.pos = 0;
 230  
 231      this.v0l = B2B_IV[0] | 0;
 232      this.v0h = B2B_IV[1] | 0;
 233      this.v1l = B2B_IV[2] | 0;
 234      this.v1h = B2B_IV[3] | 0;
 235      this.v2l = B2B_IV[4] | 0;
 236      this.v2h = B2B_IV[5] | 0;
 237      this.v3l = B2B_IV[6] | 0;
 238      this.v3h = B2B_IV[7] | 0;
 239      this.v4l = B2B_IV[8] | 0;
 240      this.v4h = B2B_IV[9] | 0;
 241      this.v5l = B2B_IV[10] | 0;
 242      this.v5h = B2B_IV[11] | 0;
 243      this.v6l = B2B_IV[12] | 0;
 244      this.v6h = B2B_IV[13] | 0;
 245      this.v7l = B2B_IV[14] | 0;
 246      this.v7h = B2B_IV[15] | 0;
 247  
 248      checkBlake2Opts(olen, opts, 64, 16, 16);
 249      let { key, personalization, salt } = opts;
 250      let keyLength = 0;
 251      if (key !== undefined) {
 252        key = toBytes(key);
 253        keyLength = key.length;
 254      }
 255      this.v0l ^= this.outputLen | (keyLength << 8) | (0x01 << 16) | (0x01 << 24);
 256      if (salt !== undefined) {
 257        salt = toBytes(salt);
 258        const slt = u32(salt);
 259        this.v4l ^= swap8IfBE(slt[0]);
 260        this.v4h ^= swap8IfBE(slt[1]);
 261        this.v5l ^= swap8IfBE(slt[2]);
 262        this.v5h ^= swap8IfBE(slt[3]);
 263      }
 264      if (personalization !== undefined) {
 265        personalization = toBytes(personalization);
 266        const pers = u32(personalization);
 267        this.v6l ^= swap8IfBE(pers[0]);
 268        this.v6h ^= swap8IfBE(pers[1]);
 269        this.v7l ^= swap8IfBE(pers[2]);
 270        this.v7h ^= swap8IfBE(pers[3]);
 271      }
 272      if (key !== undefined) {
 273        const tmp = new Uint8Array(this.blockLen);
 274        tmp.set(key);
 275        this.update(tmp);
 276      }
 277    }
 278  
 279    _get() {
 280      const { v0l, v0h, v1l, v1h, v2l, v2h, v3l, v3h, v4l, v4h, v5l, v5h, v6l, v6h, v7l, v7h } = this;
 281      return [v0l, v0h, v1l, v1h, v2l, v2h, v3l, v3h, v4l, v4h, v5l, v5h, v6l, v6h, v7l, v7h];
 282    }
 283  
 284    _set(v0l, v0h, v1l, v1h, v2l, v2h, v3l, v3h, v4l, v4h, v5l, v5h, v6l, v6h, v7l, v7h) {
 285      this.v0l = v0l | 0;
 286      this.v0h = v0h | 0;
 287      this.v1l = v1l | 0;
 288      this.v1h = v1h | 0;
 289      this.v2l = v2l | 0;
 290      this.v2h = v2h | 0;
 291      this.v3l = v3l | 0;
 292      this.v3h = v3h | 0;
 293      this.v4l = v4l | 0;
 294      this.v4h = v4h | 0;
 295      this.v5l = v5l | 0;
 296      this.v5h = v5h | 0;
 297      this.v6l = v6l | 0;
 298      this.v6h = v6h | 0;
 299      this.v7l = v7l | 0;
 300      this.v7h = v7h | 0;
 301    }
 302  
 303    _compress(msg, offset, isLast) {
 304      this._get().forEach((v, i) => (BBUF[i] = v));
 305      BBUF.set(B2B_IV, 16);
 306      let { h, l } = fromBig(BigInt(this.length));
 307      BBUF[24] = B2B_IV[8] ^ l;
 308      BBUF[25] = B2B_IV[9] ^ h;
 309      if (isLast) {
 310        BBUF[28] = ~BBUF[28];
 311        BBUF[29] = ~BBUF[29];
 312      }
 313      let j = 0;
 314      const s = BSIGMA;
 315      for (let i = 0; i < 12; i++) {
 316        G1b(0, 4, 8, 12, msg, offset + 2 * s[j++]);
 317        G2b(0, 4, 8, 12, msg, offset + 2 * s[j++]);
 318        G1b(1, 5, 9, 13, msg, offset + 2 * s[j++]);
 319        G2b(1, 5, 9, 13, msg, offset + 2 * s[j++]);
 320        G1b(2, 6, 10, 14, msg, offset + 2 * s[j++]);
 321        G2b(2, 6, 10, 14, msg, offset + 2 * s[j++]);
 322        G1b(3, 7, 11, 15, msg, offset + 2 * s[j++]);
 323        G2b(3, 7, 11, 15, msg, offset + 2 * s[j++]);
 324        G1b(0, 5, 10, 15, msg, offset + 2 * s[j++]);
 325        G2b(0, 5, 10, 15, msg, offset + 2 * s[j++]);
 326        G1b(1, 6, 11, 12, msg, offset + 2 * s[j++]);
 327        G2b(1, 6, 11, 12, msg, offset + 2 * s[j++]);
 328        G1b(2, 7, 8, 13, msg, offset + 2 * s[j++]);
 329        G2b(2, 7, 8, 13, msg, offset + 2 * s[j++]);
 330        G1b(3, 4, 9, 14, msg, offset + 2 * s[j++]);
 331        G2b(3, 4, 9, 14, msg, offset + 2 * s[j++]);
 332      }
 333      this.v0l ^= BBUF[0] ^ BBUF[16];
 334      this.v0h ^= BBUF[1] ^ BBUF[17];
 335      this.v1l ^= BBUF[2] ^ BBUF[18];
 336      this.v1h ^= BBUF[3] ^ BBUF[19];
 337      this.v2l ^= BBUF[4] ^ BBUF[20];
 338      this.v2h ^= BBUF[5] ^ BBUF[21];
 339      this.v3l ^= BBUF[6] ^ BBUF[22];
 340      this.v3h ^= BBUF[7] ^ BBUF[23];
 341      this.v4l ^= BBUF[8] ^ BBUF[24];
 342      this.v4h ^= BBUF[9] ^ BBUF[25];
 343      this.v5l ^= BBUF[10] ^ BBUF[26];
 344      this.v5h ^= BBUF[11] ^ BBUF[27];
 345      this.v6l ^= BBUF[12] ^ BBUF[28];
 346      this.v6h ^= BBUF[13] ^ BBUF[29];
 347      this.v7l ^= BBUF[14] ^ BBUF[30];
 348      this.v7h ^= BBUF[15] ^ BBUF[31];
 349      clean(BBUF);
 350    }
 351  
 352    update(data) {
 353      aexists(this);
 354      data = toBytes(data);
 355      abytes(data);
 356      const { blockLen, buffer, buffer32 } = this;
 357      const len = data.length;
 358      const offset = data.byteOffset;
 359      const buf = data.buffer;
 360      for (let pos = 0; pos < len; ) {
 361        if (this.pos === blockLen) {
 362          swap32IfBE(buffer32);
 363          this._compress(buffer32, 0, false);
 364          swap32IfBE(buffer32);
 365          this.pos = 0;
 366        }
 367        const take = Math.min(blockLen - this.pos, len - pos);
 368        const dataOffset = offset + pos;
 369        if (take === blockLen && !(dataOffset % 4) && pos + take < len) {
 370          const data32 = new Uint32Array(buf, dataOffset, Math.floor((len - pos) / 4));
 371          swap32IfBE(data32);
 372          for (let pos32 = 0; pos + blockLen < len; pos32 += buffer32.length, pos += blockLen) {
 373            this.length += blockLen;
 374            this._compress(data32, pos32, false);
 375          }
 376          swap32IfBE(data32);
 377          continue;
 378        }
 379        buffer.set(data.subarray(pos, pos + take), this.pos);
 380        this.pos += take;
 381        this.length += take;
 382        pos += take;
 383      }
 384      return this;
 385    }
 386  
 387    digestInto(out) {
 388      aexists(this);
 389      aoutput(out, this);
 390      const { pos, buffer32 } = this;
 391      this.finished = true;
 392      clean(this.buffer.subarray(pos));
 393      swap32IfBE(buffer32);
 394      this._compress(buffer32, 0, true);
 395      swap32IfBE(buffer32);
 396      const out32 = u32(out);
 397      this._get().forEach((v, i) => (out32[i] = swap8IfBE(v)));
 398    }
 399  
 400    digest() {
 401      const { buffer, outputLen } = this;
 402      this.digestInto(buffer);
 403      const res = buffer.slice(0, outputLen);
 404      this.destroy();
 405      return res;
 406    }
 407  
 408    _cloneInto(to) {
 409      const { buffer, length, finished, destroyed, outputLen, pos } = this;
 410      to = to || new BLAKE2b({ dkLen: outputLen });
 411      to._set(...this._get());
 412      to.buffer.set(buffer);
 413      to.destroyed = destroyed;
 414      to.finished = finished;
 415      to.length = length;
 416      to.pos = pos;
 417      to.outputLen = outputLen;
 418      return to;
 419    }
 420  
 421    clone() {
 422      return this._cloneInto();
 423    }
 424  
 425    destroy() {
 426      this.destroyed = true;
 427      clean(this.buffer32);
 428      this._set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
 429    }
 430  }
 431  
 432  // blake2b wrapper matching noble-hashes createOptHasher interface
 433  const blake2b = function (msg, opts) {
 434    return new BLAKE2b(opts).update(toBytes(msg)).digest();
 435  };
 436  blake2b.create = (opts) => new BLAKE2b(opts);
 437  blake2b.outputLen = 64;
 438  blake2b.blockLen = 128;
 439  
 440  // ============================================================
 441  // argon2.ts — full implementation
 442  // ============================================================
 443  
 444  const AT = { Argond2d: 0, Argon2i: 1, Argon2id: 2 };
 445  
 446  const ARGON2_SYNC_POINTS = 4;
 447  
 448  const abytesOrZero = (buf) => {
 449    if (buf === undefined) return Uint8Array.of();
 450    return kdfInputToBytes(buf);
 451  };
 452  
 453  function mul(a, b) {
 454    const aL = a & 0xffff;
 455    const aH = a >>> 16;
 456    const bL = b & 0xffff;
 457    const bH = b >>> 16;
 458    const ll = Math.imul(aL, bL);
 459    const hl = Math.imul(aH, bL);
 460    const lh = Math.imul(aL, bH);
 461    const hh = Math.imul(aH, bH);
 462    const carry = (ll >>> 16) + (hl & 0xffff) + lh;
 463    const high = (hh + (hl >>> 16) + (carry >>> 16)) | 0;
 464    const low = (carry << 16) | (ll & 0xffff);
 465    return { h: high, l: low };
 466  }
 467  
 468  function mul2(a, b) {
 469    const { h, l } = mul(a, b);
 470    return { h: ((h << 1) | (l >>> 31)) & 0xffff_ffff, l: (l << 1) & 0xffff_ffff };
 471  }
 472  
 473  function blamka(Ah, Al, Bh, Bl) {
 474    const { h: Ch, l: Cl } = mul2(Al, Bl);
 475    const Rll = add3L(Al, Bl, Cl);
 476    return { h: add3H(Rll, Ah, Bh, Ch), l: Rll | 0 };
 477  }
 478  
 479  const A2_BUF = new Uint32Array(256);
 480  
 481  function G(a, b, c, d) {
 482    let Al = A2_BUF[2*a], Ah = A2_BUF[2*a + 1];
 483    let Bl = A2_BUF[2*b], Bh = A2_BUF[2*b + 1];
 484    let Cl = A2_BUF[2*c], Ch = A2_BUF[2*c + 1];
 485    let Dl = A2_BUF[2*d], Dh = A2_BUF[2*d + 1];
 486  
 487    ({ h: Ah, l: Al } = blamka(Ah, Al, Bh, Bl));
 488    ({ Dh, Dl } = { Dh: Dh ^ Ah, Dl: Dl ^ Al });
 489    ({ Dh, Dl } = { Dh: rotr32H(Dh, Dl), Dl: rotr32L(Dh, Dl) });
 490  
 491    ({ h: Ch, l: Cl } = blamka(Ch, Cl, Dh, Dl));
 492    ({ Bh, Bl } = { Bh: Bh ^ Ch, Bl: Bl ^ Cl });
 493    ({ Bh, Bl } = { Bh: rotrSH(Bh, Bl, 24), Bl: rotrSL(Bh, Bl, 24) });
 494  
 495    ({ h: Ah, l: Al } = blamka(Ah, Al, Bh, Bl));
 496    ({ Dh, Dl } = { Dh: Dh ^ Ah, Dl: Dl ^ Al });
 497    ({ Dh, Dl } = { Dh: rotrSH(Dh, Dl, 16), Dl: rotrSL(Dh, Dl, 16) });
 498  
 499    ({ h: Ch, l: Cl } = blamka(Ch, Cl, Dh, Dl));
 500    ({ Bh, Bl } = { Bh: Bh ^ Ch, Bl: Bl ^ Cl });
 501    ({ Bh, Bl } = { Bh: rotrBH(Bh, Bl, 63), Bl: rotrBL(Bh, Bl, 63) });
 502  
 503    (A2_BUF[2 * a] = Al), (A2_BUF[2 * a + 1] = Ah);
 504    (A2_BUF[2 * b] = Bl), (A2_BUF[2 * b + 1] = Bh);
 505    (A2_BUF[2 * c] = Cl), (A2_BUF[2 * c + 1] = Ch);
 506    (A2_BUF[2 * d] = Dl), (A2_BUF[2 * d + 1] = Dh);
 507  }
 508  
 509  function P(
 510    v00, v01, v02, v03, v04, v05, v06, v07,
 511    v08, v09, v10, v11, v12, v13, v14, v15,
 512  ) {
 513    G(v00, v04, v08, v12);
 514    G(v01, v05, v09, v13);
 515    G(v02, v06, v10, v14);
 516    G(v03, v07, v11, v15);
 517    G(v00, v05, v10, v15);
 518    G(v01, v06, v11, v12);
 519    G(v02, v07, v08, v13);
 520    G(v03, v04, v09, v14);
 521  }
 522  
 523  function block(x, xPos, yPos, outPos, needXor) {
 524    for (let i = 0; i < 256; i++) A2_BUF[i] = x[xPos + i] ^ x[yPos + i];
 525    for (let i = 0; i < 128; i += 16) {
 526      P(
 527        i, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7,
 528        i + 8, i + 9, i + 10, i + 11, i + 12, i + 13, i + 14, i + 15
 529      );
 530    }
 531    for (let i = 0; i < 16; i += 2) {
 532      P(
 533        i, i + 1, i + 16, i + 17, i + 32, i + 33, i + 48, i + 49,
 534        i + 64, i + 65, i + 80, i + 81, i + 96, i + 97, i + 112, i + 113
 535      );
 536    }
 537  
 538    if (needXor) for (let i = 0; i < 256; i++) x[outPos + i] ^= A2_BUF[i] ^ x[xPos + i] ^ x[yPos + i];
 539    else for (let i = 0; i < 256; i++) x[outPos + i] = A2_BUF[i] ^ x[xPos + i] ^ x[yPos + i];
 540    clean(A2_BUF);
 541  }
 542  
 543  function Hp(A, dkLen) {
 544    const A8 = u8(A);
 545    const T = new Uint32Array(1);
 546    const T8 = u8(T);
 547    T[0] = dkLen;
 548    if (dkLen <= 64) return blake2b.create({ dkLen }).update(T8).update(A8).digest();
 549    const out = new Uint8Array(dkLen);
 550    let V = blake2b.create({}).update(T8).update(A8).digest();
 551    let pos = 0;
 552    out.set(V.subarray(0, 32));
 553    pos += 32;
 554    for (; dkLen - pos > 64; pos += 32) {
 555      const Vh = blake2b.create({}).update(V);
 556      Vh.digestInto(V);
 557      Vh.destroy();
 558      out.set(V.subarray(0, 32), pos);
 559    }
 560    out.set(blake2b(V, { dkLen: dkLen - pos }), pos);
 561    clean(V, T);
 562    return u32(out);
 563  }
 564  
 565  function indexAlpha(r, s, laneLen, segmentLen, index, randL, sameLane = false) {
 566    let area;
 567    if (r === 0) {
 568      if (s === 0) area = index - 1;
 569      else if (sameLane) area = s * segmentLen + index - 1;
 570      else area = s * segmentLen + (index == 0 ? -1 : 0);
 571    } else if (sameLane) area = laneLen - segmentLen + index - 1;
 572    else area = laneLen - segmentLen + (index == 0 ? -1 : 0);
 573    const startPos = r !== 0 && s !== ARGON2_SYNC_POINTS - 1 ? (s + 1) * segmentLen : 0;
 574    const rel = area - 1 - mul(area, mul(randL, randL).h).h;
 575    return (startPos + rel) % laneLen;
 576  }
 577  
 578  const maxUint32 = Math.pow(2, 32);
 579  function isU32(num) {
 580    return Number.isSafeInteger(num) && num >= 0 && num < maxUint32;
 581  }
 582  
 583  function argon2Opts(opts) {
 584    const merged = {
 585      version: 0x13,
 586      dkLen: 32,
 587      maxmem: maxUint32 - 1,
 588      asyncTick: 10,
 589    };
 590    for (let [k, v] of Object.entries(opts)) if (v != null) merged[k] = v;
 591  
 592    const { dkLen, p, m, t, version, onProgress } = merged;
 593    if (!isU32(dkLen) || dkLen < 4) throw new Error('dkLen should be at least 4 bytes');
 594    if (!isU32(p) || p < 1 || p >= Math.pow(2, 24)) throw new Error('p should be 1 <= p < 2^24');
 595    if (!isU32(m)) throw new Error('m should be 0 <= m < 2^32');
 596    if (!isU32(t) || t < 1) throw new Error('t (iterations) should be 1 <= t < 2^32');
 597    if (onProgress !== undefined && typeof onProgress !== 'function')
 598      throw new Error('progressCb should be function');
 599    if (!isU32(m) || m < 8 * p) throw new Error('memory should be at least 8*p bytes');
 600    if (version !== 0x10 && version !== 0x13) throw new Error('unknown version=' + version);
 601    return merged;
 602  }
 603  
 604  function argon2Init(password, salt, type, opts) {
 605    password = kdfInputToBytes(password);
 606    salt = kdfInputToBytes(salt);
 607    abytes(password);
 608    abytes(salt);
 609    if (!isU32(password.length)) throw new Error('password should be less than 4 GB');
 610    if (!isU32(salt.length) || salt.length < 8)
 611      throw new Error('salt should be at least 8 bytes and less than 4 GB');
 612    if (!Object.values(AT).includes(type)) throw new Error('invalid type');
 613    let { p, dkLen, m, t, version, key, personalization, maxmem, onProgress, asyncTick } =
 614      argon2Opts(opts);
 615  
 616    key = abytesOrZero(key);
 617    personalization = abytesOrZero(personalization);
 618  
 619    const h = blake2b.create({});
 620    const BUF = new Uint32Array(1);
 621    const BUF8 = u8(BUF);
 622    for (let item of [p, dkLen, m, t, version, type]) {
 623      BUF[0] = item;
 624      h.update(BUF8);
 625    }
 626    for (let i of [password, salt, key, personalization]) {
 627      BUF[0] = i.length;
 628      h.update(BUF8).update(i);
 629    }
 630    const H0 = new Uint32Array(18);
 631    const H0_8 = u8(H0);
 632    h.digestInto(H0_8);
 633  
 634    const lanes = p;
 635    const mP = 4 * p * Math.floor(m / (ARGON2_SYNC_POINTS * p));
 636    const laneLen = Math.floor(mP / p);
 637    const segmentLen = Math.floor(laneLen / ARGON2_SYNC_POINTS);
 638    const memUsed = mP * 256;
 639    if (!isU32(maxmem) || memUsed > maxmem)
 640      throw new Error(
 641        'mem should be less than 2**32, got: maxmem=' + maxmem + ', memused=' + memUsed
 642      );
 643    const B = new Uint32Array(memUsed);
 644    for (let l = 0; l < p; l++) {
 645      const i = 256 * laneLen * l;
 646      H0[17] = l;
 647      H0[16] = 0;
 648      B.set(Hp(H0, 1024), i);
 649      H0[16] = 1;
 650      B.set(Hp(H0, 1024), i + 256);
 651    }
 652    let perBlock = () => {};
 653    if (onProgress) {
 654      const totalBlock = t * ARGON2_SYNC_POINTS * p * segmentLen;
 655      const callbackPer = Math.max(Math.floor(totalBlock / 10000), 1);
 656      let blockCnt = 0;
 657      perBlock = () => {
 658        blockCnt++;
 659        if (onProgress && (!(blockCnt % callbackPer) || blockCnt === totalBlock))
 660          onProgress(blockCnt / totalBlock);
 661      };
 662    }
 663    clean(BUF, H0);
 664    return { type, mP, p, t, version, B, laneLen, lanes, segmentLen, dkLen, perBlock, asyncTick };
 665  }
 666  
 667  function argon2Output(B, p, laneLen, dkLen) {
 668    const B_final = new Uint32Array(256);
 669    for (let l = 0; l < p; l++)
 670      for (let j = 0; j < 256; j++) B_final[j] ^= B[256 * (laneLen * l + laneLen - 1) + j];
 671    const res = u8(Hp(B_final, dkLen));
 672    clean(B_final);
 673    return res;
 674  }
 675  
 676  function processBlock(
 677    B, address, l, r, s, index,
 678    laneLen, segmentLen, lanes, offset, prev,
 679    dataIndependent, needXor
 680  ) {
 681    if (offset % laneLen) prev = offset - 1;
 682    let randL, randH;
 683    if (dataIndependent) {
 684      let i128 = index % 128;
 685      if (i128 === 0) {
 686        address[256 + 12]++;
 687        block(address, 256, 2 * 256, 0, false);
 688        block(address, 0, 2 * 256, 0, false);
 689      }
 690      randL = address[2 * i128];
 691      randH = address[2 * i128 + 1];
 692    } else {
 693      const T = 256 * prev;
 694      randL = B[T];
 695      randH = B[T + 1];
 696    }
 697    const refLane = r === 0 && s === 0 ? l : randH % lanes;
 698    const refPos = indexAlpha(r, s, laneLen, segmentLen, index, randL, refLane == l);
 699    const refBlock = laneLen * refLane + refPos;
 700    block(B, 256 * prev, 256 * refBlock, offset * 256, needXor);
 701  }
 702  
 703  function argon2(type, password, salt, opts) {
 704    const { mP, p, t, version, B, laneLen, lanes, segmentLen, dkLen, perBlock } = argon2Init(
 705      password,
 706      salt,
 707      type,
 708      opts
 709    );
 710    const address = new Uint32Array(3 * 256);
 711    address[256 + 6] = mP;
 712    address[256 + 8] = t;
 713    address[256 + 10] = type;
 714    for (let r = 0; r < t; r++) {
 715      const needXor = r !== 0 && version === 0x13;
 716      address[256 + 0] = r;
 717      for (let s = 0; s < ARGON2_SYNC_POINTS; s++) {
 718        address[256 + 4] = s;
 719        const dataIndependent = type == AT.Argon2i || (type == AT.Argon2id && r === 0 && s < 2);
 720        for (let l = 0; l < p; l++) {
 721          address[256 + 2] = l;
 722          address[256 + 12] = 0;
 723          let startPos = 0;
 724          if (r === 0 && s === 0) {
 725            startPos = 2;
 726            if (dataIndependent) {
 727              address[256 + 12]++;
 728              block(address, 256, 2 * 256, 0, false);
 729              block(address, 0, 2 * 256, 0, false);
 730            }
 731          }
 732          let offset = l * laneLen + s * segmentLen + startPos;
 733          let prev = offset % laneLen ? offset - 1 : offset + laneLen - 1;
 734          for (let index = startPos; index < segmentLen; index++, offset++, prev++) {
 735            perBlock();
 736            processBlock(
 737              B, address, l, r, s, index,
 738              laneLen, segmentLen, lanes, offset, prev,
 739              dataIndependent, needXor
 740            );
 741          }
 742        }
 743      }
 744    }
 745    clean(address);
 746    return argon2Output(B, p, laneLen, dkLen);
 747  }
 748  
 749  function _argon2id(password, salt, opts) {
 750    return argon2(AT.Argon2id, password, salt, opts);
 751  }
 752  
 753  // ============================================================
 754  // Export: Go slice bridge
 755  // ============================================================
 756  
 757  // Called from Go jsbridge — parameters are Go slices.
 758  export function Argon2id(password, salt, t, m, p, dkLen, fn) {
 759    const pw = sliceToU8(password);
 760    const sl = sliceToU8(salt);
 761    const result = _argon2id(pw, sl, { t, m, p, dkLen });
 762    fn(u8ToSlice(result));
 763  }
 764  
 765  // Called from JS (subtle.mjs) — password is JS string, salt is Uint8Array.
 766  export function argon2id(password, salt, opts) {
 767    return _argon2id(password, salt, opts);
 768  }
 769