UNPKG

openpgp

Version:

OpenPGP.js is a Javascript implementation of the OpenPGP protocol. This is defined in RFC 4880.

1,260 lines (1,253 loc) 125 kB
/*! OpenPGP.js v6.1.0 - 2025-01-30 - this is LGPL licensed code, see LICENSE/our website https://openpgpjs.org/ for more information. */ const globalThis = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; import { H as Hash, h as hash, t as toBytes, e as exists, b as bytes, c as concatBytes$1, r as randomBytes, s as sha256, a as sha384, d as sha512, w as wrapConstructor, u as utf8ToBytes$1, f as shake256 } from './sha3.mjs'; // HMAC (RFC 2104) class HMAC extends Hash { constructor(hash$1, _key) { super(); this.finished = false; this.destroyed = false; hash(hash$1); const key = toBytes(_key); this.iHash = hash$1.create(); if (typeof this.iHash.update !== 'function') throw new Error('Expected instance of class which extends utils.Hash'); this.blockLen = this.iHash.blockLen; this.outputLen = this.iHash.outputLen; const blockLen = this.blockLen; const pad = new Uint8Array(blockLen); // blockLen can be bigger than outputLen pad.set(key.length > blockLen ? hash$1.create().update(key).digest() : key); for (let i = 0; i < pad.length; i++) pad[i] ^= 0x36; this.iHash.update(pad); // By doing update (processing of first block) of outer hash here we can re-use it between multiple calls via clone this.oHash = hash$1.create(); // Undo internal XOR && apply outer XOR for (let i = 0; i < pad.length; i++) pad[i] ^= 0x36 ^ 0x5c; this.oHash.update(pad); pad.fill(0); } update(buf) { exists(this); this.iHash.update(buf); return this; } digestInto(out) { exists(this); bytes(out, this.outputLen); this.finished = true; this.iHash.digestInto(out); this.oHash.update(out); this.oHash.digestInto(out); this.destroy(); } digest() { const out = new Uint8Array(this.oHash.outputLen); this.digestInto(out); return out; } _cloneInto(to) { // Create new instance without calling constructor since key already in state and we don't know it. to || (to = Object.create(Object.getPrototypeOf(this), {})); const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this; to = to; to.finished = finished; to.destroyed = destroyed; to.blockLen = blockLen; to.outputLen = outputLen; to.oHash = oHash._cloneInto(to.oHash); to.iHash = iHash._cloneInto(to.iHash); return to; } destroy() { this.destroyed = true; this.oHash.destroy(); this.iHash.destroy(); } } /** * HMAC: RFC2104 message authentication code. * @param hash - function that would be used e.g. sha256 * @param key - message key * @param message - message data * @example * import { hmac } from '@noble/hashes/hmac'; * import { sha256 } from '@noble/hashes/sha2'; * const mac1 = hmac(sha256, 'key', 'message'); */ const hmac = (hash, key, message) => new HMAC(hash, key).update(message).digest(); hmac.create = (hash, key) => new HMAC(hash, key); /*! 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$5 = /* @__PURE__ */ BigInt(0); const _1n$7 = /* @__PURE__ */ BigInt(1); const _2n$4 = /* @__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'); } function abool(title, value) { if (typeof value !== 'boolean') throw new Error(`${title} must be valid boolean, got "${value}".`); } // 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 } // Is positive bigint const isPosBig = (n) => typeof n === 'bigint' && _0n$5 <= 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 ${typeof n} ${n}`); } // 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$5; n >>= _1n$7, 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$7; } /** * Sets single bit at position. */ function bitSet(n, pos, value) { return n | ((value ? _1n$7 : _0n$5) << 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$4 << BigInt(n - 1)) - _1n$7; // 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' }); /** * throws not implemented error */ const notImplemented = () => { throw new Error('not implemented'); }; /** * 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; }; } var ut = /*#__PURE__*/Object.freeze({ __proto__: null, aInRange: aInRange, abool: abool, abytes: abytes, bitGet: bitGet, bitLen: bitLen, bitMask: bitMask, bitSet: bitSet, bytesToHex: bytesToHex, bytesToNumberBE: bytesToNumberBE, bytesToNumberLE: bytesToNumberLE, concatBytes: concatBytes, createHmacDrbg: createHmacDrbg, ensureBytes: ensureBytes, equalBytes: equalBytes, hexToBytes: hexToBytes, hexToNumber: hexToNumber, inRange: inRange, isBytes: isBytes, memoized: memoized, notImplemented: notImplemented, numberToBytesBE: numberToBytesBE, numberToBytesLE: numberToBytesLE, numberToHexUnpadded: numberToHexUnpadded, numberToVarBytesBE: numberToVarBytesBE, utf8ToBytes: utf8ToBytes, validateObject: validateObject }); /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ // Utilities for modular arithmetics and finite fields // prettier-ignore const _0n$4 = BigInt(0), _1n$6 = BigInt(1), _2n$3 = BigInt(2), _3n$2 = BigInt(3); // prettier-ignore const _4n = BigInt(4), _5n = BigInt(5), _8n$1 = BigInt(8); // prettier-ignore BigInt(9); BigInt(16); // Calculates a modulo b function mod(a, b) { const result = a % b; return result >= _0n$4 ? result : b + result; } /** * Efficiently raise num to power and do modular division. * Unsafe in some contexts: uses ladder, so can expose bigint bits. * @example * pow(2n, 6n, 11n) // 64n % 11n == 9n */ // TODO: use field version && remove function pow(num, power, modulo) { if (modulo <= _0n$4 || power < _0n$4) throw new Error('Expected power/modulo > 0'); if (modulo === _1n$6) return _0n$4; let res = _1n$6; while (power > _0n$4) { if (power & _1n$6) res = (res * num) % modulo; num = (num * num) % modulo; power >>= _1n$6; } return res; } // Does x ^ (2 ^ power) mod p. pow2(30, 4) == 30 ^ (2 ^ 4) function pow2(x, power, modulo) { let res = x; while (power-- > _0n$4) { res *= res; res %= modulo; } return res; } // Inverses number over modulo function invert(number, modulo) { if (number === _0n$4 || modulo <= _0n$4) { throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`); } // Euclidean GCD https://brilliant.org/wiki/extended-euclidean-algorithm/ // Fermat's little theorem "CT-like" version inv(n) = n^(m-2) mod m is 30x slower. let a = mod(number, modulo); let b = modulo; // prettier-ignore let x = _0n$4, u = _1n$6; while (a !== _0n$4) { // JIT applies optimization if those two lines follow each other const q = b / a; const r = b % a; const m = x - u * q; // prettier-ignore b = a, a = r, x = u, u = m; } const gcd = b; if (gcd !== _1n$6) throw new Error('invert: does not exist'); return mod(x, modulo); } /** * Tonelli-Shanks square root search algorithm. * 1. https://eprint.iacr.org/2012/685.pdf (page 12) * 2. Square Roots from 1; 24, 51, 10 to Dan Shanks * Will start an infinite loop if field order P is not prime. * @param P field order * @returns function that takes field Fp (created from P) and number n */ function tonelliShanks(P) { // Legendre constant: used to calculate Legendre symbol (a | p), // which denotes the value of a^((p-1)/2) (mod p). // (a | p) ≡ 1 if a is a square (mod p) // (a | p) ≡ -1 if a is not a square (mod p) // (a | p) ≡ 0 if a ≡ 0 (mod p) const legendreC = (P - _1n$6) / _2n$3; let Q, S, Z; // Step 1: By factoring out powers of 2 from p - 1, // find q and s such that p - 1 = q*(2^s) with q odd for (Q = P - _1n$6, S = 0; Q % _2n$3 === _0n$4; Q /= _2n$3, S++) ; // Step 2: Select a non-square z such that (z | p) ≡ -1 and set c ≡ zq for (Z = _2n$3; Z < P && pow(Z, legendreC, P) !== P - _1n$6; Z++) ; // Fast-path if (S === 1) { const p1div4 = (P + _1n$6) / _4n; return function tonelliFast(Fp, n) { const root = Fp.pow(n, p1div4); if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root'); return root; }; } // Slow-path const Q1div2 = (Q + _1n$6) / _2n$3; return function tonelliSlow(Fp, n) { // Step 0: Check that n is indeed a square: (n | p) should not be ≡ -1 if (Fp.pow(n, legendreC) === Fp.neg(Fp.ONE)) throw new Error('Cannot find square root'); let r = S; // TODO: will fail at Fp2/etc let g = Fp.pow(Fp.mul(Fp.ONE, Z), Q); // will update both x and b let x = Fp.pow(n, Q1div2); // first guess at the square root let b = Fp.pow(n, Q); // first guess at the fudge factor while (!Fp.eql(b, Fp.ONE)) { if (Fp.eql(b, Fp.ZERO)) return Fp.ZERO; // https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm (4. If t = 0, return r = 0) // Find m such b^(2^m)==1 let m = 1; for (let t2 = Fp.sqr(b); m < r; m++) { if (Fp.eql(t2, Fp.ONE)) break; t2 = Fp.sqr(t2); // t2 *= t2 } // NOTE: r-m-1 can be bigger than 32, need to convert to bigint before shift, otherwise there will be overflow const ge = Fp.pow(g, _1n$6 << BigInt(r - m - 1)); // ge = 2^(r-m-1) g = Fp.sqr(ge); // g = ge * ge x = Fp.mul(x, ge); // x *= ge b = Fp.mul(b, g); // b *= g r = m; } return x; }; } function FpSqrt(P) { // NOTE: different algorithms can give different roots, it is up to user to decide which one they want. // For example there is FpSqrtOdd/FpSqrtEven to choice root based on oddness (used for hash-to-curve). // P ≡ 3 (mod 4) // √n = n^((P+1)/4) if (P % _4n === _3n$2) { // Not all roots possible! // const ORDER = // 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaabn; // const NUM = 72057594037927816n; const p1div4 = (P + _1n$6) / _4n; return function sqrt3mod4(Fp, n) { const root = Fp.pow(n, p1div4); // Throw if root**2 != n if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root'); return root; }; } // Atkin algorithm for q ≡ 5 (mod 8), https://eprint.iacr.org/2012/685.pdf (page 10) if (P % _8n$1 === _5n) { const c1 = (P - _5n) / _8n$1; return function sqrt5mod8(Fp, n) { const n2 = Fp.mul(n, _2n$3); const v = Fp.pow(n2, c1); const nv = Fp.mul(n, v); const i = Fp.mul(Fp.mul(nv, _2n$3), v); const root = Fp.mul(nv, Fp.sub(i, Fp.ONE)); if (!Fp.eql(Fp.sqr(root), n)) throw new Error('Cannot find square root'); return root; }; } // Other cases: Tonelli-Shanks algorithm return tonelliShanks(P); } // prettier-ignore const FIELD_FIELDS = [ 'create', 'isValid', 'is0', 'neg', 'inv', 'sqrt', 'sqr', 'eql', 'add', 'sub', 'mul', 'pow', 'div', 'addN', 'subN', 'mulN', 'sqrN' ]; function validateField(field) { const initial = { ORDER: 'bigint', MASK: 'bigint', BYTES: 'isSafeInteger', BITS: 'isSafeInteger', }; const opts = FIELD_FIELDS.reduce((map, val) => { map[val] = 'function'; return map; }, initial); return validateObject(field, opts); } // Generic field functions /** * Same as `pow` but for Fp: non-constant-time. * Unsafe in some contexts: uses ladder, so can expose bigint bits. */ function FpPow(f, num, power) { // Should have same speed as pow for bigints // TODO: benchmark! if (power < _0n$4) throw new Error('Expected power > 0'); if (power === _0n$4) return f.ONE; if (power === _1n$6) return num; let p = f.ONE; let d = num; while (power > _0n$4) { if (power & _1n$6) p = f.mul(p, d); d = f.sqr(d); power >>= _1n$6; } return p; } /** * Efficiently invert an array of Field elements. * `inv(0)` will return `undefined` here: make sure to throw an error. */ function FpInvertBatch(f, nums) { const tmp = new Array(nums.length); // Walk from first to last, multiply them by each other MOD p const lastMultiplied = nums.reduce((acc, num, i) => { if (f.is0(num)) return acc; tmp[i] = acc; return f.mul(acc, num); }, f.ONE); // Invert last element const inverted = f.inv(lastMultiplied); // Walk from last to first, multiply them by inverted each other MOD p nums.reduceRight((acc, num, i) => { if (f.is0(num)) return acc; tmp[i] = f.mul(acc, tmp[i]); return f.mul(acc, num); }, inverted); return tmp; } // CURVE.n lengths function nLength(n, nBitLength) { // Bit size, byte size of CURVE.n const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length; const nByteLength = Math.ceil(_nBitLength / 8); return { nBitLength: _nBitLength, nByteLength }; } /** * Initializes a finite field over prime. **Non-primes are not supported.** * Do not init in loop: slow. Very fragile: always run a benchmark on a change. * Major performance optimizations: * * a) denormalized operations like mulN instead of mul * * b) same object shape: never add or remove keys * * c) Object.freeze * NOTE: operations don't check 'isValid' for all elements for performance reasons, * it is caller responsibility to check this. * This is low-level code, please make sure you know what you doing. * @param ORDER prime positive bigint * @param bitLen how many bits the field consumes * @param isLE (def: false) if encoding / decoding should be in little-endian * @param redef optional faster redefinitions of sqrt and other methods */ function Field(ORDER, bitLen, isLE = false, redef = {}) { if (ORDER <= _0n$4) throw new Error(`Expected Field ORDER > 0, got ${ORDER}`); const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen); if (BYTES > 2048) throw new Error('Field lengths over 2048 bytes are not supported'); const sqrtP = FpSqrt(ORDER); const f = Object.freeze({ ORDER, BITS, BYTES, MASK: bitMask(BITS), ZERO: _0n$4, ONE: _1n$6, create: (num) => mod(num, ORDER), isValid: (num) => { if (typeof num !== 'bigint') throw new Error(`Invalid field element: expected bigint, got ${typeof num}`); return _0n$4 <= num && num < ORDER; // 0 is valid element, but it's not invertible }, is0: (num) => num === _0n$4, isOdd: (num) => (num & _1n$6) === _1n$6, neg: (num) => mod(-num, ORDER), eql: (lhs, rhs) => lhs === rhs, sqr: (num) => mod(num * num, ORDER), add: (lhs, rhs) => mod(lhs + rhs, ORDER), sub: (lhs, rhs) => mod(lhs - rhs, ORDER), mul: (lhs, rhs) => mod(lhs * rhs, ORDER), pow: (num, power) => FpPow(f, num, power), div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER), // Same as above, but doesn't normalize sqrN: (num) => num * num, addN: (lhs, rhs) => lhs + rhs, subN: (lhs, rhs) => lhs - rhs, mulN: (lhs, rhs) => lhs * rhs, inv: (num) => invert(num, ORDER), sqrt: redef.sqrt || ((n) => sqrtP(f, n)), invertBatch: (lst) => FpInvertBatch(f, lst), // TODO: do we really need constant cmov? // We don't have const-time bigints anyway, so probably will be not very useful cmov: (a, b, c) => (c ? b : a), toBytes: (num) => (isLE ? numberToBytesLE(num, BYTES) : numberToBytesBE(num, BYTES)), fromBytes: (bytes) => { if (bytes.length !== BYTES) throw new Error(`Fp.fromBytes: expected ${BYTES}, got ${bytes.length}`); return isLE ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes); }, }); return Object.freeze(f); } /** * Returns total number of bytes consumed by the field element. * For example, 32 bytes for usual 256-bit weierstrass curve. * @param fieldOrder number of field elements, usually CURVE.n * @returns byte length of field */ function getFieldBytesLength(fieldOrder) { if (typeof fieldOrder !== 'bigint') throw new Error('field order must be bigint'); const bitLength = fieldOrder.toString(2).length; return Math.ceil(bitLength / 8); } /** * Returns minimal amount of bytes that can be safely reduced * by field order. * Should be 2^-128 for 128-bit curve such as P256. * @param fieldOrder number of field elements, usually CURVE.n * @returns byte length of target hash */ function getMinHashLength(fieldOrder) { const length = getFieldBytesLength(fieldOrder); return length + Math.ceil(length / 2); } /** * "Constant-time" private key generation utility. * Can take (n + n/2) or more bytes of uniform input e.g. from CSPRNG or KDF * and convert them into private scalar, with the modulo bias being negligible. * Needs at least 48 bytes of input for 32-byte private key. * https://research.kudelskisecurity.com/2020/07/28/the-definitive-guide-to-modulo-bias-and-how-to-avoid-it/ * FIPS 186-5, A.2 https://csrc.nist.gov/publications/detail/fips/186/5/final * RFC 9380, https://www.rfc-editor.org/rfc/rfc9380#section-5 * @param hash hash output from SHA3 or a similar function * @param groupOrder size of subgroup - (e.g. secp256k1.CURVE.n) * @param isLE interpret hash bytes as LE num * @returns valid private scalar */ function mapHashToField(key, fieldOrder, isLE = false) { const len = key.length; const fieldLen = getFieldBytesLength(fieldOrder); const minLen = getMinHashLength(fieldOrder); // No small numbers: need to understand bias story. No huge numbers: easier to detect JS timings. if (len < 16 || len < minLen || len > 1024) throw new Error(`expected ${minLen}-1024 bytes of input, got ${len}`); const num = isLE ? bytesToNumberBE(key) : bytesToNumberLE(key); // `mod(x, 11)` can sometimes produce 0. `mod(x, 10) + 1` is the same, but no 0 const reduced = mod(num, fieldOrder - _1n$6) + _1n$6; return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen); } /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ // Abelian group utilities const _0n$3 = BigInt(0); const _1n$5 = BigInt(1); // Since points in different groups cannot be equal (different object constructor), // we can have single place to store precomputes const pointPrecomputes = new WeakMap(); const pointWindowSizes = new WeakMap(); // This allows use make points immutable (nothing changes inside) // Elliptic curve multiplication of Point by scalar. Fragile. // Scalars should always be less than curve order: this should be checked inside of a curve itself. // Creates precomputation tables for fast multiplication: // - private scalar is split by fixed size windows of W bits // - every window point is collected from window's table & added to accumulator // - since windows are different, same point inside tables won't be accessed more than once per calc // - each multiplication is 'Math.ceil(CURVE_ORDER / 𝑊) + 1' point additions (fixed for any scalar) // - +1 window is neccessary for wNAF // - wNAF reduces table size: 2x less memory + 2x faster generation, but 10% slower multiplication // TODO: Research returning 2d JS array of windows, instead of a single window. This would allow // windows to be in different memory locations function wNAF(c, bits) { const constTimeNegate = (condition, item) => { const neg = item.negate(); return condition ? neg : item; }; const validateW = (W) => { if (!Number.isSafeInteger(W) || W <= 0 || W > bits) throw new Error(`Wrong window size=${W}, should be [1..${bits}]`); }; const opts = (W) => { validateW(W); const windows = Math.ceil(bits / W) + 1; // +1, because const windowSize = 2 ** (W - 1); // -1 because we skip zero return { windows, windowSize }; }; return { constTimeNegate, // non-const time multiplication ladder unsafeLadder(elm, n) { let p = c.ZERO; let d = elm; while (n > _0n$3) { if (n & _1n$5) p = p.add(d); d = d.double(); n >>= _1n$5; } return p; }, /** * Creates a wNAF precomputation window. Used for caching. * Default window size is set by `utils.precompute()` and is equal to 8. * Number of precomputed points depends on the curve size: * 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where: * - 𝑊 is the window size * - 𝑛 is the bitlength of the curve order. * For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224. * @returns precomputed point tables flattened to a single array */ precomputeWindow(elm, W) { const { windows, windowSize } = opts(W); const points = []; let p = elm; let base = p; for (let window = 0; window < windows; window++) { base = p; points.push(base); // =1, because we skip zero for (let i = 1; i < windowSize; i++) { base = base.add(p); points.push(base); } p = base.double(); } return points; }, /** * Implements ec multiplication using precomputed tables and w-ary non-adjacent form. * @param W window size * @param precomputes precomputed tables * @param n scalar (we don't check here, but should be less than curve order) * @returns real and fake (for const-time) points */ wNAF(W, precomputes, n) { // TODO: maybe check that scalar is less than group order? wNAF behavious is undefined otherwise // But need to carefully remove other checks before wNAF. ORDER == bits here const { windows, windowSize } = opts(W); let p = c.ZERO; let f = c.BASE; const mask = BigInt(2 ** W - 1); // Create mask with W ones: 0b1111 for W=4 etc. const maxNumber = 2 ** W; const shiftBy = BigInt(W); for (let window = 0; window < windows; window++) { const offset = window * windowSize; // Extract W bits. let wbits = Number(n & mask); // Shift number by W bits. n >>= shiftBy; // If the bits are bigger than max size, we'll split those. // +224 => 256 - 32 if (wbits > windowSize) { wbits -= maxNumber; n += _1n$5; } // This code was first written with assumption that 'f' and 'p' will never be infinity point: // since each addition is multiplied by 2 ** W, it cannot cancel each other. However, // there is negate now: it is possible that negated element from low value // would be the same as high element, which will create carry into next window. // It's not obvious how this can fail, but still worth investigating later. // Check if we're onto Zero point. // Add random point inside current window to f. const offset1 = offset; const offset2 = offset + Math.abs(wbits) - 1; // -1 because we skip zero const cond1 = window % 2 !== 0; const cond2 = wbits < 0; if (wbits === 0) { // The most important part for const-time getPublicKey f = f.add(constTimeNegate(cond1, precomputes[offset1])); } else { p = p.add(constTimeNegate(cond2, precomputes[offset2])); } } // JIT-compiler should not eliminate f here, since it will later be used in normalizeZ() // Even if the variable is still unused, there are some checks which will // throw an exception, so compiler needs to prove they won't happen, which is hard. // At this point there is a way to F be infinity-point even if p is not, // which makes it less const-time: around 1 bigint multiply. return { p, f }; }, wNAFCached(P, n, transform) { const W = pointWindowSizes.get(P) || 1; // Calculate precomputes on a first run, reuse them after let comp = pointPrecomputes.get(P); if (!comp) { comp = this.precomputeWindow(P, W); if (W !== 1) pointPrecomputes.set(P, transform(comp)); } return this.wNAF(W, comp, n); }, // We calculate precomputes for elliptic curve point multiplication // using windowed method. This specifies window size and // stores precomputed values. Usually only base point would be precomputed. setWindowSize(P, W) { validateW(W); pointWindowSizes.set(P, W); pointPrecomputes.delete(P); }, }; } /** * Pippenger algorithm for multi-scalar multiplication (MSM). * MSM is basically (Pa + Qb + Rc + ...). * 30x faster vs naive addition on L=4096, 10x faster with precomputes. * For N=254bit, L=1, it does: 1024 ADD + 254 DBL. For L=5: 1536 ADD + 254 DBL. * Algorithmically constant-time (for same L), even when 1 point + scalar, or when scalar = 0. * @param c Curve Point constructor * @param field field over CURVE.N - important that it's not over CURVE.P * @param points array of L curve points * @param scalars array of L scalars (aka private keys / bigints) */ function pippenger(c, field, points, scalars) { // If we split scalars by some window (let's say 8 bits), every chunk will only // take 256 buckets even if there are 4096 scalars, also re-uses double. // TODO: // - https://eprint.iacr.org/2024/750.pdf // - https://tches.iacr.org/index.php/TCHES/article/view/10287 // 0 is accepted in scalars if (!Array.isArray(points) || !Array.isArray(scalars) || scalars.length !== points.length) throw new Error('arrays of points and scalars must have equal length'); scalars.forEach((s, i) => { if (!field.isValid(s)) throw new Error(`wrong scalar at index ${i}`); }); points.forEach((p, i) => { if (!(p instanceof c)) throw new Error(`wrong point at index ${i}`); }); const wbits = bitLen(BigInt(points.length)); const windowSize = wbits > 12 ? wbits - 3 : wbits > 4 ? wbits - 2 : wbits ? 2 : 1; // in bits const MASK = (1 << windowSize) - 1; const buckets = new Array(MASK + 1).fill(c.ZERO); // +1 for zero array const lastBits = Math.floor((field.BITS - 1) / windowSize) * windowSize; let sum = c.ZERO; for (let i = lastBits; i >= 0; i -= windowSize) { buckets.fill(c.ZERO); for (let j = 0; j < scalars.length; j++) { const scalar = scalars[j]; const wbits = Number((scalar >> BigInt(i)) & BigInt(MASK)); buckets[wbits] = buckets[wbits].add(points[j]); } let resI = c.ZERO; // not using this will do small speed-up, but will lose ct // Skip first bucket, because it is zero for (let j = buckets.length - 1, sumI = c.ZERO; j > 0; j--) { sumI = sumI.add(buckets[j]); resI = resI.add(sumI); } sum = sum.add(resI); if (i !== 0) for (let j = 0; j < windowSize; j++) sum = sum.double(); } return sum; } function validateBasic(curve) { validateField(curve.Fp); validateObject(curve, { n: 'bigint', h: 'bigint', Gx: 'field', Gy: 'field', }, { nBitLength: 'isSafeInteger', nByteLength: 'isSafeInteger', }); // Set defaults return Object.freeze({ ...nLength(curve.n, curve.nBitLength), ...curve, ...{ p: curve.Fp.ORDER }, }); } /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ // Short Weierstrass curve. The formula is: y² = x³ + ax + b function validateSigVerOpts(opts) { if (opts.lowS !== undefined) abool('lowS', opts.lowS); if (opts.prehash !== undefined) abool('prehash', opts.prehash); } function validatePointOpts(curve) { const opts = validateBasic(curve); validateObject(opts, { a: 'field', b: 'field', }, { allowedPrivateKeyLengths: 'array', wrapPrivateKey: 'boolean', isTorsionFree: 'function', clearCofactor: 'function', allowInfinityPoint: 'boolean', fromBytes: 'function', toBytes: 'function', }); const { endo, Fp, a } = opts; if (endo) { if (!Fp.eql(a, Fp.ZERO)) { throw new Error('Endomorphism can only be defined for Koblitz curves that have a=0'); } if (typeof endo !== 'object' || typeof endo.beta !== 'bigint' || typeof endo.splitScalar !== 'function') { throw new Error('Expected endomorphism with beta: bigint and splitScalar: function'); } } return Object.freeze({ ...opts }); } const { bytesToNumberBE: b2n, hexToBytes: h2b } = ut; /** * ASN.1 DER encoding utilities. ASN is very complex & fragile. Format: * * [0x30 (SEQUENCE), bytelength, 0x02 (INTEGER), intLength, R, 0x02 (INTEGER), intLength, S] * * Docs: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/, https://luca.ntop.org/Teaching/Appunti/asn1.html */ const DER = { // asn.1 DER encoding utils Err: class DERErr extends Error { constructor(m = '') { super(m); } }, // Basic building block is TLV (Tag-Length-Value) _tlv: { encode: (tag, data) => { const { Err: E } = DER; if (tag < 0 || tag > 256) throw new E('tlv.encode: wrong tag'); if (data.length & 1) throw new E('tlv.encode: unpadded data'); const dataLen = data.length / 2; const len = numberToHexUnpadded(dataLen); if ((len.length / 2) & 128) throw new E('tlv.encode: long form length too big'); // length of length with long form flag const lenLen = dataLen > 127 ? numberToHexUnpadded((len.length / 2) | 128) : ''; return `${numberToHexUnpadded(tag)}${lenLen}${len}${data}`; }, // v - value, l - left bytes (unparsed) decode(tag, data) { const { Err: E } = DER; let pos = 0; if (tag < 0 || tag > 256) throw new E('tlv.encode: wrong tag'); if (data.length < 2 || data[pos++] !== tag) throw new E('tlv.decode: wrong tlv'); const first = data[pos++]; const isLong = !!(first & 128); // First bit of first length byte is flag for short/long form let length = 0; if (!isLong) length = first; else { // Long form: [longFlag(1bit), lengthLength(7bit), length (BE)] const lenLen = first & 127; if (!lenLen) throw new E('tlv.decode(long): indefinite length not supported'); if (lenLen > 4) throw new E('tlv.decode(long): byte length is too big'); // this will overflow u32 in js const lengthBytes = data.subarray(pos, pos + lenLen); if (lengthBytes.length !== lenLen) throw new E('tlv.decode: length bytes not complete'); if (lengthBytes[0] === 0) throw new E('tlv.decode(long): zero leftmost byte'); for (const b of lengthBytes) length = (length << 8) | b; pos += lenLen; if (length < 128) throw new E('tlv.decode(long): not minimal encoding'); } const v = data.subarray(pos, pos + length); if (v.length !== length) throw new E('tlv.decode: wrong value length'); return { v, l: data.subarray(pos + length) }; }, }, // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag, // since we always use positive integers here. It must always be empty: // - add zero byte if exists // - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding) _int: { encode(num) { const { Err: E } = DER; if (num < _0n$2) throw new E('integer: negative integers are not allowed'); let hex = numberToHexUnpadded(num); // Pad with zero byte if negative flag is present if (Number.parseInt(hex[0], 16) & 0b1000) hex = '00' + hex; if (hex.length & 1) throw new E('unexpected assertion'); return hex; }, decode(data) { const { Err: E } = DER; if (data[0] & 128) throw new E('Invalid signature integer: negative'); if (data[0] === 0x00 && !(data[1] & 128)) throw new E('Invalid signature integer: unnecessary leading zero'); return b2n(data); }, }, toSig(hex) { // parse DER signature const { Err: E, _int: int, _tlv: tlv } = DER; const data = typeof hex === 'string' ? h2b(hex) : hex; abytes(data); const { v: seqBytes, l: seqLeftBytes } = tlv.decode(0x30, data); if (seqLeftBytes.length) throw new E('Invalid signature: left bytes after parsing'); const { v: rBytes, l: rLeftBytes } = tlv.decode(0x02, seqBytes); const { v: sBytes, l: sLeftBytes } = tlv.decode(0x02, rLeftBytes); if (sLeftBytes.length) throw new E('Invalid signature: left bytes after parsing'); return { r: int.decode(rBytes), s: int.decode(sBytes) }; }, hexFromSig(sig) { const { _tlv: tlv, _int: int } = DER; const seq = `${tlv.encode(0x02, int.encode(sig.r))}${tlv.encode(0x02, int.encode(sig.s))}`; return tlv.encode(0x30, seq); }, }; // Be friendly to bad ECMAScript parsers by not using bigint literals // prettier-ignore const _0n$2 = BigInt(0), _1n$4 = BigInt(1); BigInt(2); const _3n$1 = BigInt(3); BigInt(4); function weierstrassPoints(opts) { const CURVE = validatePointOpts(opts); const { Fp } = CURVE; // All curves has same field / group length as for now, but they can differ const Fn = Field(CURVE.n, CURVE.nBitLength); const toBytes = CURVE.toBytes || ((_c, point, _isCompressed) => { const a = point.toAffine(); return concatBytes(Uint8Array.from([0x04]), Fp.toBytes(a.x), Fp.toBytes(a.y)); }); const fromBytes = CURVE.fromBytes || ((bytes) => { // const head = bytes[0]; const tail = bytes.subarray(1); // if (head !== 0x04) throw new Error('Only non-compressed encoding is supported'); const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES)); const y = Fp.fromBytes(tail.subarray(Fp.BYTES, 2 * Fp.BYTES)); return { x, y }; }); /** * y² = x³ + ax + b: Short weierstrass curve formula * @returns y² */ function weierstrassEquation(x) { const { a, b } = CURVE; const x2 = Fp.sqr(x); // x * x const x3 = Fp.mul(x2, x); // x2 * x return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x3 + a * x + b } // Validate whether the passed curve params are valid. // We check if curve equation works for generator point. // `assertValidity()` won't work: `isTorsionFree()` is not available at this point in bls12-381. // ProjectivePoint class has not been initialized yet. if (!Fp.eql(Fp.sqr(CURVE.Gy), weierstrassEquation(CURVE.Gx))) throw new Error('bad generator point: equation left != right'); // Valid group elements reside in range 1..n-1 function isWithinCurveOrder(num) { return inRange(num, _1n$4, CURVE.n); } // Validates if priv key is valid and converts it to bigint. // Supports options allowedPrivateKeyLengths and wrapPrivateKey. function normPrivateKeyToScalar(key) { const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n: N } = CURVE; if (lengths && typeof key !== 'bigint') { if (isBytes(key)) key = bytesToHex(key); // Normalize to hex string, pad. E.g. P521 would norm 130-132 char hex to 132-char bytes if (typeof key !== 'string' || !lengths.includes(key.length)) throw new Error('Invalid key'); key = key.padStart(nByteLength * 2, '0'); } let num; try { num = typeof key === 'bigint' ? key : bytesToNumberBE(ensureBytes('private key', key, nByteLength)); } catch (error) { throw new Error(`private key must be ${nByteLength} bytes, hex or bigint, not ${typeof key}`); } if (wrapPrivateKey) num = mod(num, N); // disabled by default, enabled for BLS aInRange('private key', num, _1n$4, N); // num in range [1..N-1] return num; } function assertPrjPoint(other) { if (!(other instanceof Point)) throw new Error('ProjectivePoint expected'); } // Memoized toAffine / validity check. They are heavy. Points are immutable. // Converts Projective point to affine (x, y) coordinates. // Can accept precomputed Z^-1 - for example, from invertBatch. // (x, y, z) ∋ (x=x/z, y=y/z) const toAffineMemo = memoized((p, iz) => { const { px: x, py: y, pz: z } = p; // Fast-path for normalized points if (Fp.eql(z, Fp.ONE)) return { x, y }; const is0 = p.is0(); // If invZ was 0, we return zero point. However we still want to execute // all operations, so we replace invZ with a random number, 1. if (iz == null) iz = is0 ? Fp.ONE : Fp