From 6113f13d3ac8fe264ebd329f96737bb50e142574 Mon Sep 17 00:00:00 2001 From: Beaudan Date: Thu, 13 Dec 2018 16:33:54 +1100 Subject: [PATCH] Rip the worker logic out of message_receiver and add the functionality for it to work with pow. Fix pow tests to work with those changes --- .gitignore | 1 + Gruntfile.js | 22 + bower.json | 5 + components/JSBI/dist/jsbi.mjs | 1784 +++++++++++++++++ js/modules/loki_message_api.js | 41 +- js/modules/util_worker_interface.js | 124 ++ js/util_worker_tasks.js | 8 +- libloki/proof-of-work.js | 238 ++- libloki/test/index.html | 6 +- .../test}/proof-of-work_test.js | 18 +- libtextsecure/message_receiver.js | 105 +- package.json | 1 - preload.js | 6 +- test/index.html | 2 +- yarn.lock | 12 - 15 files changed, 2082 insertions(+), 291 deletions(-) create mode 100644 components/JSBI/dist/jsbi.mjs create mode 100644 js/modules/util_worker_interface.js rename {test/app => libloki/test}/proof-of-work_test.js (89%) diff --git a/.gitignore b/.gitignore index cd183624e..aeb814020 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ js/libtextsecure.js js/libloki.js libtextsecure/components.js libtextsecure/test/test.js +libloki/test/components.js libloki/test/test.js stylesheets/*.css test/test.js diff --git a/Gruntfile.js b/Gruntfile.js index 5b521683f..6b84a2a0a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -25,6 +25,12 @@ module.exports = grunt => { libtextsecurecomponents.push(bower.concat.libtextsecure[i]); } + const liblokicomponents = []; + // eslint-disable-next-line guard-for-in, no-restricted-syntax + for (const i in bower.concat.libloki) { + liblokicomponents.push(bower.concat.libloki[i]); + } + grunt.loadNpmTasks('grunt-sass'); grunt.initConfig({ @@ -37,6 +43,8 @@ module.exports = grunt => { util_worker: { src: [ 'components/bytebuffer/dist/ByteBufferAB.js', + 'components/JSBI/dist/jsbi.mjs', + 'libloki/proof-of-work.js', 'components/long/dist/Long.js', 'js/util_worker_tasks.js', ], @@ -46,6 +54,10 @@ module.exports = grunt => { src: libtextsecurecomponents, dest: 'libtextsecure/components.js', }, + liblokicomponents: { + src: liblokicomponents, + dest: 'libloki/test/components.js', + }, test: { src: [ 'node_modules/mocha/mocha.js', @@ -141,6 +153,16 @@ module.exports = grunt => { files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'], tasks: ['concat:libtextsecure'], }, + utilworker: { + files: [ + 'components/bytebuffer/dist/ByteBufferAB.js', + 'components/JSBI/dist/jsbi.mjs', + 'libloki/proof-of-work.js', + 'components/long/dist/Long.js', + 'js/util_worker_tasks.js', + ], + tasks: ['concat:util_worker'], + }, libloki: { files: ['./libloki/*.js'], tasks: ['concat:libloki'], diff --git a/bower.json b/bower.json index 7b71e67b5..d0d67968b 100644 --- a/bower.json +++ b/bower.json @@ -64,6 +64,11 @@ "components/long/**/*.js", "components/bytebuffer/**/*.js", "components/protobuf/**/*.js" + ], + "libloki": [ + "components/long/**/*.js", + "components/bytebuffer/**/*.js", + "components/JSBI/dist/jsbi.mjs" ] } } diff --git a/components/JSBI/dist/jsbi.mjs b/components/JSBI/dist/jsbi.mjs new file mode 100644 index 000000000..b5d155dbf --- /dev/null +++ b/components/JSBI/dist/jsbi.mjs @@ -0,0 +1,1784 @@ +// Copyright 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the “License”); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// . +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an “AS IS” BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +class JSBI extends Array { + constructor(length, sign) { + if (length > JSBI.__kMaxLength) { + throw new RangeError('Maximum BigInt size exceeded'); + } + super(length); + this.sign = sign; + } + + static BigInt(arg) { + if (typeof arg === 'number') { + if (arg === 0) return JSBI.__zero(); + if ((arg | 0) === arg) { + if (arg < 0) { + return JSBI.__oneDigit(-arg, true); + } + return JSBI.__oneDigit(arg, false); + } + if (!Number.isFinite(arg) || Math.floor(arg) !== arg) { + throw new RangeError('The number ' + arg + ' cannot be converted to ' + + 'BigInt because it is not an integer'); + } + return JSBI.__fromDouble(arg); + } else if (typeof arg === 'string') { + const result = JSBI.__fromString(arg); + if (result === null) { + throw new SyntaxError('Cannot convert ' + arg + ' to a BigInt'); + } + return result; + } else if (typeof arg === 'boolean') { + if (arg === true) { + return JSBI.__oneDigit(1, false); + } + return JSBI.__zero(); + } else if (typeof arg === 'object') { + if (arg.constructor === JSBI) return arg; + const primitive = JSBI.__toPrimitive(arg); + return JSBI.BigInt(primitive); + } + throw new TypeError('Cannot convert ' + arg + ' to a BigInt'); + } + + toDebugString() { + const result = ['BigInt[']; + for (const digit of this) { + result.push((digit ? (digit >>> 0).toString(16) : digit) + ', '); + } + result.push(']'); + return result.join(''); + } + + toString(radix = 10) { + if (radix < 2 || radix > 36) { + throw new RangeError( + 'toString() radix argument must be between 2 and 36'); + } + if (this.length === 0) return '0'; + if ((radix & (radix - 1)) === 0) { + return JSBI.__toStringBasePowerOfTwo(this, radix); + } + return JSBI.__toStringGeneric(this, radix, false); + } + + // Equivalent of "Number(my_bigint)" in the native implementation. + static toNumber(x) { + const xLength = x.length; + if (xLength === 0) return 0; + if (xLength === 1) { + const value = x.__unsignedDigit(0); + return x.sign ? -value : value; + } + const xMsd = x.__digit(xLength - 1); + const msdLeadingZeros = Math.clz32(xMsd); + const xBitLength = xLength * 32 - msdLeadingZeros; + if (xBitLength > 1024) return x.sign ? -Infinity : Infinity; + let exponent = xBitLength - 1; + let currentDigit = xMsd; + let digitIndex = xLength - 1; + const shift = msdLeadingZeros + 1; + let mantissaHigh = (shift === 32) ? 0 : currentDigit << shift; + mantissaHigh >>>= 12; + const mantissaHighBitsUnset = shift - 12; + let mantissaLow = (shift >= 12) ? 0 : (currentDigit << (20 + shift)); + let mantissaLowBitsUnset = 20 + shift; + if (mantissaHighBitsUnset > 0 && digitIndex > 0) { + digitIndex--; + currentDigit = x.__digit(digitIndex); + mantissaHigh |= (currentDigit >>> (32 - mantissaHighBitsUnset)); + mantissaLow = currentDigit << mantissaHighBitsUnset; + mantissaLowBitsUnset = mantissaHighBitsUnset; + } + if (mantissaLowBitsUnset > 0 && digitIndex > 0) { + digitIndex--; + currentDigit = x.__digit(digitIndex); + mantissaLow |= (currentDigit >>> (32 - mantissaLowBitsUnset)); + mantissaLowBitsUnset -= 32; + } + const rounding = JSBI.__decideRounding(x, mantissaLowBitsUnset, + digitIndex, currentDigit); + if (rounding === 1 || (rounding === 0 && (mantissaLow & 1) === 1)) { + mantissaLow = (mantissaLow + 1) >>> 0; + if (mantissaLow === 0) { + // Incrementing mantissaLow overflowed. + mantissaHigh++; + if ((mantissaHigh >>> 20) !== 0) { + // Incrementing mantissaHigh overflowed. + mantissaHigh = 0; + exponent++; + if (exponent > 1023) { + // Incrementing the exponent overflowed. + return x.sign ? -Infinity : Infinity; + } + } + } + } + const signBit = x.sign ? (1 << 31) : 0; + exponent = (exponent + 0x3FF) << 20; + JSBI.__kBitConversionInts[1] = signBit | exponent | mantissaHigh; + JSBI.__kBitConversionInts[0] = mantissaLow; + return JSBI.__kBitConversionDouble[0]; + } + + // Operations. + + static unaryMinus(x) { + if (x.length === 0) return x; + const result = x.__copy(); + result.sign = !x.sign; + return result; + } + + static bitwiseNot(x) { + if (x.sign) { + // ~(-x) == ~(~(x-1)) == x-1 + return JSBI.__absoluteSubOne(x).__trim(); + } + // ~x == -x-1 == -(x+1) + return JSBI.__absoluteAddOne(x, true); + } + + static exponentiate(x, y) { + if (y.sign) { + throw new RangeError('Exponent must be positive'); + } + if (y.length === 0) { + return JSBI.__oneDigit(1, false); + } + if (x.length === 0) return x; + if (x.length === 1 && x.__digit(0) === 1) { + // (-1) ** even_number == 1. + if (x.sign && (y.__digit(0) & 1) === 0) { + return JSBI.unaryMinus(x); + } + // (-1) ** odd_number == -1, 1 ** anything == 1. + return x; + } + // For all bases >= 2, very large exponents would lead to unrepresentable + // results. + if (y.length > 1) throw new RangeError('BigInt too big'); + let expValue = y.__unsignedDigit(0); + if (expValue === 1) return x; + if (expValue >= JSBI.__kMaxLengthBits) { + throw new RangeError('BigInt too big'); + } + if (x.length === 1 && x.__digit(0) === 2) { + // Fast path for 2^n. + const neededDigits = 1 + (expValue >>> 5); + const sign = x.sign && ((expValue & 1) !== 0); + const result = new JSBI(neededDigits, sign); + result.__initializeDigits(); + // All bits are zero. Now set the n-th bit. + const msd = 1 << (expValue & 31); + result.__setDigit(neededDigits - 1, msd); + return result; + } + let result = null; + let runningSquare = x; + // This implicitly sets the result's sign correctly. + if ((expValue & 1) !== 0) result = x; + expValue >>= 1; + for (; expValue !== 0; expValue >>= 1) { + runningSquare = JSBI.multiply(runningSquare, runningSquare); + if ((expValue & 1) !== 0) { + if (result === null) { + result = runningSquare; + } else { + result = JSBI.multiply(result, runningSquare); + } + } + } + return result; + } + + static multiply(x, y) { + if (x.length === 0) return x; + if (y.length === 0) return y; + let resultLength = x.length + y.length; + if (x.__clzmsd() + y.__clzmsd() >= 32) { + resultLength--; + } + const result = new JSBI(resultLength, x.sign !== y.sign); + result.__initializeDigits(); + for (let i = 0; i < x.length; i++) { + JSBI.__multiplyAccumulate(y, x.__digit(i), result, i); + } + return result.__trim(); + } + + static divide(x, y) { + if (y.length === 0) throw new RangeError('Division by zero'); + if (JSBI.__absoluteCompare(x, y) < 0) return JSBI.__zero(); + const resultSign = x.sign !== y.sign; + const divisor = y.__unsignedDigit(0); + let quotient; + if (y.length === 1 && divisor <= 0xFFFF) { + if (divisor === 1) { + return resultSign === x.sign ? x : JSBI.unaryMinus(x); + } + quotient = JSBI.__absoluteDivSmall(x, divisor, null); + } else { + quotient = JSBI.__absoluteDivLarge(x, y, true, false); + } + quotient.sign = resultSign; + return quotient.__trim(); + } + + static remainder(x, y) { + if (y.length === 0) throw new RangeError('Division by zero'); + if (JSBI.__absoluteCompare(x, y) < 0) return x; + const divisor = y.__unsignedDigit(0); + if (y.length === 1 && divisor <= 0xFFFF) { + if (divisor === 1) return JSBI.__zero(); + const remainderDigit = JSBI.__absoluteModSmall(x, divisor); + if (remainderDigit === 0) return JSBI.__zero(); + return JSBI.__oneDigit(remainderDigit, x.sign); + } + const remainder = JSBI.__absoluteDivLarge(x, y, false, true); + remainder.sign = x.sign; + return remainder.__trim(); + } + + static add(x, y) { + const sign = x.sign; + if (sign === y.sign) { + // x + y == x + y + // -x + -y == -(x + y) + return JSBI.__absoluteAdd(x, y, sign); + } + // x + -y == x - y == -(y - x) + // -x + y == y - x == -(x - y) + if (JSBI.__absoluteCompare(x, y) >= 0) { + return JSBI.__absoluteSub(x, y, sign); + } + return JSBI.__absoluteSub(y, x, !sign); + } + + static subtract(x, y) { + const sign = x.sign; + if (sign !== y.sign) { + // x - (-y) == x + y + // (-x) - y == -(x + y) + return JSBI.__absoluteAdd(x, y, sign); + } + // x - y == -(y - x) + // (-x) - (-y) == y - x == -(x - y) + if (JSBI.__absoluteCompare(x, y) >= 0) { + return JSBI.__absoluteSub(x, y, sign); + } + return JSBI.__absoluteSub(y, x, !sign); + } + + static leftShift(x, y) { + if (y.length === 0 || x.length === 0) return x; + if (y.sign) return JSBI.__rightShiftByAbsolute(x, y); + return JSBI.__leftShiftByAbsolute(x, y); + } + + static signedRightShift(x, y) { + if (y.length === 0 || x.length === 0) return x; + if (y.sign) return JSBI.__leftShiftByAbsolute(x, y); + return JSBI.__rightShiftByAbsolute(x, y); + } + + static unsignedRightShift() { + throw new TypeError( + 'BigInts have no unsigned right shift; use >> instead'); + } + + static lessThan(x, y) { + return JSBI.__compareToBigInt(x, y) < 0; + } + + static lessThanOrEqual(x, y) { + return JSBI.__compareToBigInt(x, y) <= 0; + } + + static greaterThan(x, y) { + return JSBI.__compareToBigInt(x, y) > 0; + } + + static greaterThanOrEqual(x, y) { + return JSBI.__compareToBigInt(x, y) >= 0; + } + + static equal(x, y) { + if (x.sign !== y.sign) return false; + if (x.length !== y.length) return false; + for (let i = 0; i < x.length; i++) { + if (x.__digit(i) !== y.__digit(i)) return false; + } + return true; + } + + static bitwiseAnd(x, y) { + if (!x.sign && !y.sign) { + return JSBI.__absoluteAnd(x, y).__trim(); + } else if (x.sign && y.sign) { + const resultLength = Math.max(x.length, y.length) + 1; + // (-x) & (-y) == ~(x-1) & ~(y-1) == ~((x-1) | (y-1)) + // == -(((x-1) | (y-1)) + 1) + let result = JSBI.__absoluteSubOne(x, resultLength); + const y1 = JSBI.__absoluteSubOne(y); + result = JSBI.__absoluteOr(result, y1, result); + return JSBI.__absoluteAddOne(result, true, result).__trim(); + } + // Assume that x is the positive BigInt. + if (x.sign) { + [x, y] = [y, x]; + } + // x & (-y) == x & ~(y-1) == x &~ (y-1) + return JSBI.__absoluteAndNot(x, JSBI.__absoluteSubOne(y)).__trim(); + } + + static bitwiseXor(x, y) { + if (!x.sign && !y.sign) { + return JSBI.__absoluteXor(x, y).__trim(); + } else if (x.sign && y.sign) { + // (-x) ^ (-y) == ~(x-1) ^ ~(y-1) == (x-1) ^ (y-1) + const resultLength = Math.max(x.length, y.length); + const result = JSBI.__absoluteSubOne(x, resultLength); + const y1 = JSBI.__absoluteSubOne(y); + return JSBI.__absoluteXor(result, y1, result).__trim(); + } + const resultLength = Math.max(x.length, y.length) + 1; + // Assume that x is the positive BigInt. + if (x.sign) { + [x, y] = [y, x]; + } + // x ^ (-y) == x ^ ~(y-1) == ~(x ^ (y-1)) == -((x ^ (y-1)) + 1) + let result = JSBI.__absoluteSubOne(y, resultLength); + result = JSBI.__absoluteXor(result, x, result); + return JSBI.__absoluteAddOne(result, true, result).__trim(); + } + + static bitwiseOr(x, y) { + const resultLength = Math.max(x.length, y.length); + if (!x.sign && !y.sign) { + return JSBI.__absoluteOr(x, y).__trim(); + } else if (x.sign && y.sign) { + // (-x) | (-y) == ~(x-1) | ~(y-1) == ~((x-1) & (y-1)) + // == -(((x-1) & (y-1)) + 1) + let result = JSBI.__absoluteSubOne(x, resultLength); + const y1 = JSBI.__absoluteSubOne(y); + result = JSBI.__absoluteAnd(result, y1, result); + return JSBI.__absoluteAddOne(result, true, result).__trim(); + } + // Assume that x is the positive BigInt. + if (x.sign) { + [x, y] = [y, x]; + } + // x | (-y) == x | ~(y-1) == ~((y-1) &~ x) == -(((y-1) ~& x) + 1) + let result = JSBI.__absoluteSubOne(y, resultLength); + result = JSBI.__absoluteAndNot(result, x, result); + return JSBI.__absoluteAddOne(result, true, result).__trim(); + } + + // Operators. + + static ADD(x, y) { + x = JSBI.__toPrimitive(x); + y = JSBI.__toPrimitive(y); + if (typeof x === 'string') { + if (typeof y !== 'string') y = y.toString(); + return x + y; + } + if (typeof y === 'string') { + return x.toString() + y; + } + x = JSBI.__toNumeric(x); + y = JSBI.__toNumeric(y); + if (JSBI.__isBigInt(x) && JSBI.__isBigInt(y)) { + return JSBI.add(x, y); + } + if (typeof x === 'number' && typeof y === 'number') { + return x + y; + } + throw new TypeError( + 'Cannot mix BigInt and other types, use explicit conversions'); + } + + static LT(x, y) { + return JSBI.__compare(x, y, 0); + } + static LE(x, y) { + return JSBI.__compare(x, y, 1); + } + static GT(x, y) { + return JSBI.__compare(x, y, 2); + } + static GE(x, y) { + return JSBI.__compare(x, y, 3); + } + + static EQ(x, y) { + while (true) { + if (JSBI.__isBigInt(x)) { + if (JSBI.__isBigInt(y)) return JSBI.equal(x, y); + return JSBI.EQ(y, x); + } else if (typeof x === 'number') { + if (JSBI.__isBigInt(y)) return JSBI.__equalToNumber(y, x); + if (typeof y !== 'object') return x == y; + y = JSBI.__toPrimitive(y); + } else if (typeof x === 'string') { + if (JSBI.__isBigInt(y)) { + x = JSBI.__fromString(x); + if (x === null) return false; + return JSBI.equal(x, y); + } + if (typeof y !== 'object') return x == y; + y = JSBI.__toPrimitive(y); + } else if (typeof x === 'boolean') { + if (JSBI.__isBigInt(y)) return JSBI.__equalToNumber(y, +x); + if (typeof y !== 'object') return x == y; + y = JSBI.__toPrimitive(y); + } else if (typeof x === 'symbol') { + if (JSBI.__isBigInt(y)) return false; + if (typeof y !== 'object') return x == y; + y = JSBI.__toPrimitive(y); + } else if (typeof x === 'object') { + if (typeof y === 'object' && y.constructor !== JSBI) return x == y; + x = JSBI.__toPrimitive(x); + } else { + return x == y; + } + } + } + + // Helpers. + + static __zero() { + return new JSBI(0, false); + } + + static __oneDigit(value, sign) { + const result = new JSBI(1, sign); + result.__setDigit(0, value); + return result; + } + + __copy() { + const result = new JSBI(this.length, this.sign); + for (let i = 0; i < this.length; i++) { + result[i] = this[i]; + } + return result; + } + + __trim() { + let newLength = this.length; + let last = this[newLength - 1]; + while (last === 0) { + newLength--; + last = this[newLength - 1]; + this.pop(); + } + if (newLength === 0) this.sign = false; + return this; + } + + __initializeDigits() { + for (let i = 0; i < this.length; i++) { + this[i] = 0; + } + } + + static __decideRounding(x, mantissaBitsUnset, digitIndex, currentDigit) { + if (mantissaBitsUnset > 0) return -1; + let topUnconsumedBit; + if (mantissaBitsUnset < 0) { + topUnconsumedBit = -mantissaBitsUnset - 1; + } else { + // {currentDigit} fit the mantissa exactly; look at the next digit. + if (digitIndex === 0) return -1; + digitIndex--; + currentDigit = x.__digit(digitIndex); + topUnconsumedBit = 31; + } + // If the most significant remaining bit is 0, round down. + let mask = 1 << topUnconsumedBit; + if ((currentDigit & mask) === 0) return -1; + // If any other remaining bit is set, round up. + mask -= 1; + if ((currentDigit & mask) !== 0) return 1; + while (digitIndex > 0) { + digitIndex--; + if (x.__digit(digitIndex) !== 0) return 1; + } + return 0; + } + + static __fromDouble(value) { + const sign = value < 0; + JSBI.__kBitConversionDouble[0] = value; + const rawExponent = (JSBI.__kBitConversionInts[1] >>> 20) & 0x7FF; + const exponent = rawExponent - 0x3FF; + const digits = (exponent >>> 5) + 1; + const result = new JSBI(digits, sign); + const kHiddenBit = 0x00100000; + let mantissaHigh = (JSBI.__kBitConversionInts[1] & 0xFFFFF) | kHiddenBit; + let mantissaLow = JSBI.__kBitConversionInts[0]; + const kMantissaHighTopBit = 20; + // 0-indexed position of most significant bit in most significant digit. + const msdTopBit = exponent & 31; + // Number of unused bits in the mantissa. We'll keep them shifted to the + // left (i.e. most significant part). + let remainingMantissaBits = 0; + // Next digit under construction. + let digit; + // First, build the MSD by shifting the mantissa appropriately. + if (msdTopBit < kMantissaHighTopBit) { + const shift = kMantissaHighTopBit - msdTopBit; + remainingMantissaBits = shift + 32; + digit = mantissaHigh >>> shift; + mantissaHigh = (mantissaHigh << (32 - shift)) | + (mantissaLow >>> shift); + mantissaLow = mantissaLow << (32 - shift); + } else if (msdTopBit === kMantissaHighTopBit) { + remainingMantissaBits = 32; + digit = mantissaHigh; + mantissaHigh = mantissaLow; + } else { + const shift = msdTopBit - kMantissaHighTopBit; + remainingMantissaBits = 32 - shift; + digit = (mantissaHigh << shift) | (mantissaLow >>> (32 - shift)); + mantissaHigh = mantissaLow << shift; + } + result.__setDigit(digits - 1, digit); + // Then fill in the rest of the digits. + for (let digitIndex = digits - 2; digitIndex >= 0; digitIndex--) { + if (remainingMantissaBits > 0) { + remainingMantissaBits -= 32; + digit = mantissaHigh; + mantissaHigh = mantissaLow; + } else { + digit = 0; + } + result.__setDigit(digitIndex, digit); + } + return result.__trim(); + } + + static __isWhitespace(c) { + if (c <= 0x0D && c >= 0x09) return true; + if (c <= 0x9F) return c === 0x20; + if (c <= 0x01FFFF) { + return c === 0xA0 || c === 0x1680; + } + if (c <= 0x02FFFF) { + c &= 0x01FFFF; + return c <= 0x0A || c === 0x28 || c === 0x29 || c === 0x2F || + c === 0x5F || c === 0x1000; + } + return c === 0xFEFF; + } + + static __fromString(string, radix = 0) { + let sign = 0; + let leadingZero = false; + const length = string.length; + let cursor = 0; + if (cursor === length) return JSBI.__zero(); + let current = string.charCodeAt(cursor); + // Skip whitespace. + while (JSBI.__isWhitespace(current)) { + if (++cursor === length) return JSBI.__zero(); + current = string.charCodeAt(cursor); + } + + // Detect radix. + if (current === 0x2B) { // '+' + if (++cursor === length) return null; + current = string.charCodeAt(cursor); + sign = 1; + } else if (current === 0x2D) { // '-' + if (++cursor === length) return null; + current = string.charCodeAt(cursor); + sign = -1; + } + + if (radix === 0) { + radix = 10; + if (current === 0x30) { // '0' + if (++cursor === length) return JSBI.__zero(); + current = string.charCodeAt(cursor); + if (current === 0x58 || current === 0x78) { // 'X' or 'x' + radix = 16; + if (++cursor === length) return null; + current = string.charCodeAt(cursor); + } else if (current === 0x4F || current === 0x6F) { // 'O' or 'o' + radix = 8; + if (++cursor === length) return null; + current = string.charCodeAt(cursor); + } else if (current === 0x42 || current === 0x62) { // 'B' or 'b' + radix = 2; + if (++cursor === length) return null; + current = string.charCodeAt(cursor); + } else { + leadingZero = true; + } + } + } else if (radix === 16) { + if (current === 0x30) { // '0' + // Allow "0x" prefix. + if (++cursor === length) return JSBI.__zero(); + current = string.charCodeAt(cursor); + if (current === 0x58 || current === 0x78) { // 'X' or 'x' + if (++cursor === length) return null; + current = string.charCodeAt(cursor); + } else { + leadingZero = true; + } + } + } + // Skip leading zeros. + while (current === 0x30) { + leadingZero = true; + if (++cursor === length) return JSBI.__zero(); + current = string.charCodeAt(cursor); + } + + // Allocate result. + const chars = length - cursor; + let bitsPerChar = JSBI.__kMaxBitsPerChar[radix]; + let roundup = JSBI.__kBitsPerCharTableMultiplier - 1; + if (chars > (1 << 30) / bitsPerChar) return null; + const bitsMin = + (bitsPerChar * chars + roundup) >>> JSBI.__kBitsPerCharTableShift; + const resultLength = (bitsMin + 31) >>> 5; + const result = new JSBI(resultLength, false); + + // Parse. + const limDigit = radix < 10 ? radix : 10; + const limAlpha = radix > 10 ? radix - 10 : 0; + + if ((radix & (radix - 1)) === 0) { + // Power-of-two radix. + bitsPerChar >>= JSBI.__kBitsPerCharTableShift; + const parts = []; + const partsBits = []; + let done = false; + do { + let part = 0; + let bits = 0; + while (true) { + let d; + if (((current - 48) >>> 0) < limDigit) { + d = current - 48; + } else if ((((current | 32) - 97) >>> 0) < limAlpha) { + d = (current | 32) - 87; + } else { + done = true; + break; + } + bits += bitsPerChar; + part = (part << bitsPerChar) | d; + if (++cursor === length) { + done = true; + break; + } + current = string.charCodeAt(cursor); + if (bits + bitsPerChar > 32) break; + } + parts.push(part); + partsBits.push(bits); + } while (!done); + JSBI.__fillFromParts(result, parts, partsBits); + } else { + result.__initializeDigits(); + let done = false; + let charsSoFar = 0; + do { + let part = 0; + let multiplier = 1; + while (true) { + let d; + if (((current - 48) >>> 0) < limDigit) { + d = current - 48; + } else if ((((current | 32) - 97) >>> 0) < limAlpha) { + d = (current | 32) - 87; + } else { + done = true; + break; + } + + const m = multiplier * radix; + if (m > 0xFFFFFFFF) break; + multiplier = m; + part = part * radix + d; + charsSoFar++; + if (++cursor === length) { + done = true; + break; + } + current = string.charCodeAt(cursor); + } + roundup = JSBI.__kBitsPerCharTableMultiplier * 32 - 1; + const digitsSoFar = (bitsPerChar * charsSoFar + roundup) >>> + (JSBI.__kBitsPerCharTableShift + 5); + result.__inplaceMultiplyAdd(multiplier, part, digitsSoFar); + } while (!done); + } + + while (cursor !== length) { + if (!JSBI.__isWhitespace(current)) return null; + current = string.charCodeAt(cursor++); + } + + // Get result. + if (sign !== 0 && radix !== 10) return null; + result.sign = (sign === -1); + return result.__trim(); + } + + static __fillFromParts(result, parts, partsBits) { + let digitIndex = 0; + let digit = 0; + let bitsInDigit = 0; + for (let i = parts.length - 1; i >= 0; i--) { + const part = parts[i]; + const partBits = partsBits[i]; + digit |= (part << bitsInDigit); + bitsInDigit += partBits; + if (bitsInDigit === 32) { + result.__setDigit(digitIndex++, digit); + bitsInDigit = 0; + digit = 0; + } else if (bitsInDigit > 32) { + result.__setDigit(digitIndex++, digit); + bitsInDigit -= 32; + digit = part >>> (partBits - bitsInDigit); + } + } + if (digit !== 0) { + if (digitIndex >= result.length) throw new Error('implementation bug'); + result.__setDigit(digitIndex++, digit); + } + for (; digitIndex < result.length; digitIndex++) { + result.__setDigit(digitIndex, 0); + } + } + + static __toStringBasePowerOfTwo(x, radix) { + const length = x.length; + let bits = radix - 1; + bits = ((bits >>> 1) & 0x55) + (bits & 0x55); + bits = ((bits >>> 2) & 0x33) + (bits & 0x33); + bits = ((bits >>> 4) & 0x0F) + (bits & 0x0F); + const bitsPerChar = bits; + const charMask = radix - 1; + const msd = x.__digit(length - 1); + const msdLeadingZeros = Math.clz32(msd); + const bitLength = length * 32 - msdLeadingZeros; + let charsRequired = + ((bitLength + bitsPerChar - 1) / bitsPerChar) | 0; + if (x.sign) charsRequired++; + if (charsRequired > (1 << 28)) throw new Error('string too long'); + const result = new Array(charsRequired); + let pos = charsRequired - 1; + let digit = 0; + let availableBits = 0; + for (let i = 0; i < length - 1; i++) { + const newDigit = x.__digit(i); + const current = (digit | (newDigit << availableBits)) & charMask; + result[pos--] = JSBI.__kConversionChars[current]; + const consumedBits = bitsPerChar - availableBits; + digit = newDigit >>> consumedBits; + availableBits = 32 - consumedBits; + while (availableBits >= bitsPerChar) { + result[pos--] = JSBI.__kConversionChars[digit & charMask]; + digit >>>= bitsPerChar; + availableBits -= bitsPerChar; + } + } + const current = (digit | (msd << availableBits)) & charMask; + result[pos--] = JSBI.__kConversionChars[current]; + digit = msd >>> (bitsPerChar - availableBits); + while (digit !== 0) { + result[pos--] = JSBI.__kConversionChars[digit & charMask]; + digit >>>= bitsPerChar; + } + if (x.sign) result[pos--] = '-'; + if (pos !== -1) throw new Error('implementation bug'); + return result.join(''); + } + + static __toStringGeneric(x, radix, isRecursiveCall) { + const length = x.length; + if (length === 0) return ''; + if (length === 1) { + let result = x.__unsignedDigit(0).toString(radix); + if (isRecursiveCall === false && x.sign) { + result = '-' + result; + } + return result; + } + const bitLength = length * 32 - Math.clz32(x.__digit(length - 1)); + const maxBitsPerChar = JSBI.__kMaxBitsPerChar[radix]; + const minBitsPerChar = maxBitsPerChar - 1; + let charsRequired = bitLength * JSBI.__kBitsPerCharTableMultiplier; + charsRequired += minBitsPerChar - 1; + charsRequired = (charsRequired / minBitsPerChar) | 0; + const secondHalfChars = (charsRequired + 1) >> 1; + // Divide-and-conquer: split by a power of {radix} that's approximately + // the square root of {x}, then recurse. + const conqueror = JSBI.exponentiate(JSBI.__oneDigit(radix, false), + JSBI.__oneDigit(secondHalfChars, false)); + let quotient; + let secondHalf; + const divisor = conqueror.__unsignedDigit(0); + if (conqueror.length === 1 && divisor <= 0xFFFF) { + quotient = new JSBI(x.length, false); + quotient.__initializeDigits(); + let remainder = 0; + for (let i = x.length * 2 - 1; i >= 0; i--) { + const input = (remainder << 16) | x.__halfDigit(i); + quotient.__setHalfDigit(i, (input / divisor) | 0); + remainder = (input % divisor) | 0; + } + secondHalf = remainder.toString(radix); + } else { + const divisionResult = JSBI.__absoluteDivLarge(x, conqueror, true, true); + quotient = divisionResult.quotient; + const remainder = divisionResult.remainder.__trim(); + secondHalf = JSBI.__toStringGeneric(remainder, radix, true); + } + quotient.__trim(); + let firstHalf = JSBI.__toStringGeneric(quotient, radix, true); + while (secondHalf.length < secondHalfChars) { + secondHalf = '0' + secondHalf; + } + if (isRecursiveCall === false && x.sign) { + firstHalf = '-' + firstHalf; + } + return firstHalf + secondHalf; + } + + static __unequalSign(leftNegative) { + return leftNegative ? -1 : 1; + } + static __absoluteGreater(bothNegative) { + return bothNegative ? -1 : 1; + } + static __absoluteLess(bothNegative) { + return bothNegative ? 1 : -1; + } + + static __compareToBigInt(x, y) { + const xSign = x.sign; + if (xSign !== y.sign) return JSBI.__unequalSign(xSign); + const result = JSBI.__absoluteCompare(x, y); + if (result > 0) return JSBI.__absoluteGreater(xSign); + if (result < 0) return JSBI.__absoluteLess(xSign); + return 0; + } + + static __compareToNumber(x, y) { + if (y | 0 === 0) { + const xSign = x.sign; + const ySign = (y < 0); + if (xSign !== ySign) return JSBI.__unequalSign(xSign); + if (x.length === 0) { + if (ySign) throw new Error('implementation bug'); + return y === 0 ? 0 : -1; + } + // Any multi-digit BigInt is bigger than an int32. + if (x.length > 1) return JSBI.__absoluteGreater(xSign); + const yAbs = Math.abs(y); + const xDigit = x.__unsignedDigit(0); + if (xDigit > yAbs) return JSBI.__absoluteGreater(xSign); + if (xDigit < yAbs) return JSBI.__absoluteLess(xSign); + return 0; + } + return JSBI.__compareToDouble(x, y); + } + + static __compareToDouble(x, y) { + if (y !== y) return y; // NaN. + if (y === Infinity) return -1; + if (y === -Infinity) return 1; + const xSign = x.sign; + const ySign = (y < 0); + if (xSign !== ySign) return JSBI.__unequalSign(xSign); + if (y === 0) { + throw new Error('implementation bug: should be handled elsewhere'); + } + if (x.length === 0) return -1; + JSBI.__kBitConversionDouble[0] = y; + const rawExponent = (JSBI.__kBitConversionInts[1] >>> 20) & 0x7FF; + if (rawExponent === 0x7FF) { + throw new Error('implementation bug: handled elsewhere'); + } + const exponent = rawExponent - 0x3FF; + if (exponent < 0) { + // The absolute value of y is less than 1. Only 0n has an absolute + // value smaller than that, but we've already covered that case. + return JSBI.__absoluteGreater(xSign); + } + const xLength = x.length; + let xMsd = x.__digit(xLength - 1); + const msdLeadingZeros = Math.clz32(xMsd); + const xBitLength = xLength * 32 - msdLeadingZeros; + const yBitLength = exponent + 1; + if (xBitLength < yBitLength) return JSBI.__absoluteLess(xSign); + if (xBitLength > yBitLength) return JSBI.__absoluteGreater(xSign); + // Same sign, same bit length. Shift mantissa to align with x and compare + // bit for bit. + const kHiddenBit = 0x00100000; + let mantissaHigh = (JSBI.__kBitConversionInts[1] & 0xFFFFF) | kHiddenBit; + let mantissaLow = JSBI.__kBitConversionInts[0]; + const kMantissaHighTopBit = 20; + const msdTopBit = 31 - msdLeadingZeros; + if (msdTopBit !== ((xBitLength - 1) % 31)) { + throw new Error('implementation bug'); + } + let compareMantissa; // Shifted chunk of mantissa. + let remainingMantissaBits = 0; + // First, compare most significant digit against beginning of mantissa. + if (msdTopBit < kMantissaHighTopBit) { + const shift = kMantissaHighTopBit - msdTopBit; + remainingMantissaBits = shift + 32; + compareMantissa = mantissaHigh >>> shift; + mantissaHigh = (mantissaHigh << (32 - shift)) | (mantissaLow >>> shift); + mantissaLow = mantissaLow << (32 - shift); + } else if (msdTopBit === kMantissaHighTopBit) { + remainingMantissaBits = 32; + compareMantissa = mantissaHigh; + mantissaHigh = mantissaLow; + } else { + const shift = msdTopBit - kMantissaHighTopBit; + remainingMantissaBits = 32 - shift; + compareMantissa = + (mantissaHigh << shift) | (mantissaLow >>> (32 - shift)); + mantissaHigh = mantissaLow << shift; + } + xMsd = xMsd >>> 0; + compareMantissa = compareMantissa >>> 0; + if (xMsd > compareMantissa) return JSBI.__absoluteGreater(xSign); + if (xMsd < compareMantissa) return JSBI.__absoluteLess(xSign); + // Then, compare additional digits against remaining mantissa bits. + for (let digitIndex = xLength - 2; digitIndex >= 0; digitIndex--) { + if (remainingMantissaBits > 0) { + remainingMantissaBits -= 32; + compareMantissa = mantissaHigh >>> 0; + mantissaHigh = mantissaLow; + mantissaLow = 0; + } else { + compareMantissa = 0; + } + const digit = x.__unsignedDigit(digitIndex); + if (digit > compareMantissa) return JSBI.__absoluteGreater(xSign); + if (digit < compareMantissa) return JSBI.__absoluteLess(xSign); + } + // Integer parts are equal; check whether {y} has a fractional part. + if (mantissaHigh !== 0 || mantissaLow !== 0) { + if (remainingMantissaBits === 0) throw new Error('implementation bug'); + return JSBI.__absoluteLess(xSign); + } + return 0; + } + + static __equalToNumber(x, y) { + if (y | 0 === y) { + if (y === 0) return x.length === 0; + // Any multi-digit BigInt is bigger than an int32. + return (x.length === 1) && (x.sign === (y < 0)) && + (x.__unsignedDigit(0) === Math.abs(y)); + } + return JSBI.__compareToDouble(x, y) === 0; + } + + // Comparison operations, chosen such that "op ^ 2" reverses direction: + // 0 - lessThan + // 1 - lessThanOrEqual + // 2 - greaterThan + // 3 - greaterThanOrEqual + static __comparisonResultToBool(result, op) { + switch (op) { + case 0: return result < 0; + case 1: return result <= 0; + case 2: return result > 0; + case 3: return result >= 0; + } + throw new Error('unreachable'); + } + + static __compare(x, y, op) { + x = JSBI.__toPrimitive(x); + y = JSBI.__toPrimitive(y); + if (typeof x === 'string' && typeof y === 'string') { + switch (op) { + case 0: return x < y; + case 1: return x <= y; + case 2: return x > y; + case 3: return x >= y; + } + } + if (JSBI.__isBigInt(x) && typeof y === 'string') { + y = JSBI.__fromString(y); + if (y === null) return false; + return JSBI.__comparisonResultToBool(JSBI.__compareToBigInt(x, y), op); + } + if (typeof x === 'string' && JSBI.__isBigInt(y)) { + x = JSBI.__fromString(x); + if (x === null) return false; + return JSBI.__comparisonResultToBool(JSBI.__compareToBigInt(x, y), op); + } + x = JSBI.__toNumeric(x); + y = JSBI.__toNumeric(y); + if (JSBI.__isBigInt(x)) { + if (JSBI.__isBigInt(y)) { + return JSBI.__comparisonResultToBool(JSBI.__compareToBigInt(x, y), op); + } + if (typeof y !== 'number') throw new Error('implementation bug'); + return JSBI.__comparisonResultToBool(JSBI.__compareToNumber(x, y), op); + } + if (typeof x !== 'number') throw new Error('implementation bug'); + if (JSBI.__isBigInt(y)) { + // Note that "op ^ 2" reverses the op's direction. + return JSBI.__comparisonResultToBool(JSBI.__compareToNumber(y, x), + op ^ 2); + } + if (typeof y !== 'number') throw new Error('implementation bug'); + switch (op) { + case 0: return x < y; + case 1: return x <= y; + case 2: return x > y; + case 3: return x >= y; + } + } + + __clzmsd() { + return Math.clz32(this[this.length - 1]); + } + + static __absoluteAdd(x, y, resultSign) { + if (x.length < y.length) return JSBI.__absoluteAdd(y, x, resultSign); + if (x.length === 0) return x; + if (y.length === 0) return x.sign === resultSign ? x : JSBI.unaryMinus(x); + let resultLength = x.length; + if (x.__clzmsd() === 0 || (y.length === x.length && y.__clzmsd() === 0)) { + resultLength++; + } + const result = new JSBI(resultLength, resultSign); + let carry = 0; + let i = 0; + for (; i < y.length; i++) { + const yDigit = y.__digit(i); + const xDigit = x.__digit(i); + const rLow = (xDigit & 0xFFFF) + (yDigit & 0xFFFF) + carry; + const rHigh = (xDigit >>> 16) + (yDigit >>> 16) + (rLow >>> 16); + carry = rHigh >>> 16; + result.__setDigit(i, (rLow & 0xFFFF) | (rHigh << 16)); + } + for (; i < x.length; i++) { + const xDigit = x.__digit(i); + const rLow = (xDigit & 0xFFFF) + carry; + const rHigh = (xDigit >>> 16) + (rLow >>> 16); + carry = rHigh >>> 16; + result.__setDigit(i, (rLow & 0xFFFF) | (rHigh << 16)); + } + if (i < result.length) { + result.__setDigit(i, carry); + } + return result.__trim(); + } + + static __absoluteSub(x, y, resultSign) { + if (x.length === 0) return x; + if (y.length === 0) return x.sign === resultSign ? x : JSBI.unaryMinus(x); + const result = new JSBI(x.length, resultSign); + let borrow = 0; + let i = 0; + for (; i < y.length; i++) { + const xDigit = x.__digit(i); + const yDigit = y.__digit(i); + const rLow = (xDigit & 0xFFFF) - (yDigit & 0xFFFF) - borrow; + borrow = (rLow >>> 16) & 1; + const rHigh = (xDigit >>> 16) - (yDigit >>> 16) - borrow; + borrow = (rHigh >>> 16) & 1; + result.__setDigit(i, (rLow & 0xFFFF) | (rHigh << 16)); + } + for (; i < x.length; i++) { + const xDigit = x.__digit(i); + const rLow = (xDigit & 0xFFFF) - borrow; + borrow = (rLow >>> 16) & 1; + const rHigh = (xDigit >>> 16) - borrow; + borrow = (rHigh >>> 16) & 1; + result.__setDigit(i, (rLow & 0xFFFF) | (rHigh << 16)); + } + return result.__trim(); + } + + static __absoluteAddOne(x, sign, result = null) { + const inputLength = x.length; + if (result === null) { + result = new JSBI(inputLength, sign); + } else { + result.sign = sign; + } + let carry = true; + for (let i = 0; i < inputLength; i++) { + let digit = x.__digit(i); + const newCarry = digit === (0xFFFFFFFF | 0); + if (carry) digit = (digit + 1) | 0; + carry = newCarry; + result.__setDigit(i, digit); + } + if (carry) { + result.__setDigitGrow(inputLength, 1); + } + return result; + } + + static __absoluteSubOne(x, resultLength) { + const length = x.length; + resultLength = resultLength || length; + const result = new JSBI(resultLength, false); + let borrow = true; + for (let i = 0; i < length; i++) { + let digit = x.__digit(i); + const newBorrow = digit === 0; + if (borrow) digit = (digit - 1) | 0; + borrow = newBorrow; + result.__setDigit(i, digit); + } + for (let i = length; i < resultLength; i++) { + result.__setDigit(i, 0); + } + return result; + } + + static __absoluteAnd(x, y, result = null) { + let xLength = x.length; + let yLength = y.length; + let numPairs = yLength; + if (xLength < yLength) { + numPairs = xLength; + const tmp = x; + const tmpLength = xLength; + x = y; + xLength = yLength; + y = tmp; + yLength = tmpLength; + } + let resultLength = numPairs; + if (result === null) { + result = new JSBI(resultLength, false); + } else { + resultLength = result.length; + } + let i = 0; + for (; i < numPairs; i++) { + result.__setDigit(i, x.__digit(i) & y.__digit(i)); + } + for (; i < resultLength; i++) { + result.__setDigit(i, 0); + } + return result; + } + + static __absoluteAndNot(x, y, result = null) { + const xLength = x.length; + const yLength = y.length; + let numPairs = yLength; + if (xLength < yLength) { + numPairs = xLength; + } + let resultLength = xLength; + if (result === null) { + result = new JSBI(resultLength, false); + } else { + resultLength = result.length; + } + let i = 0; + for (; i < numPairs; i++) { + result.__setDigit(i, x.__digit(i) & ~y.__digit(i)); + } + for (; i < xLength; i++) { + result.__setDigit(i, x.__digit(i)); + } + for (; i < resultLength; i++) { + result.__setDigit(i, 0); + } + return result; + } + + static __absoluteOr(x, y, result = null) { + let xLength = x.length; + let yLength = y.length; + let numPairs = yLength; + if (xLength < yLength) { + numPairs = xLength; + const tmp = x; + const tmpLength = xLength; + x = y; + xLength = yLength; + y = tmp; + yLength = tmpLength; + } + let resultLength = xLength; + if (result === null) { + result = new JSBI(resultLength, false); + } else { + resultLength = result.length; + } + let i = 0; + for (; i < numPairs; i++) { + result.__setDigit(i, x.__digit(i) | y.__digit(i)); + } + for (; i < xLength; i++) { + result.__setDigit(i, x.__digit(i)); + } + for (; i < resultLength; i++) { + result.__setDigit(i, 0); + } + return result; + } + + static __absoluteXor(x, y, result = null) { + let xLength = x.length; + let yLength = y.length; + let numPairs = yLength; + if (xLength < yLength) { + numPairs = xLength; + const tmp = x; + const tmpLength = xLength; + x = y; + xLength = yLength; + y = tmp; + yLength = tmpLength; + } + let resultLength = xLength; + if (result === null) { + result = new JSBI(resultLength, false); + } else { + resultLength = result.length; + } + let i = 0; + for (; i < numPairs; i++) { + result.__setDigit(i, x.__digit(i) ^ y.__digit(i)); + } + for (; i < xLength; i++) { + result.__setDigit(i, x.__digit(i)); + } + for (; i < resultLength; i++) { + result.__setDigit(i, 0); + } + return result; + } + + static __absoluteCompare(x, y) { + const diff = x.length - y.length; + if (diff !== 0) return diff; + let i = x.length - 1; + while (i >= 0 && x.__digit(i) === y.__digit(i)) i--; + if (i < 0) return 0; + return x.__unsignedDigit(i) > y.__unsignedDigit(i) ? 1 : -1; + } + + static __multiplyAccumulate(multiplicand, multiplier, accumulator, + accumulatorIndex) { + if (multiplier === 0) return; + const m2Low = multiplier & 0xFFFF; + const m2High = multiplier >>> 16; + let carry = 0; + let highLower = 0; + let highHigher = 0; + for (let i = 0; i < multiplicand.length; i++, accumulatorIndex++) { + let acc = accumulator.__digit(accumulatorIndex); + let accLow = acc & 0xFFFF; + let accHigh = acc >>> 16; + const m1 = multiplicand.__digit(i); + const m1Low = m1 & 0xFFFF; + const m1High = m1 >>> 16; + const rLow = Math.imul(m1Low, m2Low); + const rMid1 = Math.imul(m1Low, m2High); + const rMid2 = Math.imul(m1High, m2Low); + const rHigh = Math.imul(m1High, m2High); + accLow += highLower + (rLow & 0xFFFF); + accHigh += highHigher + carry + (accLow >>> 16) + (rLow >>> 16) + + (rMid1 & 0xFFFF) + (rMid2 & 0xFFFF); + carry = accHigh >>> 16; + highLower = (rMid1 >>> 16) + (rMid2 >>> 16) + (rHigh & 0xFFFF) + carry; + carry = highLower >>> 16; + highLower &= 0xFFFF; + highHigher = rHigh >>> 16; + acc = (accLow & 0xFFFF) | (accHigh << 16); + accumulator.__setDigit(accumulatorIndex, acc); + } + for (; carry !== 0 || highLower !== 0 || highHigher !== 0; + accumulatorIndex++) { + let acc = accumulator.__digit(accumulatorIndex); + const accLow = (acc & 0xFFFF) + highLower; + const accHigh = (acc >>> 16) + (accLow >>> 16) + highHigher + carry; + highLower = 0; + highHigher = 0; + carry = accHigh >>> 16; + acc = (accLow & 0xFFFF) | (accHigh << 16); + accumulator.__setDigit(accumulatorIndex, acc); + } + } + + static __internalMultiplyAdd(source, factor, summand, n, result) { + let carry = summand; + let high = 0; + for (let i = 0; i < n; i++) { + const digit = source.__digit(i); + const rx = Math.imul(digit & 0xFFFF, factor); + const r0 = (rx & 0xFFFF) + high + carry; + carry = r0 >>> 16; + const ry = Math.imul(digit >>> 16, factor); + const r16 = (ry & 0xFFFF) + (rx >>> 16) + carry; + carry = r16 >>> 16; + high = ry >>> 16; + result.__setDigit(i, (r16 << 16) | (r0 & 0xFFFF)); + } + if (result.length > n) { + result.__setDigit(n++, carry + high); + while (n < result.length) { + result.__setDigit(n++, 0); + } + } else { + if (carry + high !== 0) throw new Error('implementation bug'); + } + } + + __inplaceMultiplyAdd(multiplier, summand, length) { + if (length > this.length) length = this.length; + const mLow = multiplier & 0xFFFF; + const mHigh = multiplier >>> 16; + let carry = 0; + let highLower = summand & 0xFFFF; + let highHigher = summand >>> 16; + for (let i = 0; i < length; i++) { + const d = this.__digit(i); + const dLow = d & 0xFFFF; + const dHigh = d >>> 16; + const pLow = Math.imul(dLow, mLow); + const pMid1 = Math.imul(dLow, mHigh); + const pMid2 = Math.imul(dHigh, mLow); + const pHigh = Math.imul(dHigh, mHigh); + const rLow = highLower + (pLow & 0xFFFF); + const rHigh = highHigher + carry + (rLow >>> 16) + (pLow >>> 16) + + (pMid1 & 0xFFFF) + (pMid2 & 0xFFFF); + highLower = (pMid1 >>> 16) + (pMid2 >>> 16) + (pHigh & 0xFFFF) + + (rHigh >>> 16); + carry = highLower >>> 16; + highLower &= 0xFFFF; + highHigher = pHigh >>> 16; + const result = (rLow & 0xFFFF) | (rHigh << 16); + this.__setDigit(i, result); + } + if (carry !== 0 || highLower !== 0 || highHigher !== 0) { + throw new Error('implementation bug'); + } + } + + static __absoluteDivSmall(x, divisor, quotient) { + if (quotient === null) quotient = new JSBI(x.length, false); + let remainder = 0; + for (let i = x.length * 2 - 1; i >= 0; i -= 2) { + let input = ((remainder << 16) | x.__halfDigit(i)) >>> 0; + const upperHalf = (input / divisor) | 0; + remainder = (input % divisor) | 0; + input = ((remainder << 16) | x.__halfDigit(i - 1)) >>> 0; + const lowerHalf = (input / divisor) | 0; + remainder = (input % divisor) | 0; + quotient.__setDigit(i >>> 1, (upperHalf << 16) | lowerHalf); + } + return quotient; + } + + static __absoluteModSmall(x, divisor) { + let remainder = 0; + for (let i = x.length * 2 - 1; i >= 0; i--) { + const input = ((remainder << 16) | x.__halfDigit(i)) >>> 0; + remainder = (input % divisor) | 0; + } + return remainder; + } + + static __absoluteDivLarge(dividend, divisor, wantQuotient, wantRemainder) { + const n = divisor.__halfDigitLength(); + const n2 = divisor.length; + const m = dividend.__halfDigitLength() - n; + let q = null; + if (wantQuotient) { + q = new JSBI((m + 2) >>> 1, false); + q.__initializeDigits(); + } + const qhatv = new JSBI((n + 2) >>> 1, false); + qhatv.__initializeDigits(); + // D1. + const shift = JSBI.__clz16(divisor.__halfDigit(n - 1)); + if (shift > 0) { + divisor = JSBI.__specialLeftShift(divisor, shift, 0 /* add no digits*/); + } + const u = JSBI.__specialLeftShift(dividend, shift, 1 /* add one digit */); + // D2. + const vn1 = divisor.__halfDigit(n - 1); + let halfDigitBuffer = 0; + for (let j = m; j >= 0; j--) { + // D3. + let qhat = 0xFFFF; + const ujn = u.__halfDigit(j + n); + if (ujn !== vn1) { + const input = ((ujn << 16) | u.__halfDigit(j + n - 1)) >>> 0; + qhat = (input / vn1) | 0; + let rhat = (input % vn1) | 0; + const vn2 = divisor.__halfDigit(n - 2); + const ujn2 = u.__halfDigit(j + n - 2); + while ((Math.imul(qhat, vn2) >>> 0) > (((rhat << 16) | ujn2) >>> 0)) { + qhat--; + rhat += vn1; + if (rhat > 0xFFFF) break; + } + } + // D4. + JSBI.__internalMultiplyAdd(divisor, qhat, 0, n2, qhatv); + let c = u.__inplaceSub(qhatv, j, n + 1); + if (c !== 0) { + c = u.__inplaceAdd(divisor, j, n); + u.__setHalfDigit(j + n, u.__halfDigit(j + n) + c); + qhat--; + } + if (wantQuotient) { + if (j & 1) { + halfDigitBuffer = qhat << 16; + } else { + q.__setDigit(j >>> 1, halfDigitBuffer | qhat); + } + } + } + if (wantRemainder) { + u.__inplaceRightShift(shift); + if (wantQuotient) { + return {quotient: q, remainder: u}; + } + return u; + } + if (wantQuotient) return q; + } + + static __clz16(value) { + return Math.clz32(value) - 16; + } + + // TODO: work on full digits, like __inplaceSub? + __inplaceAdd(summand, startIndex, halfDigits) { + let carry = 0; + for (let i = 0; i < halfDigits; i++) { + const sum = this.__halfDigit(startIndex + i) + + summand.__halfDigit(i) + + carry; + carry = sum >>> 16; + this.__setHalfDigit(startIndex + i, sum); + } + return carry; + } + + __inplaceSub(subtrahend, startIndex, halfDigits) { + const fullSteps = (halfDigits - 1) >>> 1; + let borrow = 0; + if (startIndex & 1) { + // this: [..][..][..] + // subtr.: [..][..] + startIndex >>= 1; + let current = this.__digit(startIndex); + let r0 = current & 0xFFFF; + let i = 0; + for (; i < fullSteps; i++) { + const sub = subtrahend.__digit(i); + const r16 = (current >>> 16) - (sub & 0xFFFF) - borrow; + borrow = (r16 >>> 16) & 1; + this.__setDigit(startIndex + i, (r16 << 16) | (r0 & 0xFFFF)); + current = this.__digit(startIndex + i + 1); + r0 = (current & 0xFFFF) - (sub >>> 16) - borrow; + borrow = (r0 >>> 16) & 1; + } + // Unrolling the last iteration gives a 5% performance benefit! + const sub = subtrahend.__digit(i); + const r16 = (current >>> 16) - (sub & 0xFFFF) - borrow; + borrow = (r16 >>> 16) & 1; + this.__setDigit(startIndex + i, (r16 << 16) | (r0 & 0xFFFF)); + const subTop = sub >>> 16; + if (startIndex + i + 1 >= this.length) { + throw new RangeError('out of bounds'); + } + if ((halfDigits & 1) === 0) { + current = this.__digit(startIndex + i + 1); + r0 = (current & 0xFFFF) - subTop - borrow; + borrow = (r0 >>> 16) & 1; + this.__setDigit(startIndex + subtrahend.length, + (current & 0xFFFF0000) | (r0 & 0xFFFF)); + } + } else { + startIndex >>= 1; + let i = 0; + for (; i < subtrahend.length - 1; i++) { + const current = this.__digit(startIndex + i); + const sub = subtrahend.__digit(i); + const r0 = (current & 0xFFFF) - (sub & 0xFFFF) - borrow; + borrow = (r0 >>> 16) & 1; + const r16 = (current >>> 16) - (sub >>> 16) - borrow; + borrow = (r16 >>> 16) & 1; + this.__setDigit(startIndex + i, (r16 << 16) | (r0 & 0xFFFF)); + } + const current = this.__digit(startIndex + i); + const sub = subtrahend.__digit(i); + const r0 = (current & 0xFFFF) - (sub & 0xFFFF) - borrow; + borrow = (r0 >>> 16) & 1; + let r16 = 0; + if ((halfDigits & 1) === 0) { + r16 = (current >>> 16) - (sub >>> 16) - borrow; + borrow = (r16 >>> 16) & 1; + } + this.__setDigit(startIndex + i, (r16 << 16) | (r0 & 0xFFFF)); + } + return borrow; + } + + __inplaceRightShift(shift) { + if (shift === 0) return; + let carry = this.__digit(0) >>> shift; + const last = this.length - 1; + for (let i = 0; i < last; i++) { + const d = this.__digit(i + 1); + this.__setDigit(i, (d << (32 - shift)) | carry); + carry = d >>> shift; + } + this.__setDigit(last, carry); + } + + static __specialLeftShift(x, shift, addDigit) { + const n = x.length; + const resultLength = n + addDigit; + const result = new JSBI(resultLength, false); + if (shift === 0) { + for (let i = 0; i < n; i++) result.__setDigit(i, x.__digit(i)); + if (addDigit > 0) result.__setDigit(n, 0); + return result; + } + let carry = 0; + for (let i = 0; i < n; i++) { + const d = x.__digit(i); + result.__setDigit(i, (d << shift) | carry); + carry = d >>> (32 - shift); + } + if (addDigit > 0) { + result.__setDigit(n, carry); + } + return result; + } + + static __leftShiftByAbsolute(x, y) { + const shift = JSBI.__toShiftAmount(y); + if (shift < 0) throw new RangeError('BigInt too big'); + const digitShift = shift >>> 5; + const bitsShift = shift & 31; + const length = x.length; + const grow = bitsShift !== 0 && + (x.__digit(length - 1) >>> (32 - bitsShift)) !== 0; + const resultLength = length + digitShift + (grow ? 1 : 0); + const result = new JSBI(resultLength, x.sign); + if (bitsShift === 0) { + let i = 0; + for (; i < digitShift; i++) result.__setDigit(i, 0); + for (; i < resultLength; i++) { + result.__setDigit(i, x.__digit(i - digitShift)); + } + } else { + let carry = 0; + for (let i = 0; i < digitShift; i++) result.__setDigit(i, 0); + for (let i = 0; i < length; i++) { + const d = x.__digit(i); + result.__setDigit(i + digitShift, (d << bitsShift) | carry); + carry = d >>> (32 - bitsShift); + } + if (grow) { + result.__setDigit(length + digitShift, carry); + } else { + if (carry !== 0) throw new Error('implementation bug'); + } + } + return result.__trim(); + } + + static __rightShiftByAbsolute(x, y) { + const length = x.length; + const sign = x.sign; + const shift = JSBI.__toShiftAmount(y); + if (shift < 0) return JSBI.__rightShiftByMaximum(sign); + const digitShift = shift >>> 5; + const bitsShift = shift & 31; + let resultLength = length - digitShift; + if (resultLength <= 0) return JSBI.__rightShiftByMaximum(sign); + // For negative numbers, round down if any bit was shifted out (so that + // e.g. -5n >> 1n == -3n and not -2n). Check now whether this will happen + // and whether itc an cause overflow into a new digit. If we allocate the + // result large enough up front, it avoids having to do grow it later. + let mustRoundDown = false; + if (sign) { + const mask = (1 << bitsShift) - 1; + if ((x.__digit(digitShift) & mask) !== 0) { + mustRoundDown = true; + } else { + for (let i = 0; i < digitShift; i++) { + if (x.__digit(i) !== 0) { + mustRoundDown = true; + break; + } + } + } + } + // If bitsShift is non-zero, it frees up bits, preventing overflow. + if (mustRoundDown && bitsShift === 0) { + // Overflow cannot happen if the most significant digit has unset bits. + const msd = x.__digit(length - 1); + const roundingCanOverflow = ~msd === 0; + if (roundingCanOverflow) resultLength++; + } + let result = new JSBI(resultLength, sign); + if (bitsShift === 0) { + for (let i = digitShift; i < length; i++) { + result.__setDigit(i - digitShift, x.__digit(i)); + } + } else { + let carry = x.__digit(digitShift) >>> bitsShift; + const last = length - digitShift - 1; + for (let i = 0; i < last; i++) { + const d = x.__digit(i + digitShift + 1); + result.__setDigit(i, (d << (32 - bitsShift)) | carry); + carry = d >>> bitsShift; + } + result.__setDigit(last, carry); + } + if (mustRoundDown) { + // Since the result is negative, rounding down means adding one to its + // absolute value. This cannot overflow. + result = JSBI.__absoluteAddOne(result, true, result); + } + return result.__trim(); + } + + static __rightShiftByMaximum(sign) { + if (sign) { + return JSBI.__oneDigit(1, true); + } + return JSBI.__zero(); + } + + static __toShiftAmount(x) { + if (x.length > 1) return -1; + const value = x.__unsignedDigit(0); + if (value > JSBI.__kMaxLengthBits) return -1; + return value; + } + + static __toPrimitive(obj, hint='default') { + if (typeof obj !== 'object') return obj; + if (obj.constructor === JSBI) return obj; + const exoticToPrim = obj[Symbol.toPrimitive]; + if (exoticToPrim) { + const primitive = exoticToPrim(hint); + if (typeof primitive !== 'object') return primitive; + throw new TypeError('Cannot convert object to primitive value'); + } + const valueOf = obj.valueOf; + if (valueOf) { + const primitive = valueOf.call(obj); + if (typeof primitive !== 'object') return primitive; + } + const toString = obj.toString; + if (toString) { + const primitive = toString.call(obj); + if (typeof primitive !== 'object') return primitive; + } + throw new TypeError('Cannot convert object to primitive value'); + } + + static __toNumeric(value) { + if (JSBI.__isBigInt(value)) return value; + return +value; + } + + static __isBigInt(value) { + return typeof value === 'object' && value.constructor === JSBI; + } + + // Digit helpers. + __digit(i) { + return this[i]; + } + __unsignedDigit(i) { + return this[i] >>> 0; + } + __setDigit(i, digit) { + this[i] = digit | 0; + } + __setDigitGrow(i, digit) { + this[i] = digit | 0; + } + __halfDigitLength() { + const len = this.length; + if (this.__unsignedDigit(len - 1) <= 0xFFFF) return len * 2 - 1; + return len*2; + } + __halfDigit(i) { + return (this[i >>> 1] >>> ((i & 1) << 4)) & 0xFFFF; + } + __setHalfDigit(i, value) { + const digitIndex = i >>> 1; + const previous = this.__digit(digitIndex); + const updated = (i & 1) ? (previous & 0xFFFF) | (value << 16) + : (previous & 0xFFFF0000) | (value & 0xFFFF); + this.__setDigit(digitIndex, updated); + } + + static __digitPow(base, exponent) { + let result = 1; + while (exponent > 0) { + if (exponent & 1) result *= base; + exponent >>>= 1; + base *= base; + } + return result; + } +} + +JSBI.__kMaxLength = 1 << 25; +JSBI.__kMaxLengthBits = JSBI.__kMaxLength << 5; +// Lookup table for the maximum number of bits required per character of a +// base-N string representation of a number. To increase accuracy, the array +// value is the actual value multiplied by 32. To generate this table: +// +// for (let i = 0; i <= 36; i++) { +// console.log(Math.ceil(Math.log2(i) * 32) + ','); +// } +JSBI.__kMaxBitsPerChar = [ + 0, 0, 32, 51, 64, 75, 83, 90, 96, // 0..8 + 102, 107, 111, 115, 119, 122, 126, 128, // 9..16 + 131, 134, 136, 139, 141, 143, 145, 147, // 17..24 + 149, 151, 153, 154, 156, 158, 159, 160, // 25..32 + 162, 163, 165, 166, // 33..36 +]; +JSBI.__kBitsPerCharTableShift = 5; +JSBI.__kBitsPerCharTableMultiplier = 1 << JSBI.__kBitsPerCharTableShift; +JSBI.__kConversionChars = '0123456789abcdefghijklmnopqrstuvwxyz'.split(''); +JSBI.__kBitConversionBuffer = new ArrayBuffer(8); +JSBI.__kBitConversionDouble = new Float64Array(JSBI.__kBitConversionBuffer); +JSBI.__kBitConversionInts = new Int32Array(JSBI.__kBitConversionBuffer); diff --git a/js/modules/loki_message_api.js b/js/modules/loki_message_api.js index e36d6823e..a72717ce4 100644 --- a/js/modules/loki_message_api.js +++ b/js/modules/loki_message_api.js @@ -1,41 +1,7 @@ -/* global log, dcodeIO, window */ +/* global log, dcodeIO, window, callWorker */ const fetch = require('node-fetch'); const is = require('@sindresorhus/is'); -const { fork } = require('child_process'); - -const development = (window.getEnvironment() !== 'production'); - -function getPoWNonce(timestamp, ttl, pubKey, data) { - return new Promise((resolve, reject) => { - // Create forked node process to calculate PoW without blocking main process - const child = fork('./libloki/proof-of-work.js'); - - // Send data required for PoW to child process - child.send({ - timestamp, - ttl, - pubKey, - data, - development, - }); - - // Handle child process error (should never happen) - child.on('error', err => { - reject(err); - }); - - // Callback to receive PoW result - child.on('message', msg => { - if (msg.err) { - reject(msg.err); - } else { - child.kill(); - resolve(msg.nonce); - } - }); - }); -} class LokiServer { @@ -62,11 +28,12 @@ class LokiServer { pubKey, timestamp: messageTimeStamp, }); - nonce = await getPoWNonce(timestamp, ttl, pubKey, data64); + const development = window.getEnvironment() !== 'production'; + nonce = await callWorker('calcPoW', timestamp, ttl, pubKey, data64, development); } catch (err) { // Something went horribly wrong // TODO: Handle gracefully - log.error('Error computing PoW'); + throw err; } const options = { diff --git a/js/modules/util_worker_interface.js b/js/modules/util_worker_interface.js new file mode 100644 index 000000000..1c7795887 --- /dev/null +++ b/js/modules/util_worker_interface.js @@ -0,0 +1,124 @@ +/* global Worker, window, setTimeout */ + +const WORKER_TIMEOUT = 60 * 1000; // one minute + +class TimedOutError extends Error { + constructor(message) { + super(message); + this.name = this.constructor.name; + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, this.constructor); + } else { + this.stack = (new Error(message)).stack; + } + } +} + +class WorkerInterface { + constructor(path) { + this._utilWorker = new Worker(path); + this._jobs = Object.create(null); + this._DEBUG = false; + this._jobCounter = 0; + + this._utilWorker.onmessage = e => { + const [jobId, errorForDisplay, result] = e.data; + + const job = this._getJob(jobId); + if (!job) { + throw new Error( + `Received worker reply to job ${jobId}, but did not have it in our registry!` + ); + } + + const { resolve, reject, fnName } = job; + + if (errorForDisplay) { + return reject( + new Error( + `Error received from worker job ${jobId} (${fnName}): ${errorForDisplay}` + ) + ); + } + + return resolve(result); + }; + } + + _makeJob (fnName) { + this._jobCounter += 1; + const id = this._jobCounter; + + if (this._DEBUG) { + window.log.info(`Worker job ${id} (${fnName}) started`); + } + this._jobs[id] = { + fnName, + start: Date.now(), + }; + + return id; + }; + + _updateJob(id, data) { + const { resolve, reject } = data; + const { fnName, start } = this._jobs[id]; + + this._jobs[id] = { + ...this._jobs[id], + ...data, + resolve: value => { + this._removeJob(id); + const end = Date.now(); + window.log.info( + `Worker job ${id} (${fnName}) succeeded in ${end - start}ms` + ); + return resolve(value); + }, + reject: error => { + this._removeJob(id); + const end = Date.now(); + window.log.info( + `Worker job ${id} (${fnName}) failed in ${end - start}ms` + ); + return reject(error); + }, + }; + }; + + _removeJob(id) { + if (this._DEBUG) { + this._jobs[id].complete = true; + } else { + delete this._jobs[id]; + } + } + + _getJob(id) { + return this._jobs[id]; + }; + + callWorker(fnName, ...args) { + const jobId = this._makeJob(fnName); + + return new Promise((resolve, reject) => { + this._utilWorker.postMessage([jobId, fnName, ...args]); + + this._updateJob(jobId, { + resolve, + reject, + args: this._DEBUG ? args : null, + }); + + setTimeout( + () => reject(new TimedOutError(`Worker job ${jobId} (${fnName}) timed out`)), + WORKER_TIMEOUT + ); + }); + }; +} + +module.exports = { + WorkerInterface, + TimedOutError, +}; diff --git a/js/util_worker_tasks.js b/js/util_worker_tasks.js index 4d6c80bda..f09325fa1 100644 --- a/js/util_worker_tasks.js +++ b/js/util_worker_tasks.js @@ -1,11 +1,10 @@ -/* global dcodeIO */ +/* global dcodeIO, pow */ /* eslint-disable strict */ -'use strict'; - const functions = { stringToArrayBufferBase64, arrayBufferToStringBase64, + calcPoW, }; onmessage = async e => { @@ -42,3 +41,6 @@ function stringToArrayBufferBase64(string) { function arrayBufferToStringBase64(arrayBuffer) { return dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64'); } +function calcPoW(timestamp, ttl, pubKey, data, development) { + return pow.calcPoW(timestamp, ttl, pubKey, data, development); +} diff --git a/libloki/proof-of-work.js b/libloki/proof-of-work.js index 2034919f4..2bea4f9ef 100644 --- a/libloki/proof-of-work.js +++ b/libloki/proof-of-work.js @@ -1,134 +1,132 @@ -const hash = require('js-sha512'); -const bb = require('bytebuffer'); -const { BigInteger } = require('jsbn'); - -module.exports = { - calcTarget, - incrementNonce, - bufferToBase64, - bigIntToUint8Array, - greaterThan, -}; - +/* global dcodeIO, crypto, JSBI */ const NONCE_LEN = 8; // Modify this value for difficulty scaling const DEV_NONCE_TRIALS = 10; const PROD_NONCE_TRIALS = 1000; -let development = true; -// Increment Uint8Array nonce by 1 with carrying -function incrementNonce(nonce) { - let idx = NONCE_LEN - 1; - const newNonce = new Uint8Array(nonce); - newNonce[idx] += 1; - // Nonce will just reset to 0 if all values are 255 causing infinite loop - while (newNonce[idx] === 0 && idx > 0) { - idx -= 1; +const pow = { + // Increment Uint8Array nonce by 1 with carrying + incrementNonce(nonce) { + let idx = NONCE_LEN - 1; + const newNonce = new Uint8Array(nonce); newNonce[idx] += 1; - } - return newNonce; -} + // Nonce will just reset to 0 if all values are 255 causing infinite loop + while (newNonce[idx] === 0 && idx > 0) { + idx -= 1; + newNonce[idx] += 1; + } + return newNonce; + }, -// Convert a Uint8Array to a base64 string -function bufferToBase64(buf) { - function mapFn(ch) { - return String.fromCharCode(ch); - } - const binaryString = Array.prototype.map.call(buf, mapFn).join(''); - return bb.btoa(binaryString); -} + // Convert a Uint8Array to a base64 string + bufferToBase64(buf) { + function mapFn(ch) { + return String.fromCharCode(ch); + } + const binaryString = Array.prototype.map.call(buf, mapFn).join(''); + return dcodeIO.ByteBuffer.btoa(binaryString); + }, -// Convert BigInteger to Uint8Array of length NONCE_LEN -function bigIntToUint8Array(bigInt) { - const arr = new Uint8Array(NONCE_LEN); - let n; - for (let idx = NONCE_LEN - 1; idx >= 0; idx -= 1) { - n = NONCE_LEN - (idx + 1); - // 256 ** n is the value of one bit in arr[idx], modulus to carry over - // (bigInt / 256**n) % 256; - const uint8Val = bigInt - .divide(new BigInteger('256').pow(n)) - .mod(new BigInteger('256')); - arr[idx] = uint8Val.intValue(); - } - return arr; -} + // Convert BigInteger to Uint8Array of length NONCE_LEN + bigIntToUint8Array(bigInt) { + const arr = new Uint8Array(NONCE_LEN); + let n; + for (let idx = NONCE_LEN - 1; idx >= 0; idx -= 1) { + n = NONCE_LEN - (idx + 1); + // 256 ** n is the value of one bit in arr[idx], modulus to carry over + // (bigInt / 256**n) % 256; + const denominator = JSBI.exponentiate( + JSBI.BigInt('256'), + JSBI.BigInt(n) + ); + const fraction = JSBI.divide(bigInt, denominator); + const uint8Val = JSBI.remainder(fraction, JSBI.BigInt(256)); + arr[idx] = JSBI.toNumber(uint8Val); + } + return arr; + }, -// Compare two Uint8Arrays, return true if arr1 is > arr2 -function greaterThan(arr1, arr2) { - // Early exit if lengths are not equal. Should never happen - if (arr1.length !== arr2.length) return false; + // Compare two Uint8Arrays, return true if arr1 is > arr2 + greaterThan(arr1, arr2) { + // Early exit if lengths are not equal. Should never happen + if (arr1.length !== arr2.length) return false; - for (let i = 0, len = arr1.length; i < len; i += 1) { - if (arr1[i] > arr2[i]) return true; - if (arr1[i] < arr2[i]) return false; - } - return false; -} + for (let i = 0, len = arr1.length; i < len; i += 1) { + if (arr1[i] > arr2[i]) return true; + if (arr1[i] < arr2[i]) return false; + } + return false; + }, -// Return nonce that hashes together with payload lower than the target -function calcPoW(timestamp, ttl, pubKey, data) { - const payload = new Uint8Array( - bb.wrap(timestamp.toString() + ttl.toString() + pubKey + data, 'binary').toArrayBuffer() - ); + // Return nonce that hashes together with payload lower than the target + async calcPoW(timestamp, ttl, pubKey, data, development = false) { + const payload = new Uint8Array( + dcodeIO.ByteBuffer.wrap( + timestamp.toString() + ttl.toString() + pubKey + data, + 'binary' + ).toArrayBuffer() + ); - const target = calcTarget(ttl, payload.length); + const nonceTrials = development ? DEV_NONCE_TRIALS : PROD_NONCE_TRIALS; + const target = pow.calcTarget(ttl, payload.length, nonceTrials); - let nonce = new Uint8Array(NONCE_LEN); - let trialValue = bigIntToUint8Array( - new BigInteger(Number.MAX_SAFE_INTEGER.toString()) - ); - const initialHash = new Uint8Array( - bb.wrap(hash(payload), 'hex').toArrayBuffer() - ); - const innerPayload = new Uint8Array(initialHash.length + NONCE_LEN); - innerPayload.set(initialHash, NONCE_LEN); - let resultHash; - while (greaterThan(trialValue, target)) { - nonce = incrementNonce(nonce); - innerPayload.set(nonce); - resultHash = hash(innerPayload); - trialValue = new Uint8Array( - bb.wrap(resultHash, 'hex').toArrayBuffer() - ).slice(0, NONCE_LEN); - } - return bufferToBase64(nonce); -} + let nonce = new Uint8Array(NONCE_LEN); + let trialValue = pow.bigIntToUint8Array( + JSBI.BigInt(Number.MAX_SAFE_INTEGER) + ); + const initialHash = new Uint8Array( + await crypto.subtle.digest('SHA-512', payload) + ); + const innerPayload = new Uint8Array(initialHash.length + NONCE_LEN); + innerPayload.set(initialHash, NONCE_LEN); + let resultHash; + while (pow.greaterThan(trialValue, target)) { + nonce = pow.incrementNonce(nonce); + innerPayload.set(nonce); + // eslint-disable-next-line no-await-in-loop + resultHash = await crypto.subtle.digest('SHA-512', innerPayload); + trialValue = new Uint8Array( + dcodeIO.ByteBuffer.wrap(resultHash, 'hex').toArrayBuffer() + ).slice(0, NONCE_LEN); + } + return pow.bufferToBase64(nonce); + }, -function calcTarget(ttl, payloadLen) { - // payloadLength + NONCE_LEN - const totalLen = new BigInteger(payloadLen.toString()).add( - new BigInteger(NONCE_LEN.toString()) - ); - // ttl * totalLen - const ttlMult = new BigInteger(ttl.toString()).multiply(totalLen); - // ttlMult / (2^16 - 1) - const innerFrac = ttlMult.divide( - new BigInteger('2').pow(16).subtract(new BigInteger('1')) - ); - // totalLen + innerFrac - const lenPlusInnerFrac = totalLen.add(innerFrac); - // nonceTrials * lenPlusInnerFrac - const nonceTrials = development ? DEV_NONCE_TRIALS : PROD_NONCE_TRIALS; - const denominator = new BigInteger(nonceTrials.toString()).multiply( - lenPlusInnerFrac - ); - // 2^64 - 1 - const two64 = new BigInteger('2').pow(64).subtract(new BigInteger('1')); - // two64 / denominator - const targetNum = two64.divide(denominator); - return bigIntToUint8Array(targetNum); -} - -// Start calculation in child process when main process sends message data -process.on('message', msg => { - ({ development } = msg); - process.send({ - nonce: calcPoW( - msg.timestamp, - msg.ttl, - msg.pubKey, - msg.data - ), - }); -}); + calcTarget(ttl, payloadLen, nonceTrials = PROD_NONCE_TRIALS) { + // payloadLength + NONCE_LEN + const totalLen = JSBI.add( + JSBI.BigInt(payloadLen), + JSBI.BigInt(NONCE_LEN) + ); + // ttl * totalLen + const ttlMult = JSBI.multiply( + JSBI.BigInt(ttl), + JSBI.BigInt(totalLen) + ); + // 2^16 - 1 + const two16 = JSBI.subtract( + JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(16)), // 2^16 + JSBI.BigInt(1) + ); + // ttlMult / two16 + const innerFrac = JSBI.divide( + ttlMult, + two16 + ); + // totalLen + innerFrac + const lenPlusInnerFrac = JSBI.add(totalLen, innerFrac); + // nonceTrials * lenPlusInnerFrac + const denominator = JSBI.multiply( + JSBI.BigInt(nonceTrials), + lenPlusInnerFrac + ); + // 2^64 - 1 + const two64 = JSBI.subtract( + JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(64)), // 2^64 + JSBI.BigInt(1) + ); + // two64 / denominator + const targetNum = JSBI.divide(two64, denominator); + return pow.bigIntToUint8Array(targetNum); + }, +}; diff --git a/libloki/test/index.html b/libloki/test/index.html index 2245c97e2..83a5b8319 100644 --- a/libloki/test/index.html +++ b/libloki/test/index.html @@ -13,9 +13,10 @@ - + - + + @@ -25,6 +26,7 @@ + diff --git a/test/app/proof-of-work_test.js b/libloki/test/proof-of-work_test.js similarity index 89% rename from test/app/proof-of-work_test.js rename to libloki/test/proof-of-work_test.js index 311380548..f31f1b4bc 100644 --- a/test/app/proof-of-work_test.js +++ b/libloki/test/proof-of-work_test.js @@ -1,6 +1,4 @@ -/* global require */ -const { assert } = require('chai'); -const { BigInteger } = require('jsbn'); +/* global assert, JSBI, pow */ const { calcTarget, @@ -8,7 +6,7 @@ const { bufferToBase64, bigIntToUint8Array, greaterThan, -} = require('../../libloki/proof-of-work'); +} = pow; describe('Proof of Work Worker', () => { it('should increment a Uint8Array nonce correctly', () => { @@ -42,11 +40,11 @@ describe('Proof of Work Worker', () => { const ttl = 86400; let expectedTarget = new Uint8Array([0,4,119,164,35,224,222,64]); - let actualTarget = calcTarget(ttl, payloadLen); + let actualTarget = calcTarget(ttl, payloadLen, 10); assert.deepEqual(actualTarget, expectedTarget); payloadLen = 6597; expectedTarget = new Uint8Array([0,0,109,145,174,146,124,3]); - actualTarget = calcTarget(ttl, payloadLen); + actualTarget = calcTarget(ttl, payloadLen, 10); assert.deepEqual(actualTarget, expectedTarget); }); @@ -81,16 +79,16 @@ describe('Proof of Work Worker', () => { }); it('should correclty convert a BigInteger to a Uint8Array', () => { - let bigInt = new BigInteger(Number.MAX_SAFE_INTEGER.toString()); + let bigInt = JSBI.BigInt(Number.MAX_SAFE_INTEGER); let expected = new Uint8Array([0, 31, 255, 255, 255, 255, 255, 255]); assert.deepEqual(bigIntToUint8Array(bigInt), expected); - bigInt = new BigInteger('0'); + bigInt = JSBI.BigInt('0'); expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]); assert.deepEqual(bigIntToUint8Array(bigInt), expected); - bigInt = new BigInteger('255'); + bigInt = JSBI.BigInt('255'); expected = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 255]); assert.deepEqual(bigIntToUint8Array(bigInt), expected); - bigInt = new BigInteger('256'); + bigInt = JSBI.BigInt('256'); expected = new Uint8Array([0, 0, 0, 0, 0, 0, 1, 0]); assert.deepEqual(bigIntToUint8Array(bigInt), expected); }); diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 5c07badb0..62c22f156 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -1,6 +1,6 @@ /* global window: false */ +/* global callWorker: false */ /* global textsecure: false */ -/* global storage: false */ /* global StringView: false */ /* global libloki: false */ /* global libsignal: false */ @@ -11,114 +11,11 @@ /* global HttpResource: false */ /* global ContactBuffer: false */ /* global GroupBuffer: false */ -/* global Worker: false */ /* global WebSocketResource: false */ /* eslint-disable more/no-then */ /* eslint-disable no-unreachable */ -const WORKER_TIMEOUT = 60 * 1000; // one minute - -const _utilWorker = new Worker('js/util_worker.js'); -const _jobs = Object.create(null); -const _DEBUG = false; -let _jobCounter = 0; - -function _makeJob(fnName) { - _jobCounter += 1; - const id = _jobCounter; - - if (_DEBUG) { - window.log.info(`Worker job ${id} (${fnName}) started`); - } - _jobs[id] = { - fnName, - start: Date.now(), - }; - - return id; -} - -function _updateJob(id, data) { - const { resolve, reject } = data; - const { fnName, start } = _jobs[id]; - - _jobs[id] = { - ..._jobs[id], - ...data, - resolve: value => { - _removeJob(id); - const end = Date.now(); - window.log.info( - `Worker job ${id} (${fnName}) succeeded in ${end - start}ms` - ); - return resolve(value); - }, - reject: error => { - _removeJob(id); - const end = Date.now(); - window.log.info( - `Worker job ${id} (${fnName}) failed in ${end - start}ms` - ); - return reject(error); - }, - }; -} - -function _removeJob(id) { - if (_DEBUG) { - _jobs[id].complete = true; - } else { - delete _jobs[id]; - } -} - -function _getJob(id) { - return _jobs[id]; -} - -async function callWorker(fnName, ...args) { - const jobId = _makeJob(fnName); - - return new Promise((resolve, reject) => { - _utilWorker.postMessage([jobId, fnName, ...args]); - - _updateJob(jobId, { - resolve, - reject, - args: _DEBUG ? args : null, - }); - - setTimeout( - () => reject(new Error(`Worker job ${jobId} (${fnName}) timed out`)), - WORKER_TIMEOUT - ); - }); -} - -_utilWorker.onmessage = e => { - const [jobId, errorForDisplay, result] = e.data; - - const job = _getJob(jobId); - if (!job) { - throw new Error( - `Received worker reply to job ${jobId}, but did not have it in our registry!` - ); - } - - const { resolve, reject, fnName } = job; - - if (errorForDisplay) { - return reject( - new Error( - `Error received from worker job ${jobId} (${fnName}): ${errorForDisplay}` - ) - ); - } - - return resolve(result); -}; - function MessageReceiver(username, password, signalingKey, options = {}) { this.count = 0; diff --git a/package.json b/package.json index 2d634dc15..44b929a89 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "blueimp-load-image": "^2.18.0", "buffer-crc32": "^0.2.1", "bunyan": "^1.8.12", - "bytebuffer": "^5.0.1", "classnames": "^2.2.5", "config": "^1.28.1", "electron-editor-context-menu": "^1.1.1", diff --git a/preload.js b/preload.js index 830079b3c..97c19b59d 100644 --- a/preload.js +++ b/preload.js @@ -1,6 +1,6 @@ /* global Whisper: false */ /* global window: false */ - +const path = require('path'); const electron = require('electron'); const semver = require('semver'); @@ -267,6 +267,10 @@ window.LokiAPI = new LokiServer({ }); window.mnemonic = require('./libloki/mnemonic'); +const { WorkerInterface } = require('./js/modules/util_worker_interface'); + +const worker = new WorkerInterface(path.join(app.getAppPath(), 'js', 'util_worker.js')); +window.callWorker = (fnName, ...args) => worker.callWorker(fnName, ...args); // Linux seems to periodically let the event loop stop, so this is a global workaround setInterval(() => { diff --git a/test/index.html b/test/index.html index 0152ccafe..192c6f556 100644 --- a/test/index.html +++ b/test/index.html @@ -385,7 +385,7 @@ - + diff --git a/yarn.lock b/yarn.lock index 4bbcad44e..68bccb227 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1432,13 +1432,6 @@ bunyan@^1.8.12: mv "~2" safe-json-stringify "~1" -bytebuffer@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd" - integrity sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0= - dependencies: - long "~3" - bytes@1: version "1.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" @@ -5932,11 +5925,6 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== -long@~3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" - integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s= - longest-streak@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e"