313 lines
11 KiB
JavaScript
313 lines
11 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.bitMask = void 0;
|
|
exports.isBytes = isBytes;
|
|
exports.abytes = abytes;
|
|
exports.bytesToHex = bytesToHex;
|
|
exports.numberToHexUnpadded = numberToHexUnpadded;
|
|
exports.hexToNumber = hexToNumber;
|
|
exports.hexToBytes = hexToBytes;
|
|
exports.bytesToNumberBE = bytesToNumberBE;
|
|
exports.bytesToNumberLE = bytesToNumberLE;
|
|
exports.numberToBytesBE = numberToBytesBE;
|
|
exports.numberToBytesLE = numberToBytesLE;
|
|
exports.numberToVarBytesBE = numberToVarBytesBE;
|
|
exports.ensureBytes = ensureBytes;
|
|
exports.concatBytes = concatBytes;
|
|
exports.equalBytes = equalBytes;
|
|
exports.utf8ToBytes = utf8ToBytes;
|
|
exports.bitLen = bitLen;
|
|
exports.bitGet = bitGet;
|
|
exports.bitSet = bitSet;
|
|
exports.createHmacDrbg = createHmacDrbg;
|
|
exports.validateObject = validateObject;
|
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
// 100 lines of code in the file are duplicated from noble-hashes (utils).
|
|
// This is OK: `abstract` directory does not use noble-hashes.
|
|
// User may opt-in into using different hashing library. This way, noble-hashes
|
|
// won't be included into their bundle.
|
|
const _0n = /* @__PURE__ */ BigInt(0);
|
|
const _1n = /* @__PURE__ */ BigInt(1);
|
|
const _2n = /* @__PURE__ */ BigInt(2);
|
|
function isBytes(a) {
|
|
return (a instanceof Uint8Array ||
|
|
(a != null && typeof a === 'object' && a.constructor.name === 'Uint8Array'));
|
|
}
|
|
function abytes(item) {
|
|
if (!isBytes(item))
|
|
throw new Error('Uint8Array expected');
|
|
}
|
|
// Array where index 0xf0 (240) is mapped to string 'f0'
|
|
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
|
|
/**
|
|
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
|
|
*/
|
|
function bytesToHex(bytes) {
|
|
abytes(bytes);
|
|
// pre-caching improves the speed 6x
|
|
let hex = '';
|
|
for (let i = 0; i < bytes.length; i++) {
|
|
hex += hexes[bytes[i]];
|
|
}
|
|
return hex;
|
|
}
|
|
function numberToHexUnpadded(num) {
|
|
const hex = num.toString(16);
|
|
return hex.length & 1 ? `0${hex}` : hex;
|
|
}
|
|
function hexToNumber(hex) {
|
|
if (typeof hex !== 'string')
|
|
throw new Error('hex string expected, got ' + typeof hex);
|
|
// Big Endian
|
|
return BigInt(hex === '' ? '0' : `0x${hex}`);
|
|
}
|
|
// We use optimized technique to convert hex string to byte array
|
|
const asciis = { _0: 48, _9: 57, _A: 65, _F: 70, _a: 97, _f: 102 };
|
|
function asciiToBase16(char) {
|
|
if (char >= asciis._0 && char <= asciis._9)
|
|
return char - asciis._0;
|
|
if (char >= asciis._A && char <= asciis._F)
|
|
return char - (asciis._A - 10);
|
|
if (char >= asciis._a && char <= asciis._f)
|
|
return char - (asciis._a - 10);
|
|
return;
|
|
}
|
|
/**
|
|
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
|
|
*/
|
|
function hexToBytes(hex) {
|
|
if (typeof hex !== 'string')
|
|
throw new Error('hex string expected, got ' + typeof hex);
|
|
const hl = hex.length;
|
|
const al = hl / 2;
|
|
if (hl % 2)
|
|
throw new Error('padded 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));
|
|
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
|
|
if (n1 === undefined || n2 === undefined) {
|
|
const char = hex[hi] + hex[hi + 1];
|
|
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
|
}
|
|
array[ai] = n1 * 16 + n2;
|
|
}
|
|
return array;
|
|
}
|
|
// BE: Big Endian, LE: Little Endian
|
|
function bytesToNumberBE(bytes) {
|
|
return hexToNumber(bytesToHex(bytes));
|
|
}
|
|
function bytesToNumberLE(bytes) {
|
|
abytes(bytes);
|
|
return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
|
|
}
|
|
function numberToBytesBE(n, len) {
|
|
return hexToBytes(n.toString(16).padStart(len * 2, '0'));
|
|
}
|
|
function numberToBytesLE(n, len) {
|
|
return numberToBytesBE(n, len).reverse();
|
|
}
|
|
// Unpadded, rarely used
|
|
function numberToVarBytesBE(n) {
|
|
return hexToBytes(numberToHexUnpadded(n));
|
|
}
|
|
/**
|
|
* Takes hex string or Uint8Array, converts to Uint8Array.
|
|
* Validates output length.
|
|
* Will throw error for other types.
|
|
* @param title descriptive title for an error e.g. 'private key'
|
|
* @param hex hex string or Uint8Array
|
|
* @param expectedLength optional, will compare to result array's length
|
|
* @returns
|
|
*/
|
|
function ensureBytes(title, hex, expectedLength) {
|
|
let res;
|
|
if (typeof hex === 'string') {
|
|
try {
|
|
res = hexToBytes(hex);
|
|
}
|
|
catch (e) {
|
|
throw new Error(`${title} must be valid hex string, got "${hex}". Cause: ${e}`);
|
|
}
|
|
}
|
|
else if (isBytes(hex)) {
|
|
// Uint8Array.from() instead of hash.slice() because node.js Buffer
|
|
// is instance of Uint8Array, and its slice() creates **mutable** copy
|
|
res = Uint8Array.from(hex);
|
|
}
|
|
else {
|
|
throw new Error(`${title} must be hex string or Uint8Array`);
|
|
}
|
|
const len = res.length;
|
|
if (typeof expectedLength === 'number' && len !== expectedLength)
|
|
throw new Error(`${title} expected ${expectedLength} bytes, got ${len}`);
|
|
return res;
|
|
}
|
|
/**
|
|
* Copies several Uint8Arrays into one.
|
|
*/
|
|
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;
|
|
}
|
|
// Compares 2 u8a-s in kinda constant time
|
|
function equalBytes(a, 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;
|
|
}
|
|
/**
|
|
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
|
|
*/
|
|
function utf8ToBytes(str) {
|
|
if (typeof str !== 'string')
|
|
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
|
|
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
|
|
}
|
|
// Bit operations
|
|
/**
|
|
* Calculates amount of bits in a bigint.
|
|
* Same as `n.toString(2).length`
|
|
*/
|
|
function bitLen(n) {
|
|
let len;
|
|
for (len = 0; n > _0n; n >>= _1n, len += 1)
|
|
;
|
|
return len;
|
|
}
|
|
/**
|
|
* Gets single bit at position.
|
|
* NOTE: first bit position is 0 (same as arrays)
|
|
* Same as `!!+Array.from(n.toString(2)).reverse()[pos]`
|
|
*/
|
|
function bitGet(n, pos) {
|
|
return (n >> BigInt(pos)) & _1n;
|
|
}
|
|
/**
|
|
* Sets single bit at position.
|
|
*/
|
|
function bitSet(n, pos, value) {
|
|
return n | ((value ? _1n : _0n) << BigInt(pos));
|
|
}
|
|
/**
|
|
* Calculate mask for N bits. Not using ** operator with bigints because of old engines.
|
|
* Same as BigInt(`0b${Array(i).fill('1').join('')}`)
|
|
*/
|
|
const bitMask = (n) => (_2n << BigInt(n - 1)) - _1n;
|
|
exports.bitMask = bitMask;
|
|
// DRBG
|
|
const u8n = (data) => new Uint8Array(data); // creates Uint8Array
|
|
const u8fr = (arr) => Uint8Array.from(arr); // another shortcut
|
|
/**
|
|
* Minimal HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
|
|
* @returns function that will call DRBG until 2nd arg returns something meaningful
|
|
* @example
|
|
* const drbg = createHmacDRBG<Key>(32, 32, hmac);
|
|
* drbg(seed, bytesToKey); // bytesToKey must return Key or undefined
|
|
*/
|
|
function createHmacDrbg(hashLen, qByteLen, hmacFn) {
|
|
if (typeof hashLen !== 'number' || hashLen < 2)
|
|
throw new Error('hashLen must be a number');
|
|
if (typeof qByteLen !== 'number' || qByteLen < 2)
|
|
throw new Error('qByteLen must be a number');
|
|
if (typeof hmacFn !== 'function')
|
|
throw new Error('hmacFn must be a function');
|
|
// Step B, Step C: set hashLen to 8*ceil(hlen/8)
|
|
let v = u8n(hashLen); // Minimal non-full-spec HMAC-DRBG from NIST 800-90 for RFC6979 sigs.
|
|
let k = u8n(hashLen); // Steps B and C of RFC6979 3.2: set hashLen, in our case always same
|
|
let i = 0; // Iterations counter, will throw when over 1000
|
|
const reset = () => {
|
|
v.fill(1);
|
|
k.fill(0);
|
|
i = 0;
|
|
};
|
|
const h = (...b) => hmacFn(k, v, ...b); // hmac(k)(v, ...values)
|
|
const reseed = (seed = u8n()) => {
|
|
// HMAC-DRBG reseed() function. Steps D-G
|
|
k = h(u8fr([0x00]), seed); // k = hmac(k || v || 0x00 || seed)
|
|
v = h(); // v = hmac(k || v)
|
|
if (seed.length === 0)
|
|
return;
|
|
k = h(u8fr([0x01]), seed); // k = hmac(k || v || 0x01 || seed)
|
|
v = h(); // v = hmac(k || v)
|
|
};
|
|
const gen = () => {
|
|
// HMAC-DRBG generate() function
|
|
if (i++ >= 1000)
|
|
throw new Error('drbg: tried 1000 values');
|
|
let len = 0;
|
|
const out = [];
|
|
while (len < qByteLen) {
|
|
v = h();
|
|
const sl = v.slice();
|
|
out.push(sl);
|
|
len += v.length;
|
|
}
|
|
return concatBytes(...out);
|
|
};
|
|
const genUntil = (seed, pred) => {
|
|
reset();
|
|
reseed(seed); // Steps D-G
|
|
let res = undefined; // Step H: grind until k is in [1..n-1]
|
|
while (!(res = pred(gen())))
|
|
reseed();
|
|
reset();
|
|
return res;
|
|
};
|
|
return genUntil;
|
|
}
|
|
// Validating curves and fields
|
|
const validatorFns = {
|
|
bigint: (val) => typeof val === 'bigint',
|
|
function: (val) => typeof val === 'function',
|
|
boolean: (val) => typeof val === 'boolean',
|
|
string: (val) => typeof val === 'string',
|
|
stringOrUint8Array: (val) => typeof val === 'string' || isBytes(val),
|
|
isSafeInteger: (val) => Number.isSafeInteger(val),
|
|
array: (val) => Array.isArray(val),
|
|
field: (val, object) => object.Fp.isValid(val),
|
|
hash: (val) => typeof val === 'function' && Number.isSafeInteger(val.outputLen),
|
|
};
|
|
// type Record<K extends string | number | symbol, T> = { [P in K]: T; }
|
|
function validateObject(object, validators, optValidators = {}) {
|
|
const checkField = (fieldName, type, isOptional) => {
|
|
const checkVal = validatorFns[type];
|
|
if (typeof checkVal !== 'function')
|
|
throw new Error(`Invalid validator "${type}", expected function`);
|
|
const val = object[fieldName];
|
|
if (isOptional && val === undefined)
|
|
return;
|
|
if (!checkVal(val, object)) {
|
|
throw new Error(`Invalid param ${String(fieldName)}=${val} (${typeof val}), expected ${type}`);
|
|
}
|
|
};
|
|
for (const [fieldName, type] of Object.entries(validators))
|
|
checkField(fieldName, type, false);
|
|
for (const [fieldName, type] of Object.entries(optValidators))
|
|
checkField(fieldName, type, true);
|
|
return object;
|
|
}
|
|
// validate type tests
|
|
// const o: { a: number; b: number; c: number } = { a: 1, b: 5, c: 6 };
|
|
// const z0 = validateObject(o, { a: 'isSafeInteger' }, { c: 'bigint' }); // Ok!
|
|
// // Should fail type-check
|
|
// const z1 = validateObject(o, { a: 'tmp' }, { c: 'zz' });
|
|
// const z2 = validateObject(o, { a: 'isSafeInteger' }, { c: 'zz' });
|
|
// const z3 = validateObject(o, { test: 'boolean', z: 'bug' });
|
|
// const z4 = validateObject(o, { a: 'boolean', z: 'bug' });
|
|
//# sourceMappingURL=utils.js.map
|