diff --git a/dist/index.js b/dist/index.js index e0883a0c..e5069c77 100644 --- a/dist/index.js +++ b/dist/index.js @@ -52968,33 +52968,32 @@ exports.murmur332 = murmur332; /***/ }), /***/ 27766: -/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { "use strict"; -/*! noble-ed25519 - MIT License (c) Paul Miller (paulmillr.com) */ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; +/*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) */ Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.utils = exports.verify = exports.sign = exports.getPublicKey = exports.SignResult = exports.Signature = exports.Point = exports.ExtendedPoint = exports.CURVE = void 0; -const crypto_1 = __importDefault(__nccwpck_require__(6113)); +exports.utils = exports.curve25519 = exports.getSharedSecret = exports.sync = exports.verify = exports.sign = exports.getPublicKey = exports.Signature = exports.Point = exports.RistrettoPoint = exports.ExtendedPoint = exports.CURVE = void 0; +const nodeCrypto = __nccwpck_require__(6113); const _0n = BigInt(0); const _1n = BigInt(1); const _2n = BigInt(2); -const _255n = BigInt(255); -const CURVE = { +const CU_O = BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'); +const CURVE = Object.freeze({ a: BigInt(-1), d: BigInt('37095705934669439343138083508754565189542113879843219016388785533085940283555'), - P: _2n ** _255n - BigInt(19), - n: _2n ** BigInt(252) + BigInt('27742317777372353535851937790883648493'), + P: BigInt('57896044618658097711785492504343953926634992332820282019728792003956564819949'), + l: CU_O, + n: CU_O, h: BigInt(8), Gx: BigInt('15112221349535400772501151409588531511454012693041857206046113283949847762202'), Gy: BigInt('46316835694926478169428394003475163141307993866256225615783033603165251855960'), -}; +}); exports.CURVE = CURVE; -const B32 = 32; +const POW_2_256 = BigInt('0x10000000000000000000000000000000000000000000000000000000000000000'); const SQRT_M1 = BigInt('19681161376707505956807079304988542015446066515923890162744021073123829784752'); +const SQRT_D = BigInt('6853475219497561581579357271197624642482790079785650197046958215289687604742'); const SQRT_AD_MINUS_ONE = BigInt('25063068953384623474111414158702152701244531502492656460079210482610430750235'); const INVSQRT_A_MINUS_D = BigInt('54469307008909316920995813868745141605393597292927456921205312896311721017578'); const ONE_MINUS_D_SQ = BigInt('1159843021668779879193775521855586647937357759715417654439879720876111806838'); @@ -53021,114 +53020,31 @@ class ExtendedPoint { static normalizeZ(points) { return this.toAffineBatch(points).map(this.fromAffine); } - static fromRistrettoHash(hash) { - if (typeof hash === 'string') - hash = hexToBytes(hash); - if (hash.length !== 64) - throw new Error('Invalid ristretto hash, need 64 bytes'); - const r1 = bytes255ToNumberLE(hash.slice(0, B32)); - const R1 = this.calcElligatorRistrettoMap(r1); - const r2 = bytes255ToNumberLE(hash.slice(B32, B32 * 2)); - const R2 = this.calcElligatorRistrettoMap(r2); - return R1.add(R2); - } - static calcElligatorRistrettoMap(r0) { - const { d } = CURVE; - const r = mod(SQRT_M1 * r0 * r0); - const Ns = mod((r + _1n) * ONE_MINUS_D_SQ); - let c = BigInt(-1); - const D = mod((c - d * r) * mod(r + d)); - let { isValid: Ns_D_is_sq, value: s } = uvRatio(Ns, D); - let s_ = mod(s * r0); - if (!edIsNegative(s_)) - s_ = mod(-s_); - if (!Ns_D_is_sq) - s = s_; - if (!Ns_D_is_sq) - c = r; - const Nt = mod(c * (r - _1n) * D_MINUS_ONE_SQ - D); - const s2 = s * s; - const W0 = mod((s + s) * D); - const W1 = mod(Nt * SQRT_AD_MINUS_ONE); - const W2 = mod(_1n - s2); - const W3 = mod(_1n + s2); - return new ExtendedPoint(mod(W0 * W3), mod(W2 * W1), mod(W1 * W3), mod(W0 * W2)); - } - static fromRistrettoBytes(bytes) { - if (typeof bytes === 'string') - bytes = hexToBytes(bytes); - if (bytes.length !== 32) - throw new Error('Invalid ristretto hash, need 64 bytes'); - const { a, d } = CURVE; - const emsg = 'ExtendedPoint.fromRistrettoBytes: Cannot convert bytes to Ristretto Point'; - const s = bytes255ToNumberLE(bytes); - if (!equalBytes(numberToBytesPadded(s, B32), bytes) || edIsNegative(s)) - throw new Error(emsg); - const s2 = mod(s * s); - const u1 = mod(_1n + a * s2); - const u2 = mod(_1n - a * s2); - const u1_2 = mod(u1 * u1); - const u2_2 = mod(u2 * u2); - const v = mod(a * d * u1_2 - u2_2); - const { isValid, value: I } = invertSqrt(mod(v * u2_2)); - const Dx = mod(I * u2); - const Dy = mod(I * Dx * v); - let x = mod((s + s) * Dx); - if (edIsNegative(x)) - x = mod(-x); - const y = mod(u1 * Dy); - const t = mod(x * y); - if (!isValid || edIsNegative(t) || y === _0n) - throw new Error(emsg); - return new ExtendedPoint(x, y, _1n, t); - } - toRistrettoBytes() { - let { x, y, z, t } = this; - const u1 = mod(mod(z + y) * mod(z - y)); - const u2 = mod(x * y); - const { value: invsqrt } = invertSqrt(mod(u1 * u2 ** _2n)); - const D1 = mod(invsqrt * u1); - const D2 = mod(invsqrt * u2); - const zInv = mod(D1 * D2 * t); - let D; - if (edIsNegative(t * zInv)) { - let _x = mod(y * SQRT_M1); - let _y = mod(x * SQRT_M1); - x = _x; - y = _y; - D = mod(D1 * INVSQRT_A_MINUS_D); - } - else { - D = D2; - } - if (edIsNegative(x * zInv)) - y = mod(-y); - let s = mod((z - y) * D); - if (edIsNegative(s)) - s = mod(-s); - return numberToBytesPadded(s, B32); - } equals(other) { - const a = this; - const b = other; - return mod(a.t * b.z) === mod(b.t * a.z); + assertExtPoint(other); + const { x: X1, y: Y1, z: Z1 } = this; + const { x: X2, y: Y2, z: Z2 } = other; + const X1Z2 = mod(X1 * Z2); + const X2Z1 = mod(X2 * Z1); + const Y1Z2 = mod(Y1 * Z2); + const Y2Z1 = mod(Y2 * Z1); + return X1Z2 === X2Z1 && Y1Z2 === Y2Z1; } negate() { return new ExtendedPoint(mod(-this.x), this.y, this.z, mod(-this.t)); } double() { - const X1 = this.x; - const Y1 = this.y; - const Z1 = this.z; + const { x: X1, y: Y1, z: Z1 } = this; const { a } = CURVE; - const A = mod(X1 ** _2n); - const B = mod(Y1 ** _2n); - const C = mod(_2n * Z1 ** _2n); + const A = mod(X1 * X1); + const B = mod(Y1 * Y1); + const C = mod(_2n * mod(Z1 * Z1)); const D = mod(a * A); - const E = mod((X1 + Y1) ** _2n - A - B); - const G = mod(D + B); - const F = mod(G - C); - const H = mod(D - B); + const x1y1 = X1 + Y1; + const E = mod(mod(x1y1 * x1y1) - A - B); + const G = D + B; + const F = G - C; + const H = D - B; const X3 = mod(E * F); const Y3 = mod(G * H); const T3 = mod(E * H); @@ -53136,25 +53052,19 @@ class ExtendedPoint { return new ExtendedPoint(X3, Y3, Z3, T3); } add(other) { - const X1 = this.x; - const Y1 = this.y; - const Z1 = this.z; - const T1 = this.t; - const X2 = other.x; - const Y2 = other.y; - const Z2 = other.z; - const T2 = other.t; + assertExtPoint(other); + const { x: X1, y: Y1, z: Z1, t: T1 } = this; + const { x: X2, y: Y2, z: Z2, t: T2 } = other; const A = mod((Y1 - X1) * (Y2 + X2)); const B = mod((Y1 + X1) * (Y2 - X2)); const F = mod(B - A); - if (F === _0n) { + if (F === _0n) return this.double(); - } const C = mod(Z1 * _2n * T2); const D = mod(T1 * _2n * Z2); - const E = mod(D + C); - const G = mod(B + A); - const H = mod(D - C); + const E = D + C; + const G = B + A; + const H = D - C; const X3 = mod(E * F); const Y3 = mod(G * H); const T3 = mod(E * H); @@ -53164,23 +53074,9 @@ class ExtendedPoint { subtract(other) { return this.add(other.negate()); } - multiplyUnsafe(scalar) { - let n = normalizeScalar(scalar); - if (n === _1n) - return this; - let p = ExtendedPoint.ZERO; - let d = this; - while (n > _0n) { - if (n & _1n) - p = p.add(d); - d = d.double(); - n >>= _1n; - } - return p; - } precomputeWindow(W) { - const windows = 256 / W + 1; - let points = []; + const windows = 1 + 256 / W; + const points = []; let p = this; let base = p; for (let window = 0; window < windows; window++) { @@ -53211,7 +53107,7 @@ class ExtendedPoint { } let p = ExtendedPoint.ZERO; let f = ExtendedPoint.ZERO; - const windows = 256 / W + 1; + const windows = 1 + 256 / W; const windowSize = 2 ** (W - 1); const mask = BigInt(2 ** W - 1); const maxNumber = 2 ** W; @@ -53237,21 +53133,188 @@ class ExtendedPoint { p = p.add(cached); } } - return [p, f]; + return ExtendedPoint.normalizeZ([p, f])[0]; } multiply(scalar, affinePoint) { - const n = normalizeScalar(scalar); - return ExtendedPoint.normalizeZ(this.wNAF(n, affinePoint))[0]; + return this.wNAF(normalizeScalar(scalar, CURVE.l), affinePoint); + } + multiplyUnsafe(scalar) { + let n = normalizeScalar(scalar, CURVE.l, false); + const G = ExtendedPoint.BASE; + const P0 = ExtendedPoint.ZERO; + if (n === _0n) + return P0; + if (this.equals(P0) || n === _1n) + return this; + if (this.equals(G)) + return this.wNAF(n); + let p = P0; + let d = this; + while (n > _0n) { + if (n & _1n) + p = p.add(d); + d = d.double(); + n >>= _1n; + } + return p; + } + isSmallOrder() { + return this.multiplyUnsafe(CURVE.h).equals(ExtendedPoint.ZERO); + } + isTorsionFree() { + return this.multiplyUnsafe(CURVE.l).equals(ExtendedPoint.ZERO); } toAffine(invZ = invert(this.z)) { - const x = mod(this.x * invZ); - const y = mod(this.y * invZ); - return new Point(x, y); + const { x, y, z } = this; + const ax = mod(x * invZ); + const ay = mod(y * invZ); + const zz = mod(z * invZ); + if (zz !== _1n) + throw new Error('invZ was invalid'); + return new Point(ax, ay); + } + fromRistrettoBytes() { + legacyRist(); + } + toRistrettoBytes() { + legacyRist(); + } + fromRistrettoHash() { + legacyRist(); } } exports.ExtendedPoint = ExtendedPoint; ExtendedPoint.BASE = new ExtendedPoint(CURVE.Gx, CURVE.Gy, _1n, mod(CURVE.Gx * CURVE.Gy)); ExtendedPoint.ZERO = new ExtendedPoint(_0n, _1n, _1n, _0n); +function assertExtPoint(other) { + if (!(other instanceof ExtendedPoint)) + throw new TypeError('ExtendedPoint expected'); +} +function assertRstPoint(other) { + if (!(other instanceof RistrettoPoint)) + throw new TypeError('RistrettoPoint expected'); +} +function legacyRist() { + throw new Error('Legacy method: switch to RistrettoPoint'); +} +class RistrettoPoint { + constructor(ep) { + this.ep = ep; + } + static calcElligatorRistrettoMap(r0) { + const { d } = CURVE; + const r = mod(SQRT_M1 * r0 * r0); + const Ns = mod((r + _1n) * ONE_MINUS_D_SQ); + let c = BigInt(-1); + const D = mod((c - d * r) * mod(r + d)); + let { isValid: Ns_D_is_sq, value: s } = uvRatio(Ns, D); + let s_ = mod(s * r0); + if (!edIsNegative(s_)) + s_ = mod(-s_); + if (!Ns_D_is_sq) + s = s_; + if (!Ns_D_is_sq) + c = r; + const Nt = mod(c * (r - _1n) * D_MINUS_ONE_SQ - D); + const s2 = s * s; + const W0 = mod((s + s) * D); + const W1 = mod(Nt * SQRT_AD_MINUS_ONE); + const W2 = mod(_1n - s2); + const W3 = mod(_1n + s2); + return new ExtendedPoint(mod(W0 * W3), mod(W2 * W1), mod(W1 * W3), mod(W0 * W2)); + } + static hashToCurve(hex) { + hex = ensureBytes(hex, 64); + const r1 = bytes255ToNumberLE(hex.slice(0, 32)); + const R1 = this.calcElligatorRistrettoMap(r1); + const r2 = bytes255ToNumberLE(hex.slice(32, 64)); + const R2 = this.calcElligatorRistrettoMap(r2); + return new RistrettoPoint(R1.add(R2)); + } + static fromHex(hex) { + hex = ensureBytes(hex, 32); + const { a, d } = CURVE; + const emsg = 'RistrettoPoint.fromHex: the hex is not valid encoding of RistrettoPoint'; + const s = bytes255ToNumberLE(hex); + if (!equalBytes(numberTo32BytesLE(s), hex) || edIsNegative(s)) + throw new Error(emsg); + const s2 = mod(s * s); + const u1 = mod(_1n + a * s2); + const u2 = mod(_1n - a * s2); + const u1_2 = mod(u1 * u1); + const u2_2 = mod(u2 * u2); + const v = mod(a * d * u1_2 - u2_2); + const { isValid, value: I } = invertSqrt(mod(v * u2_2)); + const Dx = mod(I * u2); + const Dy = mod(I * Dx * v); + let x = mod((s + s) * Dx); + if (edIsNegative(x)) + x = mod(-x); + const y = mod(u1 * Dy); + const t = mod(x * y); + if (!isValid || edIsNegative(t) || y === _0n) + throw new Error(emsg); + return new RistrettoPoint(new ExtendedPoint(x, y, _1n, t)); + } + toRawBytes() { + let { x, y, z, t } = this.ep; + const u1 = mod(mod(z + y) * mod(z - y)); + const u2 = mod(x * y); + const u2sq = mod(u2 * u2); + const { value: invsqrt } = invertSqrt(mod(u1 * u2sq)); + const D1 = mod(invsqrt * u1); + const D2 = mod(invsqrt * u2); + const zInv = mod(D1 * D2 * t); + let D; + if (edIsNegative(t * zInv)) { + let _x = mod(y * SQRT_M1); + let _y = mod(x * SQRT_M1); + x = _x; + y = _y; + D = mod(D1 * INVSQRT_A_MINUS_D); + } + else { + D = D2; + } + if (edIsNegative(x * zInv)) + y = mod(-y); + let s = mod((z - y) * D); + if (edIsNegative(s)) + s = mod(-s); + return numberTo32BytesLE(s); + } + toHex() { + return bytesToHex(this.toRawBytes()); + } + toString() { + return this.toHex(); + } + equals(other) { + assertRstPoint(other); + const a = this.ep; + const b = other.ep; + const one = mod(a.x * b.y) === mod(a.y * b.x); + const two = mod(a.y * b.y) === mod(a.x * b.x); + return one || two; + } + add(other) { + assertRstPoint(other); + return new RistrettoPoint(this.ep.add(other.ep)); + } + subtract(other) { + assertRstPoint(other); + return new RistrettoPoint(this.ep.subtract(other.ep)); + } + multiply(scalar) { + return new RistrettoPoint(this.ep.multiply(scalar)); + } + multiplyUnsafe(scalar) { + return new RistrettoPoint(this.ep.multiplyUnsafe(scalar)); + } +} +exports.RistrettoPoint = RistrettoPoint; +RistrettoPoint.BASE = new RistrettoPoint(ExtendedPoint.BASE); +RistrettoPoint.ZERO = new RistrettoPoint(ExtendedPoint.ZERO); const pointPrecomputes = new WeakMap(); class Point { constructor(x, y) { @@ -53262,18 +53325,16 @@ class Point { this._WINDOW_SIZE = windowSize; pointPrecomputes.delete(this); } - static fromHex(hash) { + static fromHex(hex, strict = true) { const { d, P } = CURVE; - const bytes = hash instanceof Uint8Array ? hash : hexToBytes(hash); - if (bytes.length !== 32) - throw new Error('Point.fromHex: expected 32 bytes'); - const last = bytes[31]; - const normedLast = last & ~0x80; - const isLastByteOdd = (last & 0x80) !== 0; - const normed = Uint8Array.from(Array.from(bytes.slice(0, 31)).concat(normedLast)); + hex = ensureBytes(hex, 32); + const normed = hex.slice(); + normed[31] = hex[31] & ~0x80; const y = bytesToNumberLE(normed); - if (y >= P) - throw new Error('Point.fromHex expects hex <= Fp'); + if (strict && y >= P) + throw new Error('Expected 0 < hex < P'); + if (!strict && y >= POW_2_256) + throw new Error('Expected 0 < hex < 2**256'); const y2 = mod(y * y); const u = mod(y2 - _1n); const v = mod(d * y2 + _1n); @@ -53281,30 +53342,30 @@ class Point { if (!isValid) throw new Error('Point.fromHex: invalid y coordinate'); const isXOdd = (x & _1n) === _1n; + const isLastByteOdd = (hex[31] & 0x80) !== 0; if (isLastByteOdd !== isXOdd) { x = mod(-x); } return new Point(x, y); } static async fromPrivateKey(privateKey) { - const privBytes = await getPrivateBytes(privateKey); - return Point.BASE.multiply(encodePrivate(privBytes)); + return (await getExtendedPublicKey(privateKey)).point; } toRawBytes() { - const hex = numberToHex(this.y); - const u8 = new Uint8Array(B32); - for (let i = hex.length - 2, j = 0; j < B32 && i >= 0; i -= 2, j++) { - u8[j] = parseHexByte(hex[i] + hex[i + 1]); - } - const mask = this.x & _1n ? 0x80 : 0; - u8[B32 - 1] |= mask; - return u8; + const bytes = numberTo32BytesLE(this.y); + bytes[31] |= this.x & _1n ? 0x80 : 0; + return bytes; } toHex() { return bytesToHex(this.toRawBytes()); } toX25519() { - return mod((_1n + this.y) * invert(_1n - this.y)); + const { y } = this; + const u = mod((_1n + y) * invert(_1n - y)); + return numberTo32BytesLE(u); + } + isTorsionFree() { + return ExtendedPoint.fromAffine(this).isTorsionFree(); } equals(other) { return this.x === other.x && this.y === other.y; @@ -53329,31 +53390,35 @@ class Signature { constructor(r, s) { this.r = r; this.s = s; + this.assertValidity(); } static fromHex(hex) { - hex = ensureBytes(hex); - const r = Point.fromHex(hex.slice(0, 32)); - const s = bytesToNumberLE(hex.slice(32)); - if (!isWithinCurveOrder(s)) - throw new Error('Signature.fromHex expects s <= CURVE.n'); + const bytes = ensureBytes(hex, 64); + const r = Point.fromHex(bytes.slice(0, 32), false); + const s = bytesToNumberLE(bytes.slice(32, 64)); return new Signature(r, s); } + assertValidity() { + const { r, s } = this; + if (!(r instanceof Point)) + throw new Error('Expected Point instance'); + normalizeScalar(s, CURVE.l, false); + return this; + } toRawBytes() { - const numberBytes = hexToBytes(numberToHex(this.s)).reverse(); - const sBytes = new Uint8Array(B32); - sBytes.set(numberBytes); - const res = new Uint8Array(B32 * 2); - res.set(this.r.toRawBytes()); - res.set(sBytes, 32); - return res; + const u8 = new Uint8Array(64); + u8.set(this.r.toRawBytes()); + u8.set(numberTo32BytesLE(this.s), 32); + return u8; } toHex() { return bytesToHex(this.toRawBytes()); } } exports.Signature = Signature; -exports.SignResult = Signature; function concatBytes(...arrays) { + if (!arrays.every((a) => a instanceof Uint8Array)) + throw new Error('Expected Uint8Array list'); if (arrays.length === 1) return arrays[0]; const length = arrays.reduce((a, arr) => a + arr.length, 0); @@ -53367,18 +53432,14 @@ function concatBytes(...arrays) { } const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0')); function bytesToHex(uint8a) { + if (!(uint8a instanceof Uint8Array)) + throw new Error('Uint8Array expected'); let hex = ''; for (let i = 0; i < uint8a.length; i++) { hex += hexes[uint8a[i]]; } return hex; } -function parseHexByte(hexByte) { - const byte = Number.parseInt(hexByte, 16); - if (Number.isNaN(byte)) - throw new Error('Invalid byte sequence'); - return byte; -} function hexToBytes(hex) { if (typeof hex !== 'string') { throw new TypeError('hexToBytes: expected string, got ' + typeof hex); @@ -53388,30 +53449,33 @@ function hexToBytes(hex) { const array = new Uint8Array(hex.length / 2); for (let i = 0; i < array.length; i++) { const j = i * 2; - array[i] = parseHexByte(hex.slice(j, j + 2)); + const hexByte = hex.slice(j, j + 2); + const byte = Number.parseInt(hexByte, 16); + if (Number.isNaN(byte) || byte < 0) + throw new Error('Invalid byte sequence'); + array[i] = byte; } return array; } -function numberToHex(num) { - const hex = num.toString(16); - return hex.length & 1 ? `0${hex}` : hex; +function numberTo32BytesBE(num) { + const length = 32; + const hex = num.toString(16).padStart(length * 2, '0'); + return hexToBytes(hex); } -function numberToBytesPadded(num, length = B32) { - const hex = numberToHex(num).padStart(length * 2, '0'); - return hexToBytes(hex).reverse(); +function numberTo32BytesLE(num) { + return numberTo32BytesBE(num).reverse(); } function edIsNegative(num) { return (mod(num) & _1n) === _1n; } function bytesToNumberLE(uint8a) { - let value = _0n; - for (let i = 0; i < uint8a.length; i++) { - value += BigInt(uint8a[i]) << (BigInt(8) * BigInt(i)); - } - return value; + if (!(uint8a instanceof Uint8Array)) + throw new Error('Expected Uint8Array'); + return BigInt('0x' + bytesToHex(Uint8Array.from(uint8a).reverse())); } +const MAX_255B = BigInt('0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); function bytes255ToNumberLE(bytes) { - return mod(bytesToNumberLE(bytes) & (_2n ** _255n - _1n)); + return mod(bytesToNumberLE(bytes) & MAX_255B); } function mod(a, b = CURVE.P) { const res = a % b; @@ -53436,25 +53500,22 @@ function invert(number, modulo = CURVE.P) { throw new Error('invert: does not exist'); return mod(x, modulo); } -function invertBatch(nums, modulo = CURVE.P) { - const len = nums.length; - const scratch = new Array(len); - let acc = _1n; - for (let i = 0; i < len; i++) { - if (nums[i] === _0n) - continue; - scratch[i] = acc; - acc = mod(acc * nums[i], modulo); - } - acc = invert(acc, modulo); - for (let i = len - 1; i >= 0; i--) { - if (nums[i] === _0n) - continue; - let tmp = mod(acc * nums[i], modulo); - nums[i] = mod(acc * scratch[i], modulo); - acc = tmp; - } - return nums; +function invertBatch(nums, p = CURVE.P) { + const tmp = new Array(nums.length); + const lastMultiplied = nums.reduce((acc, num, i) => { + if (num === _0n) + return acc; + tmp[i] = acc; + return mod(acc * num, p); + }, _1n); + const inverted = invert(lastMultiplied, p); + nums.reduceRight((acc, num, i) => { + if (num === _0n) + return acc; + tmp[i] = mod(acc * tmp[i], p); + return mod(acc * num, p); + }, inverted); + return tmp; } function pow2(x, power) { const { P } = CURVE; @@ -53467,7 +53528,11 @@ function pow2(x, power) { } function pow_2_252_3(x) { const { P } = CURVE; - const [_5n, _10n, _20n, _40n, _80n] = [5, 10, 20, 40, 80].map((n) => BigInt(n)); + const _5n = BigInt(5); + const _10n = BigInt(10); + const _20n = BigInt(20); + const _40n = BigInt(40); + const _80n = BigInt(80); const x2 = (x * x) % P; const b2 = (x2 * x) % P; const b4 = (pow2(b2, _2n) * b2) % P; @@ -53480,12 +53545,13 @@ function pow_2_252_3(x) { const b240 = (pow2(b160, _80n) * b80) % P; const b250 = (pow2(b240, _10n) * b10) % P; const pow_p_5_8 = (pow2(b250, _2n) * x) % P; - return pow_p_5_8; + return { pow_p_5_8, b2 }; } function uvRatio(u, v) { const v3 = mod(v * v * v); const v7 = mod(v3 * v3 * v); - let x = mod(u * v3 * pow_2_252_3(u * v7)); + const pow = pow_2_252_3(u * v7).pow_p_5_8; + let x = mod(u * v3 * pow); const vx2 = mod(v * x * x); const root1 = x; const root2 = mod(x * SQRT_M1); @@ -53503,22 +53569,8 @@ function uvRatio(u, v) { function invertSqrt(number) { return uvRatio(_1n, number); } -async function sha512ToNumberLE(...args) { - const messageArray = concatBytes(...args); - const hash = await exports.utils.sha512(messageArray); - const value = bytesToNumberLE(hash); - return mod(value, CURVE.n); -} -function keyPrefix(privateBytes) { - return privateBytes.slice(B32); -} -function encodePrivate(privateBytes) { - const last = B32 - 1; - const head = privateBytes.slice(0, B32); - head[0] &= 248; - head[last] &= 127; - head[last] |= 64; - return mod(bytesToNumberLE(head), CURVE.n); +function modlLE(hash) { + return mod(bytesToNumberLE(hash), CURVE.l); } function equalBytes(b1, b2) { if (b1.length !== b2.length) { @@ -53531,84 +53583,218 @@ function equalBytes(b1, b2) { } return true; } -function ensureBytes(hash) { - return hash instanceof Uint8Array ? hash : hexToBytes(hash); -} -function isWithinCurveOrder(num) { - return 0 < num && num < CURVE.n; +function ensureBytes(hex, expectedLength) { + const bytes = hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes(hex); + if (typeof expectedLength === 'number' && bytes.length !== expectedLength) + throw new Error(`Expected ${expectedLength} bytes`); + return bytes; } -const MAX_PRIV_KEY = _2n ** BigInt(256) - _1n; -function normalizePrivateKey(key) { - let bytes; - let err = 'Expected 32 bytes of private key'; - if (typeof key === 'bigint' || (typeof key === 'number' && Number.isSafeInteger(key))) { - let num = BigInt(key); - if (num < 0 || num > MAX_PRIV_KEY) - throw new Error(err); - bytes = hexToBytes(num.toString(16).padStart(B32 * 2, '0')); - } - else if (typeof key === 'string') { - if (key.length !== 64) - throw new Error(err); - bytes = hexToBytes(key); - } - else if (key instanceof Uint8Array) { - if (key.length !== 32) - throw new Error(err); - bytes = key; - } - else { - throw new TypeError('Expected valid private key'); +function normalizeScalar(num, max, strict = true) { + if (!max) + throw new TypeError('Specify max value'); + if (typeof num === 'number' && Number.isSafeInteger(num)) + num = BigInt(num); + if (typeof num === 'bigint' && num < max) { + if (strict) { + if (_0n < num) + return num; + } + else { + if (_0n <= num) + return num; + } } - return bytes; + throw new TypeError('Expected valid scalar: 0 < scalar < max'); } -async function getPrivateBytes(privateKey) { - return await exports.utils.sha512(normalizePrivateKey(privateKey)); +function adjustBytes25519(bytes) { + bytes[0] &= 248; + bytes[31] &= 127; + bytes[31] |= 64; + return bytes; } -function normalizeScalar(num) { - if (typeof num === 'number' && num > 0 && Number.isSafeInteger(num)) - return BigInt(num); - if (typeof num === 'bigint' && isWithinCurveOrder(num)) - return num; - throw new TypeError('Expected valid private scalar: 0 < scalar < curve.n'); +function decodeScalar25519(n) { + return bytesToNumberLE(adjustBytes25519(ensureBytes(n, 32))); +} +function checkPrivateKey(key) { + key = + typeof key === 'bigint' || typeof key === 'number' + ? numberTo32BytesBE(normalizeScalar(key, POW_2_256)) + : ensureBytes(key); + if (key.length !== 32) + throw new Error(`Expected 32 bytes`); + return key; +} +function getKeyFromHash(hashed) { + const head = adjustBytes25519(hashed.slice(0, 32)); + const prefix = hashed.slice(32, 64); + const scalar = modlLE(head); + const point = Point.BASE.multiply(scalar); + const pointBytes = point.toRawBytes(); + return { head, prefix, scalar, point, pointBytes }; +} +let _sha512Sync; +function sha512s(...m) { + if (typeof _sha512Sync !== 'function') + throw new Error('utils.sha512Sync must be set to use sync methods'); + return _sha512Sync(...m); +} +async function getExtendedPublicKey(key) { + return getKeyFromHash(await exports.utils.sha512(checkPrivateKey(key))); +} +function getExtendedPublicKeySync(key) { + return getKeyFromHash(sha512s(checkPrivateKey(key))); } async function getPublicKey(privateKey) { - const key = await Point.fromPrivateKey(privateKey); - return key.toRawBytes(); + return (await getExtendedPublicKey(privateKey)).pointBytes; } exports.getPublicKey = getPublicKey; -async function sign(msgHash, privateKey) { - const privBytes = await getPrivateBytes(privateKey); - const p = encodePrivate(privBytes); - const P = Point.BASE.multiply(p); - const msg = ensureBytes(msgHash); - const r = await sha512ToNumberLE(keyPrefix(privBytes), msg); +function getPublicKeySync(privateKey) { + return getExtendedPublicKeySync(privateKey).pointBytes; +} +async function sign(message, privateKey) { + message = ensureBytes(message); + const { prefix, scalar, pointBytes } = await getExtendedPublicKey(privateKey); + const r = modlLE(await exports.utils.sha512(prefix, message)); const R = Point.BASE.multiply(r); - const h = await sha512ToNumberLE(R.toRawBytes(), P.toRawBytes(), msg); - const S = mod(r + h * p, CURVE.n); - const sig = new Signature(R, S); - return sig.toRawBytes(); + const k = modlLE(await exports.utils.sha512(R.toRawBytes(), pointBytes, message)); + const s = mod(r + k * scalar, CURVE.l); + return new Signature(R, s).toRawBytes(); } exports.sign = sign; -async function verify(sig, msgHash, publicKey) { - msgHash = ensureBytes(msgHash); +function signSync(message, privateKey) { + message = ensureBytes(message); + const { prefix, scalar, pointBytes } = getExtendedPublicKeySync(privateKey); + const r = modlLE(sha512s(prefix, message)); + const R = Point.BASE.multiply(r); + const k = modlLE(sha512s(R.toRawBytes(), pointBytes, message)); + const s = mod(r + k * scalar, CURVE.l); + return new Signature(R, s).toRawBytes(); +} +function prepareVerification(sig, message, publicKey) { + message = ensureBytes(message); if (!(publicKey instanceof Point)) - publicKey = Point.fromHex(publicKey); - if (!(sig instanceof Signature)) - sig = Signature.fromHex(sig); - const hs = await sha512ToNumberLE(sig.r.toRawBytes(), publicKey.toRawBytes(), msgHash); - const Ph = ExtendedPoint.fromAffine(publicKey).multiplyUnsafe(hs); - const Gs = ExtendedPoint.BASE.multiply(sig.s); - const RPh = ExtendedPoint.fromAffine(sig.r).add(Ph); - return RPh.subtract(Gs).multiplyUnsafe(CURVE.h).equals(ExtendedPoint.ZERO); + publicKey = Point.fromHex(publicKey, false); + const { r, s } = sig instanceof Signature ? sig.assertValidity() : Signature.fromHex(sig); + const SB = ExtendedPoint.BASE.multiplyUnsafe(s); + return { r, s, SB, pub: publicKey, msg: message }; +} +function finishVerification(publicKey, r, SB, hashed) { + const k = modlLE(hashed); + const kA = ExtendedPoint.fromAffine(publicKey).multiplyUnsafe(k); + const RkA = ExtendedPoint.fromAffine(r).add(kA); + return RkA.subtract(SB).multiplyUnsafe(CURVE.h).equals(ExtendedPoint.ZERO); +} +async function verify(sig, message, publicKey) { + const { r, SB, msg, pub } = prepareVerification(sig, message, publicKey); + const hashed = await exports.utils.sha512(r.toRawBytes(), pub.toRawBytes(), msg); + return finishVerification(pub, r, SB, hashed); } exports.verify = verify; +function verifySync(sig, message, publicKey) { + const { r, SB, msg, pub } = prepareVerification(sig, message, publicKey); + const hashed = sha512s(r.toRawBytes(), pub.toRawBytes(), msg); + return finishVerification(pub, r, SB, hashed); +} +exports.sync = { + getExtendedPublicKey: getExtendedPublicKeySync, + getPublicKey: getPublicKeySync, + sign: signSync, + verify: verifySync, +}; +async function getSharedSecret(privateKey, publicKey) { + const { head } = await getExtendedPublicKey(privateKey); + const u = Point.fromHex(publicKey).toX25519(); + return exports.curve25519.scalarMult(head, u); +} +exports.getSharedSecret = getSharedSecret; Point.BASE._setWindowSize(8); +function cswap(swap, x_2, x_3) { + const dummy = mod(swap * (x_2 - x_3)); + x_2 = mod(x_2 - dummy); + x_3 = mod(x_3 + dummy); + return [x_2, x_3]; +} +function montgomeryLadder(pointU, scalar) { + const { P } = CURVE; + const u = normalizeScalar(pointU, P); + const k = normalizeScalar(scalar, P); + const a24 = BigInt(121665); + const x_1 = u; + let x_2 = _1n; + let z_2 = _0n; + let x_3 = u; + let z_3 = _1n; + let swap = _0n; + let sw; + for (let t = BigInt(255 - 1); t >= _0n; t--) { + const k_t = (k >> t) & _1n; + swap ^= k_t; + sw = cswap(swap, x_2, x_3); + x_2 = sw[0]; + x_3 = sw[1]; + sw = cswap(swap, z_2, z_3); + z_2 = sw[0]; + z_3 = sw[1]; + swap = k_t; + const A = x_2 + z_2; + const AA = mod(A * A); + const B = x_2 - z_2; + const BB = mod(B * B); + const E = AA - BB; + const C = x_3 + z_3; + const D = x_3 - z_3; + const DA = mod(D * A); + const CB = mod(C * B); + const dacb = DA + CB; + const da_cb = DA - CB; + x_3 = mod(dacb * dacb); + z_3 = mod(x_1 * mod(da_cb * da_cb)); + x_2 = mod(AA * BB); + z_2 = mod(E * (AA + mod(a24 * E))); + } + sw = cswap(swap, x_2, x_3); + x_2 = sw[0]; + x_3 = sw[1]; + sw = cswap(swap, z_2, z_3); + z_2 = sw[0]; + z_3 = sw[1]; + const { pow_p_5_8, b2 } = pow_2_252_3(z_2); + const xp2 = mod(pow2(pow_p_5_8, BigInt(3)) * b2); + return mod(x_2 * xp2); +} +function encodeUCoordinate(u) { + return numberTo32BytesLE(mod(u, CURVE.P)); +} +function decodeUCoordinate(uEnc) { + const u = ensureBytes(uEnc, 32); + u[31] &= 127; + return bytesToNumberLE(u); +} +exports.curve25519 = { + BASE_POINT_U: '0900000000000000000000000000000000000000000000000000000000000000', + scalarMult(privateKey, publicKey) { + const u = decodeUCoordinate(publicKey); + const p = decodeScalar25519(privateKey); + const pu = montgomeryLadder(u, p); + if (pu === _0n) + throw new Error('Invalid private or public key received'); + return encodeUCoordinate(pu); + }, + scalarMultBase(privateKey) { + return exports.curve25519.scalarMult(privateKey, exports.curve25519.BASE_POINT_U); + }, +}; const crypto = { - node: crypto_1.default, + node: nodeCrypto, web: typeof self === 'object' && 'crypto' in self ? self.crypto : undefined, }; exports.utils = { + bytesToHex, + hexToBytes, + concatBytes, + getExtendedPublicKey, + mod, + invert, TORSION_SUBGROUP: [ '0100000000000000000000000000000000000000000000000000000000000000', 'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a', @@ -53619,7 +53805,12 @@ exports.utils = { '0000000000000000000000000000000000000000000000000000000000000000', 'c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa', ], - bytesToHex, + hashToPrivateScalar: (hash) => { + hash = ensureBytes(hash); + if (hash.length < 40 || hash.length > 1024) + throw new Error('Expected 40-1024 bytes of private key as per FIPS 186'); + return mod(bytesToNumberLE(hash), CURVE.l - _1n) + _1n; + }, randomBytes: (bytesLength = 32) => { if (crypto.web) { return crypto.web.getRandomValues(new Uint8Array(bytesLength)); @@ -53635,7 +53826,8 @@ exports.utils = { randomPrivateKey: () => { return exports.utils.randomBytes(32); }, - sha512: async (message) => { + sha512: async (...messages) => { + const message = concatBytes(...messages); if (crypto.web) { const buffer = await crypto.web.subtle.digest('SHA-512', message.buffer); return new Uint8Array(buffer); @@ -53650,10 +53842,23 @@ exports.utils = { precompute(windowSize = 8, point = Point.BASE) { const cached = point.equals(Point.BASE) ? point : new Point(point.x, point.y); cached._setWindowSize(windowSize); - cached.multiply(_1n); + cached.multiply(_2n); return cached; }, + sha512Sync: undefined, }; +Object.defineProperties(exports.utils, { + sha512Sync: { + configurable: false, + get() { + return _sha512Sync; + }, + set(val) { + if (!_sha512Sync) + _sha512Sync = val; + }, + }, +}); /***/ }), @@ -121832,6 +122037,8 @@ var _getValueLength = function(bytes, remaining) { * @param [options] object with options or boolean strict flag * [strict] true to be strict when checking value lengths, false to * allow truncated values (default: true). + * [parseAllBytes] true to ensure all bytes are parsed + * (default: true) * [decodeBitStrings] true to attempt to decode the content of * BIT STRINGs (not OCTET STRINGs) using strict mode. Note that * without schema support to understand the data context this can @@ -121839,24 +122046,31 @@ var _getValueLength = function(bytes, remaining) { * flag will be deprecated or removed as soon as schema support is * available. (default: true) * + * @throws Will throw an error for various malformed input conditions. + * * @return the parsed asn1 object. */ asn1.fromDer = function(bytes, options) { if(options === undefined) { options = { strict: true, + parseAllBytes: true, decodeBitStrings: true }; } if(typeof options === 'boolean') { options = { strict: options, + parseAllBytes: true, decodeBitStrings: true }; } if(!('strict' in options)) { options.strict = true; } + if(!('parseAllBytes' in options)) { + options.parseAllBytes = true; + } if(!('decodeBitStrings' in options)) { options.decodeBitStrings = true; } @@ -121866,7 +122080,15 @@ asn1.fromDer = function(bytes, options) { bytes = forge.util.createBuffer(bytes); } - return _fromDer(bytes, bytes.length(), 0, options); + var byteCount = bytes.length(); + var value = _fromDer(bytes, bytes.length(), 0, options); + if(options.parseAllBytes && bytes.length() !== 0) { + var error = new Error('Unparsed DER bytes remain after ASN.1 parsing.'); + error.byteCount = byteCount; + error.remaining = bytes.length(); + throw error; + } + return value; }; /** @@ -121987,7 +122209,6 @@ function _fromDer(bytes, remaining, depth, options) { start = bytes.length(); var subOptions = { // enforce strict mode to avoid parsing ASN.1 from plain data - verbose: options.verbose, strict: true, decodeBitStrings: true }; @@ -122036,6 +122257,7 @@ function _fromDer(bytes, remaining, depth, options) { } } else { value = bytes.getBytes(length); + remaining -= length; } } @@ -122812,7 +123034,16 @@ asn1.prettyPrint = function(obj, level, indentation) { } rval += '0x' + forge.util.bytesToHex(obj.value); } else if(obj.type === asn1.Type.UTF8) { - rval += forge.util.decodeUtf8(obj.value); + try { + rval += forge.util.decodeUtf8(obj.value); + } catch(e) { + if(e.message === 'URI malformed') { + rval += + '0x' + forge.util.bytesToHex(obj.value) + ' (malformed UTF8)'; + } else { + throw e; + } + } } else if(obj.type === asn1.Type.PRINTABLESTRING || obj.type === asn1.Type.IA5String) { rval += obj.value; @@ -126279,9 +126510,15 @@ _IN('1.2.840.10040.4.3', 'dsa-with-sha1'); _IN('1.3.14.3.2.7', 'desCBC'); _IN('1.3.14.3.2.26', 'sha1'); +// Deprecated equivalent of sha1WithRSAEncryption +_IN('1.3.14.3.2.29', 'sha1WithRSASignature'); _IN('2.16.840.1.101.3.4.2.1', 'sha256'); _IN('2.16.840.1.101.3.4.2.2', 'sha384'); _IN('2.16.840.1.101.3.4.2.3', 'sha512'); +_IN('2.16.840.1.101.3.4.2.4', 'sha224'); +_IN('2.16.840.1.101.3.4.2.5', 'sha512-224'); +_IN('2.16.840.1.101.3.4.2.6', 'sha512-256'); +_IN('1.2.840.113549.2.2', 'md2'); _IN('1.2.840.113549.2.5', 'md5'); // pkcs#7 content types @@ -126341,16 +126578,19 @@ _IN('2.16.840.1.101.3.4.1.42', 'aes256-CBC'); // certificate issuer/subject OIDs _IN('2.5.4.3', 'commonName'); -_IN('2.5.4.5', 'serialName'); +_IN('2.5.4.4', 'surname'); +_IN('2.5.4.5', 'serialNumber'); _IN('2.5.4.6', 'countryName'); _IN('2.5.4.7', 'localityName'); _IN('2.5.4.8', 'stateOrProvinceName'); _IN('2.5.4.9', 'streetAddress'); _IN('2.5.4.10', 'organizationName'); _IN('2.5.4.11', 'organizationalUnitName'); +_IN('2.5.4.12', 'title'); _IN('2.5.4.13', 'description'); _IN('2.5.4.15', 'businessCategory'); _IN('2.5.4.17', 'postalCode'); +_IN('2.5.4.42', 'givenName'); _IN('1.3.6.1.4.1.311.60.2.1.2', 'jurisdictionOfIncorporationStateOrProvinceName'); _IN('1.3.6.1.4.1.311.60.2.1.3', 'jurisdictionOfIncorporationCountryName'); @@ -127768,8 +128008,15 @@ pem.decode = function(str) { break; } + // accept "NEW CERTIFICATE REQUEST" as "CERTIFICATE REQUEST" + // https://datatracker.ietf.org/doc/html/rfc7468#section-7 + var type = match[1]; + if(type === 'NEW CERTIFICATE REQUEST') { + type = 'CERTIFICATE REQUEST'; + } + var msg = { - type: match[1], + type: type, procType: null, contentDomain: null, dekInfo: null, @@ -128803,7 +129050,7 @@ prng.create = function(plugin) { // throw in more pseudo random next = seed >>> (i << 3); next ^= Math.floor(Math.random() * 0x0100); - b.putByte(String.fromCharCode(next & 0xFF)); + b.putByte(next & 0xFF); } } } @@ -129791,6 +130038,43 @@ var publicKeyValidator = forge.pki.rsa.publicKeyValidator = { }] }; +// validator for a DigestInfo structure +var digestInfoValidator = { + name: 'DigestInfo', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.SEQUENCE, + constructed: true, + value: [{ + name: 'DigestInfo.DigestAlgorithm', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.SEQUENCE, + constructed: true, + value: [{ + name: 'DigestInfo.DigestAlgorithm.algorithmIdentifier', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.OID, + constructed: false, + capture: 'algorithmIdentifier' + }, { + // NULL paramters + name: 'DigestInfo.DigestAlgorithm.parameters', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.NULL, + // captured only to check existence for md2 and md5 + capture: 'parameters', + optional: true, + constructed: false + }] + }, { + // digest + name: 'DigestInfo.digest', + tagClass: asn1.Class.UNIVERSAL, + type: asn1.Type.OCTETSTRING, + constructed: false, + capture: 'digest' + }] +}; + /** * Wrap digest in DigestInfo object. * @@ -130619,15 +130903,27 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) { * a Forge PSS object for RSASSA-PSS, * 'NONE' or null for none, DigestInfo will not be expected, but * PKCS#1 v1.5 padding will still be used. + * @param options optional verify options + * _parseAllDigestBytes testing flag to control parsing of all + * digest bytes. Unsupported and not for general usage. + * (default: true) * * @return true if the signature was verified, false if not. */ - key.verify = function(digest, signature, scheme) { + key.verify = function(digest, signature, scheme, options) { if(typeof scheme === 'string') { scheme = scheme.toUpperCase(); } else if(scheme === undefined) { scheme = 'RSASSA-PKCS1-V1_5'; } + if(options === undefined) { + options = { + _parseAllDigestBytes: true + }; + } + if(!('_parseAllDigestBytes' in options)) { + options._parseAllDigestBytes = true; + } if(scheme === 'RSASSA-PKCS1-V1_5') { scheme = { @@ -130635,9 +130931,51 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) { // remove padding d = _decodePkcs1_v1_5(d, key, true); // d is ASN.1 BER-encoded DigestInfo - var obj = asn1.fromDer(d); + var obj = asn1.fromDer(d, { + parseAllBytes: options._parseAllDigestBytes + }); + + // validate DigestInfo + var capture = {}; + var errors = []; + if(!asn1.validate(obj, digestInfoValidator, capture, errors)) { + var error = new Error( + 'ASN.1 object does not contain a valid RSASSA-PKCS1-v1_5 ' + + 'DigestInfo value.'); + error.errors = errors; + throw error; + } + // check hash algorithm identifier + // see PKCS1-v1-5DigestAlgorithms in RFC 8017 + // FIXME: add support to vaidator for strict value choices + var oid = asn1.derToOid(capture.algorithmIdentifier); + if(!(oid === forge.oids.md2 || + oid === forge.oids.md5 || + oid === forge.oids.sha1 || + oid === forge.oids.sha224 || + oid === forge.oids.sha256 || + oid === forge.oids.sha384 || + oid === forge.oids.sha512 || + oid === forge.oids['sha512-224'] || + oid === forge.oids['sha512-256'])) { + var error = new Error( + 'Unknown RSASSA-PKCS1-v1_5 DigestAlgorithm identifier.'); + error.oid = oid; + throw error; + } + + // special check for md2 and md5 that NULL parameters exist + if(oid === forge.oids.md2 || oid === forge.oids.md5) { + if(!('parameters' in capture)) { + throw new Error( + 'ASN.1 object does not contain a valid RSASSA-PKCS1-v1_5 ' + + 'DigestInfo value. ' + + 'Missing algorithm identifer NULL parameters.'); + } + } + // compare the given digest to the decrypted one - return digest === obj.value[1].value; + return digest === capture.digest; } }; } else if(scheme === 'NONE' || scheme === 'NULL' || scheme === null) { @@ -134878,261 +135216,6 @@ util.clearItems = function(api, id, location) { _callStorageFunction(_clearItems, arguments, location); }; -/** - * Parses the scheme, host, and port from an http(s) url. - * - * @param str the url string. - * - * @return the parsed url object or null if the url is invalid. - */ -util.parseUrl = function(str) { - // FIXME: this regex looks a bit broken - var regex = /^(https?):\/\/([^:&^\/]*):?(\d*)(.*)$/g; - regex.lastIndex = 0; - var m = regex.exec(str); - var url = (m === null) ? null : { - full: str, - scheme: m[1], - host: m[2], - port: m[3], - path: m[4] - }; - if(url) { - url.fullHost = url.host; - if(url.port) { - if(url.port !== 80 && url.scheme === 'http') { - url.fullHost += ':' + url.port; - } else if(url.port !== 443 && url.scheme === 'https') { - url.fullHost += ':' + url.port; - } - } else if(url.scheme === 'http') { - url.port = 80; - } else if(url.scheme === 'https') { - url.port = 443; - } - url.full = url.scheme + '://' + url.fullHost; - } - return url; -}; - -/* Storage for query variables */ -var _queryVariables = null; - -/** - * Returns the window location query variables. Query is parsed on the first - * call and the same object is returned on subsequent calls. The mapping - * is from keys to an array of values. Parameters without values will have - * an object key set but no value added to the value array. Values are - * unescaped. - * - * ...?k1=v1&k2=v2: - * { - * "k1": ["v1"], - * "k2": ["v2"] - * } - * - * ...?k1=v1&k1=v2: - * { - * "k1": ["v1", "v2"] - * } - * - * ...?k1=v1&k2: - * { - * "k1": ["v1"], - * "k2": [] - * } - * - * ...?k1=v1&k1: - * { - * "k1": ["v1"] - * } - * - * ...?k1&k1: - * { - * "k1": [] - * } - * - * @param query the query string to parse (optional, default to cached - * results from parsing window location search query). - * - * @return object mapping keys to variables. - */ -util.getQueryVariables = function(query) { - var parse = function(q) { - var rval = {}; - var kvpairs = q.split('&'); - for(var i = 0; i < kvpairs.length; i++) { - var pos = kvpairs[i].indexOf('='); - var key; - var val; - if(pos > 0) { - key = kvpairs[i].substring(0, pos); - val = kvpairs[i].substring(pos + 1); - } else { - key = kvpairs[i]; - val = null; - } - if(!(key in rval)) { - rval[key] = []; - } - // disallow overriding object prototype keys - if(!(key in Object.prototype) && val !== null) { - rval[key].push(unescape(val)); - } - } - return rval; - }; - - var rval; - if(typeof(query) === 'undefined') { - // set cached variables if needed - if(_queryVariables === null) { - if(typeof(window) !== 'undefined' && window.location && window.location.search) { - // parse window search query - _queryVariables = parse(window.location.search.substring(1)); - } else { - // no query variables available - _queryVariables = {}; - } - } - rval = _queryVariables; - } else { - // parse given query - rval = parse(query); - } - return rval; -}; - -/** - * Parses a fragment into a path and query. This method will take a URI - * fragment and break it up as if it were the main URI. For example: - * /bar/baz?a=1&b=2 - * results in: - * { - * path: ["bar", "baz"], - * query: {"k1": ["v1"], "k2": ["v2"]} - * } - * - * @return object with a path array and query object. - */ -util.parseFragment = function(fragment) { - // default to whole fragment - var fp = fragment; - var fq = ''; - // split into path and query if possible at the first '?' - var pos = fragment.indexOf('?'); - if(pos > 0) { - fp = fragment.substring(0, pos); - fq = fragment.substring(pos + 1); - } - // split path based on '/' and ignore first element if empty - var path = fp.split('/'); - if(path.length > 0 && path[0] === '') { - path.shift(); - } - // convert query into object - var query = (fq === '') ? {} : util.getQueryVariables(fq); - - return { - pathString: fp, - queryString: fq, - path: path, - query: query - }; -}; - -/** - * Makes a request out of a URI-like request string. This is intended to - * be used where a fragment id (after a URI '#') is parsed as a URI with - * path and query parts. The string should have a path beginning and - * delimited by '/' and optional query parameters following a '?'. The - * query should be a standard URL set of key value pairs delimited by - * '&'. For backwards compatibility the initial '/' on the path is not - * required. The request object has the following API, (fully described - * in the method code): - * { - * path: . - * query: , - * getPath(i): get part or all of the split path array, - * getQuery(k, i): get part or all of a query key array, - * getQueryLast(k, _default): get last element of a query key array. - * } - * - * @return object with request parameters. - */ -util.makeRequest = function(reqString) { - var frag = util.parseFragment(reqString); - var req = { - // full path string - path: frag.pathString, - // full query string - query: frag.queryString, - /** - * Get path or element in path. - * - * @param i optional path index. - * - * @return path or part of path if i provided. - */ - getPath: function(i) { - return (typeof(i) === 'undefined') ? frag.path : frag.path[i]; - }, - /** - * Get query, values for a key, or value for a key index. - * - * @param k optional query key. - * @param i optional query key index. - * - * @return query, values for a key, or value for a key index. - */ - getQuery: function(k, i) { - var rval; - if(typeof(k) === 'undefined') { - rval = frag.query; - } else { - rval = frag.query[k]; - if(rval && typeof(i) !== 'undefined') { - rval = rval[i]; - } - } - return rval; - }, - getQueryLast: function(k, _default) { - var rval; - var vals = req.getQuery(k); - if(vals) { - rval = vals[vals.length - 1]; - } else { - rval = _default; - } - return rval; - } - }; - return req; -}; - -/** - * Makes a URI out of a path, an object with query parameters, and a - * fragment. Uses jQuery.param() internally for query string creation. - * If the path is an array, it will be joined with '/'. - * - * @param path string path or array of strings. - * @param query object with query parameters. (optional) - * @param fragment fragment string. (optional) - * - * @return string object with request parameters. - */ -util.makeLink = function(path, query, fragment) { - // join path parts if needed - path = jQuery.isArray(path) ? path.join('/') : path; - - var qstr = jQuery.param(query || {}); - fragment = fragment || ''; - return path + - ((qstr.length > 0) ? ('?' + qstr) : '') + - ((fragment.length > 0) ? ('#' + fragment) : ''); -}; - /** * Check if an object is empty. *