New:Socket for Asana Is Now Available.Learn more
Get Started

@noble/hashes

Package Overview
Dependencies
Maintainers
1
Versions
46
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@noble/hashes - npm Package Compare versions

Comparing version
2.3.0
to
2.4.0
+13
-13
argon2.d.ts
import { type KDFInput, type TArg, type TRet } from './utils.ts';
/** Argon2 cost, output, and optional secret/personalization inputs. */
export type ArgonOpts = {
/** Time cost measured in iterations. */
t: number;
/** Memory cost in kibibytes. */
m: number;
/** Parallelization parameter. */
p: number;
/** Time cost measured in iterations. Defaults to `3`. */
t?: number;
/** Memory cost in kibibytes. Defaults to `1024 ** 2` (1 GiB). */
m?: number;
/** Parallelization parameter. Defaults to `1`. */
p?: number;
/** Argon2 version number. Defaults to `0x13`. */

@@ -20,3 +20,3 @@ version?: number;

asyncTick?: number;
/** Maximum temporary memory budget in bytes. */
/** Maximum temporary memory budget in bytes. Defaults to 1 GiB. */
maxmem?: number;

@@ -61,3 +61,3 @@ /**

*/
export declare const argon2d: (password: TArg<KDFInput>, salt: TArg<KDFInput>, opts: TArg<ArgonOpts>) => TRet<Uint8Array>;
export declare const argon2d: (password: TArg<KDFInput>, salt: TArg<KDFInput>, opts?: TArg<ArgonOpts>) => TRet<Uint8Array>;
/**

@@ -76,3 +76,3 @@ * Argon2i side-channel-resistant version.

*/
export declare const argon2i: (password: TArg<KDFInput>, salt: TArg<KDFInput>, opts: TArg<ArgonOpts>) => TRet<Uint8Array>;
export declare const argon2i: (password: TArg<KDFInput>, salt: TArg<KDFInput>, opts?: TArg<ArgonOpts>) => TRet<Uint8Array>;
/**

@@ -91,3 +91,3 @@ * Argon2id, combining i+d, the most popular version from RFC 9106.

*/
export declare const argon2id: (password: TArg<KDFInput>, salt: TArg<KDFInput>, opts: TArg<ArgonOpts>) => TRet<Uint8Array>;
export declare const argon2id: (password: TArg<KDFInput>, salt: TArg<KDFInput>, opts?: TArg<ArgonOpts>) => TRet<Uint8Array>;
/**

@@ -124,3 +124,3 @@ * Argon2d async GPU-resistant version.

*/
export declare const argon2dAsync: (password: TArg<KDFInput>, salt: TArg<KDFInput>, opts: TArg<ArgonOpts>) => Promise<TRet<Uint8Array>>;
export declare const argon2dAsync: (password: TArg<KDFInput>, salt: TArg<KDFInput>, opts?: TArg<ArgonOpts>) => Promise<TRet<Uint8Array>>;
/**

@@ -139,3 +139,3 @@ * Argon2i async side-channel-resistant version.

*/
export declare const argon2iAsync: (password: TArg<KDFInput>, salt: TArg<KDFInput>, opts: TArg<ArgonOpts>) => Promise<TRet<Uint8Array>>;
export declare const argon2iAsync: (password: TArg<KDFInput>, salt: TArg<KDFInput>, opts?: TArg<ArgonOpts>) => Promise<TRet<Uint8Array>>;
/**

@@ -154,2 +154,2 @@ * Argon2id async, combining i+d, the most popular version from RFC 9106.

*/
export declare const argon2idAsync: (password: TArg<KDFInput>, salt: TArg<KDFInput>, opts: TArg<ArgonOpts>) => Promise<TRet<Uint8Array>>;
export declare const argon2idAsync: (password: TArg<KDFInput>, salt: TArg<KDFInput>, opts?: TArg<ArgonOpts>) => Promise<TRet<Uint8Array>>;
+164
-137

@@ -11,3 +11,2 @@ /**

*/
import { rotr32H, rotr32L, rotrBH, rotrBL, rotrSH, rotrSL } from "./_u64.js";
import { blake2b } from "./blake2.js";

@@ -27,27 +26,2 @@ import { anumber, checkOpts, clean, kdfInputToBytes, nextTick, swap32IfBE, swap8IfBE, u32, u8, } from "./utils.js";

};
// Unsigned `u32 * u32 = { h, l }`, returned as split 64-bit halves.
function mul(a, b) {
// Split into 16-bit limbs so each partial product stays exact under `Math.imul`.
const aL = a & 0xffff;
const aH = a >>> 16;
const bL = b & 0xffff;
const bH = b >>> 16;
const ll = Math.imul(aL, bL);
const hl = Math.imul(aH, bL);
const lh = Math.imul(aL, bH);
const hh = Math.imul(aH, bH);
const carry = (ll >>> 16) + (hl & 0xffff) + lh;
const high = (hh + (hl >>> 16) + (carry >>> 16)) | 0;
const low = (carry << 16) | (ll & 0xffff);
return { h: high, l: low };
}
// High 32 bits of unsigned u32 multiply, via the same 16-bit limb split as `mul` below.
// Kept single-purpose and number-returning so V8 inlines it (object-returning
// helpers here cost 2.2x of the whole derivation, measured; small helpers
// returning one number are free — see rotr* usage everywhere).
function mulHi(a, b) {
const aL = a & 0xffff, aH = a >>> 16, bL = b & 0xffff, bH = b >>> 16; // prettier-ignore
const carry = (Math.imul(aL, bL) >>> 16) + (Math.imul(aH, bL) & 0xffff) + Math.imul(aL, bH);
return (Math.imul(aH, bH) + (Math.imul(aH, bL) >>> 16) + (carry >>> 16)) | 0;
}
// Temporary block buffer.

@@ -58,5 +32,7 @@ // 1024-byte block: 256 u32 = 128 interleaved low/high halves = RFC's

// Quarter-round over 64-bit word indices into `A2_BUF`; each index maps to adjacent low/high u32s.
// Each BlaMka step `X = X + Y + 2 * trunc(X) * trunc(Y)` (trunc = low 32 bits) is three lines:
// `Math.imul` is the low product half, `mulHi` the high half, then a split 64-bit add with the
// doubling folded in. RFC 9106 Figure 19 GB rotates by 32, 24, 16, and 63 bits after each XOR.
// Each BlaMka step `X = X + Y + 2 * trunc(X) * trunc(Y)` (trunc = low 32 bits) starts
// with an exact low product from `Math.imul`. The rounded double product is within 1024 of the
// exact u64 product; subtracting that exact low half and rounding to the nearest multiple of
// 2^32 therefore recovers the exact high half. RFC 9106 Figure 19 GB then rotates by 32, 24,
// 16, and 63 bits after each XOR.
function G(a, b, c, d) {

@@ -70,3 +46,3 @@ let Al = A2_BUF[2 * a], Ah = A2_BUF[2 * a + 1]; // prettier-ignore

ml = Math.imul(Al, Bl);
mh = mulHi(Al, Bl); // prettier-ignore
mh = (((Al >>> 0) * (Bl >>> 0) - (ml >>> 0)) / 0x100000000 + 0.5) | 0; // prettier-ignore
rl = (Al >>> 0) + (Bl >>> 0) + ((ml << 1) >>> 0);

@@ -77,7 +53,7 @@ Ah = (Ah + Bh + ((mh << 1) | (ml >>> 31)) + ((rl / 0x100000000) | 0)) | 0;

xl = Dl ^ Al; // prettier-ignore
Dh = rotr32H(xh, xl);
Dl = rotr32L(xh, xl); // prettier-ignore
Dh = xl;
Dl = xh; // prettier-ignore
// C = blamka(C, D); B = rotr64(B ^ C, 24)
ml = Math.imul(Cl, Dl);
mh = mulHi(Cl, Dl); // prettier-ignore
mh = (((Cl >>> 0) * (Dl >>> 0) - (ml >>> 0)) / 0x100000000 + 0.5) | 0; // prettier-ignore
rl = (Cl >>> 0) + (Dl >>> 0) + ((ml << 1) >>> 0);

@@ -88,7 +64,7 @@ Ch = (Ch + Dh + ((mh << 1) | (ml >>> 31)) + ((rl / 0x100000000) | 0)) | 0;

xl = Bl ^ Cl; // prettier-ignore
Bh = rotrSH(xh, xl, 24);
Bl = rotrSL(xh, xl, 24); // prettier-ignore
Bh = (xh >>> 24) | (xl << 8);
Bl = (xh << 8) | (xl >>> 24); // prettier-ignore
// A = blamka(A, B); D = rotr64(D ^ A, 16)
ml = Math.imul(Al, Bl);
mh = mulHi(Al, Bl); // prettier-ignore
mh = (((Al >>> 0) * (Bl >>> 0) - (ml >>> 0)) / 0x100000000 + 0.5) | 0; // prettier-ignore
rl = (Al >>> 0) + (Bl >>> 0) + ((ml << 1) >>> 0);

@@ -99,7 +75,7 @@ Ah = (Ah + Bh + ((mh << 1) | (ml >>> 31)) + ((rl / 0x100000000) | 0)) | 0;

xl = Dl ^ Al; // prettier-ignore
Dh = rotrSH(xh, xl, 16);
Dl = rotrSL(xh, xl, 16); // prettier-ignore
Dh = (xh >>> 16) | (xl << 16);
Dl = (xh << 16) | (xl >>> 16); // prettier-ignore
// C = blamka(C, D); B = rotr64(B ^ C, 63)
ml = Math.imul(Cl, Dl);
mh = mulHi(Cl, Dl); // prettier-ignore
mh = (((Cl >>> 0) * (Dl >>> 0) - (ml >>> 0)) / 0x100000000 + 0.5) | 0; // prettier-ignore
rl = (Cl >>> 0) + (Dl >>> 0) + ((ml << 1) >>> 0);

@@ -110,4 +86,4 @@ Ch = (Ch + Dh + ((mh << 1) | (ml >>> 31)) + ((rl / 0x100000000) | 0)) | 0;

xl = Bl ^ Cl; // prettier-ignore
Bh = rotrBH(xh, xl, 63);
Bl = rotrBL(xh, xl, 63); // prettier-ignore
Bh = (xh << 1) | (xl >>> 31);
Bl = (xh >>> 31) | (xl << 1); // prettier-ignore
((A2_BUF[2 * a] = Al), (A2_BUF[2 * a + 1] = Ah));

@@ -134,4 +110,18 @@ ((A2_BUF[2 * b] = Bl), (A2_BUF[2 * b + 1] = Bh));

function block(x, xPos, yPos, outPos, needXor) {
for (let i = 0; i < 256; i++)
A2_BUF[i] = x[xPos + i] ^ x[yPos + i];
// Stage R = X xor Y in the destination before permuting it. This avoids rereading both source
// blocks when folding R into the permuted scratch block below.
if (needXor) {
for (let i = 0; i < 256; i++) {
const r = x[xPos + i] ^ x[yPos + i];
A2_BUF[i] = r;
x[outPos + i] ^= r;
}
}
else {
for (let i = 0; i < 256; i++) {
const r = x[xPos + i] ^ x[yPos + i];
A2_BUF[i] = r;
x[outPos + i] = r;
}
}
// rows (8 consecutive 16-register groups)

@@ -148,8 +138,4 @@ for (let i = 0; i < 128; i += 16) {

// RFC 9106 step 6: passes after the first XOR the old destination block into the new G(X, Y).
if (needXor)
for (let i = 0; i < 256; i++)
x[outPos + i] ^= A2_BUF[i] ^ x[xPos + i] ^ x[yPos + i];
else
for (let i = 0; i < 256; i++)
x[outPos + i] = A2_BUF[i] ^ x[xPos + i] ^ x[yPos + i];
for (let i = 0; i < 256; i++)
x[outPos + i] ^= A2_BUF[i];
clean(A2_BUF);

@@ -205,5 +191,8 @@ }

const startPos = r !== 0 && s !== ARGON2_SYNC_POINTS - 1 ? (s + 1) * segmentLen : 0;
// RFC 9106 Figure 13: `mul(randL, randL).h` is `floor(J_1^2 / 2^32)`, and the outer high-half
// multiply computes `floor(|W| * x / 2^32)` without floating-point math.
const rel = area - 1 - mul(area, mul(randL, randL).h).h;
// Use the same exact-high recovery as G. `areaHigh` is floor(|W| * J1^2/2^32 / 2^32).
const randLow = Math.imul(randL, randL);
const randHigh = (((randL >>> 0) * (randL >>> 0) - (randLow >>> 0)) / 0x100000000 + 0.5) | 0;
const areaLow = Math.imul(area, randHigh);
const areaHigh = (((area >>> 0) * (randHigh >>> 0) - (areaLow >>> 0)) / 0x100000000 + 0.5) | 0;
const rel = area - 1 - areaHigh;
return (startPos + rel) % laneLen;

@@ -213,2 +202,4 @@ }

const maxUint32 = Math.pow(2, 32);
const ARGON2_DEFAULT_MEMORY = 1024 ** 2; // KiB: 1 GiB
const ARGON2_DEFAULT_MAXMEM = ARGON2_DEFAULT_MEMORY * 1024;
// Validate safe JS integers in `[0, 2^32 - 1]`.

@@ -218,8 +209,11 @@ function isU32(num) {

}
function argon2Opts(opts) {
function argon2Opts(opts = {}) {
opts = checkOpts({}, opts);
const merged = {
t: 3,
m: ARGON2_DEFAULT_MEMORY,
p: 1,
version: 0x13,
dkLen: 32,
maxmem: maxUint32 - 1,
maxmem: ARGON2_DEFAULT_MAXMEM,
asyncTick: 10,

@@ -257,80 +251,110 @@ };

}
function argon2Init(password, salt, type, opts) {
password = kdfInputToBytes(password, 'password');
salt = kdfInputToBytes(salt, 'salt');
if (!isU32(password.length))
throw new Error('"password" must be less of length 1..4Gb');
// RFC 9106 §3.1 only requires S <= 2^32-1 bytes and says 16 bytes is RECOMMENDED for password
// hashing; this library intentionally takes the stricter common >=8-byte salt path.
if (!isU32(salt.length) || salt.length < 8)
throw new Error('"salt" must be of length 8..4Gb');
if (!Object.values(AT).includes(type))
throw new Error('"type" was invalid');
let { p, dkLen, m, t, version, key, personalization, maxmem, onProgress, asyncTick } = argon2Opts(opts);
// Validation
key = abytesOrZero(key, 'key');
personalization = abytesOrZero(personalization, 'personalization');
// H_0 = H^(64)(LE32(p) || LE32(T) || LE32(m) || LE32(t) ||
// LE32(v) || LE32(y) || LE32(length(P)) || P ||
// LE32(length(S)) || S || LE32(length(K)) || K ||
// LE32(length(X)) || X)
const h = blake2b.create();
function argon2InitialHash(password, salt, type, opts) {
const ownedInputs = [];
const BUF = new Uint32Array(1);
const BUF8 = u8(BUF);
for (let item of [p, dkLen, m, t, version, type]) {
// RFC 9106 H0 encodes these scalars as LE32, so normalize the host word before exposing bytes.
BUF[0] = swap8IfBE(item);
h.update(BUF8);
let h;
let H0;
let succeeded = false;
const rememberOwned = (input, bytes) => {
if (typeof input === 'string')
ownedInputs.push(bytes);
return bytes;
};
try {
const passwordBytes = rememberOwned(password, kdfInputToBytes(password, 'password'));
const saltBytes = rememberOwned(salt, kdfInputToBytes(salt, 'salt'));
if (!isU32(passwordBytes.length))
throw new Error('"password" must be less of length 1..4Gb');
// RFC 9106 §3.1 only requires S <= 2^32-1 bytes and says 16 bytes is RECOMMENDED for password
// hashing; this library intentionally takes the stricter common >=8-byte salt path.
if (!isU32(saltBytes.length) || saltBytes.length < 8)
throw new Error('"salt" must be of length 8..4Gb');
if (!Object.values(AT).includes(type))
throw new Error('"type" was invalid');
let { p, dkLen, m, t, version, key, personalization, maxmem, onProgress, asyncTick } = argon2Opts(opts);
// Validation
const keyInput = key;
key = rememberOwned(keyInput, abytesOrZero(keyInput, 'key'));
const personalizationInput = personalization;
personalization = rememberOwned(personalizationInput, abytesOrZero(personalizationInput, 'personalization'));
// H_0 = H^(64)(LE32(p) || LE32(T) || LE32(m) || LE32(t) ||
// LE32(v) || LE32(y) || LE32(length(P)) || P ||
// LE32(length(S)) || S || LE32(length(K)) || K ||
// LE32(length(X)) || X)
h = blake2b.create();
for (let item of [p, dkLen, m, t, version, type]) {
// RFC 9106 H0 encodes these scalars as LE32, so normalize the host word before
// exposing bytes.
BUF[0] = swap8IfBE(item);
h.update(BUF8);
}
for (let i of [passwordBytes, saltBytes, key, personalization]) {
BUF[0] = swap8IfBE(i.length); // BUF is u32 array, this is valid once normalized to LE bytes
h.update(BUF8).update(i);
}
// Reserve two extra LE32 words after the 64-byte `H_0` so Figures 3-4 can append
// `LE32(0 or 1) || LE32(i)` in place for the lane-starting blocks.
H0 = new Uint32Array(18);
h.digestInto(u8(H0));
succeeded = true;
return { H0, p, dkLen, m, t, version, maxmem, onProgress, asyncTick };
}
for (let i of [password, salt, key, personalization]) {
BUF[0] = swap8IfBE(i.length); // BUF is u32 array, this is valid once normalized to LE bytes
h.update(BUF8).update(i);
finally {
// digestInto() does not destroy BLAKE2 state. It and all string-derived inputs contain secrets.
if (h)
h.destroy();
clean(BUF, ...ownedInputs);
if (!succeeded && H0)
clean(H0);
}
// Reserve two extra LE32 words after the 64-byte `H_0` so Figures 3-4 can append
// `LE32(0 or 1) || LE32(i)` in place for the lane-starting blocks.
const H0 = new Uint32Array(18);
const H0_8 = u8(H0);
h.digestInto(H0_8);
}
function argon2Init(password, salt, type, opts) {
const { H0, p, dkLen, m, t, version, maxmem, onProgress, asyncTick } = argon2InitialHash(password, salt, type, opts);
// 256 u32 = 1024 (BLOCK_SIZE), fills A2_BUF on processing
// Params
const lanes = p;
// m' = 4 * p * floor (m / 4p)
const mP = 4 * p * Math.floor(m / (ARGON2_SYNC_POINTS * p));
//q = m' / p columns
const laneLen = Math.floor(mP / p);
const segmentLen = Math.floor(laneLen / ARGON2_SYNC_POINTS);
// `maxmem` is documented in bytes; compare against the actual 1024-byte block allocation.
const memUsed = mP * 1024;
if (!isU32(maxmem))
throw new Error('"maxmem" expected <2**32, got ' + maxmem);
if (memUsed > maxmem)
throw new Error('"maxmem" limit was hit: memUsed(mP*1024)=' + memUsed + ', maxmem=' + maxmem);
const B = new Uint32Array(memUsed / 4);
// Fill first blocks
for (let l = 0; l < p; l++) {
const i = 256 * laneLen * l;
// B[i][0] = H'^(1024)(H_0 || LE32(0) || LE32(i))
H0[17] = swap8IfBE(l);
H0[16] = swap8IfBE(0);
B.set(swap32IfBE(u32(Hp(H0, 1024))), i);
// B[i][1] = H'^(1024)(H_0 || LE32(1) || LE32(i))
H0[16] = swap8IfBE(1);
B.set(swap32IfBE(u32(Hp(H0, 1024))), i + 256);
try {
// Params
const lanes = p;
// m' = 4 * p * floor (m / 4p)
const mP = 4 * p * Math.floor(m / (ARGON2_SYNC_POINTS * p));
//q = m' / p columns
const laneLen = Math.floor(mP / p);
const segmentLen = Math.floor(laneLen / ARGON2_SYNC_POINTS);
// `maxmem` is documented in bytes; compare against the actual 1024-byte block allocation.
const memUsed = mP * 1024;
if (!isU32(maxmem))
throw new Error('"maxmem" expected <2**32, got ' + maxmem);
if (memUsed > maxmem)
throw new Error('"maxmem" limit was hit: memUsed(mP*1024)=' + memUsed + ', maxmem=' + maxmem);
const B = new Uint32Array(memUsed / 4);
// Fill first blocks
for (let l = 0; l < p; l++) {
const i = 256 * laneLen * l;
// B[i][0] = H'^(1024)(H_0 || LE32(0) || LE32(i))
H0[17] = swap8IfBE(l);
H0[16] = swap8IfBE(0);
B.set(swap32IfBE(u32(Hp(H0, 1024))), i);
// B[i][1] = H'^(1024)(H_0 || LE32(1) || LE32(i))
H0[16] = swap8IfBE(1);
B.set(swap32IfBE(u32(Hp(H0, 1024))), i + 256);
}
let perBlock = () => { };
if (onProgress) {
// The first segment of the first pass skips two preinitialized blocks per lane.
const totalBlock = t * ARGON2_SYNC_POINTS * p * segmentLen - 2 * p;
// Invoke callback if progress changes from 10.01 to 10.02
// Allows to draw smooth progress bar on up to 8K screen
const callbackPer = Math.max(Math.floor(totalBlock / 10000), 1);
let blockCnt = 0;
perBlock = () => {
blockCnt++;
if (onProgress && (!(blockCnt % callbackPer) || blockCnt === totalBlock))
onProgress(blockCnt / totalBlock);
};
}
return { type, mP, p, t, version, B, laneLen, lanes, segmentLen, dkLen, perBlock, asyncTick };
}
let perBlock = () => { };
if (onProgress) {
// The first segment of the first pass skips two preinitialized blocks per lane.
const totalBlock = t * ARGON2_SYNC_POINTS * p * segmentLen - 2 * p;
// Invoke callback if progress changes from 10.01 to 10.02
// Allows to draw smooth progress bar on up to 8K screen
const callbackPer = Math.max(Math.floor(totalBlock / 10000), 1);
let blockCnt = 0;
perBlock = () => {
blockCnt++;
if (onProgress && (!(blockCnt % callbackPer) || blockCnt === totalBlock))
onProgress(blockCnt / totalBlock);
};
finally {
clean(H0);
}
clean(BUF, H0);
return { type, mP, p, t, version, B, laneLen, lanes, segmentLen, dkLen, perBlock, asyncTick };
}

@@ -355,6 +379,5 @@ function argon2Output(B, p, laneLen, dkLen) {

*/
function* argon2Blocks(ctx) {
function* argon2Blocks(ctx, address) {
const { type, mP, p, t, version, B, laneLen, lanes, segmentLen, perBlock } = ctx;
// [address, input, zero_block] format so we can pass single U32 to block function
const address = new Uint32Array(3 * 256);
address[256 + 6] = mP;

@@ -426,3 +449,3 @@ address[256 + 8] = t;

const ctx = argon2Init(password, salt, type, opts);
const blocks = argon2Blocks(ctx);
const blocks = argon2Blocks(ctx, new Uint32Array(3 * 256));
while (!blocks.next().done) { }

@@ -463,3 +486,3 @@ return argon2Output(ctx.B, ctx.p, ctx.laneLen, ctx.dkLen);

*/
export const argon2d = (password, salt, opts) => argon2(AT.Argon2d, password, salt, opts);
export const argon2d = (password, salt, opts = {}) => argon2(AT.Argon2d, password, salt, opts);
/**

@@ -478,3 +501,3 @@ * Argon2i side-channel-resistant version.

*/
export const argon2i = (password, salt, opts) => argon2(AT.Argon2i, password, salt, opts);
export const argon2i = (password, salt, opts = {}) => argon2(AT.Argon2i, password, salt, opts);
/**

@@ -493,6 +516,9 @@ * Argon2id, combining i+d, the most popular version from RFC 9106.

*/
export const argon2id = (password, salt, opts) => argon2(AT.Argon2id, password, salt, opts);
export const argon2id = (password, salt, opts = {}) => argon2(AT.Argon2id, password, salt, opts);
async function argon2Async(type, password, salt, opts) {
const ctx = argon2Init(password, salt, type, opts);
const blocks = argon2Blocks(ctx);
// Keep the generator-local address block reachable so an aborted scheduler yield can wipe it.
const address = new Uint32Array(3 * 256);
const blocks = argon2Blocks(ctx, address);
const abort = () => clean(address, ctx.B);
let ts = Date.now();

@@ -505,4 +531,5 @@ while (!blocks.next().done) {

continue;
await nextTick();
ts += diff;
await nextTick(abort);
// Scheduler delay is outside the synchronous work budget.
ts = Date.now();
}

@@ -542,3 +569,3 @@ return argon2Output(ctx.B, ctx.p, ctx.laneLen, ctx.dkLen);

*/
export const argon2dAsync = (password, salt, opts) => argon2Async(AT.Argon2d, password, salt, opts);
export const argon2dAsync = (password, salt, opts = {}) => argon2Async(AT.Argon2d, password, salt, opts);
/**

@@ -557,3 +584,3 @@ * Argon2i async side-channel-resistant version.

*/
export const argon2iAsync = (password, salt, opts) => argon2Async(AT.Argon2i, password, salt, opts);
export const argon2iAsync = (password, salt, opts = {}) => argon2Async(AT.Argon2i, password, salt, opts);
/**

@@ -572,2 +599,2 @@ * Argon2id async, combining i+d, the most popular version from RFC 9106.

*/
export const argon2idAsync = (password, salt, opts) => argon2Async(AT.Argon2id, password, salt, opts);
export const argon2idAsync = (password, salt, opts = {}) => argon2Async(AT.Argon2id, password, salt, opts);

@@ -32,4 +32,2 @@ /**

const EMPTY_SALT = /* @__PURE__ */ new Uint32Array(8);
// Base destroy logic only clears salt-derived state; the partial message buffer and length/position
// bookkeeping remain until the instance or backing buffer is reused.
class BLAKE1 {

@@ -111,2 +109,5 @@ canXOF = false;

this.destroyed = true;
clean(this.buffer);
this.length = 0;
this.pos = 0;
if (this.salt !== EMPTY_SALT) {

@@ -113,0 +114,0 @@ clean(this.salt, this.constants);

@@ -137,3 +137,5 @@ /**

// 4 (100) - leafs finished at depth=1 and depth=2
for (let last, chunks = this.chunksDone + 1; isLast || !(chunks & 1); chunks >>= 1) {
// Reading the low bit is safe for parity, but keep division full-width: shifting would
// truncate the safe-integer chunk counter to 32 bits.
for (let last, chunks = this.chunksDone + 1; isLast || !(chunks & 1); chunks = Math.floor(chunks / 2)) {
if (!(last = this.stack.pop()))

@@ -140,0 +142,0 @@ break;

{
"name": "@noble/hashes",
"version": "2.3.0",
"version": "2.4.0",
"description": "Audited & minimal 0-dependency JS implementation of SHA, RIPEMD, BLAKE, HMAC, HKDF, PBKDF & Scrypt",

@@ -11,7 +11,8 @@ "files": [

"devDependencies": {
"@paulmillr/jsbt": "0.6.5",
"@types/node": "25.3.0",
"@paulmillr/jsbt": "0.7.1",
"bismar": "0.1.8",
"@types/node": "26.2.0",
"fast-check": "4.2.0",
"prettier": "3.6.2",
"typescript": "6.0.2"
"prettier": "3.9.6",
"typescript": "6.0.3"
},

@@ -21,3 +22,4 @@ "scripts": {

"benchmark:thirdparty": "cd benchmark/thirdparty; npm ci; JSBT_BENCHMARK_DIMENSIONS='algorithm,buffer,library' node hashes.ts",
"benchmark:thirdparty-scrypt": "cd benchmark/thirdparty; npm ci; JSBT_BENCHMARK_DIMENSIONS='iters,library' JSBT_BENCHMARK_FILTER='async' node scrypt.ts",
"benchmark:thirdparty-scrypt": "cd benchmark/thirdparty; npm ci; JSBT_BENCHMARK_DIMENSIONS='iters,library' FILTER='async' node scrypt.ts",
"benchmark:size": "bismar -bsm",
"build": "tsc",

@@ -31,3 +33,5 @@ "build:clean": "rm *.{js,d.ts} 2> /dev/null",

"test:acvp": "node test/slow-acvp.test.ts",
"test:kdf": "node test/slow-kdf.test.ts"
"test:kdf": "node test/slow-kdf.test.ts",
"test:ultra": "node test/slow-ultra.test.ts",
"test:ultra:scrypt": "node test/slow-extreme-scrypt.test.ts"
},

@@ -34,0 +38,0 @@ "exports": {

@@ -26,10 +26,23 @@ /**

const p = kdfInputToBytes(_password, 'password');
const s = kdfInputToBytes(_salt, 'salt');
// DK = PBKDF2(PRF, Password, Salt, c, dkLen);
const DK = new Uint8Array(dkLen);
const { iHash, oHash, outputLen } = hmac.create(hash, p);
// Drive keyed hashes directly; the wrapper is only needed to initialize their HMAC midstates.
const u = new Uint8Array(outputLen);
const eng = pbkdf2Engine(iHash, oHash, s, u);
return { c, dkLen, asyncTick, DK, outputLen, eng };
try {
const s = kdfInputToBytes(_salt, 'salt');
try {
// DK = PBKDF2(PRF, Password, Salt, c, dkLen);
const DK = new Uint8Array(dkLen);
const { iHash, oHash, outputLen } = hmac.create(hash, p);
// Drive keyed hashes directly; the wrapper is only needed to initialize their HMAC midstates.
const u = new Uint8Array(outputLen);
const eng = pbkdf2Engine(iHash, oHash, s, u);
return { c, dkLen, asyncTick, DK, outputLen, eng };
}
finally {
// Uint8Array inputs belong to the caller; only wipe our UTF-8 conversion.
if (typeof _salt === 'string')
clean(s);
}
}
finally {
if (typeof _password === 'string')
clean(p);
}
}

@@ -144,2 +157,7 @@ // Per-call PRF driver writes U1 into both `u` and `Ti`, then later digests into `u`;

const { c, dkLen, asyncTick, DK, outputLen, eng } = pbkdf2Init(hash, password, salt, opts);
// Reuse normal state destruction, then wipe the incomplete output if a host yield aborts.
const abort = () => {
eng.output(DK);
clean(DK);
};
// DK = T1 + T2 + ⋯ + Tdklen/hlen

@@ -157,5 +175,5 @@ for (let ti = 1, pos = 0; pos < dkLen; ti++, pos += outputLen) {

eng.rounds(2, Ti); // c=2 runs exactly one PRF iteration per callback.
});
}, abort);
}
return eng.output(DK);
}
+105
-206

@@ -6,3 +6,3 @@ # noble-hashes

- 🔒 [**Audited**](#security) by an independent security firm
- 🪶 Minimal: 2.7KB (gzipped) sha256, unused code is excluded from your builds
- 🪶 Minimal: 2.8KB (gzipped) sha256, unused code is excluded from your builds
- 🏎 Fast: hand-optimized for caveats of JS engines

@@ -46,30 +46,4 @@ - 🔍 Reliable: chained / ACVP tests ensure correctness

// import * from '@noble/hashes'; // Error: use sub-imports, to ensure small app size
import { sha256 as noble_sha256 } from '@noble/hashes/sha2.js';
const hash = noble_sha256(Uint8Array.from([0xca, 0xfe, 0x01, 0x23]));
// Available modules
import { sha256, sha384, sha512, sha224, sha512_224, sha512_256 } from '@noble/hashes/sha2.js';
import {
sha3_256, sha3_512,
keccak_256, keccak_512,
shake128, shake256,
} from '@noble/hashes/sha3.js';
import {
cshake256, turboshake256, kmac256, tuplehash256,
kt128, kt256, keccakprg,
} from '@noble/hashes/sha3-addons.js';
import { blake3 } from '@noble/hashes/blake3.js';
import { blake2b, blake2s } from '@noble/hashes/blake2.js';
import { blake256, blake512 } from '@noble/hashes/blake1.js';
import { sha1, md5, ripemd160 } from '@noble/hashes/legacy.js';
import { hmac } from '@noble/hashes/hmac.js';
import { hkdf } from '@noble/hashes/hkdf.js';
import { pbkdf2, pbkdf2Async } from '@noble/hashes/pbkdf2.js';
import { scrypt, scryptAsync } from '@noble/hashes/scrypt.js';
import { argon2d, argon2i, argon2id } from '@noble/hashes/argon2.js';
import { eskdf } from '@noble/hashes/eskdf.js';
import * as webcrypto from '@noble/hashes/webcrypto.js';
// const { sha256, sha384, sha512, hmac, hkdf, pbkdf2 } = webcrypto;
import * as utils from '@noble/hashes/utils.js';
const { bytesToHex, concatBytes, equalBytes, hexToBytes } = utils;
import { sha256 } from '@noble/hashes/sha2.js';
const hash = sha256(Uint8Array.from([0xca, 0xfe, 0x01, 0x23]));
```

@@ -102,12 +76,5 @@

import { sha224, sha256, sha384, sha512, sha512_224, sha512_256 } from '@noble/hashes/sha2.js';
const res = sha256(Uint8Array.from([0xbc])); // basic
for (let hash of [sha256, sha384, sha512, sha224, sha512_224, sha512_256]) {
const arr = Uint8Array.from([0x10, 0x20, 0x30]);
const a = hash(arr);
const b = hash.create().update(arr).digest();
}
const res = sha256(Uint8Array.from([0xbc]));
```
Check out [RFC 6234](https://datatracker.ietf.org/doc/html/rfc6234) and
[the paper on truncated SHA512/256](https://eprint.iacr.org/2010/548.pdf).

@@ -122,10 +89,3 @@ #### sha3: FIPS, SHAKE, Keccak

} from '@noble/hashes/sha3.js';
for (let hash of [
sha3_224, sha3_256, sha3_384, sha3_512,
keccak_224, keccak_256, keccak_384, keccak_512,
]) {
const arr = Uint8Array.from([0x10, 0x20, 0x30]);
const a = hash(arr);
const b = hash.create().update(arr).digest();
}
const s = sha3_256(Uint8Array.from([0x10, 0x20, 0x30]));
const shka = shake128(Uint8Array.from([0x10]), { dkLen: 512 });

@@ -135,7 +95,2 @@ const shkb = shake256(Uint8Array.from([0x30]), { dkLen: 512 });

Check out [FIPS-202](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf),
[Website](https://keccak.team/keccak.html).
Check out [the differences between SHA-3 and Keccak](https://crypto.stackexchange.com/questions/15727/what-are-the-key-differences-between-the-draft-sha-3-standard-and-the-keccak-sub)
#### sha3-addons: cSHAKE, KMAC, KT128, TurboSHAKE

@@ -151,28 +106,15 @@

const data = Uint8Array.from([0x10, 0x20, 0x30]);
const personalization = new TextEncoder().encode('def');
const ec1 = cshake128(data, { personalization });
const ec2 = cshake256(data, { personalization });
const et1 = turboshake128(data);
const et2 = turboshake256(data, { D: 0x05 });
const ec = cshake128(data, { personalization: new TextEncoder().encode('def') });
const et = turboshake256(data, { D: 0x05 });
// tuplehash(['ab', 'c']) !== tuplehash(['a', 'bc']) !== tuplehash([data])
const et3 = tuplehash256([new TextEncoder().encode('ab'), new TextEncoder().encode('c')]);
const eu = tuplehash256([new TextEncoder().encode('ab'), new TextEncoder().encode('c')]);
// Not parallel in JS (similar to blake3 / kt128), added for compat
const ep1 = parallelhash256(data, { blockLen: 8 });
const kk = Uint8Array.from([0xca]);
const ek10 = kmac128(kk, data);
const ek11 = kmac256(kk, data);
const ek12 = kt128(data); // kangarootwelve 128-bit
const ek13 = kt256(data); // kangarootwelve 256-bit
// pseudo-random generator, first argument is capacity. XKCP recommends 254 bits capacity for 128-bit security strength.
const ep = parallelhash256(data, { blockLen: 8 });
const ek = kmac256(Uint8Array.from([0xca]), data);
const ekt = kt128(data);
const p = keccakprg(254);
p.addEntropy(Uint8Array.from([1, 2, 3]));
p.addEntropy();
const rand1b = p.randomBytes(32);
```
- cSHAKE, KMAC, TupleHash, ParallelHash + XOF are available, matching
[NIST SP 800-185](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf)
- Reduced-round Keccak KT128 (KangarooTwelve 🦘, K12) and TurboSHAKE are available, matching
[RFC 9861](https://datatracker.ietf.org/doc/rfc9861/).
- [KeccakPRG](https://keccak.team/files/CSF-0.1.pdf): pseudo-random generator based on Keccak
#### blake1, blake2, blake3

@@ -184,23 +126,12 @@

import { blake3 } from '@noble/hashes/blake3.js';
const ab = Uint8Array.from([0x01]);
blake256(ab);
for (let hash of [blake224, blake256, blake384, blake512, blake2b, blake2s, blake3]) {
const arr = Uint8Array.from([0x10, 0x20, 0x30]);
const a = hash(arr);
const b = hash.create().update(arr).digest();
}
// blake2 advanced usage
const ab = Uint8Array.from([0x01]);
const txt = new TextEncoder();
blake2s(ab);
blake2s(ab, { key: new Uint8Array(32) });
blake2s(ab, { personalization: txt.encode('pers1234') });
blake2s(ab, { salt: txt.encode('salt1234') });
blake2b(ab);
blake2b(ab, { key: new Uint8Array(64) });
blake2b(ab, { personalization: txt.encode('pers1234pers1234') });
blake2b(ab, { salt: txt.encode('salt1234salt1234') });
blake2s(ab, { key: new Uint8Array(32) }); // blake2b keys can be 64 bytes
blake2s(ab, { personalization: txt.encode('pers1234') }); // 16 bytes for blake2b
blake2s(ab, { salt: txt.encode('salt1234') }); // 16 bytes for blake2b
// blake3 advanced usage
blake3(ab);
blake3(ab, { dkLen: 256 });

@@ -211,22 +142,7 @@ blake3(ab, { key: new Uint8Array(32) });

- Blake1 is legacy hash, one of SHA3 proposals. It is rarely used anywhere. See [pdf](https://www.aumasson.jp/blake/blake.pdf).
- Blake2 is popular fast hash. blake2b focuses on 64-bit platforms while blake2s is for 8-bit to 32-bit ones. See [RFC 7693](https://datatracker.ietf.org/doc/html/rfc7693), [Website](https://www.blake2.net)
- Blake3 is faster, reduced-round blake2. See [Website & specs](https://blake3.io)
#### legacy: sha1, md5, ripemd160
SHA1 (RFC 3174), MD5 (RFC 1321) and RIPEMD160 (ISO/IEC 10118-3) legacy, weak hash functions.
Don't use them in a new protocol. What "weak" means:
- Collisions can be made with 2^24 effort in MD5 (seconds on commodity hardware), 2^61 in SHA1 (demonstrated in practice), 2^80 in RIPEMD160.
- No practical pre-image attacks (only theoretical, 2^123.4)
- HMAC seems kinda ok: https://datatracker.ietf.org/doc/html/rfc6151
```typescript
import { md5, ripemd160, sha1 } from '@noble/hashes/legacy.js';
for (let hash of [md5, ripemd160, sha1]) {
const arr = Uint8Array.from([0x10, 0x20, 0x30]);
const a = hash(arr);
const b = hash.create().update(arr).digest();
}
const h = sha1(Uint8Array.from([0x10, 0x20, 0x30]));
```

@@ -245,4 +161,2 @@

Conforms to [RFC 2104](https://datatracker.ietf.org/doc/html/rfc2104).
#### hkdf

@@ -265,4 +179,2 @@

Conforms to [RFC 5869](https://datatracker.ietf.org/doc/html/rfc5869).
#### pbkdf2

@@ -281,4 +193,2 @@

Conforms to [RFC 8018](https://datatracker.ietf.org/doc/html/rfc8018).
#### scrypt

@@ -298,9 +208,6 @@

},
maxmem: 128 * 8 * (2 ** 17 + 1 + 1), // 128 * r * (N + p + 1)
// maxmem: 128 * 8 * (2 ** 17 + 1 + 1), // 128 * r * (N + p + 1)
});
```
Conforms to [RFC 7914](https://datatracker.ietf.org/doc/html/rfc7914),
[Website](https://www.tarsnap.com/scrypt.html)
- `N, r, p` are work factors. It is common to only adjust N, while keeping `r: 8, p: 1`.

@@ -311,18 +218,7 @@ See [the blog post](https://blog.filippo.io/the-scrypt-parameters/).

- `onProgress` can be used with async version of the function to report progress to a user.
- `maxmem` prevents DoS and is limited to `1GB + 1KB` (`2**30 + 2**10`), but can be adjusted using formula: `128 * r * (N + p + 1)`
- `maxmem` prevents DoS and defaults to `1GiB + 2KiB` (`2**30 + 2**11`), enough for `N: 2**20, r: 8, p: 1`. It can be adjusted using formula: `128 * r * (N + p + 1)`
Time it takes to derive Scrypt key under different values of N (2\*\*N) on Apple M4 (mobile phones can be 1x-4x slower):
On Apple M4, `N: 2**16` takes 0.1s and 64MB RAM; each increment of N doubles both,
up to `N: 2**24` at 27s and 16GB. Mobile phones can be 1x-4x slower.
| N pow | Time | RAM |
| ----- | ---- | ----- |
| 16 | 0.1s | 64MB |
| 17 | 0.2s | 128MB |
| 18 | 0.4s | 256MB |
| 19 | 0.8s | 512MB |
| 20 | 1.5s | 1GB |
| 21 | 3.1s | 2GB |
| 22 | 6.2s | 4GB |
| 23 | 13s | 8GB |
| 24 | 27s | 16GB |
> [!NOTE]

@@ -338,7 +234,6 @@ > We support N larger than `2**20` where available, however,

import { argon2d, argon2i, argon2id } from '@noble/hashes/argon2.js';
const arg1 = argon2id('password', 'saltsalt', { t: 2, m: 65536, p: 1, maxmem: 2 ** 32 - 1 });
// Defaults to t=3, m=1GiB (specified in KiB), p=1, and a 1GiB maxmem limit.
const arg1 = argon2id('password', 'saltsalt');
```
Argon2 [RFC 9106](https://datatracker.ietf.org/doc/html/rfc9106) implementation.
> [!WARNING]

@@ -382,11 +277,5 @@ > Argon2 can't be fast in JS, because there is no fast Uint64Array.

Sometimes people want to use built-in `crypto.subtle` instead of pure JS implementation.
However, it has terrible API.
A thin wrapper over built-in `crypto.subtle`, mirroring the noble-hashes API and validating
inputs, in just 30+ lines of code. Webcrypto methods are always async.
We simplify access to built-ins with API which mirrors noble-hashes.
The overhead is minimal - just 30+ lines of code, which verify input correctness.
> [!NOTE]
> Webcrypto methods are always async.
#### utils

@@ -402,2 +291,21 @@

### Specs
- SHA2: [RFC 6234](https://datatracker.ietf.org/doc/html/rfc6234)
- SHA2-512/256: [pdf](https://eprint.iacr.org/2010/548.pdf)
- SHA3: [FIPS-202](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf)
- SHA3-addons: [NIST SP 800-185](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-185.pdf)
- SHA3-addons KT128 (KangarooTwelve 🦘, K12) / TurboSHAKE: [RFC 9861](https://datatracker.ietf.org/doc/rfc9861/)
- BLAKE1: [pdf](https://www.aumasson.jp/blake/blake.pdf)
- BLAKE2: [RFC 7693](https://datatracker.ietf.org/doc/html/rfc7693)
- BLAKE3: [site](https://blake3.io)
- SHA1: [RFC 3174](https://datatracker.ietf.org/doc/html/rfc3174)
- MD5: [RFC 1321](https://datatracker.ietf.org/doc/html/rfc1321)
- RIPEMD160 (ISO/IEC 10118-3)
- HMAC: [RFC 2104](https://datatracker.ietf.org/doc/html/rfc2104)
- HKDF: [RFC 5869](https://datatracker.ietf.org/doc/html/rfc5869)
- PBKDF2: [RFC 8018](https://datatracker.ietf.org/doc/html/rfc8018)
- Scrypt: [RFC 7914](https://datatracker.ietf.org/doc/html/rfc7914)
- Argon2: [RFC 9106](https://datatracker.ietf.org/doc/html/rfc9106)
## Security

@@ -407,5 +315,2 @@

- at version 2.2.0, in Apr 2026, by ourselves (self-audited)
- Scope: everything
- [Changes since audit](https://github.com/paulmillr/noble-hashes/compare/2.2.0..main)
- at version 1.0.0, in Jan 2022, independently, by [Cure53](https://cure53.de)

@@ -416,5 +321,7 @@ - PDFs: [website](https://cure53.de/pentest-report_hashing-libs.pdf), [in-repo](./audit/2022-01-05-cure53-audit-nbl2.pdf)

We've started regular AI-assisted self-audits in Apr 2026.
It is tested against official (ACVP / KAT) vectors, cross-library chained hashing,
sliding-window length sweeps and property-based tests (fast-check),
and is being fuzzed in [the separate repo](https://github.com/paulmillr/fuzzing).
and is being fuzzed in CI.

@@ -435,19 +342,11 @@ If you see anything unusual: investigate and report.

The library shares state buffers between hash
function calls. The buffers are zeroed-out after each call. However, if an attacker
can read application memory, you are doomed in any case:
The library shares state buffers between hash function calls. Library-owned working buffers are
zeroed after use, including mutable UTF-8 copies created from password-KDF string inputs.
However, if an attacker can read application memory, you are doomed in any case:
- At some point, input will be a string and strings are immutable in JS:
there is no way to overwrite them with zeros. For example: deriving
key from `scrypt(password, salt)` where password and salt are strings
- Input from a file will stay in file buffers
- Input / output will be re-used multiple times in application which means it could stay in memory
- `await anything()` will always write all internal variables (including numbers)
to memory. With async functions / Promises there are no guarantees when the code
chunk would be executed. Which means attacker can have plenty of time to read data from memory
- There is no way to guarantee anything about zeroing sensitive data without
complex tests-suite which will dump process memory and verify that there is
no sensitive data left. For JS it means testing all browsers (incl. mobile),
which is complex. And of course it will be useless without using the same
test-suite in the actual application that consumes the library
- JS strings are immutable and can't be overwritten with zeros — e.g. a password passed
to `scrypt(password, salt)` as a string stays in memory
- Inputs & outputs are re-used across the application and stay in file buffers / memory anyway
- `await anything()` writes all internal variables (including numbers) to memory, with no
guarantee of when they get overwritten — plenty of time for an attacker to read them

@@ -459,5 +358,3 @@ ### Supply chain security

- **Rare releasing** is practiced to minimize the need for re-audits by end-users.
- **Dependencies** are minimized and strictly pinned to reduce supply-chain risk.
- We use as few dependencies as possible.
- Version ranges are locked, and changes are checked with npm-diff.
- **Dependencies** are minimized, strictly pinned, and changes are checked with npm-diff.
- **Dev dependencies** are excluded from end-user installs; they’re only used for development and build steps.

@@ -515,13 +412,11 @@

`npm install && npm run build && npm test` will build the code and run tests.
There are **additional** slow suites: timing-based DoS tests `npm run test:dos`,
multi-hour large-input tests `npm run test:slow`, ACVP LDT vectors `npm run test:acvp`,
and memory-intensive KDF tests `npm run test:ultra`. The 9–17GiB scrypt cases require an
explicitly provisioned machine and run separately with `npm run test:ultra:scrypt`.
`test/misc` directory contains unrolled implementations (sha3, argon2) and misc helper scripts.
- `npm install && npm run build && npm test` will build the code and run tests.
- `npm run check` / `npm run format` will run linter / fix linter issues.
- `npm run benchmark` will run benchmarks
- `npm run bundle` will build single file
- There are **additional** slow suites: 20-min DoS test `npm run test:dos`,
multi-hour 4GB-input test `npm run test:slow`, ACVP vectors `npm run test:acvp`
and KDF vectors `npm run test:kdf`.
See [our approach to testing](./test/README.md)
Some hashes are outside of scope of the library:

@@ -551,43 +446,47 @@ - [Pedersen in micro-zk-proofs](https://github.com/paulmillr/micro-zk-proofs/blob/1ed5ce1253583b2e540eef7f3477fb52bf5344ff/src/pedersen.ts)

# 32B
sha256 x 2,016,129 ops/sec @ 496ns/op
sha512 x 740,740 ops/sec @ 1μs/op
sha3_256 x 287,686 ops/sec @ 3μs/op
sha3_512 x 288,267 ops/sec @ 3μs/op
kt128 x 476,190 ops/sec @ 2μs/op
blake2b x 410,340 ops/sec @ 2μs/op
blake2s x 942,507 ops/sec @ 1μs/op
blake3 x 1,006,036 ops/sec @ 994ns/op
ripemd160 x 1,410,437 ops/sec @ 709ns/op
md5 x 1,663,893 ops/sec @ 601ns/op
sha1 x 1,589,825 ops/sec @ 629ns/op
sha256 438 ns
sha512 1219 ns
sha3_256 1853 ns
sha3_512 1864 ns
kt128 1380 ns
kt256 1370 ns
turboshake128 1191 ns
blake256 1335 ns
blake2b 2186 ns
blake2s 1055 ns
blake3 981 ns
ripemd160 563 ns
md5 449 ns
sha1 507 ns
hmac(sha256) 1955 ns
hmac(sha512) 5126 ns
kmac256 6653 ns
blake3(key) 1120 ns
# 1MB
sha256 x 331 ops/sec @ 3ms/op
sha512 x 128 ops/sec @ 7ms/op
sha3_256 x 39 ops/sec @ 25ms/op
sha3_512 x 21 ops/sec @ 46ms/op
kt128 x 91 ops/sec @ 10ms/op
kt256 x 75 ops/sec @ 13ms/op
turboshake128 x 93 ops/sec @ 10ms/op
blake256 x 57 ops/sec @ 17ms/op
blake2b x 61 ops/sec @ 16ms/op
blake2s x 78 ops/sec @ 12ms/op
blake3 x 95 ops/sec @ 10ms/op
ripemd160 x 177 ops/sec @ 5ms/op
md5 x 250 ops/sec @ 3ms/op
sha1 x 416 ops/sec @ 2ms/op
sha256 x 297 mib/sec
sha512 x 130 mib/sec
sha3_256 x 78.1 mib/sec
sha3_512 x 41.9 mib/sec
kt128 x 184 mib/sec
kt256 x 147 mib/sec
turboshake128 x 186 mib/sec
blake256 x 56.7 mib/sec
blake2b x 66.2 mib/sec
blake2s x 62.9 mib/sec
blake3 x 90 mib/sec
ripemd160 x 179 mib/sec
md5 x 275 mib/sec
sha1 x 417 mib/sec
hmac(sha256) x 290 mib/sec
hmac(sha512) x 129 mib/sec
kmac256 x 78.4 mib/sec
blake3(key) x 90.5 mib/sec
# MAC
hmac(sha256) x 599,880 ops/sec @ 1μs/op
hmac(sha512) x 197,122 ops/sec @ 5μs/op
kmac256 x 87,981 ops/sec @ 11μs/op
blake3(key) x 796,812 ops/sec @ 1μs/op
# KDF
hkdf(sha256) x 259,942 ops/sec @ 3μs/op
blake3(context) x 424,808 ops/sec @ 2μs/op
pbkdf2(sha256, c: 2 ** 18) x 5 ops/sec @ 197ms/op
pbkdf2(sha512, c: 2 ** 18) x 1 ops/sec @ 630ms/op
scrypt(n: 2 ** 18, r: 8, p: 1) x 2 ops/sec @ 400ms/op
argon2id(t: 1, m: 256MB) 2881ms
hkdf(sha256) x 249,100 ops/sec @ 4015 ns/op
blake3(context) x 480,400 ops/sec @ 2081 ns/op
pbkdf2(sha256, c: 2 ** 18) x 5 ops/sec @ 199 ms/op
scrypt(n: 2 ** 19, r: 8, p: 1) x 1 ops/sec @ 751 ms/op
argon2id(t: 1, m: 128MB) x 3 ops/sec @ 276 ms/op
```

@@ -594,0 +493,0 @@

@@ -9,3 +9,4 @@ import { type KDFInput, type TArg, type TRet } from './utils.ts';

* - `asyncTick` - (default: 10) max time in ms for which async function can block execution
* - `maxmem` - (default: `1024 ** 3 + 1024` aka 1GB+1KB). A limit that the app could use for scrypt
* - `maxmem` - (default: `1024 ** 3 + 2 * 1024` aka 1GiB+2KiB). A limit that the app
* could use for scrypt
* - `onProgress` - callback function that would be executed for progress report

@@ -12,0 +13,0 @@ */

@@ -97,9 +97,10 @@ /**

}
// 128*r*(N+p+1) for N=2**20, r=8, p=1: 1 GiB main table plus 2 KiB workspace.
const SCRYPT_DEFAULT_MAXMEM = 128 * 8 * (2 ** 20 + 1 + 1);
// Common prologue and epilogue for sync/async functions
function scryptInit(password, salt, _opts) {
// Maxmem - 1GB+1KB by default
const opts = checkOpts({
dkLen: 32,
asyncTick: 10,
maxmem: 1024 ** 3 + 1024,
maxmem: SCRYPT_DEFAULT_MAXMEM,
}, _opts);

@@ -273,2 +274,4 @@ const { N, r, p, dkLen, asyncTick, maxmem, onProgress } = opts;

const { N, r, p, dkLen, blockSize32, V, B32, B, tmp, blockMixCb, asyncTick } = scryptInit(password, salt, opts);
// One failure handler covers both yield boundaries without putting the hot loops in a try block.
const abort = () => clean(B, V, tmp);
swap32IfBE(B32);

@@ -283,3 +286,3 @@ for (let pi = 0; pi < p; pi++) {

blockMixCb();
});
}, abort);
BlockMix(V, (N - 1) * blockSize32, B32, Pi, r); // Process last element

@@ -300,3 +303,3 @@ blockMixCb();

blockMixCb();
});
}, abort);
}

@@ -303,0 +306,0 @@ swap32IfBE(B32);

@@ -498,2 +498,3 @@ /**

protected rate: number;
private entropyAdded;
constructor(capacity: number);

@@ -504,3 +505,4 @@ protected keccak(): void;

digestInto(_out: TArg<Uint8Array>): void;
addEntropy(seed: TArg<Uint8Array>): void;
addEntropy(seed?: TArg<Uint8Array>): void;
xofInto(out: TArg<Uint8Array>): TRet<Uint8Array>;
randomBytes(length: number): TRet<Uint8Array>;

@@ -514,2 +516,5 @@ clean(): void;

* See {@link https://keccak.team/files/CSF-0.1.pdf}.
* Fresh instances reject output until `.addEntropy()` has been called. With no
* argument, `addEntropy()` obtains 32 bytes from the platform CSPRNG; callers
* may instead supply their own non-empty entropy bytes.
* @param capacity - sponge capacity in bits. Accepted values are those that

@@ -523,3 +528,6 @@ * keep `rho = 1598 - capacity` byte-aligned; the default `254` is chosen

* ```ts
* import { keccakprg } from '@noble/hashes/sha3-addons.js';
*
* const prg = keccakprg(254);
* prg.addEntropy();
* prg.randomBytes(8);

@@ -526,0 +534,0 @@ * ```

@@ -13,3 +13,3 @@ /**

import { Keccak } from "./sha3.js";
import { abytes, aexists, anumber, checkOpts, clean, copyBytes, createHasher, kdfInputToBytes, u32, } from "./utils.js";
import { abytes, aexists, anumber, checkOpts, clean, copyBytes, createHasher, randomBytes, kdfInputToBytes, u32, } from "./utils.js";
// cSHAKE && KMAC (NIST SP800-185)

@@ -868,2 +868,3 @@ const _8n = /* @__PURE__ */ BigInt(8);

rate;
entropyAdded = false;
constructor(capacity) {

@@ -899,4 +900,27 @@ anumber(capacity);

addEntropy(seed) {
this.update(seed);
// Check lifecycle before asking the system RNG for entropy that cannot be used.
aexists(this);
if (seed !== undefined) {
abytes(seed, undefined, 'seed');
if (seed.length === 0)
throw new Error('"seed" must not be empty');
}
const generated = seed === undefined;
const entropy = generated ? randomBytes() : seed;
try {
this.update(entropy);
this.entropyAdded = true;
}
finally {
// This temporary is library-owned; caller-provided entropy remains caller-owned.
if (generated)
clean(entropy);
}
}
xofInto(out) {
aexists(this, false);
if (!this.entropyAdded)
throw new Error('addEntropy() must be called before randomBytes()');
return super.xofInto(out);
}
randomBytes(length) {

@@ -924,2 +948,3 @@ return this.xof(length);

to.rate = rate;
to.entropyAdded = this.entropyAdded;
return to;

@@ -934,2 +959,5 @@ }

* See {@link https://keccak.team/files/CSF-0.1.pdf}.
* Fresh instances reject output until `.addEntropy()` has been called. With no
* argument, `addEntropy()` obtains 32 bytes from the platform CSPRNG; callers
* may instead supply their own non-empty entropy bytes.
* @param capacity - sponge capacity in bits. Accepted values are those that

@@ -943,3 +971,6 @@ * keep `rho = 1598 - capacity` byte-aligned; the default `254` is chosen

* ```ts
* import { keccakprg } from '@noble/hashes/sha3-addons.js';
*
* const prg = keccakprg(254);
* prg.addEntropy();
* prg.randomBytes(8);

@@ -946,0 +977,0 @@ * ```

@@ -104,3 +104,3 @@ /**

let processed = false;
for (let pos = 0; pos < len; ) {
for (let pos = 0; pos < len;) {
const take = Math.min(blockLen - this.pos, len - pos);

@@ -107,0 +107,0 @@ // Fast path only when there is no buffered partial block: `take === blockLen` implies

@@ -11,3 +11,2 @@ /**

*/
import { rotr32H, rotr32L, rotrBH, rotrBL, rotrSH, rotrSL } from './_u64.ts';
import { blake2b } from './blake2.ts';

@@ -43,29 +42,2 @@ import {

// Unsigned `u32 * u32 = { h, l }`, returned as split 64-bit halves.
function mul(a: number, b: number) {
// Split into 16-bit limbs so each partial product stays exact under `Math.imul`.
const aL = a & 0xffff;
const aH = a >>> 16;
const bL = b & 0xffff;
const bH = b >>> 16;
const ll = Math.imul(aL, bL);
const hl = Math.imul(aH, bL);
const lh = Math.imul(aL, bH);
const hh = Math.imul(aH, bH);
const carry = (ll >>> 16) + (hl & 0xffff) + lh;
const high = (hh + (hl >>> 16) + (carry >>> 16)) | 0;
const low = (carry << 16) | (ll & 0xffff);
return { h: high, l: low };
}
// High 32 bits of unsigned u32 multiply, via the same 16-bit limb split as `mul` below.
// Kept single-purpose and number-returning so V8 inlines it (object-returning
// helpers here cost 2.2x of the whole derivation, measured; small helpers
// returning one number are free — see rotr* usage everywhere).
function mulHi(a: number, b: number): number {
const aL = a & 0xffff, aH = a >>> 16, bL = b & 0xffff, bH = b >>> 16; // prettier-ignore
const carry = (Math.imul(aL, bL) >>> 16) + (Math.imul(aH, bL) & 0xffff) + Math.imul(aL, bH);
return (Math.imul(aH, bH) + (Math.imul(aH, bL) >>> 16) + (carry >>> 16)) | 0;
}
// Temporary block buffer.

@@ -77,5 +49,7 @@ // 1024-byte block: 256 u32 = 128 interleaved low/high halves = RFC's

// Quarter-round over 64-bit word indices into `A2_BUF`; each index maps to adjacent low/high u32s.
// Each BlaMka step `X = X + Y + 2 * trunc(X) * trunc(Y)` (trunc = low 32 bits) is three lines:
// `Math.imul` is the low product half, `mulHi` the high half, then a split 64-bit add with the
// doubling folded in. RFC 9106 Figure 19 GB rotates by 32, 24, 16, and 63 bits after each XOR.
// Each BlaMka step `X = X + Y + 2 * trunc(X) * trunc(Y)` (trunc = low 32 bits) starts
// with an exact low product from `Math.imul`. The rounded double product is within 1024 of the
// exact u64 product; subtracting that exact low half and rounding to the nearest multiple of
// 2^32 therefore recovers the exact high half. RFC 9106 Figure 19 GB then rotates by 32, 24,
// 16, and 63 bits after each XOR.
function G(a: number, b: number, c: number, d: number) {

@@ -90,3 +64,3 @@ let Al = A2_BUF[2*a], Ah = A2_BUF[2*a + 1]; // prettier-ignore

ml = Math.imul(Al, Bl);
mh = mulHi(Al, Bl); // prettier-ignore
mh = (((Al >>> 0) * (Bl >>> 0) - (ml >>> 0)) / 0x100000000 + 0.5) | 0; // prettier-ignore
rl = (Al >>> 0) + (Bl >>> 0) + ((ml << 1) >>> 0);

@@ -97,8 +71,8 @@ Ah = (Ah + Bh + ((mh << 1) | (ml >>> 31)) + ((rl / 0x100000000) | 0)) | 0;

xl = Dl ^ Al; // prettier-ignore
Dh = rotr32H(xh, xl);
Dl = rotr32L(xh, xl); // prettier-ignore
Dh = xl;
Dl = xh; // prettier-ignore
// C = blamka(C, D); B = rotr64(B ^ C, 24)
ml = Math.imul(Cl, Dl);
mh = mulHi(Cl, Dl); // prettier-ignore
mh = (((Cl >>> 0) * (Dl >>> 0) - (ml >>> 0)) / 0x100000000 + 0.5) | 0; // prettier-ignore
rl = (Cl >>> 0) + (Dl >>> 0) + ((ml << 1) >>> 0);

@@ -109,8 +83,8 @@ Ch = (Ch + Dh + ((mh << 1) | (ml >>> 31)) + ((rl / 0x100000000) | 0)) | 0;

xl = Bl ^ Cl; // prettier-ignore
Bh = rotrSH(xh, xl, 24);
Bl = rotrSL(xh, xl, 24); // prettier-ignore
Bh = (xh >>> 24) | (xl << 8);
Bl = (xh << 8) | (xl >>> 24); // prettier-ignore
// A = blamka(A, B); D = rotr64(D ^ A, 16)
ml = Math.imul(Al, Bl);
mh = mulHi(Al, Bl); // prettier-ignore
mh = (((Al >>> 0) * (Bl >>> 0) - (ml >>> 0)) / 0x100000000 + 0.5) | 0; // prettier-ignore
rl = (Al >>> 0) + (Bl >>> 0) + ((ml << 1) >>> 0);

@@ -121,8 +95,8 @@ Ah = (Ah + Bh + ((mh << 1) | (ml >>> 31)) + ((rl / 0x100000000) | 0)) | 0;

xl = Dl ^ Al; // prettier-ignore
Dh = rotrSH(xh, xl, 16);
Dl = rotrSL(xh, xl, 16); // prettier-ignore
Dh = (xh >>> 16) | (xl << 16);
Dl = (xh << 16) | (xl >>> 16); // prettier-ignore
// C = blamka(C, D); B = rotr64(B ^ C, 63)
ml = Math.imul(Cl, Dl);
mh = mulHi(Cl, Dl); // prettier-ignore
mh = (((Cl >>> 0) * (Dl >>> 0) - (ml >>> 0)) / 0x100000000 + 0.5) | 0; // prettier-ignore
rl = (Cl >>> 0) + (Dl >>> 0) + ((ml << 1) >>> 0);

@@ -133,4 +107,4 @@ Ch = (Ch + Dh + ((mh << 1) | (ml >>> 31)) + ((rl / 0x100000000) | 0)) | 0;

xl = Bl ^ Cl; // prettier-ignore
Bh = rotrBH(xh, xl, 63);
Bl = rotrBL(xh, xl, 63); // prettier-ignore
Bh = (xh << 1) | (xl >>> 31);
Bl = (xh >>> 31) | (xl << 1); // prettier-ignore

@@ -163,3 +137,17 @@ ((A2_BUF[2 * a] = Al), (A2_BUF[2 * a + 1] = Ah));

function block(x: TArg<Uint32Array>, xPos: number, yPos: number, outPos: number, needXor: boolean) {
for (let i = 0; i < 256; i++) A2_BUF[i] = x[xPos + i] ^ x[yPos + i];
// Stage R = X xor Y in the destination before permuting it. This avoids rereading both source
// blocks when folding R into the permuted scratch block below.
if (needXor) {
for (let i = 0; i < 256; i++) {
const r = x[xPos + i] ^ x[yPos + i];
A2_BUF[i] = r;
x[outPos + i] ^= r;
}
} else {
for (let i = 0; i < 256; i++) {
const r = x[xPos + i] ^ x[yPos + i];
A2_BUF[i] = r;
x[outPos + i] = r;
}
}
// rows (8 consecutive 16-register groups)

@@ -183,4 +171,3 @@ for (let i = 0; i < 128; i += 16) {

// RFC 9106 step 6: passes after the first XOR the old destination block into the new G(X, Y).
if (needXor) for (let i = 0; i < 256; i++) x[outPos + i] ^= A2_BUF[i] ^ x[xPos + i] ^ x[yPos + i];
else for (let i = 0; i < 256; i++) x[outPos + i] = A2_BUF[i] ^ x[xPos + i] ^ x[yPos + i];
for (let i = 0; i < 256; i++) x[outPos + i] ^= A2_BUF[i];
clean(A2_BUF);

@@ -239,5 +226,8 @@ }

const startPos = r !== 0 && s !== ARGON2_SYNC_POINTS - 1 ? (s + 1) * segmentLen : 0;
// RFC 9106 Figure 13: `mul(randL, randL).h` is `floor(J_1^2 / 2^32)`, and the outer high-half
// multiply computes `floor(|W| * x / 2^32)` without floating-point math.
const rel = area - 1 - mul(area, mul(randL, randL).h).h;
// Use the same exact-high recovery as G. `areaHigh` is floor(|W| * J1^2/2^32 / 2^32).
const randLow = Math.imul(randL, randL);
const randHigh = (((randL >>> 0) * (randL >>> 0) - (randLow >>> 0)) / 0x100000000 + 0.5) | 0;
const areaLow = Math.imul(area, randHigh);
const areaHigh = (((area >>> 0) * (randHigh >>> 0) - (areaLow >>> 0)) / 0x100000000 + 0.5) | 0;
const rel = area - 1 - areaHigh;
return (startPos + rel) % laneLen;

@@ -248,8 +238,8 @@ }

export type ArgonOpts = {
/** Time cost measured in iterations. */
t: number;
/** Memory cost in kibibytes. */
m: number;
/** Parallelization parameter. */
p: number;
/** Time cost measured in iterations. Defaults to `3`. */
t?: number;
/** Memory cost in kibibytes. Defaults to `1024 ** 2` (1 GiB). */
m?: number;
/** Parallelization parameter. Defaults to `1`. */
p?: number;
/** Argon2 version number. Defaults to `0x13`. */

@@ -265,3 +255,3 @@ version?: number;

asyncTick?: number;
/** Maximum temporary memory budget in bytes. */
/** Maximum temporary memory budget in bytes. Defaults to 1 GiB. */
maxmem?: number;

@@ -277,2 +267,4 @@ /**

const maxUint32 = Math.pow(2, 32);
const ARGON2_DEFAULT_MEMORY = 1024 ** 2; // KiB: 1 GiB
const ARGON2_DEFAULT_MAXMEM = ARGON2_DEFAULT_MEMORY * 1024;
// Validate safe JS integers in `[0, 2^32 - 1]`.

@@ -283,8 +275,11 @@ function isU32(num: number) {

function argon2Opts(opts: TArg<ArgonOpts>) {
function argon2Opts(opts: TArg<ArgonOpts> = {}) {
opts = checkOpts({}, opts);
const merged: any = {
t: 3,
m: ARGON2_DEFAULT_MEMORY,
p: 1,
version: 0x13,
dkLen: 32,
maxmem: maxUint32 - 1,
maxmem: ARGON2_DEFAULT_MAXMEM,
asyncTick: 10,

@@ -317,3 +312,3 @@ };

function argon2Init(
function argon2InitialHash(
password: TArg<KDFInput>,

@@ -324,77 +319,116 @@ salt: TArg<KDFInput>,

) {
password = kdfInputToBytes(password, 'password');
salt = kdfInputToBytes(salt, 'salt');
if (!isU32(password.length)) throw new Error('"password" must be less of length 1..4Gb');
// RFC 9106 §3.1 only requires S <= 2^32-1 bytes and says 16 bytes is RECOMMENDED for password
// hashing; this library intentionally takes the stricter common >=8-byte salt path.
if (!isU32(salt.length) || salt.length < 8) throw new Error('"salt" must be of length 8..4Gb');
if (!Object.values(AT).includes(type)) throw new Error('"type" was invalid');
let { p, dkLen, m, t, version, key, personalization, maxmem, onProgress, asyncTick } =
argon2Opts(opts);
// Validation
key = abytesOrZero(key, 'key');
personalization = abytesOrZero(personalization, 'personalization');
// H_0 = H^(64)(LE32(p) || LE32(T) || LE32(m) || LE32(t) ||
// LE32(v) || LE32(y) || LE32(length(P)) || P ||
// LE32(length(S)) || S || LE32(length(K)) || K ||
// LE32(length(X)) || X)
const h = blake2b.create();
const ownedInputs: Uint8Array[] = [];
const BUF = new Uint32Array(1);
const BUF8 = u8(BUF);
for (let item of [p, dkLen, m, t, version, type]) {
// RFC 9106 H0 encodes these scalars as LE32, so normalize the host word before exposing bytes.
BUF[0] = swap8IfBE(item);
h.update(BUF8);
let h: ReturnType<typeof blake2b.create> | undefined;
let H0: Uint32Array | undefined;
let succeeded = false;
const rememberOwned = (input: unknown, bytes: TArg<TRet<Uint8Array>>): TRet<Uint8Array> => {
if (typeof input === 'string') ownedInputs.push(bytes);
return bytes as TRet<Uint8Array>;
};
try {
const passwordBytes = rememberOwned(password, kdfInputToBytes(password, 'password'));
const saltBytes = rememberOwned(salt, kdfInputToBytes(salt, 'salt'));
if (!isU32(passwordBytes.length)) throw new Error('"password" must be less of length 1..4Gb');
// RFC 9106 §3.1 only requires S <= 2^32-1 bytes and says 16 bytes is RECOMMENDED for password
// hashing; this library intentionally takes the stricter common >=8-byte salt path.
if (!isU32(saltBytes.length) || saltBytes.length < 8)
throw new Error('"salt" must be of length 8..4Gb');
if (!Object.values(AT).includes(type)) throw new Error('"type" was invalid');
let { p, dkLen, m, t, version, key, personalization, maxmem, onProgress, asyncTick } =
argon2Opts(opts);
// Validation
const keyInput = key;
key = rememberOwned(keyInput, abytesOrZero(keyInput, 'key'));
const personalizationInput = personalization;
personalization = rememberOwned(
personalizationInput,
abytesOrZero(personalizationInput, 'personalization')
);
// H_0 = H^(64)(LE32(p) || LE32(T) || LE32(m) || LE32(t) ||
// LE32(v) || LE32(y) || LE32(length(P)) || P ||
// LE32(length(S)) || S || LE32(length(K)) || K ||
// LE32(length(X)) || X)
h = blake2b.create();
for (let item of [p, dkLen, m, t, version, type]) {
// RFC 9106 H0 encodes these scalars as LE32, so normalize the host word before
// exposing bytes.
BUF[0] = swap8IfBE(item);
h.update(BUF8);
}
for (let i of [passwordBytes, saltBytes, key, personalization]) {
BUF[0] = swap8IfBE(i.length); // BUF is u32 array, this is valid once normalized to LE bytes
h.update(BUF8).update(i);
}
// Reserve two extra LE32 words after the 64-byte `H_0` so Figures 3-4 can append
// `LE32(0 or 1) || LE32(i)` in place for the lane-starting blocks.
H0 = new Uint32Array(18);
h.digestInto(u8(H0));
succeeded = true;
return { H0, p, dkLen, m, t, version, maxmem, onProgress, asyncTick };
} finally {
// digestInto() does not destroy BLAKE2 state. It and all string-derived inputs contain secrets.
if (h) h.destroy();
clean(BUF, ...ownedInputs);
if (!succeeded && H0) clean(H0);
}
for (let i of [password, salt, key, personalization]) {
BUF[0] = swap8IfBE(i.length); // BUF is u32 array, this is valid once normalized to LE bytes
h.update(BUF8).update(i);
}
// Reserve two extra LE32 words after the 64-byte `H_0` so Figures 3-4 can append
// `LE32(0 or 1) || LE32(i)` in place for the lane-starting blocks.
const H0 = new Uint32Array(18);
const H0_8 = u8(H0);
h.digestInto(H0_8);
}
function argon2Init(
password: TArg<KDFInput>,
salt: TArg<KDFInput>,
type: Types,
opts: TArg<ArgonOpts>
) {
const { H0, p, dkLen, m, t, version, maxmem, onProgress, asyncTick } = argon2InitialHash(
password,
salt,
type,
opts
);
// 256 u32 = 1024 (BLOCK_SIZE), fills A2_BUF on processing
// Params
const lanes = p;
// m' = 4 * p * floor (m / 4p)
const mP = 4 * p * Math.floor(m / (ARGON2_SYNC_POINTS * p));
//q = m' / p columns
const laneLen = Math.floor(mP / p);
const segmentLen = Math.floor(laneLen / ARGON2_SYNC_POINTS);
// `maxmem` is documented in bytes; compare against the actual 1024-byte block allocation.
const memUsed = mP * 1024;
if (!isU32(maxmem)) throw new Error('"maxmem" expected <2**32, got ' + maxmem);
if (memUsed > maxmem)
throw new Error('"maxmem" limit was hit: memUsed(mP*1024)=' + memUsed + ', maxmem=' + maxmem);
const B = new Uint32Array(memUsed / 4);
// Fill first blocks
for (let l = 0; l < p; l++) {
const i = 256 * laneLen * l;
// B[i][0] = H'^(1024)(H_0 || LE32(0) || LE32(i))
H0[17] = swap8IfBE(l);
H0[16] = swap8IfBE(0);
B.set(swap32IfBE(u32(Hp(H0, 1024))), i);
// B[i][1] = H'^(1024)(H_0 || LE32(1) || LE32(i))
H0[16] = swap8IfBE(1);
B.set(swap32IfBE(u32(Hp(H0, 1024))), i + 256);
try {
// Params
const lanes = p;
// m' = 4 * p * floor (m / 4p)
const mP = 4 * p * Math.floor(m / (ARGON2_SYNC_POINTS * p));
//q = m' / p columns
const laneLen = Math.floor(mP / p);
const segmentLen = Math.floor(laneLen / ARGON2_SYNC_POINTS);
// `maxmem` is documented in bytes; compare against the actual 1024-byte block allocation.
const memUsed = mP * 1024;
if (!isU32(maxmem)) throw new Error('"maxmem" expected <2**32, got ' + maxmem);
if (memUsed > maxmem)
throw new Error('"maxmem" limit was hit: memUsed(mP*1024)=' + memUsed + ', maxmem=' + maxmem);
const B = new Uint32Array(memUsed / 4);
// Fill first blocks
for (let l = 0; l < p; l++) {
const i = 256 * laneLen * l;
// B[i][0] = H'^(1024)(H_0 || LE32(0) || LE32(i))
H0[17] = swap8IfBE(l);
H0[16] = swap8IfBE(0);
B.set(swap32IfBE(u32(Hp(H0, 1024))), i);
// B[i][1] = H'^(1024)(H_0 || LE32(1) || LE32(i))
H0[16] = swap8IfBE(1);
B.set(swap32IfBE(u32(Hp(H0, 1024))), i + 256);
}
let perBlock = () => {};
if (onProgress) {
// The first segment of the first pass skips two preinitialized blocks per lane.
const totalBlock = t * ARGON2_SYNC_POINTS * p * segmentLen - 2 * p;
// Invoke callback if progress changes from 10.01 to 10.02
// Allows to draw smooth progress bar on up to 8K screen
const callbackPer = Math.max(Math.floor(totalBlock / 10000), 1);
let blockCnt = 0;
perBlock = () => {
blockCnt++;
if (onProgress && (!(blockCnt % callbackPer) || blockCnt === totalBlock))
onProgress(blockCnt / totalBlock);
};
}
return { type, mP, p, t, version, B, laneLen, lanes, segmentLen, dkLen, perBlock, asyncTick };
} finally {
clean(H0);
}
let perBlock = () => {};
if (onProgress) {
// The first segment of the first pass skips two preinitialized blocks per lane.
const totalBlock = t * ARGON2_SYNC_POINTS * p * segmentLen - 2 * p;
// Invoke callback if progress changes from 10.01 to 10.02
// Allows to draw smooth progress bar on up to 8K screen
const callbackPer = Math.max(Math.floor(totalBlock / 10000), 1);
let blockCnt = 0;
perBlock = () => {
blockCnt++;
if (onProgress && (!(blockCnt % callbackPer) || blockCnt === totalBlock))
onProgress(blockCnt / totalBlock);
};
}
clean(BUF, H0);
return { type, mP, p, t, version, B, laneLen, lanes, segmentLen, dkLen, perBlock, asyncTick };
}

@@ -425,6 +459,8 @@

*/
function* argon2Blocks(ctx: ReturnType<typeof argon2Init>): Generator<void, void> {
function* argon2Blocks(
ctx: ReturnType<typeof argon2Init>,
address: TArg<Uint32Array>
): Generator<void, void> {
const { type, mP, p, t, version, B, laneLen, lanes, segmentLen, perBlock } = ctx;
// [address, input, zero_block] format so we can pass single U32 to block function
const address = new Uint32Array(3 * 256);
address[256 + 6] = mP;

@@ -501,3 +537,3 @@ address[256 + 8] = t;

const ctx = argon2Init(password, salt, type, opts);
const blocks = argon2Blocks(ctx);
const blocks = argon2Blocks(ctx, new Uint32Array(3 * 256));
while (!blocks.next().done) {}

@@ -542,3 +578,3 @@ return argon2Output(ctx.B, ctx.p, ctx.laneLen, ctx.dkLen);

salt: TArg<KDFInput>,
opts: TArg<ArgonOpts>
opts: TArg<ArgonOpts> = {}
): TRet<Uint8Array> => argon2(AT.Argon2d, password, salt, opts);

@@ -561,3 +597,3 @@ /**

salt: TArg<KDFInput>,
opts: TArg<ArgonOpts>
opts: TArg<ArgonOpts> = {}
): TRet<Uint8Array> => argon2(AT.Argon2i, password, salt, opts);

@@ -580,3 +616,3 @@ /**

salt: TArg<KDFInput>,
opts: TArg<ArgonOpts>
opts: TArg<ArgonOpts> = {}
): TRet<Uint8Array> => argon2(AT.Argon2id, password, salt, opts);

@@ -591,3 +627,6 @@

const ctx = argon2Init(password, salt, type, opts);
const blocks = argon2Blocks(ctx);
// Keep the generator-local address block reachable so an aborted scheduler yield can wipe it.
const address = new Uint32Array(3 * 256);
const blocks = argon2Blocks(ctx, address);
const abort = () => clean(address, ctx.B);
let ts = Date.now();

@@ -599,4 +638,5 @@ while (!blocks.next().done) {

if (diff >= 0 && diff < ctx.asyncTick) continue;
await nextTick();
ts += diff;
await nextTick(abort);
// Scheduler delay is outside the synchronous work budget.
ts = Date.now();
}

@@ -640,3 +680,3 @@ return argon2Output(ctx.B, ctx.p, ctx.laneLen, ctx.dkLen);

salt: TArg<KDFInput>,
opts: TArg<ArgonOpts>
opts: TArg<ArgonOpts> = {}
): Promise<TRet<Uint8Array>> => argon2Async(AT.Argon2d, password, salt, opts);

@@ -659,3 +699,3 @@ /**

salt: TArg<KDFInput>,
opts: TArg<ArgonOpts>
opts: TArg<ArgonOpts> = {}
): Promise<TRet<Uint8Array>> => argon2Async(AT.Argon2i, password, salt, opts);

@@ -678,3 +718,3 @@ /**

salt: TArg<KDFInput>,
opts: TArg<ArgonOpts>
opts: TArg<ArgonOpts> = {}
): Promise<TRet<Uint8Array>> => argon2Async(AT.Argon2id, password, salt, opts);

@@ -55,4 +55,2 @@ /**

// Base destroy logic only clears salt-derived state; the partial message buffer and length/position
// bookkeeping remain until the instance or backing buffer is reused.
abstract class BLAKE1<T extends BLAKE1<T>> implements Hash<T> {

@@ -122,3 +120,3 @@ readonly canXOF = false;

let dataView;
for (let pos = 0; pos < len; ) {
for (let pos = 0; pos < len;) {
const take = Math.min(blockLen - this.pos, len - pos);

@@ -150,2 +148,5 @@ // Fast path only when there is no buffered partial block: `take === blockLen` implies

this.destroyed = true;
clean(this.buffer);
this.length = 0;
this.pos = 0;
if (this.salt !== EMPTY_SALT) {

@@ -152,0 +153,0 @@ clean(this.salt, this.constants);

@@ -174,3 +174,3 @@ /**

const buf = data.buffer;
for (let pos = 0; pos < len; ) {
for (let pos = 0; pos < len;) {
// If buffer is full and we still have input (don't process last block, same as blake2s)

@@ -177,0 +177,0 @@ if (this.pos === blockLen) {

@@ -170,3 +170,9 @@ /**

// 4 (100) - leafs finished at depth=1 and depth=2
for (let last, chunks = this.chunksDone + 1; isLast || !(chunks & 1); chunks >>= 1) {
// Reading the low bit is safe for parity, but keep division full-width: shifting would
// truncate the safe-integer chunk counter to 32 bits.
for (
let last, chunks = this.chunksDone + 1;
isLast || !(chunks & 1);
chunks = Math.floor(chunks / 2)
) {
if (!(last = this.stack.pop())) break;

@@ -286,3 +292,3 @@ this.buffer32.set(last, 0);

const { blockLen, bufferOut } = this;
for (let pos = 0, len = out.length; pos < len; ) {
for (let pos = 0, len = out.length; pos < len;) {
if (this.posOut >= blockLen) this.b2CompressOut();

@@ -289,0 +295,0 @@ const take = Math.min(blockLen - this.posOut, len - pos);

@@ -51,10 +51,19 @@ /**

const p = kdfInputToBytes(_password, 'password');
const s = kdfInputToBytes(_salt, 'salt');
// DK = PBKDF2(PRF, Password, Salt, c, dkLen);
const DK = new Uint8Array(dkLen);
const { iHash, oHash, outputLen } = hmac.create(hash, p);
// Drive keyed hashes directly; the wrapper is only needed to initialize their HMAC midstates.
const u = new Uint8Array(outputLen);
const eng = pbkdf2Engine(iHash, oHash, s, u);
return { c, dkLen, asyncTick, DK, outputLen, eng };
try {
const s = kdfInputToBytes(_salt, 'salt');
try {
// DK = PBKDF2(PRF, Password, Salt, c, dkLen);
const DK = new Uint8Array(dkLen);
const { iHash, oHash, outputLen } = hmac.create(hash, p);
// Drive keyed hashes directly; the wrapper is only needed to initialize their HMAC midstates.
const u = new Uint8Array(outputLen);
const eng = pbkdf2Engine(iHash, oHash, s, u);
return { c, dkLen, asyncTick, DK, outputLen, eng };
} finally {
// Uint8Array inputs belong to the caller; only wipe our UTF-8 conversion.
if (typeof _salt === 'string') clean(s);
}
} finally {
if (typeof _password === 'string') clean(p);
}
}

@@ -186,2 +195,7 @@

const { c, dkLen, asyncTick, DK, outputLen, eng } = pbkdf2Init(hash, password, salt, opts);
// Reuse normal state destruction, then wipe the incomplete output if a host yield aborts.
const abort = () => {
eng.output(DK);
clean(DK);
};
// DK = T1 + T2 + ⋯ + Tdklen/hlen

@@ -196,8 +210,13 @@ for (let ti = 1, pos = 0; pos < dkLen; ti++, pos += outputLen) {

eng.u1(ti, Ti);
await asyncLoop(c - 1, asyncTick, () => {
// Uc = PRF(Password, Uc−1)
eng.rounds(2, Ti); // c=2 runs exactly one PRF iteration per callback.
});
await asyncLoop(
c - 1,
asyncTick,
() => {
// Uc = PRF(Password, Uc−1)
eng.rounds(2, Ti); // c=2 runs exactly one PRF iteration per callback.
},
abort
);
}
return eng.output(DK);
}

@@ -99,2 +99,5 @@ /**

// 128*r*(N+p+1) for N=2**20, r=8, p=1: 1 GiB main table plus 2 KiB workspace.
const SCRYPT_DEFAULT_MAXMEM = 128 * 8 * (2 ** 20 + 1 + 1);
/**

@@ -107,3 +110,4 @@ * Scrypt options:

* - `asyncTick` - (default: 10) max time in ms for which async function can block execution
* - `maxmem` - (default: `1024 ** 3 + 1024` aka 1GB+1KB). A limit that the app could use for scrypt
* - `maxmem` - (default: `1024 ** 3 + 2 * 1024` aka 1GiB+2KiB). A limit that the app
* could use for scrypt
* - `onProgress` - callback function that would be executed for progress report

@@ -135,3 +139,2 @@ */

function scryptInit(password: TArg<KDFInput>, salt: TArg<KDFInput>, _opts?: ScryptOpts) {
// Maxmem - 1GB+1KB by default
const opts = checkOpts(

@@ -141,3 +144,3 @@ {

asyncTick: 10,
maxmem: 1024 ** 3 + 1024,
maxmem: SCRYPT_DEFAULT_MAXMEM,
},

@@ -337,2 +340,4 @@ _opts

);
// One failure handler covers both yield boundaries without putting the hot loops in a try block.
const abort = () => clean(B, V, tmp);
swap32IfBE(B32);

@@ -343,21 +348,31 @@ for (let pi = 0; pi < p; pi++) {

let pos = 0;
await asyncLoop(N - 1, asyncTick, () => {
BlockMix(V, pos, V, (pos += blockSize32), r); // V[i] = BlockMix(V[i-1]);
blockMixCb();
});
await asyncLoop(
N - 1,
asyncTick,
() => {
BlockMix(V, pos, V, (pos += blockSize32), r); // V[i] = BlockMix(V[i-1]);
blockMixCb();
},
abort
);
BlockMix(V, (N - 1) * blockSize32, B32, Pi, r); // Process last element
blockMixCb();
await asyncLoop(N, asyncTick, () => {
// First u32 of the last 64-byte block (u32 is LE)
// RFC 7914 Integerify(X) uses the whole last 64-byte block, but mod N
// only depends on the low word here because N is a power of two and
// this implementation caps N at 2^32.
// & (N - 1) is % N as N is a power of 2, N & (N - 1) = 0 is checked
// above; >>> 0 for unsigned, input fits in u32.
const j = (B32[Pi + blockSize32 - 16] & (N - 1)) >>> 0; // j = Integrify(X) % iterations
// tmp = B ^ V[j]
for (let k = 0; k < blockSize32; k++) tmp[k] = B32[Pi + k] ^ V[j * blockSize32 + k];
BlockMix(tmp, 0, B32, Pi, r); // B = BlockMix(B ^ V[j])
blockMixCb();
});
await asyncLoop(
N,
asyncTick,
() => {
// First u32 of the last 64-byte block (u32 is LE)
// RFC 7914 Integerify(X) uses the whole last 64-byte block, but mod N
// only depends on the low word here because N is a power of two and
// this implementation caps N at 2^32.
// & (N - 1) is % N as N is a power of 2, N & (N - 1) = 0 is checked
// above; >>> 0 for unsigned, input fits in u32.
const j = (B32[Pi + blockSize32 - 16] & (N - 1)) >>> 0; // j = Integrify(X) % iterations
// tmp = B ^ V[j]
for (let k = 0; k < blockSize32; k++) tmp[k] = B32[Pi + k] ^ V[j * blockSize32 + k];
BlockMix(tmp, 0, B32, Pi, r); // B = BlockMix(B ^ V[j])
blockMixCb();
},
abort
);
}

@@ -364,0 +379,0 @@ swap32IfBE(B32);

@@ -23,2 +23,3 @@ /**

createHasher,
randomBytes,
type Hash,

@@ -526,3 +527,3 @@ type HashXOF,

const { chunkLen, leafCons } = this;
for (let pos = 0, len = data.length; pos < len; ) {
for (let pos = 0, len = data.length; pos < len;) {
if (this.chunkPos == chunkLen || !this.leafHash) {

@@ -838,3 +839,3 @@ if (this.leafHash) this.flushLeaf();

const { chunkLen, blockLen, leafLen, rounds } = this;
for (let pos = 0, len = data.length; pos < len; ) {
for (let pos = 0, len = data.length; pos < len;) {
if (this.chunkPos == chunkLen) {

@@ -1058,2 +1059,3 @@ if (this.leafHash) this.flushLeaf();

protected rate: number;
private entropyAdded = false;
constructor(capacity: number) {

@@ -1087,5 +1089,24 @@ anumber(capacity);

}
addEntropy(seed: TArg<Uint8Array>): void {
this.update(seed);
addEntropy(seed?: TArg<Uint8Array>): void {
// Check lifecycle before asking the system RNG for entropy that cannot be used.
aexists(this);
if (seed !== undefined) {
abytes(seed, undefined, 'seed');
if (seed.length === 0) throw new Error('"seed" must not be empty');
}
const generated = seed === undefined;
const entropy = generated ? randomBytes() : seed;
try {
this.update(entropy);
this.entropyAdded = true;
} finally {
// This temporary is library-owned; caller-provided entropy remains caller-owned.
if (generated) clean(entropy);
}
}
xofInto(out: TArg<Uint8Array>): TRet<Uint8Array> {
aexists(this, false);
if (!this.entropyAdded) throw new Error('addEntropy() must be called before randomBytes()');
return super.xofInto(out);
}
randomBytes(length: number): TRet<Uint8Array> {

@@ -1111,2 +1132,3 @@ return this.xof(length);

to.rate = rate;
to.entropyAdded = this.entropyAdded;
return to;

@@ -1122,2 +1144,5 @@ }

* See {@link https://keccak.team/files/CSF-0.1.pdf}.
* Fresh instances reject output until `.addEntropy()` has been called. With no
* argument, `addEntropy()` obtains 32 bytes from the platform CSPRNG; callers
* may instead supply their own non-empty entropy bytes.
* @param capacity - sponge capacity in bits. Accepted values are those that

@@ -1131,3 +1156,6 @@ * keep `rho = 1598 - capacity` byte-aligned; the default `254` is chosen

* ```ts
* import { keccakprg } from '@noble/hashes/sha3-addons.js';
*
* const prg = keccakprg(254);
* prg.addEntropy();
* prg.randomBytes(8);

@@ -1134,0 +1162,0 @@ * ```

@@ -235,3 +235,3 @@ /**

const data32 = canUseU32 && len >= blockLen ? u32(data) : undefined;
for (let pos = 0; pos < len; ) {
for (let pos = 0; pos < len;) {
if (data32 !== undefined && this.pos === 0 && pos % 4 === 0 && len - pos >= blockLen) {

@@ -273,3 +273,3 @@ for (let i = 0, o = pos / 4; i < blockLen32; i++) state32[i] ^= data32[o + i];

const { blockLen } = this;
for (let pos = 0, len = out.length; pos < len; ) {
for (let pos = 0, len = out.length; pos < len;) {
if (this.posOut >= blockLen) this.keccak();

@@ -276,0 +276,0 @@ const take = Math.min(blockLen - this.posOut, len - pos);

@@ -260,2 +260,12 @@ /**

const aopts = (value: Record<string, any>, label: string) => {
aobject(value, label);
const proto = Object.getPrototypeOf(value);
if (proto !== Object.prototype && proto !== null)
throw new TypeError(`"${label}" expected plain object`);
// Object.assign() treats an own "__proto__" source key as a write to the target's legacy
// prototype setter. Reject it before merging so inherited option values cannot be injected.
if (Object.hasOwn(value, '__proto__')) throw new TypeError(`"${label}.__proto__" is not allowed`);
};
/**

@@ -560,5 +570,8 @@ * Asserts a hash instance has not been destroyed or finished.

/**
* There is no setImmediate in browser and setTimeout is slow.
* This yields to the Promise/microtask scheduler queue, not to timers or the
* full macrotask event loop.
* Yields to the host task scheduler so timers, I/O, and rendering can make progress.
* Uses the Web Scheduling API when available or `setTimeout` as a cross-platform fallback.
* Host-task yields are much slower than microtasks (roughly 1ms with the timer fallback), so
* async loops should use `asyncTick >= 10` to amortize scheduling overhead to about 10% while
* still allowing other event-loop work to proceed.
* @param onReject - optional cleanup invoked only if the host yield fails
* @example

@@ -570,10 +583,19 @@ * Yield to the next scheduler tick.

*/
export const nextTick = async (): Promise<void> => {};
export function nextTick(onReject?: () => void): Promise<void> {
const host = globalThis as any;
if (typeof host.scheduler?.yield === 'function') {
const promise: Promise<void> = host.scheduler.yield();
// Keep the original scheduler rejection; this handler exists only for cleanup.
if (onReject) promise.catch(onReject);
return promise;
}
return new Promise((resolve) => host.setTimeout(resolve, 0));
}
/**
* Returns control to the Promise/microtask scheduler every `tick`
* milliseconds to avoid blocking long loops.
* Returns control to the host event loop every `tick` milliseconds to avoid blocking long loops.
* @param iters - number of loop iterations to run
* @param tick - maximum time slice in milliseconds
* @param cb - callback executed on each iteration
* @param onReject - optional cleanup invoked only if a host yield fails
* @throws On wrong argument types. {@link TypeError}

@@ -590,3 +612,4 @@ * @throws On wrong argument ranges or values. {@link RangeError}

tick: number,
cb: (i: number) => void
cb: (i: number) => void,
onReject?: () => void
): Promise<void> {

@@ -603,5 +626,5 @@ anumber(iters, 'iters');

if (diff >= 0 && diff < tick) continue;
await nextTick();
await nextTick(onReject);
// Track only synchronous work time; scheduler delay after yielding is outside our budget.
ts += diff;
ts = Date.now();
}

@@ -628,3 +651,10 @@ }

if (typeof str !== 'string') throw new TypeError('string expected');
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
const encoded = new TextEncoder().encode(str);
try {
// Copy into the current realm for Firefox extension contexts. Callers that own the returned
// buffer can then wipe it independently of TextEncoder's temporary result.
return new Uint8Array(encoded) as TRet<Uint8Array>; // https://bugzil.la/1681809
} finally {
clean(encoded);
}
}

@@ -735,3 +765,3 @@

* @param title - label included in thrown override errors
* @returns Merged option object. The merge mutates `defaults` in place.
* @returns Fresh merged option object with a null prototype.
* @throws On wrong argument types. {@link TypeError}

@@ -749,5 +779,7 @@ * @example

): T1 & T2 {
aobject(defaults as Record<string, any>, 'defaults');
if (opts !== undefined) aobject(opts as Record<string, any>, title);
const merged = Object.assign(defaults, opts);
aopts(defaults as Record<string, any>, 'defaults');
if (opts !== undefined) aopts(opts as Record<string, any>, title);
// Callers read optional fields directly, so omitted values must not fall through to ambient
// Object.prototype pollution (for example a forged `dkLen` changing SHAKE's default output).
const merged = Object.assign(Object.create(null), defaults, opts);
return merged as T1 & T2;

@@ -805,5 +837,6 @@ }

* Mixes fresh entropy into the current generator state.
* @param seed - Entropy bytes to absorb.
* @param seed - Non-empty entropy bytes to absorb. When omitted, the implementation uses its
* system RNG.
*/
addEntropy(seed: TArg<Uint8Array>): void;
addEntropy(seed?: TArg<Uint8Array>): void;
/**

@@ -810,0 +843,0 @@ * Produces a requested number of pseudorandom bytes.

@@ -7,2 +7,4 @@ import { type Pbkdf2Opt } from './pbkdf2.ts';

checkOpts,
clean,
copyBytes,
kdfInputToBytes,

@@ -57,5 +59,7 @@ type CHash,

function ahashWeb(hash: TArg<WebHash>) {
function ahashWeb(hash: TArg<WebHash>): string {
ahash(hash as unknown as TArg<CHash>);
if (typeof hash.webCryptoName !== 'string') throw new Error('non-web hash');
const name = hash.webCryptoName;
if (typeof name !== 'string') throw new Error('non-web hash');
return name;
}

@@ -135,15 +139,22 @@

abytes(message, undefined, 'message');
ahashWeb(hash);
// WebCrypto keys can't be zeroized
// prettier-ignore
const wkey = await crypto.importKey(
'raw',
key as BufferSource,
{ name: 'HMAC', hash: hash.webCryptoName },
false,
['sign']
);
return new Uint8Array(
await crypto.sign('HMAC', wkey, message as BufferSource)
) as TRet<Uint8Array>;
const hashName = ahashWeb(hash);
// importKey() snapshots key synchronously, but message is not passed to sign() until after
// importKey() resolves. Keep the wrapper's inputs stable across that await.
const _message = copyBytes(message);
try {
// WebCrypto keys can't be zeroized
// prettier-ignore
const wkey = await crypto.importKey(
'raw',
key as BufferSource,
{ name: 'HMAC', hash: hashName },
false,
['sign']
);
return new Uint8Array(
await crypto.sign('HMAC', wkey, _message as BufferSource)
) as TRet<Uint8Array>;
} finally {
clean(_message);
}
};

@@ -166,4 +177,3 @@ hmac_.create = (_hash: TArg<WebHash>, _key: TArg<Uint8Array>) => {

* @returns Promise resolving to derived key bytes.
* The RFC `L <= 255 * HashLen` bound is currently enforced only by backend
* `deriveBits()` rejection, not by an explicit library-side guard.
* The RFC `L <= 255 * HashLen` bound is enforced before calling WebCrypto.
* @throws If the current runtime does not provide `crypto.subtle`. {@link Error}

@@ -189,15 +199,24 @@ * @example

const crypto = _subtle();
ahashWeb(hash);
const hashName = ahashWeb(hash);
const hashOutputLen = hash.outputLen;
abytes(ikm, undefined, 'ikm');
anumber(length, 'length');
if (length > 255 * hashOutputLen) throw new Error('Length must be <= 255*HashLen');
if (salt !== undefined) abytes(salt, undefined, 'salt');
if (info !== undefined) abytes(info, undefined, 'info');
const wkey = await crypto.importKey('raw', ikm as BufferSource, 'HKDF', false, ['deriveBits']);
const opts = {
name: 'HKDF',
hash: hash.webCryptoName,
salt: salt === undefined ? new Uint8Array(0) : salt,
info: info === undefined ? new Uint8Array(0) : info,
};
return new Uint8Array(await crypto.deriveBits(opts, wkey, 8 * length)) as TRet<Uint8Array>;
// salt and info reach deriveBits() only after importKey() resolves, so snapshot them now.
const _salt = salt === undefined ? new Uint8Array(0) : copyBytes(salt);
const _info = info === undefined ? new Uint8Array(0) : copyBytes(info);
try {
const wkey = await crypto.importKey('raw', ikm as BufferSource, 'HKDF', false, ['deriveBits']);
const opts = { name: 'HKDF', hash: hashName, salt: _salt, info: _info };
const out = new Uint8Array(await crypto.deriveBits(opts, wkey, 8 * length));
if (out.length !== length) {
clean(out);
throw new Error('WebCrypto returned an invalid derived key length');
}
return out as TRet<Uint8Array>;
} finally {
clean(_salt, _info);
}
}

@@ -217,3 +236,3 @@

* `deriveBits()` rejection (for example `c = 0`), not a dedicated
* library-side guard.
* library-side guard. Values above the signed 32-bit backend range are rejected locally.
* @throws If the current runtime does not provide `crypto.subtle`. {@link Error}

@@ -234,3 +253,3 @@ * @example

const crypto = _subtle();
ahashWeb(hash);
const hashName = ahashWeb(hash);
const _opts = checkOpts({ dkLen: 32 }, opts);

@@ -240,11 +259,33 @@ const { c, dkLen } = _opts;

anumber(dkLen, 'dkLen');
// Node's native WebCrypto PBKDF2 binding accepts only a signed 32-bit iteration count and aborts
// the process on larger values instead of returning a rejected promise.
if (c > 0x7fffffff) throw new Error('"c" exceeds WebCrypto backend limit');
// RFC 8018 §5.2 defines dkLen as a positive integer.
if (dkLen < 1) throw new Error('"dkLen" must be >= 1');
// SubtleCrypto.deriveBits() accepts an unsigned-long bit count. Byte lengths at or above
// 2^29 would wrap after multiplication by eight instead of requesting the intended length.
if (dkLen >= 2 ** 29) throw new Error('derived key too long');
const _password = kdfInputToBytes(password, 'password');
const _salt = kdfInputToBytes(salt, 'salt');
const key = await crypto.importKey('raw', _password as BufferSource, 'PBKDF2', false, [
'deriveBits',
]);
const deriveOpts = { name: 'PBKDF2', salt: _salt, iterations: c, hash: hash.webCryptoName };
return new Uint8Array(await crypto.deriveBits(deriveOpts, key, 8 * dkLen)) as TRet<Uint8Array>;
try {
const saltBytes = kdfInputToBytes(salt, 'salt');
// String conversion already returns an owned array. Caller-owned byte salts need a snapshot
// because deriveBits() does not receive them until after importKey() resolves.
const _salt = typeof salt === 'string' ? saltBytes : copyBytes(saltBytes);
try {
const key = await crypto.importKey('raw', _password as BufferSource, 'PBKDF2', false, [
'deriveBits',
]);
const deriveOpts = { name: 'PBKDF2', salt: _salt, iterations: c, hash: hashName };
const out = new Uint8Array(await crypto.deriveBits(deriveOpts, key, 8 * dkLen));
if (out.length !== dkLen) {
clean(out);
throw new Error('WebCrypto returned an invalid derived key length');
}
return out as TRet<Uint8Array>;
} finally {
clean(_salt);
}
} finally {
if (typeof password === 'string') clean(_password);
}
}

@@ -305,5 +305,8 @@ /**

/**
* There is no setImmediate in browser and setTimeout is slow.
* This yields to the Promise/microtask scheduler queue, not to timers or the
* full macrotask event loop.
* Yields to the host task scheduler so timers, I/O, and rendering can make progress.
* Uses the Web Scheduling API when available or `setTimeout` as a cross-platform fallback.
* Host-task yields are much slower than microtasks (roughly 1ms with the timer fallback), so
* async loops should use `asyncTick >= 10` to amortize scheduling overhead to about 10% while
* still allowing other event-loop work to proceed.
* @param onReject - optional cleanup invoked only if the host yield fails
* @example

@@ -315,9 +318,9 @@ * Yield to the next scheduler tick.

*/
export declare const nextTick: () => Promise<void>;
export declare function nextTick(onReject?: () => void): Promise<void>;
/**
* Returns control to the Promise/microtask scheduler every `tick`
* milliseconds to avoid blocking long loops.
* Returns control to the host event loop every `tick` milliseconds to avoid blocking long loops.
* @param iters - number of loop iterations to run
* @param tick - maximum time slice in milliseconds
* @param cb - callback executed on each iteration
* @param onReject - optional cleanup invoked only if a host yield fails
* @throws On wrong argument types. {@link TypeError}

@@ -331,3 +334,3 @@ * @throws On wrong argument ranges or values. {@link RangeError}

*/
export declare function asyncLoop(iters: number, tick: number, cb: (i: number) => void): Promise<void>;
export declare function asyncLoop(iters: number, tick: number, cb: (i: number) => void, onReject?: () => void): Promise<void>;
/**

@@ -397,3 +400,3 @@ * Converts string to bytes using UTF8 encoding.

* @param title - label included in thrown override errors
* @returns Merged option object. The merge mutates `defaults` in place.
* @returns Fresh merged option object with a null prototype.
* @throws On wrong argument types. {@link TypeError}

@@ -455,5 +458,6 @@ * @example

* Mixes fresh entropy into the current generator state.
* @param seed - Entropy bytes to absorb.
* @param seed - Non-empty entropy bytes to absorb. When omitted, the implementation uses its
* system RNG.
*/
addEntropy(seed: TArg<Uint8Array>): void;
addEntropy(seed?: TArg<Uint8Array>): void;
/**

@@ -460,0 +464,0 @@ * Produces a requested number of pseudorandom bytes.

+47
-14

@@ -137,2 +137,12 @@ /**

};
const aopts = (value, label) => {
aobject(value, label);
const proto = Object.getPrototypeOf(value);
if (proto !== Object.prototype && proto !== null)
throw new TypeError(`"${label}" expected plain object`);
// Object.assign() treats an own "__proto__" source key as a write to the target's legacy
// prototype setter. Reject it before merging so inherited option values cannot be injected.
if (Object.hasOwn(value, '__proto__'))
throw new TypeError(`"${label}.__proto__" is not allowed`);
};
/**

@@ -411,5 +421,8 @@ * Asserts a hash instance has not been destroyed or finished.

/**
* There is no setImmediate in browser and setTimeout is slow.
* This yields to the Promise/microtask scheduler queue, not to timers or the
* full macrotask event loop.
* Yields to the host task scheduler so timers, I/O, and rendering can make progress.
* Uses the Web Scheduling API when available or `setTimeout` as a cross-platform fallback.
* Host-task yields are much slower than microtasks (roughly 1ms with the timer fallback), so
* async loops should use `asyncTick >= 10` to amortize scheduling overhead to about 10% while
* still allowing other event-loop work to proceed.
* @param onReject - optional cleanup invoked only if the host yield fails
* @example

@@ -421,9 +434,19 @@ * Yield to the next scheduler tick.

*/
export const nextTick = async () => { };
export function nextTick(onReject) {
const host = globalThis;
if (typeof host.scheduler?.yield === 'function') {
const promise = host.scheduler.yield();
// Keep the original scheduler rejection; this handler exists only for cleanup.
if (onReject)
promise.catch(onReject);
return promise;
}
return new Promise((resolve) => host.setTimeout(resolve, 0));
}
/**
* Returns control to the Promise/microtask scheduler every `tick`
* milliseconds to avoid blocking long loops.
* Returns control to the host event loop every `tick` milliseconds to avoid blocking long loops.
* @param iters - number of loop iterations to run
* @param tick - maximum time slice in milliseconds
* @param cb - callback executed on each iteration
* @param onReject - optional cleanup invoked only if a host yield fails
* @throws On wrong argument types. {@link TypeError}

@@ -437,3 +460,3 @@ * @throws On wrong argument ranges or values. {@link RangeError}

*/
export async function asyncLoop(iters, tick, cb) {
export async function asyncLoop(iters, tick, cb, onReject) {
anumber(iters, 'iters');

@@ -451,5 +474,5 @@ anumber(tick, 'tick');

continue;
await nextTick();
await nextTick(onReject);
// Track only synchronous work time; scheduler delay after yielding is outside our budget.
ts += diff;
ts = Date.now();
}

@@ -473,3 +496,11 @@ }

throw new TypeError('string expected');
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
const encoded = new TextEncoder().encode(str);
try {
// Copy into the current realm for Firefox extension contexts. Callers that own the returned
// buffer can then wipe it independently of TextEncoder's temporary result.
return new Uint8Array(encoded); // https://bugzil.la/1681809
}
finally {
clean(encoded);
}
}

@@ -565,3 +596,3 @@ /**

* @param title - label included in thrown override errors
* @returns Merged option object. The merge mutates `defaults` in place.
* @returns Fresh merged option object with a null prototype.
* @throws On wrong argument types. {@link TypeError}

@@ -575,6 +606,8 @@ * @example

export function checkOpts(defaults, opts, title = 'opts') {
aobject(defaults, 'defaults');
aopts(defaults, 'defaults');
if (opts !== undefined)
aobject(opts, title);
const merged = Object.assign(defaults, opts);
aopts(opts, title);
// Callers read optional fields directly, so omitted values must not fall through to ambient
// Object.prototype pollution (for example a forged `dkLen` changing SHAKE's default output).
const merged = Object.assign(Object.create(null), defaults, opts);
return merged;

@@ -581,0 +614,0 @@ }

@@ -85,4 +85,3 @@ import { type Pbkdf2Opt } from './pbkdf2.ts';

* @returns Promise resolving to derived key bytes.
* The RFC `L <= 255 * HashLen` bound is currently enforced only by backend
* `deriveBits()` rejection, not by an explicit library-side guard.
* The RFC `L <= 255 * HashLen` bound is enforced before calling WebCrypto.
* @throws If the current runtime does not provide `crypto.subtle`. {@link Error}

@@ -113,3 +112,3 @@ * @example

* `deriveBits()` rejection (for example `c = 0`), not a dedicated
* library-side guard.
* library-side guard. Values above the signed 32-bit backend range are rejected locally.
* @throws If the current runtime does not provide `crypto.subtle`. {@link Error}

@@ -116,0 +115,0 @@ * @example

import {} from "./pbkdf2.js";
import { abytes, ahash, anumber, checkOpts, kdfInputToBytes, } from "./utils.js";
import { abytes, ahash, anumber, checkOpts, clean, copyBytes, kdfInputToBytes, } from "./utils.js";
function _subtle() {

@@ -30,4 +30,6 @@ const cr = typeof globalThis === 'object' ? globalThis.crypto : null;

ahash(hash);
if (typeof hash.webCryptoName !== 'string')
const name = hash.webCryptoName;
if (typeof name !== 'string')
throw new Error('non-web hash');
return name;
}

@@ -91,7 +93,15 @@ /** WebCrypto SHA1 (RFC 3174) legacy hash function. It was cryptographically broken. */

abytes(message, undefined, 'message');
ahashWeb(hash);
// WebCrypto keys can't be zeroized
// prettier-ignore
const wkey = await crypto.importKey('raw', key, { name: 'HMAC', hash: hash.webCryptoName }, false, ['sign']);
return new Uint8Array(await crypto.sign('HMAC', wkey, message));
const hashName = ahashWeb(hash);
// importKey() snapshots key synchronously, but message is not passed to sign() until after
// importKey() resolves. Keep the wrapper's inputs stable across that await.
const _message = copyBytes(message);
try {
// WebCrypto keys can't be zeroized
// prettier-ignore
const wkey = await crypto.importKey('raw', key, { name: 'HMAC', hash: hashName }, false, ['sign']);
return new Uint8Array(await crypto.sign('HMAC', wkey, _message));
}
finally {
clean(_message);
}
};

@@ -113,4 +123,3 @@ hmac_.create = (_hash, _key) => {

* @returns Promise resolving to derived key bytes.
* The RFC `L <= 255 * HashLen` bound is currently enforced only by backend
* `deriveBits()` rejection, not by an explicit library-side guard.
* The RFC `L <= 255 * HashLen` bound is enforced before calling WebCrypto.
* @throws If the current runtime does not provide `crypto.subtle`. {@link Error}

@@ -130,5 +139,8 @@ * @example

const crypto = _subtle();
ahashWeb(hash);
const hashName = ahashWeb(hash);
const hashOutputLen = hash.outputLen;
abytes(ikm, undefined, 'ikm');
anumber(length, 'length');
if (length > 255 * hashOutputLen)
throw new Error('Length must be <= 255*HashLen');
if (salt !== undefined)

@@ -138,10 +150,18 @@ abytes(salt, undefined, 'salt');

abytes(info, undefined, 'info');
const wkey = await crypto.importKey('raw', ikm, 'HKDF', false, ['deriveBits']);
const opts = {
name: 'HKDF',
hash: hash.webCryptoName,
salt: salt === undefined ? new Uint8Array(0) : salt,
info: info === undefined ? new Uint8Array(0) : info,
};
return new Uint8Array(await crypto.deriveBits(opts, wkey, 8 * length));
// salt and info reach deriveBits() only after importKey() resolves, so snapshot them now.
const _salt = salt === undefined ? new Uint8Array(0) : copyBytes(salt);
const _info = info === undefined ? new Uint8Array(0) : copyBytes(info);
try {
const wkey = await crypto.importKey('raw', ikm, 'HKDF', false, ['deriveBits']);
const opts = { name: 'HKDF', hash: hashName, salt: _salt, info: _info };
const out = new Uint8Array(await crypto.deriveBits(opts, wkey, 8 * length));
if (out.length !== length) {
clean(out);
throw new Error('WebCrypto returned an invalid derived key length');
}
return out;
}
finally {
clean(_salt, _info);
}
}

@@ -160,3 +180,3 @@ /**

* `deriveBits()` rejection (for example `c = 0`), not a dedicated
* library-side guard.
* library-side guard. Values above the signed 32-bit backend range are rejected locally.
* @throws If the current runtime does not provide `crypto.subtle`. {@link Error}

@@ -172,3 +192,3 @@ * @example

const crypto = _subtle();
ahashWeb(hash);
const hashName = ahashWeb(hash);
const _opts = checkOpts({ dkLen: 32 }, opts);

@@ -178,12 +198,39 @@ const { c, dkLen } = _opts;

anumber(dkLen, 'dkLen');
// Node's native WebCrypto PBKDF2 binding accepts only a signed 32-bit iteration count and aborts
// the process on larger values instead of returning a rejected promise.
if (c > 0x7fffffff)
throw new Error('"c" exceeds WebCrypto backend limit');
// RFC 8018 §5.2 defines dkLen as a positive integer.
if (dkLen < 1)
throw new Error('"dkLen" must be >= 1');
// SubtleCrypto.deriveBits() accepts an unsigned-long bit count. Byte lengths at or above
// 2^29 would wrap after multiplication by eight instead of requesting the intended length.
if (dkLen >= 2 ** 29)
throw new Error('derived key too long');
const _password = kdfInputToBytes(password, 'password');
const _salt = kdfInputToBytes(salt, 'salt');
const key = await crypto.importKey('raw', _password, 'PBKDF2', false, [
'deriveBits',
]);
const deriveOpts = { name: 'PBKDF2', salt: _salt, iterations: c, hash: hash.webCryptoName };
return new Uint8Array(await crypto.deriveBits(deriveOpts, key, 8 * dkLen));
try {
const saltBytes = kdfInputToBytes(salt, 'salt');
// String conversion already returns an owned array. Caller-owned byte salts need a snapshot
// because deriveBits() does not receive them until after importKey() resolves.
const _salt = typeof salt === 'string' ? saltBytes : copyBytes(saltBytes);
try {
const key = await crypto.importKey('raw', _password, 'PBKDF2', false, [
'deriveBits',
]);
const deriveOpts = { name: 'PBKDF2', salt: _salt, iterations: c, hash: hashName };
const out = new Uint8Array(await crypto.deriveBits(deriveOpts, key, 8 * dkLen));
if (out.length !== dkLen) {
clean(out);
throw new Error('WebCrypto returned an invalid derived key length');
}
return out;
}
finally {
clean(_salt);
}
}
finally {
if (typeof password === 'string')
clean(_password);
}
}