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