You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
360 lines
14 KiB
360 lines
14 KiB
"use strict"; |
|
Object.defineProperty(exports, "__esModule", { value: true }); |
|
exports.notImplemented = exports.bitMask = exports.utf8ToBytes = exports.randomBytes = exports.isBytes = exports.hexToBytes = exports.concatBytes = exports.bytesToUtf8 = exports.bytesToHex = exports.anumber = exports.abytes = void 0; |
|
exports.abool = abool; |
|
exports._abool2 = _abool2; |
|
exports._abytes2 = _abytes2; |
|
exports.numberToHexUnpadded = numberToHexUnpadded; |
|
exports.hexToNumber = hexToNumber; |
|
exports.bytesToNumberBE = bytesToNumberBE; |
|
exports.bytesToNumberLE = bytesToNumberLE; |
|
exports.numberToBytesBE = numberToBytesBE; |
|
exports.numberToBytesLE = numberToBytesLE; |
|
exports.numberToVarBytesBE = numberToVarBytesBE; |
|
exports.ensureBytes = ensureBytes; |
|
exports.equalBytes = equalBytes; |
|
exports.copyBytes = copyBytes; |
|
exports.asciiToBytes = asciiToBytes; |
|
exports.inRange = inRange; |
|
exports.aInRange = aInRange; |
|
exports.bitLen = bitLen; |
|
exports.bitGet = bitGet; |
|
exports.bitSet = bitSet; |
|
exports.createHmacDrbg = createHmacDrbg; |
|
exports.validateObject = validateObject; |
|
exports.isHash = isHash; |
|
exports._validateObject = _validateObject; |
|
exports.memoized = memoized; |
|
/** |
|
* Hex, bytes and number utilities. |
|
* @module |
|
*/ |
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ |
|
const utils_js_1 = require("@noble/hashes/utils.js"); |
|
var utils_js_2 = require("@noble/hashes/utils.js"); |
|
Object.defineProperty(exports, "abytes", { enumerable: true, get: function () { return utils_js_2.abytes; } }); |
|
Object.defineProperty(exports, "anumber", { enumerable: true, get: function () { return utils_js_2.anumber; } }); |
|
Object.defineProperty(exports, "bytesToHex", { enumerable: true, get: function () { return utils_js_2.bytesToHex; } }); |
|
Object.defineProperty(exports, "bytesToUtf8", { enumerable: true, get: function () { return utils_js_2.bytesToUtf8; } }); |
|
Object.defineProperty(exports, "concatBytes", { enumerable: true, get: function () { return utils_js_2.concatBytes; } }); |
|
Object.defineProperty(exports, "hexToBytes", { enumerable: true, get: function () { return utils_js_2.hexToBytes; } }); |
|
Object.defineProperty(exports, "isBytes", { enumerable: true, get: function () { return utils_js_2.isBytes; } }); |
|
Object.defineProperty(exports, "randomBytes", { enumerable: true, get: function () { return utils_js_2.randomBytes; } }); |
|
Object.defineProperty(exports, "utf8ToBytes", { enumerable: true, get: function () { return utils_js_2.utf8ToBytes; } }); |
|
const _0n = /* @__PURE__ */ BigInt(0); |
|
const _1n = /* @__PURE__ */ BigInt(1); |
|
function abool(title, value) { |
|
if (typeof value !== 'boolean') |
|
throw new Error(title + ' boolean expected, got ' + value); |
|
} |
|
// tmp name until v2 |
|
function _abool2(value, title = '') { |
|
if (typeof value !== 'boolean') { |
|
const prefix = title && `"${title}"`; |
|
throw new Error(prefix + 'expected boolean, got type=' + typeof value); |
|
} |
|
return value; |
|
} |
|
// tmp name until v2 |
|
/** Asserts something is Uint8Array. */ |
|
function _abytes2(value, length, title = '') { |
|
const bytes = (0, utils_js_1.isBytes)(value); |
|
const len = value?.length; |
|
const needsLen = length !== undefined; |
|
if (!bytes || (needsLen && len !== length)) { |
|
const prefix = title && `"${title}" `; |
|
const ofLen = needsLen ? ` of length ${length}` : ''; |
|
const got = bytes ? `length=${len}` : `type=${typeof value}`; |
|
throw new Error(prefix + 'expected Uint8Array' + ofLen + ', got ' + got); |
|
} |
|
return value; |
|
} |
|
// Used in weierstrass, der |
|
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); |
|
return hex === '' ? _0n : BigInt('0x' + hex); // Big Endian |
|
} |
|
// BE: Big Endian, LE: Little Endian |
|
function bytesToNumberBE(bytes) { |
|
return hexToNumber((0, utils_js_1.bytesToHex)(bytes)); |
|
} |
|
function bytesToNumberLE(bytes) { |
|
(0, utils_js_1.abytes)(bytes); |
|
return hexToNumber((0, utils_js_1.bytesToHex)(Uint8Array.from(bytes).reverse())); |
|
} |
|
function numberToBytesBE(n, len) { |
|
return (0, utils_js_1.hexToBytes)(n.toString(16).padStart(len * 2, '0')); |
|
} |
|
function numberToBytesLE(n, len) { |
|
return numberToBytesBE(n, len).reverse(); |
|
} |
|
// Unpadded, rarely used |
|
function numberToVarBytesBE(n) { |
|
return (0, utils_js_1.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. 'secret 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 = (0, utils_js_1.hexToBytes)(hex); |
|
} |
|
catch (e) { |
|
throw new Error(title + ' must be hex string or Uint8Array, cause: ' + e); |
|
} |
|
} |
|
else if ((0, utils_js_1.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 + ' of length ' + expectedLength + ' expected, got ' + len); |
|
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; |
|
} |
|
/** |
|
* Copies Uint8Array. We can't use u8a.slice(), because u8a can be Buffer, |
|
* and Buffer#slice creates mutable copy. Never use Buffers! |
|
*/ |
|
function copyBytes(bytes) { |
|
return Uint8Array.from(bytes); |
|
} |
|
/** |
|
* Decodes 7-bit ASCII string to Uint8Array, throws on non-ascii symbols |
|
* Should be safe to use for things expected to be ASCII. |
|
* Returns exact same result as utf8ToBytes for ASCII or throws. |
|
*/ |
|
function asciiToBytes(ascii) { |
|
return Uint8Array.from(ascii, (c, i) => { |
|
const charCode = c.charCodeAt(0); |
|
if (c.length !== 1 || charCode > 127) { |
|
throw new Error(`string contains non-ASCII character "${ascii[i]}" with code ${charCode} at position ${i}`); |
|
} |
|
return charCode; |
|
}); |
|
} |
|
/** |
|
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99]) |
|
*/ |
|
// export const utf8ToBytes: typeof utf8ToBytes_ = utf8ToBytes_; |
|
/** |
|
* Converts bytes to string using UTF8 encoding. |
|
* @example bytesToUtf8(Uint8Array.from([97, 98, 99])) // 'abc' |
|
*/ |
|
// export const bytesToUtf8: typeof bytesToUtf8_ = bytesToUtf8_; |
|
// Is positive bigint |
|
const isPosBig = (n) => typeof n === 'bigint' && _0n <= n; |
|
function inRange(n, min, max) { |
|
return isPosBig(n) && isPosBig(min) && isPosBig(max) && min <= n && n < max; |
|
} |
|
/** |
|
* Asserts min <= n < max. NOTE: It's < max and not <= max. |
|
* @example |
|
* aInRange('x', x, 1n, 256n); // would assume x is in (1n..255n) |
|
*/ |
|
function aInRange(title, n, min, max) { |
|
// Why min <= n < max and not a (min < n < max) OR b (min <= n <= max)? |
|
// consider P=256n, min=0n, max=P |
|
// - a for min=0 would require -1: `inRange('x', x, -1n, P)` |
|
// - b would commonly require subtraction: `inRange('x', x, 0n, P - 1n)` |
|
// - our way is the cleanest: `inRange('x', x, 0n, P) |
|
if (!inRange(n, min, max)) |
|
throw new Error('expected valid ' + title + ': ' + min + ' <= n < ' + max + ', got ' + n); |
|
} |
|
// Bit operations |
|
/** |
|
* Calculates amount of bits in a bigint. |
|
* Same as `n.toString(2).length` |
|
* TODO: merge with nLength in modular |
|
*/ |
|
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) => (_1n << BigInt(n)) - _1n; |
|
exports.bitMask = bitMask; |
|
/** |
|
* 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) |
|
const u8n = (len) => new Uint8Array(len); // creates Uint8Array |
|
const u8of = (byte) => Uint8Array.of(byte); // another shortcut |
|
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(0)) => { |
|
// HMAC-DRBG reseed() function. Steps D-G |
|
k = h(u8of(0x00), seed); // k = hmac(k || v || 0x00 || seed) |
|
v = h(); // v = hmac(k || v) |
|
if (seed.length === 0) |
|
return; |
|
k = h(u8of(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 (0, utils_js_1.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' || (0, utils_js_1.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 function'); |
|
const val = object[fieldName]; |
|
if (isOptional && val === undefined) |
|
return; |
|
if (!checkVal(val, object)) { |
|
throw new Error('param ' + String(fieldName) + ' is invalid. Expected ' + type + ', got ' + val); |
|
} |
|
}; |
|
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' }); |
|
function isHash(val) { |
|
return typeof val === 'function' && Number.isSafeInteger(val.outputLen); |
|
} |
|
function _validateObject(object, fields, optFields = {}) { |
|
if (!object || typeof object !== 'object') |
|
throw new Error('expected valid options object'); |
|
function checkField(fieldName, expectedType, isOpt) { |
|
const val = object[fieldName]; |
|
if (isOpt && val === undefined) |
|
return; |
|
const current = typeof val; |
|
if (current !== expectedType || val === null) |
|
throw new Error(`param "${fieldName}" is invalid: expected ${expectedType}, got ${current}`); |
|
} |
|
Object.entries(fields).forEach(([k, v]) => checkField(k, v, false)); |
|
Object.entries(optFields).forEach(([k, v]) => checkField(k, v, true)); |
|
} |
|
/** |
|
* throws not implemented error |
|
*/ |
|
const notImplemented = () => { |
|
throw new Error('not implemented'); |
|
}; |
|
exports.notImplemented = notImplemented; |
|
/** |
|
* Memoizes (caches) computation result. |
|
* Uses WeakMap: the value is going auto-cleaned by GC after last reference is removed. |
|
*/ |
|
function memoized(fn) { |
|
const map = new WeakMap(); |
|
return (arg, ...args) => { |
|
const val = map.get(arg); |
|
if (val !== undefined) |
|
return val; |
|
const computed = fn(arg, ...args); |
|
map.set(arg, computed); |
|
return computed; |
|
}; |
|
} |
|
//# sourceMappingURL=utils.js.map
|