SHA256
chore: remove leftover stray vendor files from earlier fetch attempt
🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
-896
@@ -1,896 +0,0 @@
|
|||||||
/**
|
|
||||||
* Utilities for hex, bytes, CSPRNG.
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
|
|
||||||
export function aarray(item, title, inner = () => { }) {
|
|
||||||
if (!Array.isArray(item))
|
|
||||||
throw new TypeError(`"${title}" expected array, got type=${typeof item}`);
|
|
||||||
for (let i = 0; i < item.length; i++)
|
|
||||||
inner(item[i], `${title}[${i}]`);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Checks if something is Uint8Array. Be careful: nodejs Buffer will return true.
|
|
||||||
* @param a - Value to inspect.
|
|
||||||
* @returns `true` when the value is a Uint8Array view, including Node's `Buffer`.
|
|
||||||
* @example
|
|
||||||
* Guards a value before treating it as raw key material.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* isBytes(new Uint8Array());
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function isBytes(a) {
|
|
||||||
// Plain `instanceof Uint8Array` is too strict for some Buffer / proxy /
|
|
||||||
// cross-realm cases. The fallback still requires a real ArrayBuffer view
|
|
||||||
// so plain JSON-deserialized `{ constructor: ... }`
|
|
||||||
// spoofing is rejected, and `BYTES_PER_ELEMENT === 1` keeps the fallback on byte-oriented views.
|
|
||||||
return (a instanceof Uint8Array ||
|
|
||||||
(ArrayBuffer.isView(a) &&
|
|
||||||
a.constructor.name === 'Uint8Array' &&
|
|
||||||
'BYTES_PER_ELEMENT' in a &&
|
|
||||||
a.BYTES_PER_ELEMENT === 1));
|
|
||||||
}
|
|
||||||
// Shared error-message prefix builder. Only called on throw paths, so assert
|
|
||||||
// success paths never pay for the string concatenation.
|
|
||||||
const atitle = (title) => (title ? `"${title}" ` : '');
|
|
||||||
/**
|
|
||||||
* Asserts something is boolean.
|
|
||||||
* @param value - Value to validate.
|
|
||||||
* @returns The validated boolean.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Validates a boolean option before branching on it.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* abool(true);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function abool(value, title = '') {
|
|
||||||
if (typeof value !== 'boolean')
|
|
||||||
throw new TypeError(atitle(title) + 'expected boolean, got type=' + typeof value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Asserts something is a non-negative safe integer.
|
|
||||||
* @param n - Value to validate.
|
|
||||||
* @returns The validated number.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @throws On wrong argument ranges or values. {@link RangeError}
|
|
||||||
* @example
|
|
||||||
* Validates a non-negative length or counter.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* anumber(1);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function anumber(n, title = '') {
|
|
||||||
if (typeof n !== 'number')
|
|
||||||
throw new TypeError(atitle(title) + 'expected number, got ' + typeof n);
|
|
||||||
if (!Number.isSafeInteger(n) || n < 0)
|
|
||||||
throw new RangeError(atitle(title) + 'expected integer >= 0, got ' + n);
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Asserts something is Uint8Array.
|
|
||||||
* @param value - Value to validate.
|
|
||||||
* @param length - Expected byte length.
|
|
||||||
* @param title - Optional label used in error messages.
|
|
||||||
* @returns The validated byte array.
|
|
||||||
* On Node, `Buffer` is accepted too because it is a Uint8Array view.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @throws On wrong argument lengths. {@link RangeError}
|
|
||||||
* @example
|
|
||||||
* Validates a fixed-length nonce or key buffer.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* abytes(new Uint8Array([1, 2]), 2);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function abytes(value, length, title = '') {
|
|
||||||
// Success path first: this runs at the start of every update() / digestInto(), and the
|
|
||||||
// common `abytes(data)` form must not pay for length handling it does not use.
|
|
||||||
if (isBytes(value) && (length === undefined || value.length === length))
|
|
||||||
return value;
|
|
||||||
// Error path: recompute freely to build the exact message.
|
|
||||||
if (length !== undefined)
|
|
||||||
anumber(length, 'length');
|
|
||||||
const bytes = isBytes(value);
|
|
||||||
const ofLen = length !== undefined ? ` of length ${length}` : '';
|
|
||||||
const got = bytes ? `length=${value.length}` : `type=${typeof value}`;
|
|
||||||
const message = atitle(title) + 'expected Uint8Array' + ofLen + ', got ' + got;
|
|
||||||
if (!bytes)
|
|
||||||
throw new TypeError(message);
|
|
||||||
throw new RangeError(message);
|
|
||||||
}
|
|
||||||
const aobject = (value, label) => {
|
|
||||||
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
||||||
throw new TypeError(label === 'object'
|
|
||||||
? 'expected valid options object'
|
|
||||||
: `"${label}" expected object, got type=${typeof value}`);
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Asserts a hash- or MAC-like instance has not been destroyed or finished.
|
|
||||||
* @param instance - Stateful instance to validate.
|
|
||||||
* @param checkFinished - Whether to reject finished instances.
|
|
||||||
* When `false`, only `destroyed` is checked.
|
|
||||||
* @throws If the hash instance has already been destroyed or finalized. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Guards against calling `update()` or `digest()` on a finished hash.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* aexists({ destroyed: false, finished: false });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function aexists(instance, checkFinished = true) {
|
|
||||||
// Runs on every update()/digestInto(); the flags are library-owned booleans, so only their
|
|
||||||
// truthiness is checked - re-validating their type per call was pure hot-path overhead.
|
|
||||||
if (instance.destroyed)
|
|
||||||
throw new Error('hash was destroyed');
|
|
||||||
if (checkFinished && instance.finished)
|
|
||||||
throw new Error('digest() was already called');
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Asserts output is a sufficiently-sized byte array.
|
|
||||||
* @param out - Output buffer to validate.
|
|
||||||
* @param instance - Hash-like instance providing `outputLen`.
|
|
||||||
* This is the relaxed `digestInto()`-style contract: output must be at least `outputLen`,
|
|
||||||
* unlike one-shot cipher helpers elsewhere in the repo that often require exact lengths.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @throws On wrong output buffer lengths. {@link RangeError}
|
|
||||||
* @example
|
|
||||||
* Verifies that a caller-provided output buffer is large enough.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* aoutput(new Uint8Array(16), { outputLen: 16 });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function aoutput(out, instance) {
|
|
||||||
abytes(out, undefined, 'output');
|
|
||||||
// `outputLen` is a library-owned readonly number; the negated comparison keeps failing fast
|
|
||||||
// when it is missing/NaN (comparisons with undefined/NaN are false) without an anumber() call.
|
|
||||||
const min = instance.outputLen;
|
|
||||||
if (!(out.length >= min)) {
|
|
||||||
throw new RangeError('"output" expected length >= ' + min);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Asserts output is a sufficiently-sized, 4-byte-aligned byte array.
|
|
||||||
* {@link aoutput} plus an {@link isAligned32} check, for `digestInto()` paths
|
|
||||||
* that write through zero-allocation `u32` word views.
|
|
||||||
* @param out - Output buffer to validate.
|
|
||||||
* @param instance - Hash-like instance providing `outputLen`.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @throws On wrong output buffer lengths. {@link RangeError}
|
|
||||||
* @throws On wrong output buffer alignment. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Verifies that a caller-provided output buffer is large enough and aligned.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* aoutput32(new Uint8Array(16), { outputLen: 16 });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function aoutput32(out, instance) {
|
|
||||||
aoutput(out, instance);
|
|
||||||
if (!isAligned32(out))
|
|
||||||
throw new Error('invalid output, must be aligned');
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Casts a typed-array view to Uint8Array.
|
|
||||||
* @param arr - Typed-array view to reinterpret.
|
|
||||||
* @returns Uint8Array view over the same bytes.
|
|
||||||
* @example
|
|
||||||
* Views 32-bit words as raw bytes without copying.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* u8(new Uint32Array([1]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function u8(arr) {
|
|
||||||
return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Casts a typed-array view to Uint32Array.
|
|
||||||
* @param arr - Typed-array view to reinterpret.
|
|
||||||
* @returns Uint32Array view over the same bytes. Callers are expected to provide a
|
|
||||||
* 4-byte-aligned offset; trailing `1..3` bytes are silently dropped.
|
|
||||||
* @example
|
|
||||||
* Views a byte buffer as 32-bit words for block processing.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* u32(new Uint8Array(4));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function u32(arr) {
|
|
||||||
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Zeroizes typed arrays in place.
|
|
||||||
* Warning: JS provides no guarantees.
|
|
||||||
* @param arrays - Arrays to wipe.
|
|
||||||
* @example
|
|
||||||
* Wipes a temporary key buffer after use.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* const bytes = new Uint8Array([1]);
|
|
||||||
* clean(bytes);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function clean(...arrays) {
|
|
||||||
for (let i = 0; i < arrays.length; i++) {
|
|
||||||
arrays[i].fill(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Creates a DataView for byte-level manipulation.
|
|
||||||
* @param arr - Typed-array view to wrap.
|
|
||||||
* @returns DataView over the same bytes.
|
|
||||||
* @example
|
|
||||||
* Creates an endian-aware view for length encoding.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* createView(new Uint8Array(4));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function createView(arr) {
|
|
||||||
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Whether the current platform is little-endian.
|
|
||||||
* Most are; some IBM systems are not.
|
|
||||||
*/
|
|
||||||
export const isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44)();
|
|
||||||
/**
|
|
||||||
* Reverses byte order of one 32-bit word.
|
|
||||||
* @param word - Unsigned 32-bit word to swap.
|
|
||||||
* @returns The same word with bytes reversed.
|
|
||||||
* @example
|
|
||||||
* Swaps a big-endian word into little-endian byte order.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* byteSwap(0x11223344);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function byteSwap(word) {
|
|
||||||
return (((word << 24) & 0xff000000) |
|
|
||||||
((word << 8) & 0xff0000) |
|
|
||||||
((word >>> 8) & 0xff00) |
|
|
||||||
((word >>> 24) & 0xff));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Normalizes one 32-bit word to the little-endian representation expected by cipher cores.
|
|
||||||
* @param n - Unsigned 32-bit word to normalize.
|
|
||||||
* @returns Little-endian normalized word on big-endian hosts, else the input word unchanged.
|
|
||||||
* @example
|
|
||||||
* Normalizes a host-endian word before passing it into an ARX/AES core.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* swap8IfBE(0x11223344);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const swap8IfBE = isLE
|
|
||||||
? (n) => n
|
|
||||||
: (n) => byteSwap(n) >>> 0;
|
|
||||||
/**
|
|
||||||
* Byte-swaps every word of a Uint32Array in place.
|
|
||||||
* @param arr - Uint32Array whose words should be swapped.
|
|
||||||
* @returns The same array after in-place byte swapping.
|
|
||||||
* @example
|
|
||||||
* Swaps every 32-bit word in a word-view buffer.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* byteSwap32(new Uint32Array([0x11223344]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function byteSwap32(arr) {
|
|
||||||
for (let i = 0; i < arr.length; i++) {
|
|
||||||
arr[i] = byteSwap(arr[i]);
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Normalizes a Uint32Array view to the little-endian representation expected by cipher cores.
|
|
||||||
* @param u - Word view to normalize in place.
|
|
||||||
* @returns Little-endian normalized word view.
|
|
||||||
* @example
|
|
||||||
* Normalizes a word-view buffer before block processing.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* swap32IfBE(new Uint32Array([0x11223344]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const swap32IfBE = isLE
|
|
||||||
? (u) => u
|
|
||||||
: byteSwap32;
|
|
||||||
// Built-in hex conversion:
|
|
||||||
// {@link https://caniuse.com/mdn-javascript_builtins_uint8array_fromhex | caniuse entry}
|
|
||||||
const hasHexBuiltin = /* @__PURE__ */ (() =>
|
|
||||||
// @ts-ignore
|
|
||||||
typeof Uint8Array.from([]).toHex === 'function' && typeof Uint8Array.fromHex === 'function')();
|
|
||||||
// Array where index 0xf0 (240) is mapped to string 'f0'
|
|
||||||
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
|
|
||||||
/**
|
|
||||||
* Convert byte array to hex string. Uses built-in function, when available.
|
|
||||||
* @param bytes - Bytes to encode.
|
|
||||||
* @returns Lowercase hexadecimal string.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Formats ciphertext bytes for logs or test vectors.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])); // 'cafe0123'
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function bytesToHex(bytes) {
|
|
||||||
abytes(bytes);
|
|
||||||
// @ts-ignore
|
|
||||||
if (hasHexBuiltin)
|
|
||||||
return bytes.toHex();
|
|
||||||
// pre-caching improves the speed 6x
|
|
||||||
let hex = '';
|
|
||||||
for (let i = 0; i < bytes.length; i++) {
|
|
||||||
hex += hexes[bytes[i]];
|
|
||||||
}
|
|
||||||
return hex;
|
|
||||||
}
|
|
||||||
// Strict ASCII nibble parser: non-ASCII hex lookalikes are rejected as undefined.
|
|
||||||
// ASCII codes: '0'..'9' = 48..57, 'A'..'F' = 65..70, 'a'..'f' = 97..102.
|
|
||||||
// prettier-ignore
|
|
||||||
function asciiToBase16(ch) {
|
|
||||||
return ch >= 48 && ch <= 57 ? ch - 48 // '2' => 50-48
|
|
||||||
: ch >= 65 && ch <= 70 ? ch - (65 - 10) // 'B' => 66-(65-10)
|
|
||||||
: ch >= 97 && ch <= 102 ? ch - (97 - 10) // 'b' => 98-(97-10)
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Convert hex string to byte array. Uses built-in function, when available.
|
|
||||||
* @param hex - hexadecimal string to decode
|
|
||||||
* @returns Decoded bytes.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @throws On wrong argument ranges or values. {@link RangeError}
|
|
||||||
* @example
|
|
||||||
* Decode lowercase hexadecimal into bytes.
|
|
||||||
* ```ts
|
|
||||||
* hexToBytes('cafe0123'); // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function hexToBytes(hex) {
|
|
||||||
if (typeof hex !== 'string')
|
|
||||||
throw new TypeError('hex string expected, got ' + typeof hex);
|
|
||||||
if (hasHexBuiltin) {
|
|
||||||
try {
|
|
||||||
return Uint8Array.fromHex(hex);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
if (error instanceof SyntaxError)
|
|
||||||
throw new RangeError(error.message);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const hl = hex.length;
|
|
||||||
const al = hl / 2;
|
|
||||||
if (hl % 2)
|
|
||||||
throw new RangeError('hex string expected, got unpadded hex of length ' + hl);
|
|
||||||
const array = new Uint8Array(al);
|
|
||||||
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
||||||
const n1 = asciiToBase16(hex.charCodeAt(hi)); // parse first char, multiply it by 16
|
|
||||||
const n2 = asciiToBase16(hex.charCodeAt(hi + 1)); // parse second char
|
|
||||||
if (n1 === undefined || n2 === undefined) {
|
|
||||||
const char = hex[hi] + hex[hi + 1];
|
|
||||||
throw new RangeError('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
|
||||||
}
|
|
||||||
array[ai] = n1 * 16 + n2; // example: 'A9' => 10*16 + 9
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
const _0n = /* @__PURE__ */ BigInt(0);
|
|
||||||
// Used in ff1, via bytesToNumberBE
|
|
||||||
/**
|
|
||||||
* Converts a big-endian hex string into bigint.
|
|
||||||
* @param hex - Hexadecimal string without `0x`.
|
|
||||||
* @returns Parsed bigint value. The empty string is treated as `0n`.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Parses a big-endian field element or counter from hex.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* hexToNumber('ff');
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function hexToNumber(hex) {
|
|
||||||
if (typeof hex !== 'string')
|
|
||||||
throw new TypeError('hex string expected, got ' + typeof hex);
|
|
||||||
// Numeric parser, not byte-hex decoder: odd-length forms like 'f' are valid,
|
|
||||||
// and malformed syntax follows BigInt's native error behavior.
|
|
||||||
return hex === '' ? _0n : BigInt('0x' + hex); // Big Endian
|
|
||||||
}
|
|
||||||
// Used in ff1
|
|
||||||
// BE: Big Endian, LE: Little Endian
|
|
||||||
/**
|
|
||||||
* Converts big-endian bytes into bigint.
|
|
||||||
* @param bytes - Big-endian bytes.
|
|
||||||
* @returns Parsed bigint value. Empty input is treated as `0n`.
|
|
||||||
* @throws On invalid byte input passed to the internal hex conversion. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Reads a big-endian integer from serialized bytes.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* bytesToNumberBE(new Uint8Array([1, 0]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function bytesToNumberBE(bytes) {
|
|
||||||
return hexToNumber(bytesToHex(bytes));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Validates that a value is a non-negative bigint or safe integer.
|
|
||||||
* @param n - Value to validate.
|
|
||||||
* @returns The same validated value.
|
|
||||||
* @throws On wrong argument ranges or values. {@link RangeError}
|
|
||||||
*/
|
|
||||||
function abignumber(n) {
|
|
||||||
if (typeof n === 'bigint') {
|
|
||||||
if (!(_0n <= n))
|
|
||||||
throw new RangeError('positive bigint expected, got ' + n);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
anumber(n);
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
// Used in ff1
|
|
||||||
/**
|
|
||||||
* Converts a number into big-endian bytes of fixed length.
|
|
||||||
* @param n - Number to encode.
|
|
||||||
* @param len - Output length in bytes. Must be greater than zero.
|
|
||||||
* @returns Big-endian bytes padded to `len`.
|
|
||||||
* Negative values, `len = 0`, and values that do not fit are rejected before
|
|
||||||
* downstream hex parsing.
|
|
||||||
* @throws On wrong argument ranges or values. {@link RangeError}
|
|
||||||
* @throws If a documented runtime validation or state check fails. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Encodes a counter as fixed-width big-endian bytes.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* numberToBytesBE(1, 2);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function numberToBytesBE(n, len) {
|
|
||||||
anumber(len);
|
|
||||||
if (len === 0)
|
|
||||||
throw new Error('zero output length is invalid');
|
|
||||||
n = abignumber(n);
|
|
||||||
const expectedLen = len * 2;
|
|
||||||
const hex = n.toString(16);
|
|
||||||
// Detect overflow before hex parsing so oversized values don't leak the shared odd-hex error.
|
|
||||||
if (hex.length > expectedLen)
|
|
||||||
throw new RangeError('number is too large');
|
|
||||||
return hexToBytes(hex.padStart(expectedLen, '0'));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Converts string to bytes using UTF8 encoding.
|
|
||||||
* @param str - String to encode.
|
|
||||||
* @returns UTF-8 bytes in a detached fresh Uint8Array copy.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Encodes application text before encryption or MACing.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* utf8ToBytes('abc'); // new Uint8Array([97, 98, 99])
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function utf8ToBytes(str) {
|
|
||||||
if (typeof str !== 'string')
|
|
||||||
throw new TypeError('string expected');
|
|
||||||
return new Uint8Array(new TextEncoder().encode(str)); // {@link https://bugzil.la/1681809 | Firefox bug 1681809}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Converts bytes to string using UTF8 encoding.
|
|
||||||
* @param bytes - UTF-8 bytes.
|
|
||||||
* @returns Decoded string. Input validation is delegated to `TextDecoder`, and malformed
|
|
||||||
* UTF-8 is replacement-decoded instead of rejected.
|
|
||||||
* @example
|
|
||||||
* Decodes UTF-8 plaintext back into a string.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* bytesToUtf8(new Uint8Array([97, 98, 99])); // 'abc'
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function bytesToUtf8(bytes) {
|
|
||||||
return new TextDecoder().decode(bytes);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Checks if two U8A use same underlying buffer and overlaps.
|
|
||||||
* This is invalid and can corrupt data.
|
|
||||||
* @param a - First byte view.
|
|
||||||
* @param b - Second byte view.
|
|
||||||
* @returns `true` when the views overlap in memory.
|
|
||||||
* @example
|
|
||||||
* Detects whether two slices alias the same backing buffer.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* overlapBytes(new Uint8Array(4), new Uint8Array(4));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function overlapBytes(a, b) {
|
|
||||||
// Zero-length views cannot overwrite anything, even if their offset sits inside another range.
|
|
||||||
if (!a.byteLength || !b.byteLength)
|
|
||||||
return false;
|
|
||||||
return (a.buffer === b.buffer && // best we can do, may fail with an obscure Proxy
|
|
||||||
a.byteOffset < b.byteOffset + b.byteLength && // a starts before b end
|
|
||||||
b.byteOffset < a.byteOffset + a.byteLength // b starts before a end
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* If input and output overlap and input starts before output, we will overwrite end of input before
|
|
||||||
* we start processing it, so this is not supported by forward-processing ciphers.
|
|
||||||
* @param input - Input bytes.
|
|
||||||
* @param output - Output bytes.
|
|
||||||
* @throws If the output view would overwrite unread input bytes. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Rejects an in-place layout that would overwrite unread input bytes.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* const buffer = new Uint8Array(8);
|
|
||||||
* complexOverlapBytes(buffer.subarray(0, 4), buffer.subarray(2, 6));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function complexOverlapBytes(input, output) {
|
|
||||||
// This is very cursed. It works somehow, but I'm completely unsure,
|
|
||||||
// reasoning about overlapping aligned windows is very hard.
|
|
||||||
if (overlapBytes(input, output) && input.byteOffset < output.byteOffset)
|
|
||||||
throw new Error('complex overlap of input and output is not supported');
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Copies several Uint8Arrays into one.
|
|
||||||
* @param arrays - Byte arrays to concatenate.
|
|
||||||
* @returns Combined byte array.
|
|
||||||
* @throws On wrong argument types inside the byte-array list. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Builds a `nonce || ciphertext` style buffer.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* concatBytes(new Uint8Array([1]), new Uint8Array([2]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function concatBytes(...arrays) {
|
|
||||||
let sum = 0;
|
|
||||||
for (let i = 0; i < arrays.length; i++) {
|
|
||||||
const a = arrays[i];
|
|
||||||
abytes(a);
|
|
||||||
sum += a.length;
|
|
||||||
}
|
|
||||||
const res = new Uint8Array(sum);
|
|
||||||
for (let i = 0, pad = 0; i < arrays.length; i++) {
|
|
||||||
const a = arrays[i];
|
|
||||||
res.set(a, pad);
|
|
||||||
pad += a.length;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Merges user options into defaults.
|
|
||||||
* @param defaults - Default option values.
|
|
||||||
* @param opts - User-provided overrides.
|
|
||||||
* @returns Combined options object.
|
|
||||||
* `defaults` is a library-owned mutable object; user-provided `opts` only need to be
|
|
||||||
* object-shaped, since "plain object" checks reject valid proxy/cross-realm containers.
|
|
||||||
* The merge mutates `defaults` in place and returns the same object, so direct callers
|
|
||||||
* should pass a fresh defaults object unless they intentionally want shared state updated.
|
|
||||||
* @throws If options are missing or not an object. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Applies user overrides to the default cipher options.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* checkOpts({ rounds: 20 }, { rounds: 8 });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function checkOpts(defaults, opts) {
|
|
||||||
aobject(defaults, 'defaults');
|
|
||||||
aobject(opts, 'opts');
|
|
||||||
// Mutates defaults by design. __proto__ follows Object.assign semantics here and can only
|
|
||||||
// affect this local option object; callers already control this low-level options surface.
|
|
||||||
const merged = Object.assign(defaults, opts);
|
|
||||||
return merged;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Compares two byte arrays in kinda constant time once lengths already match.
|
|
||||||
* @param a - First byte array.
|
|
||||||
* @param b - Second byte array.
|
|
||||||
* @returns `true` when the arrays contain the same bytes. Different lengths still return early.
|
|
||||||
* @example
|
|
||||||
* Compares an expected authentication tag with the received one.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* equalBytes(new Uint8Array([1]), new Uint8Array([1]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function equalBytes(a, b) {
|
|
||||||
a = abytes(a);
|
|
||||||
b = abytes(b);
|
|
||||||
if (a.length !== b.length)
|
|
||||||
return false;
|
|
||||||
let diff = 0;
|
|
||||||
for (let i = 0; i < a.length; i++)
|
|
||||||
diff |= a[i] ^ b[i];
|
|
||||||
return diff === 0;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Wraps a keyed MAC constructor into a one-shot helper with `.create()`.
|
|
||||||
* @param keyLen - Valid probe-key length used to read static metadata once.
|
|
||||||
* The probe key is only used for `outputLen` / `blockLen`, so callers with several valid key sizes
|
|
||||||
* can pass any representative size as long as those values stay fixed.
|
|
||||||
* @param macCons - Keyed MAC constructor or factory.
|
|
||||||
* @param fromMsg - Optional adapter that derives extra constructor args from the one-shot message.
|
|
||||||
* @returns Callable MAC helper with `.create()`.
|
|
||||||
*/
|
|
||||||
export function wrapMacConstructor(keyLen, macCons, fromMsg) {
|
|
||||||
const mac = macCons;
|
|
||||||
const getArgs = (fromMsg || (() => []));
|
|
||||||
const macC = (msg, key) => mac(key, ...getArgs(msg))
|
|
||||||
.update(msg)
|
|
||||||
.digest();
|
|
||||||
const tmp = mac(new Uint8Array(keyLen), ...getArgs(new Uint8Array(0)));
|
|
||||||
macC.outputLen = tmp.outputLen;
|
|
||||||
macC.blockLen = tmp.blockLen;
|
|
||||||
macC.create = (key, ...args) => mac(key, ...args);
|
|
||||||
return macC;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Wraps a cipher: validates args, ensures encrypt() can only be called once.
|
|
||||||
* Used internally by the exported cipher constructors.
|
|
||||||
* Output-buffer support is inferred from the wrapped `encrypt` / `decrypt`
|
|
||||||
* arity (`fn.length === 2`), so wrapped output-capable methods must use a normal
|
|
||||||
* second parameter, not a default/rest parameter. AAD support is explicit in
|
|
||||||
* `params.withAAD`; optional AAD starts after the nonce slot when one is present.
|
|
||||||
* @__NO_SIDE_EFFECTS__
|
|
||||||
* @param params - Static cipher metadata. See {@link CipherParams}.
|
|
||||||
* @param constructor - Cipher constructor.
|
|
||||||
* @returns Wrapped constructor with validation.
|
|
||||||
*/
|
|
||||||
export const wrapCipher = (params, constructor) => {
|
|
||||||
function wrappedCipher(key, ...args) {
|
|
||||||
// Validate key
|
|
||||||
abytes(key, undefined, 'key');
|
|
||||||
// Validate nonce if nonceLength is present
|
|
||||||
if (params.nonceLength !== undefined) {
|
|
||||||
const nonce = args[0];
|
|
||||||
abytes(nonce, params.varSizeNonce ? undefined : params.nonceLength, 'nonce');
|
|
||||||
}
|
|
||||||
// Keep tag length available for decrypt-size checks after constructor validation.
|
|
||||||
const tagl = params.tagLength;
|
|
||||||
const aadStart = params.nonceLength !== undefined ? 1 : 0;
|
|
||||||
// No-AAD constructors otherwise silently ignore byte args meant as AAD.
|
|
||||||
if (!params.withAAD) {
|
|
||||||
for (let i = aadStart; i < args.length; i++)
|
|
||||||
if (isBytes(args[i]))
|
|
||||||
throw new Error('AAD not supported');
|
|
||||||
}
|
|
||||||
// Validate the first AAD slot early; rest-arg AAD constructors validate the tail themselves.
|
|
||||||
if (params.withAAD && args[aadStart] !== undefined)
|
|
||||||
abytes(args[aadStart], undefined, 'AAD');
|
|
||||||
const cipher = constructor(key, ...args);
|
|
||||||
const checkOutput = (fnLength, output) => {
|
|
||||||
if (output !== undefined) {
|
|
||||||
if (fnLength !== 2)
|
|
||||||
throw new Error('cipher output not supported');
|
|
||||||
abytes(output, undefined, 'output');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Create wrapped cipher with validation and single-use encryption
|
|
||||||
let called = false;
|
|
||||||
const wrCipher = {
|
|
||||||
encrypt(data, output) {
|
|
||||||
if (called)
|
|
||||||
throw new Error('cannot encrypt() twice with same key + nonce');
|
|
||||||
// Any encrypt attempt consumes the instance, even if validation rejects below.
|
|
||||||
called = true;
|
|
||||||
abytes(data, undefined, 'data');
|
|
||||||
checkOutput(cipher.encrypt.length, output);
|
|
||||||
return cipher.encrypt(data, output);
|
|
||||||
},
|
|
||||||
decrypt(data, output) {
|
|
||||||
abytes(data, undefined, 'data');
|
|
||||||
if (tagl && data.length < tagl)
|
|
||||||
throw new Error('"ciphertext" expected length >= tagLength=' + tagl);
|
|
||||||
checkOutput(cipher.decrypt.length, output);
|
|
||||||
return cipher.decrypt(data, output);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return wrCipher;
|
|
||||||
}
|
|
||||||
Object.assign(wrappedCipher, params);
|
|
||||||
return wrappedCipher;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* By default, returns u8a of length.
|
|
||||||
* When out is available, it checks it for validity and uses it.
|
|
||||||
* @param expectedLength - Required output length.
|
|
||||||
* @param out - Optional destination buffer.
|
|
||||||
* @param onlyAligned - Whether `out` must be 4-byte aligned.
|
|
||||||
* @returns Output buffer ready for writing.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @throws If the provided output buffer has the wrong size. {@link RangeError}
|
|
||||||
* @throws If the provided output buffer has the wrong alignment. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Reuses a caller-provided output buffer when lengths match.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* getOutput(16, new Uint8Array(16));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function getOutput(expectedLength, out, onlyAligned = true) {
|
|
||||||
if (out === undefined)
|
|
||||||
return new Uint8Array(expectedLength);
|
|
||||||
// Keep Buffer/cross-realm Uint8Array support here instead of trusting a shape-compatible object.
|
|
||||||
abytes(out, expectedLength, 'output');
|
|
||||||
if (onlyAligned && !isAligned32(out))
|
|
||||||
throw new Error('invalid output, must be aligned');
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Encodes data and AAD lengths into a 16-byte buffer.
|
|
||||||
* @param dataLength - Data length. Units are caller-defined: GCM passes bit
|
|
||||||
* lengths, ChaCha20-Poly1305 passes byte lengths — the helper writes the raw values.
|
|
||||||
* @param aadLength - AAD length, same unit convention as `dataLength`.
|
|
||||||
* The serialized block is still `aadLength || dataLength`, matching GCM/Poly1305
|
|
||||||
* conventions even though the helper parameter order is `(dataLength, aadLength)`.
|
|
||||||
* @param isLE - Whether to encode lengths as little-endian.
|
|
||||||
* @returns 16-byte length block.
|
|
||||||
* @throws On wrong argument types passed to the endian validator. {@link TypeError}
|
|
||||||
* @throws On wrong argument ranges or values. {@link RangeError}
|
|
||||||
* @example
|
|
||||||
* Builds the length block appended by GCM and Poly1305.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* u64Lengths(16, 8, true);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function u64Lengths(dataLength, aadLength, isLE) {
|
|
||||||
// Reject coercible non-number lengths like '10' and true before BigInt(...) accepts them.
|
|
||||||
anumber(dataLength);
|
|
||||||
anumber(aadLength);
|
|
||||||
abool(isLE);
|
|
||||||
const num = new Uint8Array(16);
|
|
||||||
const view = createView(num);
|
|
||||||
view.setBigUint64(0, BigInt(aadLength), isLE);
|
|
||||||
view.setBigUint64(8, BigInt(dataLength), isLE);
|
|
||||||
return num;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Checks whether a byte array is aligned to a 4-byte offset.
|
|
||||||
* @param bytes - Byte array to inspect.
|
|
||||||
* @returns `true` when the view is 4-byte aligned.
|
|
||||||
* @example
|
|
||||||
* Checks whether a buffer can be safely viewed as Uint32Array.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* isAligned32(new Uint8Array(4));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function isAligned32(bytes) {
|
|
||||||
return bytes.byteOffset % 4 === 0;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Copies bytes into a new Uint8Array.
|
|
||||||
* @param bytes - Bytes to copy.
|
|
||||||
* @returns Copied byte array.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Copies input into an aligned Uint8Array before block processing.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* copyBytes(new Uint8Array([1, 2]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function copyBytes(bytes) {
|
|
||||||
// `Uint8Array.from(...)` would also accept arrays / other typed arrays. Keep this helper strict
|
|
||||||
// because callers use it at byte-validation boundaries before mutating the detached copy.
|
|
||||||
return Uint8Array.from(abytes(bytes));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Cryptographically secure PRNG backed by `crypto.getRandomValues`.
|
|
||||||
* @param bytesLength - number of random bytes to generate
|
|
||||||
* @returns Random bytes.
|
|
||||||
* The platform `getRandomValues()` implementation still defines any
|
|
||||||
* single-call length cap, and this helper rejects oversize requests
|
|
||||||
* with a stable library `RangeError` instead of host-specific errors.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @throws On wrong argument ranges or values. {@link RangeError}
|
|
||||||
* @throws If the current runtime does not provide `crypto.getRandomValues`. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Generate a fresh random key or nonce.
|
|
||||||
* ```ts
|
|
||||||
* const key = randomBytes(16);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function randomBytes(bytesLength = 32) {
|
|
||||||
// Match the repo's other length-taking helpers instead of relying on Uint8Array coercion.
|
|
||||||
anumber(bytesLength, 'bytesLength');
|
|
||||||
const cr = typeof globalThis === 'object' ? globalThis.crypto : null;
|
|
||||||
if (typeof cr?.getRandomValues !== 'function')
|
|
||||||
throw new Error('crypto.getRandomValues must be defined');
|
|
||||||
// Web Cryptography API Level 2 §10.1.1:
|
|
||||||
// if `byteLength > 65536`, throw `QuotaExceededError`.
|
|
||||||
// Keep the guard explicit so callers can see the quota in code
|
|
||||||
// instead of discovering it by reading the spec or host errors.
|
|
||||||
// This wrapper surfaces the same quota as a stable library RangeError.
|
|
||||||
if (bytesLength > 65536)
|
|
||||||
throw new RangeError(`"bytesLength" expected <= 65536, got ${bytesLength}`);
|
|
||||||
return cr.getRandomValues(new Uint8Array(bytesLength));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Uses CSPRNG for nonce, nonce injected in ciphertext.
|
|
||||||
* For `encrypt`, a `nonceBytes`-length buffer is fetched from CSPRNG and
|
|
||||||
* prepended to encrypted ciphertext. For `decrypt`, first `nonceBytes` of ciphertext
|
|
||||||
* are treated as nonce. The wrapper always allocates a fresh `nonce || ciphertext`
|
|
||||||
* buffer on encrypt and intentionally does not support caller-provided destination buffers.
|
|
||||||
* Too-short decrypt inputs are split into short/empty nonce views and then delegated
|
|
||||||
* to the wrapped cipher instead of being rejected here first.
|
|
||||||
*
|
|
||||||
* NOTE: Under the same key, using random nonces (e.g. `managedNonce`) with AES-GCM and ChaCha
|
|
||||||
* should be limited to `2**23` (8M) messages to get a collision chance of
|
|
||||||
* `2**-50`. Stretching to `2**32` (4B) messages would raise that chance to
|
|
||||||
* `2**-33`, still negligible but creeping up.
|
|
||||||
* @param fn - Cipher constructor that expects a nonce.
|
|
||||||
* @param randomBytes_ - Random-byte source used for nonce generation.
|
|
||||||
* @returns Cipher constructor that prepends the nonce to ciphertext.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @throws On invalid nonce lengths observed at wrapper construction or use. {@link RangeError}
|
|
||||||
* @example
|
|
||||||
* Prepends a fresh random nonce to every ciphertext.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* import { gcm } from '@noble/ciphers/aes.js';
|
|
||||||
* import { managedNonce, randomBytes } from '@noble/ciphers/utils.js';
|
|
||||||
* const wrapped = managedNonce(gcm);
|
|
||||||
* const key = randomBytes(16);
|
|
||||||
* const ciphertext = wrapped(key).encrypt(new Uint8Array([1, 2, 3]));
|
|
||||||
* wrapped(key).decrypt(ciphertext);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function managedNonce(fn, randomBytes_ = randomBytes) {
|
|
||||||
if (typeof fn !== 'function')
|
|
||||||
throw new TypeError('"fn" expected cipher constructor, got type=' + typeof fn);
|
|
||||||
if (typeof randomBytes_ !== 'function')
|
|
||||||
throw new TypeError('"randomBytes_" expected function, got type=' + typeof randomBytes_);
|
|
||||||
const { nonceLength } = fn;
|
|
||||||
anumber(nonceLength, 'fn.nonceLength');
|
|
||||||
const addNonce = (nonce, ciphertext, plaintext) => {
|
|
||||||
const out = concatBytes(nonce, ciphertext);
|
|
||||||
// Wrapped ciphers may alias caller plaintext on encrypt(); never zero
|
|
||||||
// caller-owned buffers here.
|
|
||||||
if (!overlapBytes(plaintext, ciphertext))
|
|
||||||
ciphertext.fill(0);
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
// NOTE: we cannot support DST here, it would be mistake:
|
|
||||||
// - we don't know how much dst length cipher requires
|
|
||||||
// - nonce may unalign dst and break everything
|
|
||||||
// - we create new u8a anyway (concatBytes)
|
|
||||||
// - previously we passed all args to cipher, but that was mistake!
|
|
||||||
const res = ((key, ...args) => ({
|
|
||||||
encrypt(plaintext) {
|
|
||||||
abytes(plaintext, undefined, 'data');
|
|
||||||
const nonce = randomBytes_(nonceLength);
|
|
||||||
const encrypted = fn(key, nonce, ...args).encrypt(plaintext);
|
|
||||||
// @ts-ignore
|
|
||||||
if (encrypted instanceof Promise)
|
|
||||||
return encrypted.then((ct) => addNonce(nonce, ct, plaintext));
|
|
||||||
return addNonce(nonce, encrypted, plaintext);
|
|
||||||
},
|
|
||||||
decrypt(ciphertext) {
|
|
||||||
abytes(ciphertext, undefined, 'data');
|
|
||||||
const nonce = ciphertext.subarray(0, nonceLength);
|
|
||||||
const decrypted = ciphertext.subarray(nonceLength);
|
|
||||||
return fn(key, nonce, ...args).decrypt(decrypted);
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
// Auto-nonce wrappers still preserve the wrapped payload geometry.
|
|
||||||
if ('blockSize' in fn)
|
|
||||||
res.blockSize = fn.blockSize;
|
|
||||||
if ('tagLength' in fn)
|
|
||||||
res.tagLength = fn.tagLength;
|
|
||||||
if ('withAAD' in fn)
|
|
||||||
res.withAAD = fn.withAAD;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
-460
@@ -1,460 +0,0 @@
|
|||||||
/**
|
|
||||||
* ChaCha stream cipher, released
|
|
||||||
* in 2008. Developed after Salsa20, ChaCha aims to increase diffusion per round.
|
|
||||||
* It was standardized in
|
|
||||||
* {@link https://www.rfc-editor.org/rfc/rfc8439 | RFC 8439} and
|
|
||||||
* is now used in TLS 1.3.
|
|
||||||
*
|
|
||||||
* {@link https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha | XChaCha20}
|
|
||||||
* extended-nonce variant is also provided. Similar to XSalsa, it's safe to use with
|
|
||||||
* randomly-generated nonces.
|
|
||||||
*
|
|
||||||
* Check out
|
|
||||||
* {@link http://cr.yp.to/chacha/chacha-20080128.pdf | PDF},
|
|
||||||
* {@link https://en.wikipedia.org/wiki/Salsa20 | wiki}, and
|
|
||||||
* {@link https://cr.yp.to/chacha.html | website}.
|
|
||||||
*
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
import { createCipher, createPRG, rotl } from "./_arx.js";
|
|
||||||
import { poly1305 } from "./_poly1305.js";
|
|
||||||
import { abytes, clean, equalBytes, getOutput, isLE, swap32IfBE, u64Lengths, wrapCipher, } from "./utils.js";
|
|
||||||
/**
|
|
||||||
* ChaCha core function. Uses an unrolled loop (chachaCore, hchacha) - 4x
|
|
||||||
* faster than a simple loop, but larger & harder to read. A simple-loop
|
|
||||||
* reference version lives in `test/misc/micro-ciphers.ts`;
|
|
||||||
* `test/arx.test.ts` keeps the two aligned.
|
|
||||||
* The specific implementation is selected in `createCipher` below.
|
|
||||||
*/
|
|
||||||
/** RFC 8439 §2.3 block core for `state = constants | key | counter | nonce`. */
|
|
||||||
// prettier-ignore
|
|
||||||
function chachaCore(s, k, n, out, cnt, rounds = 20) {
|
|
||||||
let y00 = s[0], y01 = s[1], y02 = s[2], y03 = s[3], // "expa" "nd 3" "2-by" "te k"
|
|
||||||
y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3], // Key Key Key Key
|
|
||||||
y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7], // Key Key Key Key
|
|
||||||
y12 = cnt, y13 = n[0], y14 = n[1], y15 = n[2]; // Counter Nonce Nonce Nonce
|
|
||||||
// Save state to temporary variables
|
|
||||||
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;
|
|
||||||
for (let r = 0; r < rounds; r += 2) {
|
|
||||||
x00 = (x00 + x04) | 0;
|
|
||||||
x12 = rotl(x12 ^ x00, 16);
|
|
||||||
x08 = (x08 + x12) | 0;
|
|
||||||
x04 = rotl(x04 ^ x08, 12);
|
|
||||||
x00 = (x00 + x04) | 0;
|
|
||||||
x12 = rotl(x12 ^ x00, 8);
|
|
||||||
x08 = (x08 + x12) | 0;
|
|
||||||
x04 = rotl(x04 ^ x08, 7);
|
|
||||||
x01 = (x01 + x05) | 0;
|
|
||||||
x13 = rotl(x13 ^ x01, 16);
|
|
||||||
x09 = (x09 + x13) | 0;
|
|
||||||
x05 = rotl(x05 ^ x09, 12);
|
|
||||||
x01 = (x01 + x05) | 0;
|
|
||||||
x13 = rotl(x13 ^ x01, 8);
|
|
||||||
x09 = (x09 + x13) | 0;
|
|
||||||
x05 = rotl(x05 ^ x09, 7);
|
|
||||||
x02 = (x02 + x06) | 0;
|
|
||||||
x14 = rotl(x14 ^ x02, 16);
|
|
||||||
x10 = (x10 + x14) | 0;
|
|
||||||
x06 = rotl(x06 ^ x10, 12);
|
|
||||||
x02 = (x02 + x06) | 0;
|
|
||||||
x14 = rotl(x14 ^ x02, 8);
|
|
||||||
x10 = (x10 + x14) | 0;
|
|
||||||
x06 = rotl(x06 ^ x10, 7);
|
|
||||||
x03 = (x03 + x07) | 0;
|
|
||||||
x15 = rotl(x15 ^ x03, 16);
|
|
||||||
x11 = (x11 + x15) | 0;
|
|
||||||
x07 = rotl(x07 ^ x11, 12);
|
|
||||||
x03 = (x03 + x07) | 0;
|
|
||||||
x15 = rotl(x15 ^ x03, 8);
|
|
||||||
x11 = (x11 + x15) | 0;
|
|
||||||
x07 = rotl(x07 ^ x11, 7);
|
|
||||||
x00 = (x00 + x05) | 0;
|
|
||||||
x15 = rotl(x15 ^ x00, 16);
|
|
||||||
x10 = (x10 + x15) | 0;
|
|
||||||
x05 = rotl(x05 ^ x10, 12);
|
|
||||||
x00 = (x00 + x05) | 0;
|
|
||||||
x15 = rotl(x15 ^ x00, 8);
|
|
||||||
x10 = (x10 + x15) | 0;
|
|
||||||
x05 = rotl(x05 ^ x10, 7);
|
|
||||||
x01 = (x01 + x06) | 0;
|
|
||||||
x12 = rotl(x12 ^ x01, 16);
|
|
||||||
x11 = (x11 + x12) | 0;
|
|
||||||
x06 = rotl(x06 ^ x11, 12);
|
|
||||||
x01 = (x01 + x06) | 0;
|
|
||||||
x12 = rotl(x12 ^ x01, 8);
|
|
||||||
x11 = (x11 + x12) | 0;
|
|
||||||
x06 = rotl(x06 ^ x11, 7);
|
|
||||||
x02 = (x02 + x07) | 0;
|
|
||||||
x13 = rotl(x13 ^ x02, 16);
|
|
||||||
x08 = (x08 + x13) | 0;
|
|
||||||
x07 = rotl(x07 ^ x08, 12);
|
|
||||||
x02 = (x02 + x07) | 0;
|
|
||||||
x13 = rotl(x13 ^ x02, 8);
|
|
||||||
x08 = (x08 + x13) | 0;
|
|
||||||
x07 = rotl(x07 ^ x08, 7);
|
|
||||||
x03 = (x03 + x04) | 0;
|
|
||||||
x14 = rotl(x14 ^ x03, 16);
|
|
||||||
x09 = (x09 + x14) | 0;
|
|
||||||
x04 = rotl(x04 ^ x09, 12);
|
|
||||||
x03 = (x03 + x04) | 0;
|
|
||||||
x14 = rotl(x14 ^ x03, 8);
|
|
||||||
x09 = (x09 + x14) | 0;
|
|
||||||
x04 = rotl(x04 ^ x09, 7);
|
|
||||||
}
|
|
||||||
// RFC 8439 §2.3 / §2.3.1: add the original state words back in state order.
|
|
||||||
let oi = 0;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* hchacha hashes key and nonce into key' and nonce' for xchacha20.
|
|
||||||
* Algorithmically identical to `hchacha_small` from `test/misc/micro-ciphers.ts`,
|
|
||||||
* but this exported path normalizes word order on big-endian hosts.
|
|
||||||
* Reuses `chachaCore` and subtracts its feed-forward, keeping the hot per-block
|
|
||||||
* path untouched.
|
|
||||||
* @param s - Sigma constants as 32-bit words.
|
|
||||||
* @param k - Key words.
|
|
||||||
* @param i - Nonce-prefix words.
|
|
||||||
* @param out - Output buffer for the derived subkey.
|
|
||||||
* @example
|
|
||||||
* Derives the XChaCha subkey from sigma, key, and nonce-prefix words.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* const sigma = new Uint32Array(4);
|
|
||||||
* const key = new Uint32Array(8);
|
|
||||||
* const nonce = new Uint32Array(4);
|
|
||||||
* const out = new Uint32Array(8);
|
|
||||||
* hchacha(sigma, key, nonce, out);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
// prettier-ignore
|
|
||||||
export function hchacha(s, k, i, out) {
|
|
||||||
// Runs the shared chachaCore permutation, then subtracts the RFC 8439 feed-forward
|
|
||||||
// it applies, recovering the raw permutation words hchacha needs.
|
|
||||||
// LE hosts read the caller arrays in place (no copies); BE hosts get
|
|
||||||
// byte-swapped scratch copies, wiped before returning.
|
|
||||||
const s2 = isLE ? s : swap32IfBE(s.slice(0, 4));
|
|
||||||
const k2 = isLE ? k : swap32IfBE(k.slice(0, 8));
|
|
||||||
const i2 = isLE ? i : swap32IfBE(i.slice(0, 4));
|
|
||||||
const t = new Uint32Array(16);
|
|
||||||
chachaCore(s2, k2, i2.subarray(1), t, i2[0]);
|
|
||||||
let oi = 0;
|
|
||||||
out[oi++] = (t[0] - s2[0]) | 0;
|
|
||||||
out[oi++] = (t[1] - s2[1]) | 0;
|
|
||||||
out[oi++] = (t[2] - s2[2]) | 0;
|
|
||||||
out[oi++] = (t[3] - s2[3]) | 0;
|
|
||||||
out[oi++] = (t[12] - i2[0]) | 0;
|
|
||||||
out[oi++] = (t[13] - i2[1]) | 0;
|
|
||||||
out[oi++] = (t[14] - i2[2]) | 0;
|
|
||||||
out[oi++] = (t[15] - i2[3]) | 0;
|
|
||||||
swap32IfBE(out);
|
|
||||||
if (!isLE)
|
|
||||||
clean(s2, k2, i2);
|
|
||||||
clean(t);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Original, non-RFC chacha20 from DJB. 8-byte nonce, 8-byte counter.
|
|
||||||
* The nonce/counter layout still reserves 8 counter bytes internally, but the shared public
|
|
||||||
* `counter` argument follows noble's strict non-wrapping 32-bit policy. See `src/_arx.ts`
|
|
||||||
* near `MAX_COUNTER` for the full counter-policy rationale.
|
|
||||||
* @param key - 16-byte or 32-byte key.
|
|
||||||
* @param nonce - 8-byte nonce.
|
|
||||||
* @param data - Input bytes to xor with the keystream.
|
|
||||||
* @param output - Optional destination buffer.
|
|
||||||
* @param counter - Initial block counter.
|
|
||||||
* @returns Encrypted or decrypted bytes.
|
|
||||||
* @example
|
|
||||||
* Encrypts bytes with the original 8-byte-nonce ChaCha variant and a fresh key/nonce.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* import { chacha20orig } from '@noble/ciphers/chacha.js';
|
|
||||||
* import { randomBytes } from '@noble/ciphers/utils.js';
|
|
||||||
* const key = randomBytes(32);
|
|
||||||
* const nonce = randomBytes(8);
|
|
||||||
* chacha20orig(key, nonce, new Uint8Array(4));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const chacha20orig = /* @__PURE__ */ createCipher(chachaCore, {
|
|
||||||
counterRight: false,
|
|
||||||
counterLength: 8,
|
|
||||||
allowShortKeys: true,
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* ChaCha stream cipher. Conforms to RFC 8439 (IETF, TLS). 12-byte nonce, 4-byte counter.
|
|
||||||
* With smaller nonce, it's not safe to make it random (CSPRNG), due to collision chance.
|
|
||||||
* @param key - 32-byte key.
|
|
||||||
* @param nonce - 12-byte nonce.
|
|
||||||
* @param data - Input bytes to xor with the keystream.
|
|
||||||
* @param output - Optional destination buffer.
|
|
||||||
* @param counter - Initial block counter.
|
|
||||||
* @returns Encrypted or decrypted bytes.
|
|
||||||
* @example
|
|
||||||
* Encrypts bytes with the RFC 8439 ChaCha20 stream cipher and a fresh key/nonce.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* import { chacha20 } from '@noble/ciphers/chacha.js';
|
|
||||||
* import { randomBytes } from '@noble/ciphers/utils.js';
|
|
||||||
* const key = randomBytes(32);
|
|
||||||
* const nonce = randomBytes(12);
|
|
||||||
* chacha20(key, nonce, new Uint8Array(4));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const chacha20 = /* @__PURE__ */ createCipher(chachaCore, {
|
|
||||||
counterRight: false,
|
|
||||||
counterLength: 4,
|
|
||||||
allowShortKeys: false,
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* XChaCha eXtended-nonce ChaCha. With 24-byte nonce, it's safe to make it random (CSPRNG).
|
|
||||||
* See {@link https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha | the IRTF draft}.
|
|
||||||
* The nonce/counter layout still reserves 8 counter bytes internally, but the shared public
|
|
||||||
* `counter` argument follows noble's strict non-wrapping 32-bit policy. See `src/_arx.ts`
|
|
||||||
* near `MAX_COUNTER` for the full counter-policy rationale.
|
|
||||||
* @param key - 32-byte key.
|
|
||||||
* @param nonce - 24-byte extended nonce.
|
|
||||||
* @param data - Input bytes to xor with the keystream.
|
|
||||||
* @param output - Optional destination buffer.
|
|
||||||
* @param counter - Initial block counter.
|
|
||||||
* @returns Encrypted or decrypted bytes.
|
|
||||||
* @example
|
|
||||||
* Encrypts bytes with XChaCha20 using a fresh key and random 24-byte nonce.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* import { xchacha20 } from '@noble/ciphers/chacha.js';
|
|
||||||
* import { randomBytes } from '@noble/ciphers/utils.js';
|
|
||||||
* const key = randomBytes(32);
|
|
||||||
* const nonce = randomBytes(24);
|
|
||||||
* xchacha20(key, nonce, new Uint8Array(4));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const xchacha20 = /* @__PURE__ */ createCipher(chachaCore, {
|
|
||||||
counterRight: false,
|
|
||||||
counterLength: 8,
|
|
||||||
extendNonceFn: hchacha,
|
|
||||||
allowShortKeys: false,
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* Reduced 8-round chacha, described in original paper.
|
|
||||||
* @param key - 32-byte key.
|
|
||||||
* @param nonce - 12-byte nonce.
|
|
||||||
* @param data - Input bytes to xor with the keystream.
|
|
||||||
* @param output - Optional destination buffer.
|
|
||||||
* @param counter - Initial block counter.
|
|
||||||
* @returns Encrypted or decrypted bytes.
|
|
||||||
* @example
|
|
||||||
* Uses the reduced 8-round variant for non-critical workloads with a fresh key/nonce.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* import { chacha8 } from '@noble/ciphers/chacha.js';
|
|
||||||
* import { randomBytes } from '@noble/ciphers/utils.js';
|
|
||||||
* const key = randomBytes(32);
|
|
||||||
* const nonce = randomBytes(12);
|
|
||||||
* chacha8(key, nonce, new Uint8Array(4));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const chacha8 = /* @__PURE__ */ createCipher(chachaCore, {
|
|
||||||
counterRight: false,
|
|
||||||
counterLength: 4,
|
|
||||||
rounds: 8,
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* Reduced 12-round chacha, described in original paper.
|
|
||||||
* @param key - 32-byte key.
|
|
||||||
* @param nonce - 12-byte nonce.
|
|
||||||
* @param data - Input bytes to xor with the keystream.
|
|
||||||
* @param output - Optional destination buffer.
|
|
||||||
* @param counter - Initial block counter.
|
|
||||||
* @returns Encrypted or decrypted bytes.
|
|
||||||
* @example
|
|
||||||
* Uses the reduced 12-round variant for non-critical workloads with a fresh key/nonce.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* import { chacha12 } from '@noble/ciphers/chacha.js';
|
|
||||||
* import { randomBytes } from '@noble/ciphers/utils.js';
|
|
||||||
* const key = randomBytes(32);
|
|
||||||
* const nonce = randomBytes(12);
|
|
||||||
* chacha12(key, nonce, new Uint8Array(4));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const chacha12 = /* @__PURE__ */ createCipher(chachaCore, {
|
|
||||||
counterRight: false,
|
|
||||||
counterLength: 4,
|
|
||||||
rounds: 12,
|
|
||||||
});
|
|
||||||
// Test-only hook: exposes the unrolled production core so tests can compare it
|
|
||||||
// with the simple/reference core from `test/misc/micro-ciphers.ts`.
|
|
||||||
export const __TESTS = /* @__PURE__ */ Object.freeze({ chachaCore });
|
|
||||||
// RFC 8439 §2.8.1 pad16(x): shared zero block for AAD/ciphertext padding.
|
|
||||||
const ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
|
|
||||||
// RFC 8439 §2.8 / §2.8.1: aligned inputs add nothing, otherwise append 16-(len%16) zero bytes.
|
|
||||||
const updatePadded = (h, msg) => {
|
|
||||||
h.update(msg);
|
|
||||||
const leftover = msg.length % 16;
|
|
||||||
if (leftover)
|
|
||||||
h.update(ZEROS16.subarray(leftover));
|
|
||||||
};
|
|
||||||
// RFC 8439 §2.6.1 poly1305_key_gen returns `block[0..31]`, so AEAD key
|
|
||||||
// generation only needs 32 zero bytes.
|
|
||||||
const ZEROS32 = /* @__PURE__ */ new Uint8Array(32);
|
|
||||||
function computeTag(fn, key, nonce, ciphertext, AAD) {
|
|
||||||
if (AAD !== undefined)
|
|
||||||
abytes(AAD, undefined, 'AAD');
|
|
||||||
// RFC 8439 §2.6 / §2.8: derive the Poly1305 one-time key from counter 0,
|
|
||||||
// then MAC AAD || pad16(AAD) || ciphertext || pad16(ciphertext) || len(AAD) || len(ciphertext).
|
|
||||||
const authKey = fn(key, nonce, ZEROS32);
|
|
||||||
const lengths = u64Lengths(ciphertext.length, AAD ? AAD.length : 0, true);
|
|
||||||
// Methods below can be replaced with
|
|
||||||
// `return poly1305_computeTag_small(authKey, lengths, ciphertext, AAD)`
|
|
||||||
// from `test/misc/micro-ciphers.ts`.
|
|
||||||
const h = poly1305.create(authKey);
|
|
||||||
if (AAD)
|
|
||||||
updatePadded(h, AAD);
|
|
||||||
updatePadded(h, ciphertext);
|
|
||||||
h.update(lengths);
|
|
||||||
const res = h.digest();
|
|
||||||
clean(authKey, lengths);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* AEAD algorithm from RFC 8439.
|
|
||||||
* Salsa20 and chacha (RFC 8439) use poly1305 differently.
|
|
||||||
* We could have composed them, but it's hard because of authKey:
|
|
||||||
* In salsa20, authKey changes position in salsa stream.
|
|
||||||
* In chacha, authKey can't be computed inside computeTag, it modifies the counter.
|
|
||||||
*/
|
|
||||||
export const _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
|
|
||||||
// This borrows caller key/nonce/AAD buffers by reference; mutating them after construction
|
|
||||||
// changes future encrypt/decrypt results.
|
|
||||||
const tagLength = 16;
|
|
||||||
return {
|
|
||||||
encrypt(plaintext, output) {
|
|
||||||
const plength = plaintext.length;
|
|
||||||
output = getOutput(plength + tagLength, output, false);
|
|
||||||
output.set(plaintext);
|
|
||||||
const oPlain = output.subarray(0, -tagLength);
|
|
||||||
// RFC 8439 §2.8: payload encryption starts at counter 1 because counter 0 produced the OTK.
|
|
||||||
xorStream(key, nonce, oPlain, oPlain, 1);
|
|
||||||
const tag = computeTag(xorStream, key, nonce, oPlain, AAD);
|
|
||||||
output.set(tag, plength); // append tag
|
|
||||||
clean(tag);
|
|
||||||
return output;
|
|
||||||
},
|
|
||||||
decrypt(ciphertext, output) {
|
|
||||||
output = getOutput(ciphertext.length - tagLength, output, false);
|
|
||||||
const data = ciphertext.subarray(0, -tagLength);
|
|
||||||
const passedTag = ciphertext.subarray(-tagLength);
|
|
||||||
const tag = computeTag(xorStream, key, nonce, data, AAD);
|
|
||||||
// RFC 8439 §2.8 / §4: authenticate ciphertext before decrypting it, and compare tags with
|
|
||||||
// the constant-time equalBytes() helper rather than decrypting speculative plaintext first.
|
|
||||||
if (!equalBytes(passedTag, tag)) {
|
|
||||||
clean(tag);
|
|
||||||
throw new Error('invalid tag');
|
|
||||||
}
|
|
||||||
output.set(ciphertext.subarray(0, -tagLength));
|
|
||||||
// Actual decryption
|
|
||||||
xorStream(key, nonce, output, output, 1); // start stream with i=1
|
|
||||||
clean(tag);
|
|
||||||
return output;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* ChaCha20-Poly1305 from RFC 8439.
|
|
||||||
*
|
|
||||||
* Unsafe to use random nonces under the same key, due to collision chance.
|
|
||||||
* Prefer XChaCha instead.
|
|
||||||
* @param key - 32-byte key.
|
|
||||||
* @param nonce - 12-byte nonce.
|
|
||||||
* @param AAD - Additional authenticated data.
|
|
||||||
* @returns AEAD cipher instance.
|
|
||||||
* @example
|
|
||||||
* Encrypts and authenticates plaintext with a fresh key and nonce.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* import { chacha20poly1305 } from '@noble/ciphers/chacha.js';
|
|
||||||
* import { randomBytes } from '@noble/ciphers/utils.js';
|
|
||||||
* const key = randomBytes(32);
|
|
||||||
* const nonce = randomBytes(12);
|
|
||||||
* const aad = new TextEncoder().encode('session metadata');
|
|
||||||
* const cipher = chacha20poly1305(key, nonce, aad);
|
|
||||||
* cipher.encrypt(new Uint8Array([1, 2, 3]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const chacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 12, tagLength: 16, withAAD: true },
|
|
||||||
/* @__PURE__ */ _poly1305_aead(chacha20));
|
|
||||||
/**
|
|
||||||
* XChaCha20-Poly1305 extended-nonce chacha.
|
|
||||||
*
|
|
||||||
* Can be safely used with random nonces (CSPRNG).
|
|
||||||
* See {@link https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha | the IRTF draft}.
|
|
||||||
* @param key - 32-byte key.
|
|
||||||
* @param nonce - 24-byte nonce.
|
|
||||||
* @param AAD - Additional authenticated data.
|
|
||||||
* @returns AEAD cipher instance.
|
|
||||||
* @example
|
|
||||||
* Encrypts and authenticates plaintext with a fresh key and random 24-byte nonce.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* import { xchacha20poly1305 } from '@noble/ciphers/chacha.js';
|
|
||||||
* import { randomBytes } from '@noble/ciphers/utils.js';
|
|
||||||
* const key = randomBytes(32);
|
|
||||||
* const nonce = randomBytes(24);
|
|
||||||
* const aad = new TextEncoder().encode('session metadata');
|
|
||||||
* const cipher = xchacha20poly1305(key, nonce, aad);
|
|
||||||
* cipher.encrypt(new Uint8Array([1, 2, 3]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const xchacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 24, tagLength: 16, withAAD: true },
|
|
||||||
/* @__PURE__ */ _poly1305_aead(xchacha20));
|
|
||||||
/**
|
|
||||||
* Chacha20 CSPRNG (cryptographically secure pseudorandom number generator).
|
|
||||||
* It's best to limit usage to non-production, non-critical cases: for example, test-only.
|
|
||||||
* Compatible with libtomcrypt. It does not have a specification, so unclear how secure it is.
|
|
||||||
* @param seed - Optional seed bytes mixed into the internal `key || nonce` state. When omitted,
|
|
||||||
* only 32 random bytes are mixed into the 40-byte state.
|
|
||||||
* @returns Seeded concrete `_XorStreamPRG` instance, including `clone()`.
|
|
||||||
* @example
|
|
||||||
* Seeds the test-only ChaCha20 DRBG from fresh entropy.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* import { rngChacha20 } from '@noble/ciphers/chacha.js';
|
|
||||||
* import { randomBytes } from '@noble/ciphers/utils.js';
|
|
||||||
* const seed = randomBytes(32);
|
|
||||||
* const prg = rngChacha20(seed);
|
|
||||||
* prg.randomBytes(8);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const rngChacha20 = /* @__PURE__ */ createPRG(chacha20orig, 64, 32, 8);
|
|
||||||
/**
|
|
||||||
* Chacha20/8 CSPRNG (cryptographically secure pseudorandom number generator).
|
|
||||||
* It's best to limit usage to non-production, non-critical cases: for example, test-only.
|
|
||||||
* Faster than `rngChacha20`.
|
|
||||||
* @param seed - Optional seed bytes mixed into the internal `key || nonce` state. When omitted,
|
|
||||||
* only 32 random bytes are mixed into the 44-byte state.
|
|
||||||
* @returns Seeded concrete `_XorStreamPRG` instance, including `clone()`.
|
|
||||||
* @example
|
|
||||||
* Seeds the faster test-only ChaCha8 DRBG from fresh entropy.
|
|
||||||
*
|
|
||||||
* ```ts
|
|
||||||
* import { rngChacha8 } from '@noble/ciphers/chacha.js';
|
|
||||||
* import { randomBytes } from '@noble/ciphers/utils.js';
|
|
||||||
* const seed = randomBytes(32);
|
|
||||||
* const prg = rngChacha8(seed);
|
|
||||||
* prg.randomBytes(8);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const rngChacha8 = /* @__PURE__ */ createPRG(chacha8, 64, 32, 12);
|
|
||||||
-446
@@ -1,446 +0,0 @@
|
|||||||
/**
|
|
||||||
* SHA2 hash function. A.k.a. sha256, sha384, sha512, sha512_224, sha512_256.
|
|
||||||
* SHA256 is the fastest hash implementable in JS, even faster than Blake3.
|
|
||||||
* Check out {@link https://www.rfc-editor.org/rfc/rfc4634 | RFC 4634} and
|
|
||||||
* {@link https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf | FIPS 180-4}.
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
import { Chi, HashMD, Maj, SHA224_IV, SHA256_IV, SHA384_IV, SHA512_IV } from "./_md.js";
|
|
||||||
import * as u64 from "./_u64.js";
|
|
||||||
import { clean, createHasher, oidNist, rotr } from "./utils.js";
|
|
||||||
/**
|
|
||||||
* SHA-224 / SHA-256 round constants from RFC 6234 §5.1: the first 32 bits
|
|
||||||
* of the cube roots of the first 64 primes (2..311).
|
|
||||||
*/
|
|
||||||
// prettier-ignore
|
|
||||||
const SHA256_K = /* @__PURE__ */ Uint32Array.from([
|
|
||||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
||||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
||||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
||||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
||||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
||||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
||||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
||||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
|
||||||
]);
|
|
||||||
/** Reusable SHA-224 / SHA-256 message schedule buffer `W_t` from RFC 6234 §6.2 step 1. */
|
|
||||||
const SHA256_W = /* @__PURE__ */ new Uint32Array(64);
|
|
||||||
/** Internal SHA-224 / SHA-256 compression engine from RFC 6234 §6.2. */
|
|
||||||
class SHA2_32B extends HashMD {
|
|
||||||
// We cannot use array here since array allows indexing by variable
|
|
||||||
// which means optimizer/compiler cannot use registers.
|
|
||||||
// Numeric initializers matter: starting the fields as `undefined` changes
|
|
||||||
// V8's field representation and makes sha256 3x slower (measured).
|
|
||||||
A = 0;
|
|
||||||
B = 0;
|
|
||||||
C = 0;
|
|
||||||
D = 0;
|
|
||||||
E = 0;
|
|
||||||
F = 0;
|
|
||||||
G = 0;
|
|
||||||
H = 0;
|
|
||||||
constructor(outputLen, IV) {
|
|
||||||
super(64, outputLen, 8, false);
|
|
||||||
this.A = IV[0] | 0;
|
|
||||||
this.B = IV[1] | 0;
|
|
||||||
this.C = IV[2] | 0;
|
|
||||||
this.D = IV[3] | 0;
|
|
||||||
this.E = IV[4] | 0;
|
|
||||||
this.F = IV[5] | 0;
|
|
||||||
this.G = IV[6] | 0;
|
|
||||||
this.H = IV[7] | 0;
|
|
||||||
}
|
|
||||||
get() {
|
|
||||||
const { A, B, C, D, E, F, G, H } = this;
|
|
||||||
return [A, B, C, D, E, F, G, H];
|
|
||||||
}
|
|
||||||
// prettier-ignore
|
|
||||||
set(A, B, C, D, E, F, G, H) {
|
|
||||||
this.A = A | 0;
|
|
||||||
this.B = B | 0;
|
|
||||||
this.C = C | 0;
|
|
||||||
this.D = D | 0;
|
|
||||||
this.E = E | 0;
|
|
||||||
this.F = F | 0;
|
|
||||||
this.G = G | 0;
|
|
||||||
this.H = H | 0;
|
|
||||||
}
|
|
||||||
_cloneInto(to) {
|
|
||||||
(to ||= new this.constructor()).set(...this.get());
|
|
||||||
return this._cloneIntoMeta(to);
|
|
||||||
}
|
|
||||||
process(view, offset) {
|
|
||||||
// Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array
|
|
||||||
for (let i = 0; i < 16; i++, offset += 4)
|
|
||||||
SHA256_W[i] = view.getUint32(offset, false);
|
|
||||||
for (let i = 16; i < 64; i++) {
|
|
||||||
const W15 = SHA256_W[i - 15];
|
|
||||||
const W2 = SHA256_W[i - 2];
|
|
||||||
const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ (W15 >>> 3);
|
|
||||||
const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ (W2 >>> 10);
|
|
||||||
SHA256_W[i] = (s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16]) | 0;
|
|
||||||
}
|
|
||||||
// Compression function main loop, 64 rounds
|
|
||||||
let { A, B, C, D, E, F, G, H } = this;
|
|
||||||
for (let i = 0; i < 64; i++) {
|
|
||||||
const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
|
|
||||||
const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0;
|
|
||||||
const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
|
|
||||||
const T2 = (sigma0 + Maj(A, B, C)) | 0;
|
|
||||||
H = G;
|
|
||||||
G = F;
|
|
||||||
F = E;
|
|
||||||
E = (D + T1) | 0;
|
|
||||||
D = C;
|
|
||||||
C = B;
|
|
||||||
B = A;
|
|
||||||
A = (T1 + T2) | 0;
|
|
||||||
}
|
|
||||||
// Add the compressed chunk to the current hash value
|
|
||||||
A = (A + this.A) | 0;
|
|
||||||
B = (B + this.B) | 0;
|
|
||||||
C = (C + this.C) | 0;
|
|
||||||
D = (D + this.D) | 0;
|
|
||||||
E = (E + this.E) | 0;
|
|
||||||
F = (F + this.F) | 0;
|
|
||||||
G = (G + this.G) | 0;
|
|
||||||
H = (H + this.H) | 0;
|
|
||||||
this.set(A, B, C, D, E, F, G, H);
|
|
||||||
}
|
|
||||||
roundClean() {
|
|
||||||
clean(SHA256_W);
|
|
||||||
}
|
|
||||||
destroy() {
|
|
||||||
// HashMD callers route post-destroy usability through `destroyed`; zeroizing alone still leaves
|
|
||||||
// update()/digest() callable on reused instances.
|
|
||||||
this.destroyed = true;
|
|
||||||
this.set(0, 0, 0, 0, 0, 0, 0, 0);
|
|
||||||
clean(this.buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** Internal SHA-256 hash class grounded in RFC 6234 §6.2. */
|
|
||||||
export class _SHA256 extends SHA2_32B {
|
|
||||||
constructor() {
|
|
||||||
super(32, SHA256_IV);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** Internal SHA-224 hash class grounded in RFC 6234 §6.2 and §8.5. */
|
|
||||||
export class _SHA224 extends SHA2_32B {
|
|
||||||
constructor() {
|
|
||||||
super(28, SHA224_IV);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// SHA2-512 is slower than sha256 in js because u64 operations are slow.
|
|
||||||
// SHA-384 / SHA-512 round constants from RFC 6234 §5.2:
|
|
||||||
// 80 full 64-bit words split into high/low halves.
|
|
||||||
// prettier-ignore
|
|
||||||
const K512 = /* @__PURE__ */ (() => u64.split([
|
|
||||||
'0x428a2f98d728ae22', '0x7137449123ef65cd', '0xb5c0fbcfec4d3b2f', '0xe9b5dba58189dbbc',
|
|
||||||
'0x3956c25bf348b538', '0x59f111f1b605d019', '0x923f82a4af194f9b', '0xab1c5ed5da6d8118',
|
|
||||||
'0xd807aa98a3030242', '0x12835b0145706fbe', '0x243185be4ee4b28c', '0x550c7dc3d5ffb4e2',
|
|
||||||
'0x72be5d74f27b896f', '0x80deb1fe3b1696b1', '0x9bdc06a725c71235', '0xc19bf174cf692694',
|
|
||||||
'0xe49b69c19ef14ad2', '0xefbe4786384f25e3', '0x0fc19dc68b8cd5b5', '0x240ca1cc77ac9c65',
|
|
||||||
'0x2de92c6f592b0275', '0x4a7484aa6ea6e483', '0x5cb0a9dcbd41fbd4', '0x76f988da831153b5',
|
|
||||||
'0x983e5152ee66dfab', '0xa831c66d2db43210', '0xb00327c898fb213f', '0xbf597fc7beef0ee4',
|
|
||||||
'0xc6e00bf33da88fc2', '0xd5a79147930aa725', '0x06ca6351e003826f', '0x142929670a0e6e70',
|
|
||||||
'0x27b70a8546d22ffc', '0x2e1b21385c26c926', '0x4d2c6dfc5ac42aed', '0x53380d139d95b3df',
|
|
||||||
'0x650a73548baf63de', '0x766a0abb3c77b2a8', '0x81c2c92e47edaee6', '0x92722c851482353b',
|
|
||||||
'0xa2bfe8a14cf10364', '0xa81a664bbc423001', '0xc24b8b70d0f89791', '0xc76c51a30654be30',
|
|
||||||
'0xd192e819d6ef5218', '0xd69906245565a910', '0xf40e35855771202a', '0x106aa07032bbd1b8',
|
|
||||||
'0x19a4c116b8d2d0c8', '0x1e376c085141ab53', '0x2748774cdf8eeb99', '0x34b0bcb5e19b48a8',
|
|
||||||
'0x391c0cb3c5c95a63', '0x4ed8aa4ae3418acb', '0x5b9cca4f7763e373', '0x682e6ff3d6b2b8a3',
|
|
||||||
'0x748f82ee5defb2fc', '0x78a5636f43172f60', '0x84c87814a1f0ab72', '0x8cc702081a6439ec',
|
|
||||||
'0x90befffa23631e28', '0xa4506cebde82bde9', '0xbef9a3f7b2c67915', '0xc67178f2e372532b',
|
|
||||||
'0xca273eceea26619c', '0xd186b8c721c0c207', '0xeada7dd6cde0eb1e', '0xf57d4f7fee6ed178',
|
|
||||||
'0x06f067aa72176fba', '0x0a637dc5a2c898a6', '0x113f9804bef90dae', '0x1b710b35131c471b',
|
|
||||||
'0x28db77f523047d84', '0x32caab7b40c72493', '0x3c9ebe0a15c9bebc', '0x431d67c49c100d4c',
|
|
||||||
'0x4cc5d4becb3e42b6', '0x597f299cfc657e2a', '0x5fcb6fab3ad6faec', '0x6c44198c4a475817'
|
|
||||||
].map(n => BigInt(n))))();
|
|
||||||
const SHA512_Kh = /* @__PURE__ */ (() => K512[0])();
|
|
||||||
const SHA512_Kl = /* @__PURE__ */ (() => K512[1])();
|
|
||||||
// Reusable high-half schedule buffer for the RFC 6234 §6.4 64-bit `W_t` words.
|
|
||||||
const SHA512_W_H = /* @__PURE__ */ new Uint32Array(80);
|
|
||||||
// Reusable low-half schedule buffer for the RFC 6234 §6.4 64-bit `W_t` words.
|
|
||||||
const SHA512_W_L = /* @__PURE__ */ new Uint32Array(80);
|
|
||||||
/** Internal SHA-384 / SHA-512 compression engine from RFC 6234 §6.4. */
|
|
||||||
class SHA2_64B extends HashMD {
|
|
||||||
// We cannot use array here since array allows indexing by variable
|
|
||||||
// which means optimizer/compiler cannot use registers.
|
|
||||||
// h -- high 32 bits, l -- low 32 bits
|
|
||||||
// Numeric initializers matter: starting the fields as `undefined` changes
|
|
||||||
// V8's field representation and slows hashing down (measured on sha256).
|
|
||||||
Ah = 0;
|
|
||||||
Al = 0;
|
|
||||||
Bh = 0;
|
|
||||||
Bl = 0;
|
|
||||||
Ch = 0;
|
|
||||||
Cl = 0;
|
|
||||||
Dh = 0;
|
|
||||||
Dl = 0;
|
|
||||||
Eh = 0;
|
|
||||||
El = 0;
|
|
||||||
Fh = 0;
|
|
||||||
Fl = 0;
|
|
||||||
Gh = 0;
|
|
||||||
Gl = 0;
|
|
||||||
Hh = 0;
|
|
||||||
Hl = 0;
|
|
||||||
constructor(outputLen, IV) {
|
|
||||||
super(128, outputLen, 16, false);
|
|
||||||
this.Ah = IV[0] | 0;
|
|
||||||
this.Al = IV[1] | 0;
|
|
||||||
this.Bh = IV[2] | 0;
|
|
||||||
this.Bl = IV[3] | 0;
|
|
||||||
this.Ch = IV[4] | 0;
|
|
||||||
this.Cl = IV[5] | 0;
|
|
||||||
this.Dh = IV[6] | 0;
|
|
||||||
this.Dl = IV[7] | 0;
|
|
||||||
this.Eh = IV[8] | 0;
|
|
||||||
this.El = IV[9] | 0;
|
|
||||||
this.Fh = IV[10] | 0;
|
|
||||||
this.Fl = IV[11] | 0;
|
|
||||||
this.Gh = IV[12] | 0;
|
|
||||||
this.Gl = IV[13] | 0;
|
|
||||||
this.Hh = IV[14] | 0;
|
|
||||||
this.Hl = IV[15] | 0;
|
|
||||||
}
|
|
||||||
// prettier-ignore
|
|
||||||
get() {
|
|
||||||
const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
|
|
||||||
return [Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl];
|
|
||||||
}
|
|
||||||
// prettier-ignore
|
|
||||||
set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl) {
|
|
||||||
this.Ah = Ah | 0;
|
|
||||||
this.Al = Al | 0;
|
|
||||||
this.Bh = Bh | 0;
|
|
||||||
this.Bl = Bl | 0;
|
|
||||||
this.Ch = Ch | 0;
|
|
||||||
this.Cl = Cl | 0;
|
|
||||||
this.Dh = Dh | 0;
|
|
||||||
this.Dl = Dl | 0;
|
|
||||||
this.Eh = Eh | 0;
|
|
||||||
this.El = El | 0;
|
|
||||||
this.Fh = Fh | 0;
|
|
||||||
this.Fl = Fl | 0;
|
|
||||||
this.Gh = Gh | 0;
|
|
||||||
this.Gl = Gl | 0;
|
|
||||||
this.Hh = Hh | 0;
|
|
||||||
this.Hl = Hl | 0;
|
|
||||||
}
|
|
||||||
_cloneInto(to) {
|
|
||||||
(to ||= new this.constructor()).set(...this.get());
|
|
||||||
return this._cloneIntoMeta(to);
|
|
||||||
}
|
|
||||||
process(view, offset) {
|
|
||||||
// Extend the first 16 words into the remaining 64 words w[16..79] of the message schedule array
|
|
||||||
for (let i = 0; i < 16; i++, offset += 4) {
|
|
||||||
SHA512_W_H[i] = view.getUint32(offset);
|
|
||||||
SHA512_W_L[i] = view.getUint32((offset += 4));
|
|
||||||
}
|
|
||||||
for (let i = 16; i < 80; i++) {
|
|
||||||
// s0 := (w[i-15] rightrotate 1) xor (w[i-15] rightrotate 8) xor (w[i-15] rightshift 7)
|
|
||||||
const W15h = SHA512_W_H[i - 15] | 0;
|
|
||||||
const W15l = SHA512_W_L[i - 15] | 0;
|
|
||||||
const s0h = u64.rotrSH(W15h, W15l, 1) ^ u64.rotrSH(W15h, W15l, 8) ^ u64.shrSH(W15h, W15l, 7);
|
|
||||||
const s0l = u64.rotrSL(W15h, W15l, 1) ^ u64.rotrSL(W15h, W15l, 8) ^ u64.shrSL(W15h, W15l, 7);
|
|
||||||
// s1 := (w[i-2] rightrotate 19) xor (w[i-2] rightrotate 61) xor (w[i-2] rightshift 6)
|
|
||||||
const W2h = SHA512_W_H[i - 2] | 0;
|
|
||||||
const W2l = SHA512_W_L[i - 2] | 0;
|
|
||||||
const s1h = u64.rotrSH(W2h, W2l, 19) ^ u64.rotrBH(W2h, W2l, 61) ^ u64.shrSH(W2h, W2l, 6);
|
|
||||||
const s1l = u64.rotrSL(W2h, W2l, 19) ^ u64.rotrBL(W2h, W2l, 61) ^ u64.shrSL(W2h, W2l, 6);
|
|
||||||
// SHA512_W[i] = s0 + s1 + SHA512_W[i - 7] + SHA512_W[i - 16];
|
|
||||||
const SUMl = u64.add4L(s0l, s1l, SHA512_W_L[i - 7], SHA512_W_L[i - 16]);
|
|
||||||
const SUMh = u64.add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]);
|
|
||||||
SHA512_W_H[i] = SUMh | 0;
|
|
||||||
SHA512_W_L[i] = SUMl | 0;
|
|
||||||
}
|
|
||||||
let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
|
|
||||||
// Compression function main loop, 80 rounds
|
|
||||||
for (let i = 0; i < 80; i++) {
|
|
||||||
// S1 := (e rightrotate 14) xor (e rightrotate 18) xor (e rightrotate 41)
|
|
||||||
const sigma1h = u64.rotrSH(Eh, El, 14) ^ u64.rotrSH(Eh, El, 18) ^ u64.rotrBH(Eh, El, 41);
|
|
||||||
const sigma1l = u64.rotrSL(Eh, El, 14) ^ u64.rotrSL(Eh, El, 18) ^ u64.rotrBL(Eh, El, 41);
|
|
||||||
//const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0;
|
|
||||||
const CHIh = (Eh & Fh) ^ (~Eh & Gh);
|
|
||||||
const CHIl = (El & Fl) ^ (~El & Gl);
|
|
||||||
// T1 = H + sigma1 + Chi(E, F, G) + SHA512_K[i] + SHA512_W[i]
|
|
||||||
// prettier-ignore
|
|
||||||
const T1ll = u64.add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);
|
|
||||||
const T1h = u64.add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]);
|
|
||||||
const T1l = T1ll | 0;
|
|
||||||
// S0 := (a rightrotate 28) xor (a rightrotate 34) xor (a rightrotate 39)
|
|
||||||
const sigma0h = u64.rotrSH(Ah, Al, 28) ^ u64.rotrBH(Ah, Al, 34) ^ u64.rotrBH(Ah, Al, 39);
|
|
||||||
const sigma0l = u64.rotrSL(Ah, Al, 28) ^ u64.rotrBL(Ah, Al, 34) ^ u64.rotrBL(Ah, Al, 39);
|
|
||||||
const MAJh = (Ah & Bh) ^ (Ah & Ch) ^ (Bh & Ch);
|
|
||||||
const MAJl = (Al & Bl) ^ (Al & Cl) ^ (Bl & Cl);
|
|
||||||
Hh = Gh | 0;
|
|
||||||
Hl = Gl | 0;
|
|
||||||
Gh = Fh | 0;
|
|
||||||
Gl = Fl | 0;
|
|
||||||
Fh = Eh | 0;
|
|
||||||
Fl = El | 0;
|
|
||||||
({ h: Eh, l: El } = u64.add(Dh | 0, Dl | 0, T1h | 0, T1l | 0));
|
|
||||||
Dh = Ch | 0;
|
|
||||||
Dl = Cl | 0;
|
|
||||||
Ch = Bh | 0;
|
|
||||||
Cl = Bl | 0;
|
|
||||||
Bh = Ah | 0;
|
|
||||||
Bl = Al | 0;
|
|
||||||
const All = u64.add3L(T1l, sigma0l, MAJl);
|
|
||||||
Ah = u64.add3H(All, T1h, sigma0h, MAJh);
|
|
||||||
Al = All | 0;
|
|
||||||
}
|
|
||||||
// Add the compressed chunk to the current hash value
|
|
||||||
({ h: Ah, l: Al } = u64.add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0));
|
|
||||||
({ h: Bh, l: Bl } = u64.add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0));
|
|
||||||
({ h: Ch, l: Cl } = u64.add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0));
|
|
||||||
({ h: Dh, l: Dl } = u64.add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0));
|
|
||||||
({ h: Eh, l: El } = u64.add(this.Eh | 0, this.El | 0, Eh | 0, El | 0));
|
|
||||||
({ h: Fh, l: Fl } = u64.add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0));
|
|
||||||
({ h: Gh, l: Gl } = u64.add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0));
|
|
||||||
({ h: Hh, l: Hl } = u64.add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0));
|
|
||||||
this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl);
|
|
||||||
}
|
|
||||||
roundClean() {
|
|
||||||
clean(SHA512_W_H, SHA512_W_L);
|
|
||||||
}
|
|
||||||
destroy() {
|
|
||||||
// HashMD callers route post-destroy usability through `destroyed`; zeroizing alone still leaves
|
|
||||||
// update()/digest() callable on reused instances.
|
|
||||||
this.destroyed = true;
|
|
||||||
clean(this.buffer);
|
|
||||||
this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** Internal SHA-512 hash class grounded in RFC 6234 §6.3 and §6.4. */
|
|
||||||
export class _SHA512 extends SHA2_64B {
|
|
||||||
constructor() {
|
|
||||||
super(64, SHA512_IV);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** Internal SHA-384 hash class grounded in RFC 6234 §6.3 and §6.4. */
|
|
||||||
export class _SHA384 extends SHA2_64B {
|
|
||||||
constructor() {
|
|
||||||
super(48, SHA384_IV);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Truncated SHA512/256 and SHA512/224.
|
|
||||||
* SHA512_IV is XORed with 0xa5a5a5a5a5a5a5a5, then used as "intermediary" IV of SHA512/t.
|
|
||||||
* Then t hashes string to produce result IV.
|
|
||||||
* See the repo-side derivation recipe in `test/misc/sha2-gen-iv.js`.
|
|
||||||
* These IV literals are checked against that script rather than a dedicated
|
|
||||||
* local RFC section.
|
|
||||||
*/
|
|
||||||
/** SHA-512/224 IV derived by the SHA-512/t recipe in `test/misc/sha2-gen-iv.js` and
|
|
||||||
* stored as sixteen big-endian 32-bit halves. */
|
|
||||||
const T224_IV = /* @__PURE__ */ Uint32Array.from([
|
|
||||||
0x8c3d37c8, 0x19544da2, 0x73e19966, 0x89dcd4d6, 0x1dfab7ae, 0x32ff9c82, 0x679dd514, 0x582f9fcf,
|
|
||||||
0x0f6d2b69, 0x7bd44da8, 0x77e36f73, 0x04c48942, 0x3f9d85a8, 0x6a1d36c8, 0x1112e6ad, 0x91d692a1,
|
|
||||||
]);
|
|
||||||
/** SHA-512/256 IV derived by the SHA-512/t recipe in `test/misc/sha2-gen-iv.js` and
|
|
||||||
* stored as sixteen big-endian 32-bit halves. */
|
|
||||||
const T256_IV = /* @__PURE__ */ Uint32Array.from([
|
|
||||||
0x22312194, 0xfc2bf72c, 0x9f555fa3, 0xc84c64c2, 0x2393b86b, 0x6f53b151, 0x96387719, 0x5940eabd,
|
|
||||||
0x96283ee2, 0xa88effe3, 0xbe5e1e25, 0x53863992, 0x2b0199fc, 0x2c85b8aa, 0x0eb72ddc, 0x81c52ca2,
|
|
||||||
]);
|
|
||||||
/** Internal SHA-512/224 hash class using the derived `T224_IV` and the shared
|
|
||||||
* RFC 6234 §6.4 compression engine. */
|
|
||||||
export class _SHA512_224 extends SHA2_64B {
|
|
||||||
constructor() {
|
|
||||||
super(28, T224_IV);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** Internal SHA-512/256 hash class using the derived `T256_IV` and the shared
|
|
||||||
* RFC 6234 §6.4 compression engine. */
|
|
||||||
export class _SHA512_256 extends SHA2_64B {
|
|
||||||
constructor() {
|
|
||||||
super(32, T256_IV);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* SHA2-256 hash function from RFC 4634. In JS it's the fastest: even faster than Blake3. Some info:
|
|
||||||
*
|
|
||||||
* - Trying 2^128 hashes would get 50% chance of collision, using birthday attack.
|
|
||||||
* - BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.
|
|
||||||
* - Each sha256 hash is executing 2^18 bit operations.
|
|
||||||
* - Good 2024 ASICs can do 200Th/sec with 3500 watts of power, corresponding to 2^36 hashes/joule.
|
|
||||||
* @param msg - message bytes to hash
|
|
||||||
* @param opts - Reserved hash options.
|
|
||||||
* @returns Digest bytes.
|
|
||||||
* @example
|
|
||||||
* Hash a message with SHA2-256.
|
|
||||||
* ```ts
|
|
||||||
* sha256(new Uint8Array([97, 98, 99]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const sha256 = /* @__PURE__ */ createHasher(() => new _SHA256(),
|
|
||||||
/* @__PURE__ */ oidNist(0x01));
|
|
||||||
/**
|
|
||||||
* SHA2-224 hash function from RFC 4634.
|
|
||||||
* @param msg - message bytes to hash
|
|
||||||
* @param opts - Reserved hash options.
|
|
||||||
* @returns Digest bytes.
|
|
||||||
* @example
|
|
||||||
* Hash a message with SHA2-224.
|
|
||||||
* ```ts
|
|
||||||
* sha224(new Uint8Array([97, 98, 99]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const sha224 = /* @__PURE__ */ createHasher(() => new _SHA224(),
|
|
||||||
/* @__PURE__ */ oidNist(0x04));
|
|
||||||
/**
|
|
||||||
* SHA2-512 hash function from RFC 4634.
|
|
||||||
* @param msg - message bytes to hash
|
|
||||||
* @param opts - Reserved hash options.
|
|
||||||
* @returns Digest bytes.
|
|
||||||
* @example
|
|
||||||
* Hash a message with SHA2-512.
|
|
||||||
* ```ts
|
|
||||||
* sha512(new Uint8Array([97, 98, 99]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const sha512 = /* @__PURE__ */ createHasher(() => new _SHA512(),
|
|
||||||
/* @__PURE__ */ oidNist(0x03));
|
|
||||||
/**
|
|
||||||
* SHA2-384 hash function from RFC 4634.
|
|
||||||
* @param msg - message bytes to hash
|
|
||||||
* @param opts - Reserved hash options.
|
|
||||||
* @returns Digest bytes.
|
|
||||||
* @example
|
|
||||||
* Hash a message with SHA2-384.
|
|
||||||
* ```ts
|
|
||||||
* sha384(new Uint8Array([97, 98, 99]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const sha384 = /* @__PURE__ */ createHasher(() => new _SHA384(),
|
|
||||||
/* @__PURE__ */ oidNist(0x02));
|
|
||||||
/**
|
|
||||||
* SHA2-512/256 "truncated" hash function, with improved resistance to length extension attacks.
|
|
||||||
* See the paper on {@link https://eprint.iacr.org/2010/548.pdf | truncated SHA512}.
|
|
||||||
* @param msg - message bytes to hash
|
|
||||||
* @param opts - Reserved hash options.
|
|
||||||
* @returns Digest bytes.
|
|
||||||
* @example
|
|
||||||
* Hash a message with SHA2-512/256.
|
|
||||||
* ```ts
|
|
||||||
* sha512_256(new Uint8Array([97, 98, 99]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const sha512_256 = /* @__PURE__ */ createHasher(() => new _SHA512_256(),
|
|
||||||
/* @__PURE__ */ oidNist(0x06));
|
|
||||||
/**
|
|
||||||
* SHA2-512/224 "truncated" hash function, with improved resistance to length extension attacks.
|
|
||||||
* See the paper on {@link https://eprint.iacr.org/2010/548.pdf | truncated SHA512}.
|
|
||||||
* @param msg - message bytes to hash
|
|
||||||
* @param opts - Reserved hash options.
|
|
||||||
* @returns Digest bytes.
|
|
||||||
* @example
|
|
||||||
* Hash a message with SHA2-512/224.
|
|
||||||
* ```ts
|
|
||||||
* sha512_224(new Uint8Array([97, 98, 99]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const sha512_224 = /* @__PURE__ */ createHasher(() => new _SHA512_224(),
|
|
||||||
/* @__PURE__ */ oidNist(0x05));
|
|
||||||
-687
@@ -1,687 +0,0 @@
|
|||||||
/**
|
|
||||||
* Checks if something is Uint8Array. Be careful: nodejs Buffer will return true.
|
|
||||||
* @param a - value to test
|
|
||||||
* @returns `true` when the value is a Uint8Array-compatible view.
|
|
||||||
* @example
|
|
||||||
* Check whether a value is a Uint8Array-compatible view.
|
|
||||||
* ```ts
|
|
||||||
* isBytes(new Uint8Array([1, 2, 3]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function isBytes(a) {
|
|
||||||
// Plain `instanceof Uint8Array` is too strict for some Buffer / proxy / cross-realm cases.
|
|
||||||
// The fallback still requires a real ArrayBuffer view, so plain
|
|
||||||
// JSON-deserialized `{ constructor: ... }` spoofing is rejected, and
|
|
||||||
// `BYTES_PER_ELEMENT === 1` keeps the fallback on byte-oriented views.
|
|
||||||
return (a instanceof Uint8Array ||
|
|
||||||
(ArrayBuffer.isView(a) &&
|
|
||||||
a.constructor.name === 'Uint8Array' &&
|
|
||||||
'BYTES_PER_ELEMENT' in a &&
|
|
||||||
a.BYTES_PER_ELEMENT === 1));
|
|
||||||
}
|
|
||||||
// Shared error-message prefix builder. Only called on throw paths, so assert
|
|
||||||
// success paths never pay for the string concatenation.
|
|
||||||
const atitle = (title) => (title ? `"${title}" ` : '');
|
|
||||||
/**
|
|
||||||
* Asserts something is a non-negative integer.
|
|
||||||
* @param n - number to validate
|
|
||||||
* @param title - label included in thrown errors
|
|
||||||
* @returns The validated number.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @throws On wrong argument ranges or values. {@link RangeError}
|
|
||||||
* @example
|
|
||||||
* Validate a non-negative integer option.
|
|
||||||
* ```ts
|
|
||||||
* anumber(32, 'length');
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function anumber(n, title = '') {
|
|
||||||
if (typeof n !== 'number')
|
|
||||||
throw new TypeError(atitle(title) + 'expected number, got ' + typeof n);
|
|
||||||
if (!Number.isSafeInteger(n) || n < 0)
|
|
||||||
throw new RangeError(atitle(title) + 'expected integer >= 0, got ' + n);
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Asserts something is a boolean.
|
|
||||||
* @param value - value to validate
|
|
||||||
* @param title - label included in thrown errors
|
|
||||||
* @returns The validated boolean.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Validate a boolean option.
|
|
||||||
* ```ts
|
|
||||||
* abool(true, 'enableXOF');
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function abool(value, title = '') {
|
|
||||||
if (typeof value !== 'boolean')
|
|
||||||
throw new TypeError(atitle(title) + 'expected boolean, got type=' + typeof value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Asserts something is Uint8Array.
|
|
||||||
* @param value - value to validate
|
|
||||||
* @param length - optional exact length constraint
|
|
||||||
* @param title - label included in thrown errors
|
|
||||||
* @returns The validated byte array.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @throws On wrong argument ranges or values. {@link RangeError}
|
|
||||||
* @example
|
|
||||||
* Validate that a value is a byte array.
|
|
||||||
* ```ts
|
|
||||||
* abytes(new Uint8Array([1, 2, 3]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function abytes(value, length, title = '') {
|
|
||||||
// Success path first: this runs at the start of every update() / digestInto(), and the
|
|
||||||
// common `abytes(data)` form must not pay for length handling it does not use.
|
|
||||||
if (isBytes(value) && (length === undefined || value.length === length))
|
|
||||||
return value;
|
|
||||||
// Error path: recompute freely to build the exact message.
|
|
||||||
if (length !== undefined)
|
|
||||||
anumber(length, 'length');
|
|
||||||
const bytes = isBytes(value);
|
|
||||||
const ofLen = length !== undefined ? ` of length ${length}` : '';
|
|
||||||
const got = bytes ? `length=${value.length}` : `type=${typeof value}`;
|
|
||||||
const message = atitle(title) + 'expected Uint8Array' + ofLen + ', got ' + got;
|
|
||||||
if (!bytes)
|
|
||||||
throw new TypeError(message);
|
|
||||||
throw new RangeError(message);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Copies bytes into a fresh Uint8Array.
|
|
||||||
* Buffer-style slices can alias the same backing store, so callers that need ownership should copy.
|
|
||||||
* @param bytes - source bytes to clone
|
|
||||||
* @returns Freshly allocated copy of `bytes`.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Clone a byte array before mutating it.
|
|
||||||
* ```ts
|
|
||||||
* const copy = copyBytes(new Uint8Array([1, 2, 3]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function copyBytes(bytes) {
|
|
||||||
// `Uint8Array.from(...)` would also accept arrays / other typed arrays. Keep this helper strict
|
|
||||||
// because callers use it at byte-validation boundaries before mutating the detached copy.
|
|
||||||
return Uint8Array.from(abytes(bytes));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Asserts something is a wrapped hash constructor.
|
|
||||||
* @param h - hash constructor to validate
|
|
||||||
* @throws On wrong argument types or invalid hash wrapper shape. {@link TypeError}
|
|
||||||
* @throws On invalid hash metadata ranges or values. {@link RangeError}
|
|
||||||
* @throws If the hash metadata allows empty outputs or block sizes. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Validate a callable hash wrapper.
|
|
||||||
* ```ts
|
|
||||||
* import { ahash } from '@noble/hashes/utils.js';
|
|
||||||
* import { sha256 } from '@noble/hashes/sha2.js';
|
|
||||||
* ahash(sha256);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function ahash(h) {
|
|
||||||
if (typeof h !== 'function' || typeof h.create !== 'function')
|
|
||||||
throw new TypeError('expected hash wrapped by utils.createHasher');
|
|
||||||
anumber(h.outputLen);
|
|
||||||
anumber(h.blockLen);
|
|
||||||
// HMAC and KDF callers treat these as real byte lengths; allowing zero lets fake wrappers pass
|
|
||||||
// validation and can produce empty outputs instead of failing fast.
|
|
||||||
if (h.outputLen < 1 || h.blockLen < 1)
|
|
||||||
throw new Error('hash blockLen / outputLen must be >= 1');
|
|
||||||
}
|
|
||||||
const aobject = (value, label) => {
|
|
||||||
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
||||||
throw new TypeError((label === 'object' ? '' : `"${label}" `) + 'expected object, got type=' + typeof value);
|
|
||||||
};
|
|
||||||
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`);
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Asserts a hash instance has not been destroyed or finished.
|
|
||||||
* @param instance - hash instance to validate
|
|
||||||
* @param checkFinished - whether to reject finalized instances
|
|
||||||
* @throws If the hash instance has already been destroyed or finalized. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Validate that a hash instance is still usable.
|
|
||||||
* ```ts
|
|
||||||
* import { aexists } from '@noble/hashes/utils.js';
|
|
||||||
* import { sha256 } from '@noble/hashes/sha2.js';
|
|
||||||
* const hash = sha256.create();
|
|
||||||
* aexists(hash);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function aexists(instance, checkFinished = true) {
|
|
||||||
// Runs on every update()/digestInto(); the flags are library-owned booleans, so only their
|
|
||||||
// truthiness is checked - re-validating their type per call was pure hot-path overhead.
|
|
||||||
if (instance.destroyed)
|
|
||||||
throw new Error('hash was destroyed');
|
|
||||||
if (checkFinished && instance.finished)
|
|
||||||
throw new Error('digest() was already called');
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Asserts output is a sufficiently-sized byte array.
|
|
||||||
* @param out - destination buffer
|
|
||||||
* @param instance - hash instance providing output length
|
|
||||||
* Oversized buffers are allowed; downstream code only promises to fill the first `outputLen` bytes.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @throws On wrong argument ranges or values. {@link RangeError}
|
|
||||||
* @example
|
|
||||||
* Validate a caller-provided digest buffer.
|
|
||||||
* ```ts
|
|
||||||
* import { aoutput } from '@noble/hashes/utils.js';
|
|
||||||
* import { sha256 } from '@noble/hashes/sha2.js';
|
|
||||||
* const hash = sha256.create();
|
|
||||||
* aoutput(new Uint8Array(hash.outputLen), hash);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function aoutput(out, instance) {
|
|
||||||
abytes(out, undefined, 'output');
|
|
||||||
// `outputLen` is a library-owned readonly number; the negated comparison keeps failing fast
|
|
||||||
// when it is missing/NaN (comparisons with undefined/NaN are false) without an anumber() call.
|
|
||||||
const min = instance.outputLen;
|
|
||||||
if (!(out.length >= min)) {
|
|
||||||
throw new RangeError('"output" expected length >= ' + min);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Casts a typed array view to Uint8Array.
|
|
||||||
* @param arr - source typed array
|
|
||||||
* @returns Uint8Array view over the same buffer.
|
|
||||||
* @example
|
|
||||||
* Reinterpret a typed array as bytes.
|
|
||||||
* ```ts
|
|
||||||
* u8(new Uint32Array([1, 2]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function u8(arr) {
|
|
||||||
return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Casts a typed array view to Uint32Array.
|
|
||||||
* `arr.byteOffset` must already be 4-byte aligned or the platform
|
|
||||||
* Uint32Array constructor will throw.
|
|
||||||
* @param arr - source typed array
|
|
||||||
* @returns Uint32Array view over the same buffer.
|
|
||||||
* @example
|
|
||||||
* Reinterpret a byte array as 32-bit words.
|
|
||||||
* ```ts
|
|
||||||
* u32(new Uint8Array(8));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function u32(arr) {
|
|
||||||
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Zeroizes typed arrays in place. Warning: JS provides no guarantees.
|
|
||||||
* @param arrays - arrays to overwrite with zeros
|
|
||||||
* @example
|
|
||||||
* Zeroize sensitive buffers in place.
|
|
||||||
* ```ts
|
|
||||||
* clean(new Uint8Array([1, 2, 3]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function clean(...arrays) {
|
|
||||||
for (let i = 0; i < arrays.length; i++) {
|
|
||||||
arrays[i].fill(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Creates a DataView for byte-level manipulation.
|
|
||||||
* @param arr - source typed array
|
|
||||||
* @returns DataView over the same buffer region.
|
|
||||||
* @example
|
|
||||||
* Create a DataView over an existing buffer.
|
|
||||||
* ```ts
|
|
||||||
* createView(new Uint8Array(4));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function createView(arr) {
|
|
||||||
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Rotate-right operation for uint32 values.
|
|
||||||
* @param word - source word
|
|
||||||
* @param shift - shift amount in bits
|
|
||||||
* @returns Rotated word.
|
|
||||||
* @example
|
|
||||||
* Rotate a 32-bit word to the right.
|
|
||||||
* ```ts
|
|
||||||
* rotr(0x12345678, 8);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function rotr(word, shift) {
|
|
||||||
return (word << (32 - shift)) | (word >>> shift);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Rotate-left operation for uint32 values.
|
|
||||||
* @param word - source word
|
|
||||||
* @param shift - shift amount in bits
|
|
||||||
* @returns Rotated word.
|
|
||||||
* @example
|
|
||||||
* Rotate a 32-bit word to the left.
|
|
||||||
* ```ts
|
|
||||||
* rotl(0x12345678, 8);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function rotl(word, shift) {
|
|
||||||
return (word << shift) | ((word >>> (32 - shift)) >>> 0);
|
|
||||||
}
|
|
||||||
/** Whether the current platform is little-endian. */
|
|
||||||
export const isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44)();
|
|
||||||
/**
|
|
||||||
* Byte-swap operation for uint32 values.
|
|
||||||
* @param word - source word
|
|
||||||
* @returns Word with reversed byte order.
|
|
||||||
* @example
|
|
||||||
* Reverse the byte order of a 32-bit word.
|
|
||||||
* ```ts
|
|
||||||
* byteSwap(0x11223344);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function byteSwap(word) {
|
|
||||||
return (((word << 24) & 0xff000000) |
|
|
||||||
((word << 8) & 0xff0000) |
|
|
||||||
((word >>> 8) & 0xff00) |
|
|
||||||
((word >>> 24) & 0xff));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Conditionally byte-swaps one 32-bit word on big-endian platforms.
|
|
||||||
* @param n - source word
|
|
||||||
* @returns Original or byte-swapped word depending on platform endianness.
|
|
||||||
* @example
|
|
||||||
* Normalize a 32-bit word for host endianness.
|
|
||||||
* ```ts
|
|
||||||
* swap8IfBE(0x11223344);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const swap8IfBE = isLE
|
|
||||||
? (n) => n
|
|
||||||
: (n) => byteSwap(n) >>> 0;
|
|
||||||
/**
|
|
||||||
* Byte-swaps every word of a Uint32Array in place.
|
|
||||||
* @param arr - array to mutate
|
|
||||||
* @returns The same array after mutation; callers pass live state arrays here.
|
|
||||||
* @example
|
|
||||||
* Reverse the byte order of every word in place.
|
|
||||||
* ```ts
|
|
||||||
* byteSwap32(new Uint32Array([0x11223344]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function byteSwap32(arr) {
|
|
||||||
for (let i = 0; i < arr.length; i++) {
|
|
||||||
arr[i] = byteSwap(arr[i]);
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Conditionally byte-swaps a Uint32Array on big-endian platforms.
|
|
||||||
* @param u - array to normalize for host endianness
|
|
||||||
* @returns Original or byte-swapped array depending on platform endianness.
|
|
||||||
* On big-endian runtimes this mutates `u` in place via `byteSwap32(...)`.
|
|
||||||
* @example
|
|
||||||
* Normalize a word array for host endianness.
|
|
||||||
* ```ts
|
|
||||||
* swap32IfBE(new Uint32Array([0x11223344]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const swap32IfBE = isLE
|
|
||||||
? (u) => u
|
|
||||||
: byteSwap32;
|
|
||||||
// Built-in hex conversion https://caniuse.com/mdn-javascript_builtins_uint8array_fromhex
|
|
||||||
const hasHexBuiltin = /* @__PURE__ */ (() =>
|
|
||||||
// @ts-ignore
|
|
||||||
typeof Uint8Array.from([]).toHex === 'function' && typeof Uint8Array.fromHex === 'function')();
|
|
||||||
// Array where index 0xf0 (240) is mapped to string 'f0'
|
|
||||||
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
|
|
||||||
/**
|
|
||||||
* Convert byte array to hex string.
|
|
||||||
* Uses the built-in function when available and assumes it matches the tested
|
|
||||||
* fallback semantics.
|
|
||||||
* @param bytes - bytes to encode
|
|
||||||
* @returns Lowercase hexadecimal string.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Convert bytes to lowercase hexadecimal.
|
|
||||||
* ```ts
|
|
||||||
* bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])); // 'cafe0123'
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function bytesToHex(bytes) {
|
|
||||||
abytes(bytes);
|
|
||||||
// @ts-ignore
|
|
||||||
if (hasHexBuiltin)
|
|
||||||
return bytes.toHex();
|
|
||||||
// pre-caching improves the speed 6x
|
|
||||||
let hex = '';
|
|
||||||
for (let i = 0; i < bytes.length; i++) {
|
|
||||||
hex += hexes[bytes[i]];
|
|
||||||
}
|
|
||||||
return hex;
|
|
||||||
}
|
|
||||||
// Strict ASCII nibble parser: non-ASCII hex lookalikes are rejected as undefined.
|
|
||||||
// ASCII codes: '0'..'9' = 48..57, 'A'..'F' = 65..70, 'a'..'f' = 97..102.
|
|
||||||
// prettier-ignore
|
|
||||||
function asciiToBase16(ch) {
|
|
||||||
return ch >= 48 && ch <= 57 ? ch - 48 // '2' => 50-48
|
|
||||||
: ch >= 65 && ch <= 70 ? ch - (65 - 10) // 'B' => 66-(65-10)
|
|
||||||
: ch >= 97 && ch <= 102 ? ch - (97 - 10) // 'b' => 98-(97-10)
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Convert hex string to byte array. Uses built-in function, when available.
|
|
||||||
* @param hex - hexadecimal string to decode
|
|
||||||
* @returns Decoded bytes.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @throws On wrong argument ranges or values. {@link RangeError}
|
|
||||||
* @example
|
|
||||||
* Decode lowercase hexadecimal into bytes.
|
|
||||||
* ```ts
|
|
||||||
* hexToBytes('cafe0123'); // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function hexToBytes(hex) {
|
|
||||||
if (typeof hex !== 'string')
|
|
||||||
throw new TypeError('hex string expected, got ' + typeof hex);
|
|
||||||
if (hasHexBuiltin) {
|
|
||||||
try {
|
|
||||||
return Uint8Array.fromHex(hex);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
if (error instanceof SyntaxError)
|
|
||||||
throw new RangeError(error.message);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const hl = hex.length;
|
|
||||||
const al = hl / 2;
|
|
||||||
if (hl % 2)
|
|
||||||
throw new RangeError('hex string expected, got unpadded hex of length ' + hl);
|
|
||||||
const array = new Uint8Array(al);
|
|
||||||
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
||||||
const n1 = asciiToBase16(hex.charCodeAt(hi)); // parse first char, multiply it by 16
|
|
||||||
const n2 = asciiToBase16(hex.charCodeAt(hi + 1)); // parse second char
|
|
||||||
if (n1 === undefined || n2 === undefined) {
|
|
||||||
const char = hex[hi] + hex[hi + 1];
|
|
||||||
throw new RangeError('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
|
||||||
}
|
|
||||||
array[ai] = n1 * 16 + n2; // example: 'A9' => 10*16 + 9
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* Yield to the next scheduler tick.
|
|
||||||
* ```ts
|
|
||||||
* await nextTick();
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
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 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}
|
|
||||||
* @throws On wrong argument ranges or values. {@link RangeError}
|
|
||||||
* @example
|
|
||||||
* Run a loop that periodically yields back to the event loop.
|
|
||||||
* ```ts
|
|
||||||
* await asyncLoop(2, 0, () => {});
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export async function asyncLoop(iters, tick, cb, onReject) {
|
|
||||||
anumber(iters, 'iters');
|
|
||||||
anumber(tick, 'tick');
|
|
||||||
if (typeof cb !== 'function')
|
|
||||||
throw new TypeError('callback must be a function');
|
|
||||||
// Callback is synchronous by contract; asyncLoop only yields between sync work windows.
|
|
||||||
let ts = Date.now();
|
|
||||||
for (let i = 0; i < iters; i++) {
|
|
||||||
cb(i);
|
|
||||||
// Date.now() is not monotonic, so in case if clock goes backwards we return return control too
|
|
||||||
const diff = Date.now() - ts;
|
|
||||||
if (diff >= 0 && diff < tick)
|
|
||||||
continue;
|
|
||||||
await nextTick(onReject);
|
|
||||||
// Track only synchronous work time; scheduler delay after yielding is outside our budget.
|
|
||||||
ts = Date.now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Converts string to bytes using UTF8 encoding.
|
|
||||||
* Built-in doesn't validate input to be string: we do the check.
|
|
||||||
* Non-ASCII details are delegated to the platform `TextEncoder`.
|
|
||||||
* @param str - string to encode
|
|
||||||
* @returns UTF-8 encoded bytes.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Encode a string as UTF-8 bytes.
|
|
||||||
* ```ts
|
|
||||||
* utf8ToBytes('abc'); // Uint8Array.from([97, 98, 99])
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function utf8ToBytes(str) {
|
|
||||||
if (typeof str !== 'string')
|
|
||||||
throw new TypeError('string expected');
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Helper for KDFs: consumes Uint8Array or string.
|
|
||||||
* String inputs are UTF-8 encoded; byte-array inputs stay aliased to the caller buffer.
|
|
||||||
* @param data - user-provided KDF input
|
|
||||||
* @param errorTitle - label included in thrown errors
|
|
||||||
* @returns Byte representation of the input.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Normalize KDF input to bytes.
|
|
||||||
* ```ts
|
|
||||||
* kdfInputToBytes('password');
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function kdfInputToBytes(data, errorTitle = '') {
|
|
||||||
if (typeof data === 'string')
|
|
||||||
return utf8ToBytes(data);
|
|
||||||
return abytes(data, undefined, errorTitle);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Copies several Uint8Arrays into one.
|
|
||||||
* @param arrays - arrays to concatenate
|
|
||||||
* @returns Concatenated byte array.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Concatenate multiple byte arrays.
|
|
||||||
* ```ts
|
|
||||||
* concatBytes(new Uint8Array([1]), new Uint8Array([2]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function concatBytes(...arrays) {
|
|
||||||
let sum = 0;
|
|
||||||
for (let i = 0; i < arrays.length; i++) {
|
|
||||||
const a = arrays[i];
|
|
||||||
abytes(a);
|
|
||||||
sum += a.length;
|
|
||||||
}
|
|
||||||
const res = new Uint8Array(sum);
|
|
||||||
for (let i = 0, pad = 0; i < arrays.length; i++) {
|
|
||||||
const a = arrays[i];
|
|
||||||
res.set(a, pad);
|
|
||||||
pad += a.length;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Validates declared required and optional field types on a plain object.
|
|
||||||
* This walks field schemas and formats detailed errors, so avoid it on hot paths; use direct
|
|
||||||
* one-line guards such as `abytes()`, `abool()`, or `anumber()` instead.
|
|
||||||
* @param object - object to validate
|
|
||||||
* @param fields - map of required field names to expected types
|
|
||||||
* @param optFields - map of optional field names to expected types
|
|
||||||
* @param title - label included in thrown errors
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Validate required and optional option fields.
|
|
||||||
* ```ts
|
|
||||||
* validateObject({ N: 1024, dkLen: 32 }, { N: 'number' }, { dkLen: 'number' });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const validateObject = (object, fields = {}, optFields = {}, title = 'object') => {
|
|
||||||
aobject(object, title);
|
|
||||||
aobject(fields, 'fields');
|
|
||||||
aobject(optFields, 'optFields');
|
|
||||||
function checkField(fieldName, expectedType, isOpt) {
|
|
||||||
const label = title === 'object' ? `param "${String(fieldName)}"` : `"${title}.${String(fieldName)}"`;
|
|
||||||
// Config fields must be explicit own properties. Optional inherited values are rejected too
|
|
||||||
// because callers keep reading the same options object after validation.
|
|
||||||
const val = object[fieldName];
|
|
||||||
// Runtime objects such as Field instances intentionally satisfy required method slots
|
|
||||||
// via their shared prototype.
|
|
||||||
if (!Object.hasOwn(object, fieldName) &&
|
|
||||||
(isOpt ? val !== undefined : expectedType !== 'function')) {
|
|
||||||
throw new TypeError(`${label} is invalid: expected own property`);
|
|
||||||
}
|
|
||||||
if (isOpt && val === undefined)
|
|
||||||
return;
|
|
||||||
const current = typeof val;
|
|
||||||
if (current !== expectedType || val === null)
|
|
||||||
throw new TypeError(`${label} is invalid: expected ${expectedType}, got ${current}`);
|
|
||||||
}
|
|
||||||
const iter = (f, isOpt) => Object.entries(f).forEach(([k, v]) => checkField(k, v, isOpt));
|
|
||||||
iter(fields, false);
|
|
||||||
iter(optFields, true);
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Merges default options and passed options.
|
|
||||||
* @param defaults - base option object
|
|
||||||
* @param opts - user overrides
|
|
||||||
* @param title - label included in thrown override errors
|
|
||||||
* @returns Fresh merged option object with a null prototype.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Merge user overrides onto default options.
|
|
||||||
* ```ts
|
|
||||||
* checkOpts({ dkLen: 32 }, { asyncTick: 10 });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function checkOpts(defaults, opts, title = 'opts') {
|
|
||||||
aopts(defaults, 'defaults');
|
|
||||||
if (opts !== undefined)
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Creates a callable hash function from a stateful class constructor.
|
|
||||||
* @param hashCons - hash constructor or factory
|
|
||||||
* @param info - optional metadata such as DER OID
|
|
||||||
* @returns Frozen callable hash wrapper with `.create()`.
|
|
||||||
* Wrapper construction eagerly calls `hashCons(undefined)` once to read
|
|
||||||
* `outputLen` / `blockLen`, so constructor side effects happen at module
|
|
||||||
* init time.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @example
|
|
||||||
* Wrap a stateful hash constructor into a callable helper.
|
|
||||||
* ```ts
|
|
||||||
* import { createHasher } from '@noble/hashes/utils.js';
|
|
||||||
* import { sha256 } from '@noble/hashes/sha2.js';
|
|
||||||
* const wrapped = createHasher(sha256.create, { oid: sha256.oid });
|
|
||||||
* wrapped(new Uint8Array([1]));
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function createHasher(hashCons, info = {}) {
|
|
||||||
if (typeof hashCons !== 'function')
|
|
||||||
throw new TypeError('"hashCons" expected function, got type=' + typeof hashCons);
|
|
||||||
info = checkOpts({}, info, 'info');
|
|
||||||
const hashC = (msg, opts) => hashCons(opts)
|
|
||||||
.update(msg)
|
|
||||||
.digest();
|
|
||||||
const tmp = hashCons(undefined);
|
|
||||||
hashC.outputLen = tmp.outputLen;
|
|
||||||
hashC.blockLen = tmp.blockLen;
|
|
||||||
hashC.canXOF = tmp.canXOF;
|
|
||||||
hashC.create = (opts) => hashCons(opts);
|
|
||||||
Object.assign(hashC, info);
|
|
||||||
return Object.freeze(hashC);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Cryptographically secure PRNG backed by `crypto.getRandomValues`.
|
|
||||||
* @param bytesLength - number of random bytes to generate
|
|
||||||
* @returns Random bytes.
|
|
||||||
* The platform `getRandomValues()` implementation still defines any
|
|
||||||
* single-call length cap, and this helper rejects oversize requests
|
|
||||||
* with a stable library `RangeError` instead of host-specific errors.
|
|
||||||
* @throws On wrong argument types. {@link TypeError}
|
|
||||||
* @throws On wrong argument ranges or values. {@link RangeError}
|
|
||||||
* @throws If the current runtime does not provide `crypto.getRandomValues`. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Generate a fresh random key or nonce.
|
|
||||||
* ```ts
|
|
||||||
* const key = randomBytes(16);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function randomBytes(bytesLength = 32) {
|
|
||||||
// Match the repo's other length-taking helpers instead of relying on Uint8Array coercion.
|
|
||||||
anumber(bytesLength, 'bytesLength');
|
|
||||||
const cr = typeof globalThis === 'object' ? globalThis.crypto : null;
|
|
||||||
if (typeof cr?.getRandomValues !== 'function')
|
|
||||||
throw new Error('crypto.getRandomValues must be defined');
|
|
||||||
// Web Cryptography API Level 2 §10.1.1:
|
|
||||||
// if `byteLength > 65536`, throw `QuotaExceededError`.
|
|
||||||
// Keep the guard explicit so callers can see the quota in code
|
|
||||||
// instead of discovering it by reading the spec or host errors.
|
|
||||||
// This wrapper surfaces the same quota as a stable library RangeError.
|
|
||||||
if (bytesLength > 65536)
|
|
||||||
throw new RangeError(`"bytesLength" expected <= 65536, got ${bytesLength}`);
|
|
||||||
return cr.getRandomValues(new Uint8Array(bytesLength));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Creates OID metadata for NIST hashes with prefix `06 09 60 86 48 01 65 03 04 02`.
|
|
||||||
* @param suffix - final OID byte for the selected hash.
|
|
||||||
* The helper accepts any byte even though only the documented NIST hash
|
|
||||||
* suffixes are meaningful downstream.
|
|
||||||
* @returns Object containing the DER-encoded OID.
|
|
||||||
* @example
|
|
||||||
* Build OID metadata for a NIST hash.
|
|
||||||
* ```ts
|
|
||||||
* oidNist(0x01);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const oidNist = (suffix) => ({
|
|
||||||
// Current NIST hashAlgs suffixes used here fit in one DER subidentifier octet.
|
|
||||||
// Larger suffix values would need base-128 OID encoding and a different length byte.
|
|
||||||
oid: Uint8Array.from([0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, suffix]),
|
|
||||||
});
|
|
||||||
-577
@@ -1,577 +0,0 @@
|
|||||||
/**
|
|
||||||
* Argon2 KDF from RFC 9106. Can be used to create a key from password and salt.
|
|
||||||
* We suggest to use Scrypt. JS Argon is 2-10x slower than native code because of 64-bitness:
|
|
||||||
* * argon uses uint64, but JS doesn't have fast uint64array
|
|
||||||
* * uint64 multiplication is 1/3 of time
|
|
||||||
* * `P` function would be very nice with u64, because most of value will be in registers,
|
|
||||||
* hovewer with u32 it will require 32 registers, which is too much.
|
|
||||||
* * JS arrays do slow bound checks, so reading from `A2_BUF` slows it down
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
import { blake2b } from "./blake2.js";
|
|
||||||
import { anumber, checkOpts, clean, kdfInputToBytes, nextTick, swap32IfBE, swap8IfBE, u32, u8, } from "./utils.js";
|
|
||||||
// RFC 9106 §3.1 type `y`: 0 = Argon2d, 1 = Argon2i, 2 = Argon2id. The numeric values are the
|
|
||||||
// spec-bound part here; the object keys are internal labels.
|
|
||||||
const AT = { Argon2d: 0, Argon2i: 1, Argon2id: 2 };
|
|
||||||
// RFC 9106 sync points constant `SL = 4`, fixed by the design rather than exposed as a tuning knob.
|
|
||||||
const ARGON2_SYNC_POINTS = 4;
|
|
||||||
// Preserve Argon2's `LE32(len(X)) || X` encoding for omitted
|
|
||||||
// optional fields by emitting empty bytes.
|
|
||||||
const abytesOrZero = (buf, errorTitle = '') => {
|
|
||||||
if (buf === undefined)
|
|
||||||
return Uint8Array.of();
|
|
||||||
return kdfInputToBytes(buf, errorTitle);
|
|
||||||
};
|
|
||||||
// Temporary block buffer.
|
|
||||||
// 1024-byte block: 256 u32 = 128 interleaved low/high halves = RFC's
|
|
||||||
// 8x8 matrix of 16-byte registers.
|
|
||||||
const A2_BUF = new Uint32Array(256);
|
|
||||||
// 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) 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) {
|
|
||||||
let Al = A2_BUF[2 * a], Ah = A2_BUF[2 * a + 1]; // prettier-ignore
|
|
||||||
let Bl = A2_BUF[2 * b], Bh = A2_BUF[2 * b + 1]; // prettier-ignore
|
|
||||||
let Cl = A2_BUF[2 * c], Ch = A2_BUF[2 * c + 1]; // prettier-ignore
|
|
||||||
let Dl = A2_BUF[2 * d], Dh = A2_BUF[2 * d + 1]; // prettier-ignore
|
|
||||||
let ml = 0, mh = 0, rl = 0, xh = 0, xl = 0; // prettier-ignore
|
|
||||||
// A = blamka(A, B); D = rotr64(D ^ A, 32)
|
|
||||||
ml = Math.imul(Al, Bl);
|
|
||||||
mh = (((Al >>> 0) * (Bl >>> 0) - (ml >>> 0)) / 0x100000000 + 0.5) | 0; // prettier-ignore
|
|
||||||
rl = (Al >>> 0) + (Bl >>> 0) + ((ml << 1) >>> 0);
|
|
||||||
Ah = (Ah + Bh + ((mh << 1) | (ml >>> 31)) + ((rl / 0x100000000) | 0)) | 0;
|
|
||||||
Al = rl | 0; // prettier-ignore
|
|
||||||
xh = Dh ^ Ah;
|
|
||||||
xl = Dl ^ Al; // prettier-ignore
|
|
||||||
Dh = xl;
|
|
||||||
Dl = xh; // prettier-ignore
|
|
||||||
// C = blamka(C, D); B = rotr64(B ^ C, 24)
|
|
||||||
ml = Math.imul(Cl, Dl);
|
|
||||||
mh = (((Cl >>> 0) * (Dl >>> 0) - (ml >>> 0)) / 0x100000000 + 0.5) | 0; // prettier-ignore
|
|
||||||
rl = (Cl >>> 0) + (Dl >>> 0) + ((ml << 1) >>> 0);
|
|
||||||
Ch = (Ch + Dh + ((mh << 1) | (ml >>> 31)) + ((rl / 0x100000000) | 0)) | 0;
|
|
||||||
Cl = rl | 0; // prettier-ignore
|
|
||||||
xh = Bh ^ Ch;
|
|
||||||
xl = Bl ^ Cl; // 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 = (((Al >>> 0) * (Bl >>> 0) - (ml >>> 0)) / 0x100000000 + 0.5) | 0; // prettier-ignore
|
|
||||||
rl = (Al >>> 0) + (Bl >>> 0) + ((ml << 1) >>> 0);
|
|
||||||
Ah = (Ah + Bh + ((mh << 1) | (ml >>> 31)) + ((rl / 0x100000000) | 0)) | 0;
|
|
||||||
Al = rl | 0; // prettier-ignore
|
|
||||||
xh = Dh ^ Ah;
|
|
||||||
xl = Dl ^ Al; // 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 = (((Cl >>> 0) * (Dl >>> 0) - (ml >>> 0)) / 0x100000000 + 0.5) | 0; // prettier-ignore
|
|
||||||
rl = (Cl >>> 0) + (Dl >>> 0) + ((ml << 1) >>> 0);
|
|
||||||
Ch = (Ch + Dh + ((mh << 1) | (ml >>> 31)) + ((rl / 0x100000000) | 0)) | 0;
|
|
||||||
Cl = rl | 0; // prettier-ignore
|
|
||||||
xh = Bh ^ Ch;
|
|
||||||
xl = Bl ^ Cl; // 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));
|
|
||||||
((A2_BUF[2 * b] = Bl), (A2_BUF[2 * b + 1] = Bh));
|
|
||||||
((A2_BUF[2 * c] = Cl), (A2_BUF[2 * c + 1] = Ch));
|
|
||||||
((A2_BUF[2 * d] = Dl), (A2_BUF[2 * d + 1] = Dh));
|
|
||||||
}
|
|
||||||
// Argon2 permutation over 16 register indices into `A2_BUF`, not the register values themselves.
|
|
||||||
// RFC 9106 Figure 17: these arguments are the 16 `v0..v15` 64-bit word
|
|
||||||
// indices inside eight 16-byte inputs, not copied word values.
|
|
||||||
// prettier-ignore
|
|
||||||
function P(v00, v01, v02, v03, v04, v05, v06, v07, v08, v09, v10, v11, v12, v13, v14, v15) {
|
|
||||||
// RFC 9106 Figure 18: first apply GB across rows, then across columns of the 8x8 register matrix.
|
|
||||||
G(v00, v04, v08, v12);
|
|
||||||
G(v01, v05, v09, v13);
|
|
||||||
G(v02, v06, v10, v14);
|
|
||||||
G(v03, v07, v11, v15);
|
|
||||||
G(v00, v05, v10, v15);
|
|
||||||
G(v01, v06, v11, v12);
|
|
||||||
G(v02, v07, v08, v13);
|
|
||||||
G(v03, v04, v09, v14);
|
|
||||||
}
|
|
||||||
function block(x, xPos, yPos, outPos, needXor) {
|
|
||||||
// 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)
|
|
||||||
for (let i = 0; i < 128; i += 16) {
|
|
||||||
// prettier-ignore
|
|
||||||
P(i, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7, i + 8, i + 9, i + 10, i + 11, i + 12, i + 13, i + 14, i + 15);
|
|
||||||
}
|
|
||||||
// columns (8 strided 16-register groups)
|
|
||||||
for (let i = 0; i < 16; i += 2) {
|
|
||||||
// prettier-ignore
|
|
||||||
P(i, i + 1, i + 16, i + 17, i + 32, i + 33, i + 48, i + 49, i + 64, i + 65, i + 80, i + 81, i + 96, i + 97, i + 112, i + 113);
|
|
||||||
}
|
|
||||||
// RFC 9106 step 6: passes after the first XOR the old destination block into the new G(X, Y).
|
|
||||||
for (let i = 0; i < 256; i++)
|
|
||||||
x[outPos + i] ^= A2_BUF[i];
|
|
||||||
clean(A2_BUF);
|
|
||||||
}
|
|
||||||
// Variable-Length Hash Function H'
|
|
||||||
// Returns bytes, not words; 1024-byte block callers explicitly reinterpret with `u32(...)`.
|
|
||||||
function Hp(A, dkLen) {
|
|
||||||
const A8 = u8(A);
|
|
||||||
const T = new Uint32Array(1);
|
|
||||||
const T8 = u8(T);
|
|
||||||
// Argon2 H' prefixes dkLen as LE32; native Uint32Array writes would serialize as BE on s390x.
|
|
||||||
T[0] = swap8IfBE(dkLen);
|
|
||||||
// Fast path
|
|
||||||
if (dkLen <= 64)
|
|
||||||
return blake2b.create({ dkLen }).update(T8).update(A8).digest();
|
|
||||||
const out = new Uint8Array(dkLen);
|
|
||||||
let V = blake2b.create({}).update(T8).update(A8).digest();
|
|
||||||
let pos = 0;
|
|
||||||
// RFC 9106 Figure 8: each intermediate `V_i` contributes only `W_i`, its first 32 bytes; only
|
|
||||||
// `V_{r+1}` is emitted in full at the remaining length.
|
|
||||||
out.set(V.subarray(0, 32));
|
|
||||||
pos += 32;
|
|
||||||
// Rest blocks
|
|
||||||
for (; dkLen - pos > 64; pos += 32) {
|
|
||||||
const Vh = blake2b.create({}).update(V);
|
|
||||||
Vh.digestInto(V);
|
|
||||||
Vh.destroy();
|
|
||||||
out.set(V.subarray(0, 32), pos);
|
|
||||||
}
|
|
||||||
// Last block
|
|
||||||
out.set(blake2b(V, { dkLen: dkLen - pos }), pos);
|
|
||||||
clean(V, T);
|
|
||||||
// H' is byte-oriented; returning `u32(out)` would silently drop dkLen % 4 tail bytes.
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
// Used only inside argon2Blocks!
|
|
||||||
function indexAlpha(r, s, laneLen, segmentLen, index, randL, sameLane = false) {
|
|
||||||
// RFC 9106 §3.4.2 Figures 12-13: map `J1` / `J2` into the current lane's reference area `W`.
|
|
||||||
let area;
|
|
||||||
if (r === 0) {
|
|
||||||
if (s === 0)
|
|
||||||
area = index - 1;
|
|
||||||
else if (sameLane)
|
|
||||||
area = s * segmentLen + index - 1;
|
|
||||||
else
|
|
||||||
area = s * segmentLen + (index == 0 ? -1 : 0);
|
|
||||||
}
|
|
||||||
else if (sameLane)
|
|
||||||
area = laneLen - segmentLen + index - 1;
|
|
||||||
else
|
|
||||||
area = laneLen - segmentLen + (index == 0 ? -1 : 0);
|
|
||||||
const startPos = r !== 0 && s !== ARGON2_SYNC_POINTS - 1 ? (s + 1) * segmentLen : 0;
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
// Exclusive `2^32` sentinel used by `isU32(...)`, not the inclusive maximum u32 value.
|
|
||||||
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]`.
|
|
||||||
function isU32(num) {
|
|
||||||
return Number.isSafeInteger(num) && num >= 0 && num < maxUint32;
|
|
||||||
}
|
|
||||||
function argon2Opts(opts = {}) {
|
|
||||||
opts = checkOpts({}, opts);
|
|
||||||
const merged = {
|
|
||||||
t: 3,
|
|
||||||
m: ARGON2_DEFAULT_MEMORY,
|
|
||||||
p: 1,
|
|
||||||
version: 0x13,
|
|
||||||
dkLen: 32,
|
|
||||||
maxmem: ARGON2_DEFAULT_MAXMEM,
|
|
||||||
asyncTick: 10,
|
|
||||||
};
|
|
||||||
// Unknown keys are copied through unchanged here and later ignored unless
|
|
||||||
// destructuring consumes them.
|
|
||||||
for (let [k, v] of Object.entries(opts))
|
|
||||||
if (v !== undefined)
|
|
||||||
merged[k] = v;
|
|
||||||
const { dkLen, p, m, t, version, onProgress, asyncTick } = merged;
|
|
||||||
// RFC 9106 §3.1: tag length `T` MUST be an integer number of bytes from 4 to 2^32-1.
|
|
||||||
if (!isU32(dkLen) || dkLen < 4)
|
|
||||||
throw new Error('"dkLen" must be 4..');
|
|
||||||
if (!isU32(p) || p < 1 || p >= Math.pow(2, 24))
|
|
||||||
throw new Error('"p" must be 1..2^24');
|
|
||||||
if (!isU32(m))
|
|
||||||
throw new Error('"m" must be 0..2^32');
|
|
||||||
if (!isU32(t) || t < 1)
|
|
||||||
throw new Error('"t" (iterations) must be 1..2^32');
|
|
||||||
if (onProgress !== undefined && typeof onProgress !== 'function')
|
|
||||||
throw new Error('"onProgress" must be a function');
|
|
||||||
anumber(asyncTick, 'asyncTick');
|
|
||||||
/*
|
|
||||||
Memory size m MUST be an integer number of kibibytes from 8*p
|
|
||||||
to 2^(32)-1. The actual number of blocks is m', which is m
|
|
||||||
rounded down to the nearest multiple of 4*p.
|
|
||||||
*/
|
|
||||||
if (!isU32(m) || m < 8 * p)
|
|
||||||
throw new Error('"m" (memory) must be at least 8*p bytes');
|
|
||||||
// Accept legacy `0x10` for compatibility even though RFC 9106 profiles standardize `0x13`.
|
|
||||||
if (version !== 0x10 && version !== 0x13)
|
|
||||||
throw new Error('"version" must be 0x10 or 0x13, got ' + version);
|
|
||||||
return merged;
|
|
||||||
}
|
|
||||||
function argon2InitialHash(password, salt, type, opts) {
|
|
||||||
const ownedInputs = [];
|
|
||||||
const BUF = new Uint32Array(1);
|
|
||||||
const BUF8 = u8(BUF);
|
|
||||||
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 };
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function argon2Output(B, p, laneLen, dkLen) {
|
|
||||||
const B_final = new Uint32Array(256);
|
|
||||||
for (let l = 0; l < p; l++)
|
|
||||||
for (let j = 0; j < 256; j++)
|
|
||||||
B_final[j] ^= B[256 * (laneLen * l + laneLen - 1) + j];
|
|
||||||
// RFC 9106 steps 7-8 feed the byte string `C` into `H'^T(C)`, so normalize the xor'ed words
|
|
||||||
// back to spec byte order before `Hp(...)` reinterprets them as bytes.
|
|
||||||
const res = Hp(swap32IfBE(B_final), dkLen);
|
|
||||||
// Wipe both the xor scratch and the full working matrix once final digest bytes exist.
|
|
||||||
// JS cleanup is still only best-effort, but this local buffer is no longer needed here.
|
|
||||||
clean(B, B_final);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Fills every Argon2 block for all passes / slices / lanes, yielding once per
|
|
||||||
* processed block so callers control pacing: the sync driver just drains the
|
|
||||||
* generator, while the async driver awaits `nextTick()` between time slices.
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
address[256 + 6] = mP;
|
|
||||||
address[256 + 8] = t;
|
|
||||||
address[256 + 10] = type;
|
|
||||||
for (let r = 0; r < t; r++) {
|
|
||||||
// RFC 9106 step 6 applies the XOR-on-later-passes rule only for version `0x13`; legacy
|
|
||||||
// `0x10` keeps the older overwrite behavior used by the v16 test vectors.
|
|
||||||
const needXor = r !== 0 && version === 0x13;
|
|
||||||
address[256 + 0] = r;
|
|
||||||
for (let s = 0; s < ARGON2_SYNC_POINTS; s++) {
|
|
||||||
address[256 + 4] = s;
|
|
||||||
// RFC 9106 §3.4.1.3: Argon2id uses Argon2i's data-independent `J1` / `J2` generation only
|
|
||||||
// in pass 0, slices 0 and 1; Argon2i uses it in every segment.
|
|
||||||
const dataIndependent = type == AT.Argon2i || (type == AT.Argon2id && r === 0 && s < 2);
|
|
||||||
for (let l = 0; l < p; l++) {
|
|
||||||
address[256 + 2] = l;
|
|
||||||
address[256 + 12] = 0;
|
|
||||||
let startPos = 0;
|
|
||||||
if (r === 0 && s === 0) {
|
|
||||||
startPos = 2;
|
|
||||||
if (dataIndependent) {
|
|
||||||
address[256 + 12]++;
|
|
||||||
block(address, 256, 2 * 256, 0, false);
|
|
||||||
block(address, 0, 2 * 256, 0, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// current block position
|
|
||||||
let offset = l * laneLen + s * segmentLen + startPos;
|
|
||||||
for (let index = startPos; index < segmentLen; index++, offset++) {
|
|
||||||
perBlock();
|
|
||||||
// Previous block position: wraps to the lane's last block only at lane start,
|
|
||||||
// which can happen here only for the first block of slice 0 on passes > 0.
|
|
||||||
const prev = offset % laneLen ? offset - 1 : offset + laneLen - 1;
|
|
||||||
let randL, randH;
|
|
||||||
if (dataIndependent) {
|
|
||||||
let i128 = index % 128;
|
|
||||||
// RFC 9106 §3.4.1.2: each 1024-byte address block yields 128 `(J1, J2)` pairs, so
|
|
||||||
// regenerate it whenever the segment index crosses a multiple of 128.
|
|
||||||
if (i128 === 0) {
|
|
||||||
address[256 + 12]++;
|
|
||||||
block(address, 256, 2 * 256, 0, false);
|
|
||||||
block(address, 0, 2 * 256, 0, false);
|
|
||||||
}
|
|
||||||
randL = address[2 * i128];
|
|
||||||
randH = address[2 * i128 + 1];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const T = 256 * prev;
|
|
||||||
randL = B[T];
|
|
||||||
randH = B[T + 1];
|
|
||||||
}
|
|
||||||
// Address-block path selects `J1` / `J2`, then maps them to the reference
|
|
||||||
// lane/block per RFC 9106 §3.4.
|
|
||||||
const refLane = r === 0 && s === 0 ? l : randH % lanes;
|
|
||||||
const refPos = indexAlpha(r, s, laneLen, segmentLen, index, randL, refLane == l);
|
|
||||||
const refBlock = laneLen * refLane + refPos;
|
|
||||||
// B[i][j] = G(B[i][j-1], B[l][z])
|
|
||||||
block(B, 256 * prev, 256 * refBlock, offset * 256, needXor);
|
|
||||||
yield;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clean(address);
|
|
||||||
}
|
|
||||||
function argon2(type, password, salt, opts) {
|
|
||||||
const ctx = argon2Init(password, salt, type, opts);
|
|
||||||
const blocks = argon2Blocks(ctx, new Uint32Array(3 * 256));
|
|
||||||
while (!blocks.next().done) { }
|
|
||||||
return argon2Output(ctx.B, ctx.p, ctx.laneLen, ctx.dkLen);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Argon2d GPU-resistant version.
|
|
||||||
* @param password - password or input key material
|
|
||||||
* @param salt - unique salt value
|
|
||||||
* @param opts - Argon2 cost and optional tuning parameters. See {@link ArgonOpts}.
|
|
||||||
* @returns Derived key bytes.
|
|
||||||
* @throws If the Argon2 input or cost parameters are invalid. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Derive a key with Argon2d.
|
|
||||||
* ```ts
|
|
||||||
* argon2d('password', 'salt1234', { t: 1, m: 8, p: 1, dkLen: 32 });
|
|
||||||
* ```
|
|
||||||
* @example
|
|
||||||
* Derive a key with optional secret and scheduler controls.
|
|
||||||
* ```ts
|
|
||||||
* const progressLog: number[] = [];
|
|
||||||
* argon2d('password', 'salt1234', {
|
|
||||||
* t: 1,
|
|
||||||
* m: 8,
|
|
||||||
* p: 1,
|
|
||||||
* dkLen: 32,
|
|
||||||
* version: 0x13,
|
|
||||||
* key: 'secret',
|
|
||||||
* personalization: 'application',
|
|
||||||
* maxmem: 1024 * 1024,
|
|
||||||
* asyncTick: 1,
|
|
||||||
* onProgress(progress) {
|
|
||||||
* progressLog.push(progress);
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const argon2d = (password, salt, opts = {}) => argon2(AT.Argon2d, password, salt, opts);
|
|
||||||
/**
|
|
||||||
* Argon2i side-channel-resistant version.
|
|
||||||
* @param password - password or input key material
|
|
||||||
* @param salt - unique salt value
|
|
||||||
* @param opts - Argon2 cost and optional tuning parameters. See {@link ArgonOpts}.
|
|
||||||
* @returns Derived key bytes.
|
|
||||||
* @throws If the Argon2 input or cost parameters are invalid. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Derive a key with Argon2i.
|
|
||||||
* ```ts
|
|
||||||
* argon2i('password', 'salt1234', { t: 1, m: 8, p: 1, dkLen: 32 });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const argon2i = (password, salt, opts = {}) => argon2(AT.Argon2i, password, salt, opts);
|
|
||||||
/**
|
|
||||||
* Argon2id, combining i+d, the most popular version from RFC 9106.
|
|
||||||
* @param password - password or input key material
|
|
||||||
* @param salt - unique salt value
|
|
||||||
* @param opts - Argon2 cost and optional tuning parameters. See {@link ArgonOpts}.
|
|
||||||
* @returns Derived key bytes.
|
|
||||||
* @throws If the Argon2 input or cost parameters are invalid. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Derive a key with Argon2id.
|
|
||||||
* ```ts
|
|
||||||
* argon2id('password', 'salt1234', { t: 1, m: 8, p: 1, dkLen: 32 });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
// 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();
|
|
||||||
while (!blocks.next().done) {
|
|
||||||
// Date.now() is not monotonic. If the clock goes backwards,
|
|
||||||
// still yield control.
|
|
||||||
const diff = Date.now() - ts;
|
|
||||||
if (diff >= 0 && diff < ctx.asyncTick)
|
|
||||||
continue;
|
|
||||||
await nextTick(abort);
|
|
||||||
// Scheduler delay is outside the synchronous work budget.
|
|
||||||
ts = Date.now();
|
|
||||||
}
|
|
||||||
return argon2Output(ctx.B, ctx.p, ctx.laneLen, ctx.dkLen);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Argon2d async GPU-resistant version.
|
|
||||||
* @param password - password or input key material
|
|
||||||
* @param salt - unique salt value
|
|
||||||
* @param opts - Argon2 cost and optional tuning parameters. See {@link ArgonOpts}.
|
|
||||||
* @returns Promise resolving to derived key bytes.
|
|
||||||
* @throws If the Argon2 input or cost parameters are invalid. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Derive a key with Argon2d asynchronously.
|
|
||||||
* ```ts
|
|
||||||
* await argon2dAsync('password', 'salt1234', { t: 1, m: 8, p: 1, dkLen: 32 });
|
|
||||||
* ```
|
|
||||||
* @example
|
|
||||||
* Derive a key asynchronously with optional secret and scheduler controls.
|
|
||||||
* ```ts
|
|
||||||
* await argon2dAsync('password', 'salt1234', {
|
|
||||||
* t: 1,
|
|
||||||
* m: 8,
|
|
||||||
* p: 1,
|
|
||||||
* dkLen: 32,
|
|
||||||
* version: 0x13,
|
|
||||||
* key: 'secret',
|
|
||||||
* personalization: 'application',
|
|
||||||
* maxmem: 1024 * 1024,
|
|
||||||
* asyncTick: 1,
|
|
||||||
* onProgress(progress) {
|
|
||||||
* if (progress > 1) throw new Error('invalid progress');
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const argon2dAsync = (password, salt, opts = {}) => argon2Async(AT.Argon2d, password, salt, opts);
|
|
||||||
/**
|
|
||||||
* Argon2i async side-channel-resistant version.
|
|
||||||
* @param password - password or input key material
|
|
||||||
* @param salt - unique salt value
|
|
||||||
* @param opts - Argon2 cost and optional tuning parameters. See {@link ArgonOpts}.
|
|
||||||
* @returns Promise resolving to derived key bytes.
|
|
||||||
* @throws If the Argon2 input or cost parameters are invalid. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Derive a key with Argon2i asynchronously.
|
|
||||||
* ```ts
|
|
||||||
* await argon2iAsync('password', 'salt1234', { t: 1, m: 8, p: 1, dkLen: 32 });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const argon2iAsync = (password, salt, opts = {}) => argon2Async(AT.Argon2i, password, salt, opts);
|
|
||||||
/**
|
|
||||||
* Argon2id async, combining i+d, the most popular version from RFC 9106.
|
|
||||||
* @param password - password or input key material
|
|
||||||
* @param salt - unique salt value
|
|
||||||
* @param opts - Argon2 cost and optional tuning parameters. See {@link ArgonOpts}.
|
|
||||||
* @returns Promise resolving to derived key bytes.
|
|
||||||
* @throws If the Argon2 input or cost parameters are invalid. {@link Error}
|
|
||||||
* @example
|
|
||||||
* Derive a key with Argon2id asynchronously.
|
|
||||||
* ```ts
|
|
||||||
* await argon2idAsync('password', 'salt1234', { t: 1, m: 8, p: 1, dkLen: 32 });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const argon2idAsync = (password, salt, opts = {}) => argon2Async(AT.Argon2id, password, salt, opts);
|
|
||||||
Reference in New Issue
Block a user