LarpixClient/electron/node_modules/@noble/hashes/scrypt.js
2026-07-09 22:38:33 +02:00

257 lines
No EOL
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* RFC 7914 Scrypt KDF. Can be used to create a key from password and salt.
* @module
*/
import { pbkdf2 } from "./pbkdf2.js";
import { sha256 } from "./sha2.js";
// prettier-ignore
import { anumber, asyncLoop, checkOpts, clean, rotl, swap32IfBE, u32 } from "./utils.js";
// The main Scrypt loop: uses Salsa extensively.
// Six versions of the function were tried, this is the fastest one.
// RFC 7914 §3 / §4 step 2 applies Salsa20/8 to one 16-word (64-byte) block
// after xor'ing two such blocks.
// The local `y*` snapshot keeps the xor input stable even when `out` aliases `prev` or `input`.
// prettier-ignore
function XorAndSalsa(prev, pi, input, ii, out, oi) {
// Based on https://cr.yp.to/salsa20.html and RFC 7914's Salsa20/8 core.
// Xor blocks
let y00 = prev[pi++] ^ input[ii++], y01 = prev[pi++] ^ input[ii++];
let y02 = prev[pi++] ^ input[ii++], y03 = prev[pi++] ^ input[ii++];
let y04 = prev[pi++] ^ input[ii++], y05 = prev[pi++] ^ input[ii++];
let y06 = prev[pi++] ^ input[ii++], y07 = prev[pi++] ^ input[ii++];
let y08 = prev[pi++] ^ input[ii++], y09 = prev[pi++] ^ input[ii++];
let y10 = prev[pi++] ^ input[ii++], y11 = prev[pi++] ^ input[ii++];
let y12 = prev[pi++] ^ input[ii++], y13 = prev[pi++] ^ input[ii++];
let y14 = prev[pi++] ^ input[ii++], y15 = prev[pi++] ^ input[ii++];
// Save state to temporary variables (salsa)
let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
// Main loop (salsa)
for (let i = 0; i < 8; i += 2) {
x04 ^= rotl(x00 + x12 | 0, 7);
x08 ^= rotl(x04 + x00 | 0, 9);
x12 ^= rotl(x08 + x04 | 0, 13);
x00 ^= rotl(x12 + x08 | 0, 18);
x09 ^= rotl(x05 + x01 | 0, 7);
x13 ^= rotl(x09 + x05 | 0, 9);
x01 ^= rotl(x13 + x09 | 0, 13);
x05 ^= rotl(x01 + x13 | 0, 18);
x14 ^= rotl(x10 + x06 | 0, 7);
x02 ^= rotl(x14 + x10 | 0, 9);
x06 ^= rotl(x02 + x14 | 0, 13);
x10 ^= rotl(x06 + x02 | 0, 18);
x03 ^= rotl(x15 + x11 | 0, 7);
x07 ^= rotl(x03 + x15 | 0, 9);
x11 ^= rotl(x07 + x03 | 0, 13);
x15 ^= rotl(x11 + x07 | 0, 18);
x01 ^= rotl(x00 + x03 | 0, 7);
x02 ^= rotl(x01 + x00 | 0, 9);
x03 ^= rotl(x02 + x01 | 0, 13);
x00 ^= rotl(x03 + x02 | 0, 18);
x06 ^= rotl(x05 + x04 | 0, 7);
x07 ^= rotl(x06 + x05 | 0, 9);
x04 ^= rotl(x07 + x06 | 0, 13);
x05 ^= rotl(x04 + x07 | 0, 18);
x11 ^= rotl(x10 + x09 | 0, 7);
x08 ^= rotl(x11 + x10 | 0, 9);
x09 ^= rotl(x08 + x11 | 0, 13);
x10 ^= rotl(x09 + x08 | 0, 18);
x12 ^= rotl(x15 + x14 | 0, 7);
x13 ^= rotl(x12 + x15 | 0, 9);
x14 ^= rotl(x13 + x12 | 0, 13);
x15 ^= rotl(x14 + x13 | 0, 18);
}
// Write output (salsa)
out[oi++] = (y00 + x00) | 0;
out[oi++] = (y01 + x01) | 0;
out[oi++] = (y02 + x02) | 0;
out[oi++] = (y03 + x03) | 0;
out[oi++] = (y04 + x04) | 0;
out[oi++] = (y05 + x05) | 0;
out[oi++] = (y06 + x06) | 0;
out[oi++] = (y07 + x07) | 0;
out[oi++] = (y08 + x08) | 0;
out[oi++] = (y09 + x09) | 0;
out[oi++] = (y10 + x10) | 0;
out[oi++] = (y11 + x11) | 0;
out[oi++] = (y12 + x12) | 0;
out[oi++] = (y13 + x13) | 0;
out[oi++] = (y14 + x14) | 0;
out[oi++] = (y15 + x15) | 0;
}
function BlockMix(input, ii, out, oi, r) {
// The block B is `r` 128-byte chunks, i.e. `2r` 16-word (64-byte) Salsa blocks.
let head = oi + 0;
let tail = oi + 16 * r;
for (let i = 0; i < 16; i++)
out[tail + i] = input[ii + (2 * r - 1) * 16 + i]; // X ← B[2r1]
for (let i = 0; i < r; i++, head += 16, ii += 16) {
// RFC 7914 §4 step 3 outputs `Y[0], Y[2], ...` first, then `Y[1], Y[3], ...`;
// `head` and `tail` lay out those even/odd halves in place.
XorAndSalsa(out, tail, input, ii, out, head); // head[i] = Salsa(blockIn[2*i] ^ tail[i-1])
if (i > 0)
tail += 16; // First iteration overwrites tmp value in tail
// tail[i] = Salsa(blockIn[2*i+1] ^ head[i])
XorAndSalsa(out, head, input, (ii += 16), out, tail);
}
}
// 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,
}, _opts);
const { N, r, p, dkLen, asyncTick, maxmem, onProgress } = opts;
anumber(N, 'N');
anumber(r, 'r');
anumber(p, 'p');
anumber(dkLen, 'dkLen');
anumber(asyncTick, 'asyncTick');
anumber(maxmem, 'maxmem');
if (onProgress !== undefined && typeof onProgress !== 'function')
throw new Error('progressCb must be a function');
const blockSize = 128 * r;
const blockSize32 = blockSize / 4;
// Max N is 2^32 (Integrify is 32-bit).
// Real limit can be 2^22: some JS engines limit Uint8Array to 4GB.
// Spec check `N >= 2^(blockSize / 8)` is not done for compat with popular libs,
// which used incorrect r: 1, p: 8. Also, the check seems to be a spec error:
// https://www.rfc-editor.org/errata_search.php?rfc=7914
const pow32 = Math.pow(2, 32);
if (N <= 1 || (N & (N - 1)) !== 0 || N > pow32)
throw new Error('"N" expected a power of 2, and 2^1 <= N <= 2^32');
if (p < 1 || p > ((pow32 - 1) * 32) / blockSize)
throw new Error('"p" expected integer 1..((2^32 - 1) * 32) / (128 * r)');
// RFC 7914 §2 defines `dkLen` as a positive integer.
if (dkLen < 1 || dkLen > (pow32 - 1) * 32)
throw new Error('"dkLen" expected integer 1..(2^32 - 1) * 32');
// Include the shared `tmp` scratch block so `maxmem` matches noble's actual temporary allocation.
// Node requires more headroom here, so this accounting is intentionally noble-specific.
const memUsed = blockSize * (N + p + 1);
if (memUsed > maxmem)
throw new Error('"maxmem" limit was hit: memUsed(128*r*(N+p+1))=' + memUsed + ', maxmem=' + maxmem);
// [B0...Bp1] ← PBKDF2HMAC-SHA256(Passphrase, Salt, 1, blockSize*ParallelizationFactor)
// Since it has only one iteration there is no reason to use async variant
const B = pbkdf2(sha256, password, salt, { c: 1, dkLen: blockSize * p });
const B32 = u32(B);
// Re-used between parallel iterations. Array(iterations) of B
const V = u32(new Uint8Array(blockSize * N));
const tmp = u32(new Uint8Array(blockSize));
let blockMixCb = () => { };
if (onProgress) {
const totalBlockMix = 2 * N * 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(totalBlockMix / 10000), 1);
let blockMixCnt = 0;
blockMixCb = () => {
blockMixCnt++;
if (onProgress && (!(blockMixCnt % callbackPer) || blockMixCnt === totalBlockMix))
onProgress(blockMixCnt / totalBlockMix);
};
}
return { N, r, p, dkLen, blockSize32, V, B32, B, tmp, blockMixCb, asyncTick };
}
function scryptOutput(password, dkLen, B, V, tmp) {
// Shared final PBKDF2-and-cleanup step: keep the derived key, wipe the scrypt workspace.
const res = pbkdf2(sha256, password, B, { c: 1, dkLen });
clean(B, V, tmp);
return res;
}
/**
* Scrypt KDF from RFC 7914. See {@link ScryptOpts}.
* @param password - password or key material to derive from;
* JS string inputs are UTF-8 encoded first
* @param salt - unique salt bytes or string; JS string inputs are UTF-8 encoded first
* @param opts - Scrypt cost and memory parameters. `dkLen`, if provided,
* must be `>= 1` per RFC 7914 §2. See {@link ScryptOpts}.
* @returns Derived key bytes.
* @throws If the Scrypt cost, memory, or callback options are invalid. {@link Error}
* @example
* Derive a key with scrypt.
* ```ts
* scrypt('password', 'salt', { N: 2**18, r: 8, p: 1, dkLen: 32 });
* ```
*/
export function scrypt(password, salt, opts) {
const { N, r, p, dkLen, blockSize32, V, B32, B, tmp, blockMixCb } = scryptInit(password, salt, opts);
swap32IfBE(B32);
for (let pi = 0; pi < p; pi++) {
const Pi = blockSize32 * pi;
for (let i = 0; i < blockSize32; i++)
V[i] = B32[Pi + i]; // V[0] = B[i]
for (let i = 0, pos = 0; i < N - 1; i++) {
BlockMix(V, pos, V, (pos += blockSize32), r); // V[i] = BlockMix(V[i-1]);
blockMixCb();
}
BlockMix(V, (N - 1) * blockSize32, B32, Pi, r); // Process last element
blockMixCb();
for (let i = 0; i < N; i++) {
// 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();
}
}
swap32IfBE(B32);
return scryptOutput(password, dkLen, B, V, tmp);
}
/**
* Scrypt KDF from RFC 7914. Async version. See {@link ScryptOpts}.
* @param password - password or key material to derive from;
* JS string inputs are UTF-8 encoded first
* @param salt - unique salt bytes or string; JS string inputs are UTF-8 encoded first
* @param opts - Scrypt cost and memory parameters. `dkLen`, if provided,
* must be `>= 1` per RFC 7914 §2. `asyncTick` is only a local
* scheduler-yield control for this JS wrapper, not part of RFC 7914.
* See {@link ScryptOpts}.
* @returns Promise resolving to derived key bytes.
* @throws If the Scrypt cost, memory, or callback options are invalid. {@link Error}
* @example
* Derive a key with scrypt asynchronously.
* ```ts
* await scryptAsync('password', 'salt', { N: 2**18, r: 8, p: 1, dkLen: 32 });
* ```
*/
export async function scryptAsync(password, salt, opts) {
const { N, r, p, dkLen, blockSize32, V, B32, B, tmp, blockMixCb, asyncTick } = scryptInit(password, salt, opts);
swap32IfBE(B32);
for (let pi = 0; pi < p; pi++) {
const Pi = blockSize32 * pi;
for (let i = 0; i < blockSize32; i++)
V[i] = B32[Pi + i]; // V[0] = B[i]
let pos = 0;
await asyncLoop(N - 1, asyncTick, () => {
BlockMix(V, pos, V, (pos += blockSize32), r); // V[i] = BlockMix(V[i-1]);
blockMixCb();
});
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();
});
}
swap32IfBE(B32);
return scryptOutput(password, dkLen, B, V, tmp);
}
//# sourceMappingURL=scrypt.js.map