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