diff --git a/.eslintrc.yaml b/.eslintrc.yaml new file mode 100644 index 00000000..10b1118f --- /dev/null +++ b/.eslintrc.yaml @@ -0,0 +1,15 @@ +--- +plugins: + - es5 +extends: + - plugin:es5/no-es2015 +rules: + es5/no-es6-static-methods: + - error + - exceptMethods: + - Math.imul + semi: + - 2 + - always + no-extra-semi: 2 + no-var: 0 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..a128b42f --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,49 @@ +name: CI + +on: [push, pull_request] + +env: + CI: true + +jobs: + test: + name: Run tests + runs-on: ubuntu-latest + steps: + - name: Fetch code + uses: actions/checkout@v2 + with: + fetch-depth: 1 + + - name: Restore node_modules cache + uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} + + - name: Install dependencies + run: npm install --ignore-scripts + + - name: Run tests + run: npm run unit + + lint: + name: Run lint + runs-on: ubuntu-latest + steps: + - name: Fetch code + uses: actions/checkout@v2 + with: + fetch-depth: 1 + + - name: Restore node_modules cache + uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} + + - name: Install dependencies + run: npm install --ignore-scripts + + - name: Run lint command + run: npm run lint diff --git a/.gitignore b/.gitignore index 38bbc095..a2f23781 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ coverage/ node_modules/ + npm-debug.log -1.js +package-lock.json +yarn.lock +yarn-error.log diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 6d1eebbd..00000000 --- a/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -benchmarks/ -coverage/ -node_modules/ -npm-debug.log -1.js -logo.png diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 936b7b78..00000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -sudo: false -language: node_js -node_js: - - "0.10" - - "0.12" - - "4" - - "5" -env: - matrix: - - TEST_SUITE=unit -matrix: - include: - - node_js: "4" - env: TEST_SUITE=lint -script: npm run $TEST_SUITE diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..c77ea46f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,61 @@ +5.2.2 / 2025-04-25 +------------------ + +- fix: imuln/muln with zero (#313) + +5.2.1 / 2022-02-23 +------------------ + +- fix: serious issue in `.toString(16)` (#295) + +5.2.0 / 2021-02-23 +------------------ + +- fix: Buffer not using global in browser (#260) +- Fix LE constructor for HEX (#265) + +5.1.3 / 2020-08-14 +------------------ + +- Add support for defined but not implemented Symbol.for (#252) + +5.1.2 / 2020-05-20 +------------------ + +- Fix BN v5/v4 interoperability issue (#249) + +5.1.1 / 2019-12-24 +------------------ + +- Temporary workaround for BN#_move (#236) +- Add eslintrc instead config in package.json (#237) + +5.1.0 / 2019-12-23 +------------------ + +- Benchmark for BigInt (#226) +- Add documentation for max/min (#232) +- Update BN#inspect for Symbols (#225) +- Improve performance of toArrayLike (#222) +- temporary disable jumboMulTo in BN#mulTo (#221) +- optimize toBitArray function (#212) +- fix iaddn sign issue (#216) + +5.0.0 / 2019-07-04 +------------------ + +- travis: update node versions (#205) +- Refactor buffer constructor (#200) +- lib: fix for negative numbers: imuln, modrn, idivn (#185) +- bn: fix Red#imod (#178) +- check unexpected high bits for invalid characters (#173) +- document support very large integers (#158) +- only define toBuffer if Buffer is defined (#172) +- lib: better validation of string input (#151) +- tests: reject decimal input in constructor (#91) +- bn: make .strip() an internal method (#105) +- lib: deprecate `.modn()` introduce `.modrn()` (#112 #129 #130) +- bn: don't accept invalid characters (#141) +- package: use `files` insteadof `.npmignore` (#152) +- bn: improve allocation speed for buffers (#167) +- toJSON to default to interoperable hex (length % 2) (#164) diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..c328f040 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright Fedor Indutny, 2015. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index fee65baa..9fa12631 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,17 @@ console.log(res.toString(10)); // 57047 **Note**: decimals are not supported in this library. +## Sponsors + +[![Scout APM](./sponsors/scout-apm.png)](https://scoutapm.com/) +My Open Source work is supported by [Scout APM](https://scoutapm.com/) and +[other sponsors](https://github.com/sponsors/indutny). + ## Notation ### Prefixes -There are several prefixes to instructions that affect the way the work. Here +There are several prefixes to instructions that affect the way they work. Here is the list of them in the order of appearance in the function name: * `i` - perform operation in-place, storing the result in the host object (on @@ -37,20 +43,20 @@ is the list of them in the order of appearance in the function name: ### Postfixes -The only available postfix at the moment is: - -* `n` - which means that the argument of the function must be a plain JavaScript - number +* `n` - the argument of the function must be a plain JavaScript + Number. Decimals are not supported. The number passed must be smaller than 0x4000000 (67_108_864). Otherwise, an error is thrown. +* `rn` - both argument and return value of the function are plain JavaScript + Numbers. Decimals are not supported. ### Examples * `a.iadd(b)` - perform addition on `a` and `b`, storing the result in `a` -* `a.pmod(b)` - reduce `a` modulo `b`, returning positive value +* `a.umod(b)` - reduce `a` modulo `b`, returning positive value * `a.iushln(13)` - shift bits of `a` left by 13 ## Instructions -Prefixes/postfixes are put in parens at the of the line. `endian` - could be +Prefixes/postfixes are put in parens at the end of the line. `endian` - could be either `le` (little-endian) or `be` (big-endian). ### Utilities @@ -63,7 +69,9 @@ either `le` (little-endian) or `be` (big-endian). pad to length, throwing if already exceeding * `a.toArrayLike(type, endian, length)` - convert to an instance of `type`, which must behave like an `Array` -* `a.toBuffer(endian, length)` - convert to Node.js Buffer (if available) +* `a.toBuffer(endian, length)` - convert to Node.js Buffer (if available). `length` in bytes. For + compatibility with browserify and similar tools, use this instead: + `a.toArrayLike(Buffer, endian, length)` * `a.bitLength()` - get number of bits occupied * `a.zeroBits()` - return number of less-significant consequent zero bits (example: `1010000` has 4 zero bits) @@ -81,7 +89,9 @@ either `le` (little-endian) or `be` (big-endian). * `a.eq(b)` - `a` equals `b` (`n`) * `a.toTwos(width)` - convert to two's complement representation, where `width` is bit width * `a.fromTwos(width)` - convert from two's complement representation, where `width` is the bit width -* `a.isBN(object)` - returns true if the supplied `object` is a BN.js instance +* `BN.isBN(object)` - returns true if the supplied `object` is a BN.js instance +* `BN.max(a, b)` - return `a` if `a` bigger than `b` +* `BN.min(a, b)` - return `a` if `a` less than `b` ### Arithmetics @@ -94,6 +104,7 @@ either `le` (little-endian) or `be` (big-endian). * `a.pow(b)` - raise `a` to the power of `b` * `a.div(b)` - divide (`divn`, `idivn`) * `a.mod(b)` - reduct (`u`, `n`) (but no `umodn`) +* `a.divmod(b)` - quotient and modulus obtained by dividing * `a.divRound(b)` - rounded division ### Bit operations @@ -102,7 +113,7 @@ either `le` (little-endian) or `be` (big-endian). * `a.and(b)` - and (`i`, `u`, `iu`, `andln`) (NOTE: `andln` is going to be replaced with `andn` in future) * `a.xor(b)` - xor (`i`, `u`, `iu`) -* `a.setn(b)` - set specified bit to `1` +* `a.setn(b, value)` - set specified bit to `value` * `a.shln(b)` - shift left (`i`, `u`, `iu`) * `a.shrn(b)` - shift right (`i`, `u`, `iu`) * `a.testn(b)` - test if specified bit is set @@ -124,7 +135,7 @@ for [Mersenne Prime][1]. ### Reduction context -To enable this tricks one should create a reduction context: +To enable this trick one should create a reduction context: ```js var red = BN.red(num); @@ -150,7 +161,7 @@ Or: var red = BN.mont(num); ``` -To reduce numbers with [Montgomery trick][1]. `.mont()` is generally faster than +To reduce numbers with [Montgomery trick][0]. `.mont()` is generally faster than `.red(num)`, but slower than `BN.red(primeName)`. ### Converting numbers @@ -190,30 +201,14 @@ counterparts in red context: * `a.redNeg()` * `a.redPow(b)` - modular exponentiation +### Number Size + +Optimized for elliptic curves that work with 256-bit numbers. +There is no limitation on the size of the numbers. + ## LICENSE This software is licensed under the MIT License. -Copyright Fedor Indutny, 2015. - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to permit -persons to whom the Software is furnished to do so, subject to the -following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -USE OR OTHER DEALINGS IN THE SOFTWARE. - [0]: https://en.wikipedia.org/wiki/Montgomery_modular_multiplication [1]: https://en.wikipedia.org/wiki/Mersenne_prime diff --git a/benchmarks/index.js b/benchmarks/index.js index 3d155635..940aef7b 100644 --- a/benchmarks/index.js +++ b/benchmarks/index.js @@ -1,9 +1,15 @@ - /* eslint-disable new-cap, no-new */ +/* global BigInt */ +/* eslint-disable new-cap, no-new, no-unused-expressions */ var benchmark = require('benchmark'); var crypto = require('crypto'); var bn = require('../'); -var bignum = require('bignum'); +var bignum; +try { + bignum = require('bignum'); +} catch (err) { + console.log('Load bignum error: ' + err.message.split('\n')[0]); +} var sjcl = require('eccjs').sjcl.bn; var bigi = require('bigi'); var BigInteger = require('js-big-integer').BigInteger; @@ -31,6 +37,14 @@ function add (op, obj) { console.log('Benchmarking: ' + op); Object.keys(obj).forEach(function (name) { + if (name === 'BigInt' && typeof BigInt === 'undefined') { + return; + } + + if (name === 'bignum' && bignum === undefined) { + return; + } + if (!selfOnly || name === 'bn.js') { var testFn = obj[name]; suite.add(name + '#' + op, function () { @@ -53,7 +67,7 @@ function add (op, obj) { }) .on('complete', function () { console.log('------------------------'); - console.log('Fastest is ' + this.filter('fastest').pluck('name')); + console.log('Fastest is ' + this.filter('fastest')[0].name); }) .run(); @@ -92,73 +106,80 @@ while (fixtures.length < 25) { var aj = prng.randomBytes(768).toString('hex'); var bj = prng.randomBytes(768).toString('hex'); + fixture.a10base = new bn(a, 16).toString(10); + fixture.a16base = new bn(a, 16).toString(16); + // BN fixture.a1 = new bn(a, 16); fixture.b1 = new bn(b, 16); fixture.a1j = new bn(aj, 16); fixture.b1j = new bn(bj, 16); + fixture.as1 = fixture.a1.mul(fixture.a1).iaddn(0x2adbeef); + fixture.am1 = fixture.a1.toRed(bn.red('k256')); + fixture.pow1 = fixture.am1.fromRed(); + + // BigInt + fixture.a2 = BigInt(fixture.a1.toString(10)); + fixture.b2 = BigInt(fixture.b1.toString(10)); + fixture.a2j = BigInt(fixture.a1j.toString(10)); + fixture.b2j = BigInt(fixture.b1j.toString(10)); + fixture.as2 = fixture.a2 * fixture.a2 + 0x2adbeefn; // bignum - fixture.a2 = new bignum(a, 16); - fixture.b2 = new bignum(b, 16); - fixture.a2j = new bignum(aj, 16); - fixture.b2j = new bignum(bj, 16); + if (bignum) { + fixture.a3 = new bignum(a, 16); + fixture.b3 = new bignum(b, 16); + fixture.a3j = new bignum(aj, 16); + fixture.b3j = new bignum(bj, 16); + fixture.as3 = fixture.a3.mul(fixture.a3).add(0x2adbeef); + } // bigi fixture.a4 = new bigi(a, 16); fixture.b4 = new bigi(b, 16); fixture.a4j = new bigi(aj, 16); fixture.b4j = new bigi(bj, 16); + fixture.as4 = fixture.a4.multiply(fixture.a4).add(bigi.valueOf(0x2adbeef)); // sjcl fixture.a5 = new sjcl(a, 16); fixture.b5 = new sjcl(b, 16); fixture.a5j = new sjcl(aj, 16); fixture.b5j = new sjcl(bj, 16); + // fixture.as5 = fixture.a5.mul(fixture.a5).add(0x2adbeef); + fixture.am5 = new sjcl.prime.p256k(fixture.a5); // BigInteger fixture.a6 = new BigInteger(a, 16); fixture.b6 = new BigInteger(b, 16); fixture.a6j = new BigInteger(aj, 16); fixture.b6j = new BigInteger(bj, 16); - - // SilentMattBigInteger - fixture.a8 = SilentMattBigInteger.parse(a, 16); - fixture.b8 = SilentMattBigInteger.parse(b, 16); - - fixture.a8j = SilentMattBigInteger.parse(aj, 16); - fixture.b8j = SilentMattBigInteger.parse(aj, 16); - - // - fixture.as1 = fixture.a1.mul(fixture.a1).iaddn(0x2adbeef); - fixture.as2 = fixture.a2.mul(fixture.a2).add(0x2adbeef); - fixture.as4 = fixture.a4.multiply(fixture.a4).add(bigi.valueOf(0x2adbeef)); - // fixture.as5 = fixture.a5.mul(fixture.a5).add(0x2adbeef); fixture.as6 = fixture.a6.multiply(fixture.a6).add( - new BigInteger('2adbeef', 16)); - fixture.as8 = fixture.a8.multiply(fixture.a8).add( - SilentMattBigInteger.parse('2adbeef', 16)); + new BigInteger('2adbeef', 16)); - fixture.am1 = fixture.a1.toRed(bn.red('k256')); - fixture.am5 = new sjcl.prime.p256k(fixture.a5); - - fixture.pow1 = fixture.am1.fromRed(); - - fixture.a10base = fixture.a1.toString(10); - fixture.a16base = a; + // SilentMattBigInteger + fixture.a7 = SilentMattBigInteger.parse(a, 16); + fixture.b7 = SilentMattBigInteger.parse(b, 16); + fixture.a7j = SilentMattBigInteger.parse(aj, 16); + fixture.b7j = SilentMattBigInteger.parse(aj, 16); + fixture.as7 = fixture.a7.multiply(fixture.a7).add( + SilentMattBigInteger.parse('2adbeef', 16)); } add('create-10', { 'bn.js': function (fixture) { new bn(fixture.a10base, 10); }, - 'bignum': function (fixture) { + BigInt: function (fixture) { + BigInt(fixture.a10base); + }, + bignum: function (fixture) { new bignum(fixture.a10base, 10); }, - 'bigi': function (fixture) { + bigi: function (fixture) { new bigi(fixture.a10base, 10); }, - 'yaffle': function (fixture) { + yaffle: function (fixture) { new BigInteger(fixture.a10base, 10); }, 'silentmatt-biginteger': function (fixture) { @@ -170,16 +191,16 @@ add('create-hex', { 'bn.js': function (fixture) { new bn(fixture.a16base, 16); }, - 'bignum': function (fixture) { + bignum: function (fixture) { new bignum(fixture.a16base, 16); }, - 'bigi': function (fixture) { + bigi: function (fixture) { new bigi(fixture.a16base, 16); }, - 'sjcl': function (fixture) { + sjcl: function (fixture) { new sjcl(fixture.a16base); }, - 'yaffle': function (fixture) { + yaffle: function (fixture) { new BigInteger(fixture.a16base, 16); }, 'silentmatt-biginteger': function (fixture) { @@ -191,17 +212,20 @@ add('toString-10', { 'bn.js': function (fixture) { fixture.a1.toString(10); }, - 'bignum': function (fixture) { + BigInt: function (fixture) { fixture.a2.toString(10); }, - 'bigi': function (fixture) { + bignum: function (fixture) { + fixture.a3.toString(10); + }, + bigi: function (fixture) { fixture.a4.toString(10); }, - 'yaffle': function (fixture) { + yaffle: function (fixture) { fixture.a6.toString(10); }, 'silentmatt-biginteger': function (fixture) { - fixture.a8.toString(10); + fixture.a7.toString(10); } }); @@ -209,20 +233,23 @@ add('toString-hex', { 'bn.js': function (fixture) { fixture.a1.toString(16); }, - 'bignum': function (fixture) { + BigInt: function (fixture) { fixture.a2.toString(16); }, - 'bigi': function (fixture) { + bignum: function (fixture) { + fixture.a3.toString(16); + }, + bigi: function (fixture) { fixture.a4.toString(16); }, - 'sjcl': function (fixture) { + sjcl: function (fixture) { fixture.a5.toString(16); }, - 'yaffle': function (fixture) { + yaffle: function (fixture) { fixture.a6.toString(16); }, 'silentmatt-biginteger': function (fixture) { - fixture.a8.toString(16); + fixture.a7.toString(16); } }); @@ -230,20 +257,23 @@ add('add', { 'bn.js': function (fixture) { fixture.a1.add(fixture.b1); }, - 'bignum': function (fixture) { - fixture.a2.add(fixture.b2); + BigInt: function (fixture) { + fixture.a2 + fixture.b2; }, - 'bigi': function (fixture) { + bignum: function (fixture) { + fixture.a3.add(fixture.b3); + }, + bigi: function (fixture) { fixture.a4.add(fixture.b4); }, - 'sjcl': function (fixture) { + sjcl: function (fixture) { fixture.a5.add(fixture.b5); }, - 'yaffle': function (fixture) { + yaffle: function (fixture) { fixture.a6.add(fixture.b6); }, 'silentmatt-biginteger': function (fixture) { - fixture.a8.add(fixture.a8); + fixture.a7.add(fixture.a7); } }); @@ -251,20 +281,23 @@ add('sub', { 'bn.js': function (fixture) { fixture.b1.sub(fixture.a1); }, - 'bignum': function (fixture) { - fixture.b2.sub(fixture.a2); + BigInt: function (fixture) { + fixture.a2 - fixture.b2; + }, + bignum: function (fixture) { + fixture.b3.sub(fixture.a3); }, - 'bigi': function (fixture) { + bigi: function (fixture) { fixture.b4.subtract(fixture.a4); }, - 'sjcl': function (fixture) { + sjcl: function (fixture) { fixture.b5.sub(fixture.a5); }, - 'yaffle': function (fixture) { + yaffle: function (fixture) { fixture.b6.subtract(fixture.a6); }, 'silentmatt-biginteger': function (fixture) { - fixture.b8.subtract(fixture.a8); + fixture.b7.subtract(fixture.a7); } }); @@ -275,20 +308,23 @@ add('mul', { 'bn.js[FFT]': function (fixture) { fixture.a1.mulf(fixture.b1); }, - 'bignum': function (fixture) { - fixture.a2.mul(fixture.b2); + BigInt: function (fixture) { + fixture.a2 * fixture.b2; }, - 'bigi': function (fixture) { + bignum: function (fixture) { + fixture.a3.mul(fixture.b3); + }, + bigi: function (fixture) { fixture.a4.multiply(fixture.b4); }, - 'sjcl': function (fixture) { + sjcl: function (fixture) { fixture.a5.mul(fixture.b5); }, - 'yaffle': function (fixture) { + yaffle: function (fixture) { fixture.a6.multiply(fixture.b6); }, 'silentmatt-biginteger': function (fixture) { - fixture.a8.multiply(fixture.b8); + fixture.a7.multiply(fixture.b7); } }); @@ -299,20 +335,23 @@ add('mul-jumbo', { 'bn.js[FFT]': function (fixture) { fixture.a1j.mulf(fixture.b1j); }, - 'bignum': function (fixture) { - fixture.a2j.mul(fixture.b2j); + BigInt: function (fixture) { + fixture.a2j * fixture.b2j; + }, + bignum: function (fixture) { + fixture.a3j.mul(fixture.b3j); }, - 'bigi': function (fixture) { + bigi: function (fixture) { fixture.a4j.multiply(fixture.b4j); }, - 'sjcl': function (fixture) { + sjcl: function (fixture) { fixture.a5j.mul(fixture.b5j); }, - 'yaffle': function (fixture) { + yaffle: function (fixture) { fixture.a6j.multiply(fixture.b6j); }, 'silentmatt-biginteger': function (fixture) { - fixture.a8j.multiply(fixture.b8j); + fixture.a7j.multiply(fixture.b7j); } }); @@ -320,20 +359,23 @@ add('sqr', { 'bn.js': function (fixture) { fixture.a1.mul(fixture.a1); }, - 'bignum': function (fixture) { - fixture.a2.mul(fixture.a2); + BigInt: function (fixture) { + fixture.a2 * fixture.a2; + }, + bignum: function (fixture) { + fixture.a3.mul(fixture.a3); }, - 'bigi': function (fixture) { + bigi: function (fixture) { fixture.a4.square(); }, - 'sjcl': function (fixture) { + sjcl: function (fixture) { fixture.a5.mul(fixture.a5); }, - 'yaffle': function (fixture) { + yaffle: function (fixture) { fixture.a6.multiply(fixture.a6); }, 'silentmatt-biginteger': function (fixture) { - fixture.a8.multiply(fixture.a8); + fixture.a7.multiply(fixture.a7); } }); @@ -341,17 +383,20 @@ add('div', { 'bn.js': function (fixture) { fixture.as1.div(fixture.a1); }, - 'bignum': function (fixture) { - fixture.as2.div(fixture.a2); + BigInt: function (fixture) { + fixture.as2 / fixture.a2; }, - 'bigi': function (fixture) { + bignum: function (fixture) { + fixture.as3.div(fixture.a3); + }, + bigi: function (fixture) { fixture.as4.divide(fixture.a4); }, - 'yaffle': function (fixture) { + yaffle: function (fixture) { fixture.as6.divide(fixture.a6); }, 'silentmatt-biginteger': function (fixture) { - fixture.as8.divide(fixture.a8); + fixture.as7.divide(fixture.a7); } }); @@ -359,22 +404,25 @@ add('mod', { 'bn.js': function (fixture) { fixture.as1.mod(fixture.a1); }, - 'bignum': function (fixture) { - fixture.as2.mod(fixture.a2); + BigInt: function (fixture) { + fixture.as2 / fixture.a2; + }, + bignum: function (fixture) { + fixture.as3.mod(fixture.a3); }, - 'bigi': function (fixture) { + bigi: function (fixture) { fixture.as4.mod(fixture.a4); }, - 'yaffle': function (fixture) { + yaffle: function (fixture) { var remainder = fixture.as6.remainder(fixture.a6); return remainder.compareTo(BigInteger.ZERO) < 0 ? remainder.add(fixture.a6) : remainder; }, 'silentmatt-biginteger': function (fixture) { - var remainder = fixture.as8.remainder(fixture.a8); + var remainder = fixture.as7.remainder(fixture.a7); return remainder.compare(BigInteger.ZERO) < 0 - ? remainder.add(fixture.a8) + ? remainder.add(fixture.a7) : remainder; } }); @@ -383,14 +431,17 @@ add('mul-mod k256', { 'bn.js': function (fixture) { fixture.am1.redSqr(); }, - 'sjcl': function (fixture) { + sjcl: function (fixture) { fixture.am5.square().fullReduce(); } }); -var prime1 = new bignum( - 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', - 16); +var prime1; +if (bignum) { + prime1 = new bignum( + 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', + 16); +} // var prime4 = new bigi( // 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', // 16); @@ -401,8 +452,8 @@ add('pow k256', { 'bn.js': function (fixture) { fixture.am1.redPow(fixture.pow1); }, - 'bignum': function (fixture) { - fixture.a2.powm(fixture.a2, prime1); + bignum: function (fixture) { + fixture.a3.powm(fixture.a3, prime1); } }); @@ -410,7 +461,7 @@ add('invm k256', { 'bn.js': function (fixture) { fixture.am1.redInvm(); }, - 'sjcl': function (fixture) { + sjcl: function (fixture) { fixture.am5.inverseMod(prime5); } }); @@ -419,7 +470,7 @@ add('gcd', { 'bn.js': function (fixture) { fixture.a1.gcd(fixture.b1); }, - 'bigi': function (fixture) { + bigi: function (fixture) { fixture.a4.gcd(fixture.b4); } }); @@ -436,4 +487,10 @@ add('bitLength', { } }); +add('toArray', { + 'bn.js': function () { + fixture.a1j.toArray(); + } +}); + start(); diff --git a/benchmarks/package.json b/benchmarks/package.json index 56b7faf2..f99cb5cf 100644 --- a/benchmarks/package.json +++ b/benchmarks/package.json @@ -2,19 +2,21 @@ "name": "bn.js-benchmark", "version": "0.0.0", "description": "", + "license": "MIT", + "author": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, - "author": "", - "license": "MIT", "dependencies": { - "benchmark": "^1.0.0", - "bigi": "*", - "biginteger": "*", - "bignum": "^0.11.0", - "eccjs": "^0.3.1", - "js-big-integer": "*", - "xorshift.js": "^1.0.0" + "benchmark": "^2.1.4", + "bigi": "1.4.2", + "biginteger": "1.0.3", + "eccjs": "0.3.1", + "js-big-integer": "1.0.2", + "xorshift.js": "^1.0.3" + }, + "optionalDependencies": { + "bignum": "0.13.1" } } diff --git a/lib/bn.js b/lib/bn.js index 29a4c51a..deeda748 100644 --- a/lib/bn.js +++ b/lib/bn.js @@ -50,7 +50,11 @@ var Buffer; try { - Buffer = require('buf' + 'fer').Buffer; + if (typeof window !== 'undefined' && typeof window.Buffer !== 'undefined') { + Buffer = window.Buffer; + } else { + Buffer = require('buffer').Buffer; + } } catch (e) { } @@ -91,23 +95,19 @@ var start = 0; if (number[0] === '-') { start++; - } - - if (base === 16) { - this._parseHex(number, start); - } else { - this._parseBase(number, base, start); - } - - if (number[0] === '-') { this.negative = 1; } - this.strip(); - - if (endian !== 'le') return; - - this._initArray(this.toArray(), base, endian); + if (start < number.length) { + if (base === 16) { + this._parseHex(number, start, endian); + } else { + this._parseBase(number, base, start); + if (endian === 'le') { + this._initArray(this.toArray(), base, endian); + } + } + } }; BN.prototype._initNumber = function _initNumber (number, base, endian) { @@ -116,7 +116,7 @@ number = -number; } if (number < 0x4000000) { - this.words = [ number & 0x3ffffff ]; + this.words = [number & 0x3ffffff]; this.length = 1; } else if (number < 0x10000000000000) { this.words = [ @@ -144,7 +144,7 @@ // Perhaps a Uint8Array assert(typeof number.length === 'number'); if (number.length <= 0) { - this.words = [ 0 ]; + this.words = [0]; this.length = 1; return this; } @@ -180,34 +180,34 @@ } } } - return this.strip(); + return this._strip(); }; - function parseHex (str, start, end) { - var r = 0; - var len = Math.min(str.length, end); - for (var i = start; i < len; i++) { - var c = str.charCodeAt(i) - 48; - - r <<= 4; - - // 'a' - 'f' - if (c >= 49 && c <= 54) { - r |= c - 49 + 0xa; - - // 'A' - 'F' - } else if (c >= 17 && c <= 22) { - r |= c - 17 + 0xa; + function parseHex4Bits (string, index) { + var c = string.charCodeAt(index); + // '0' - '9' + if (c >= 48 && c <= 57) { + return c - 48; + // 'A' - 'F' + } else if (c >= 65 && c <= 70) { + return c - 55; + // 'a' - 'f' + } else if (c >= 97 && c <= 102) { + return c - 87; + } else { + assert(false, 'Invalid character in ' + string); + } + } - // '0' - '9' - } else { - r |= c & 0xf; - } + function parseHexByte (string, lowerBound, index) { + var r = parseHex4Bits(string, index); + if (index - 1 >= lowerBound) { + r |= parseHex4Bits(string, index - 1) << 4; } return r; } - BN.prototype._parseHex = function _parseHex (number, start) { + BN.prototype._parseHex = function _parseHex (number, start, endian) { // Create possibly bigger array to ensure that it fits the number this.length = Math.ceil((number.length - start) / 6); this.words = new Array(this.length); @@ -215,30 +215,44 @@ this.words[i] = 0; } - var j, w; - // Scan 24-bit chunks and add them to the number + // 24-bits chunks var off = 0; - for (i = number.length - 6, j = 0; i >= start; i -= 6) { - w = parseHex(number, i, i + 6); - this.words[j] |= (w << off) & 0x3ffffff; - // NOTE: `0x3fffff` is intentional here, 26bits max shift + 24bit hex limb - this.words[j + 1] |= w >>> (26 - off) & 0x3fffff; - off += 24; - if (off >= 26) { - off -= 26; - j++; + var j = 0; + + var w; + if (endian === 'be') { + for (i = number.length - 1; i >= start; i -= 2) { + w = parseHexByte(number, start, i) << off; + this.words[j] |= w & 0x3ffffff; + if (off >= 18) { + off -= 18; + j += 1; + this.words[j] |= w >>> 26; + } else { + off += 8; + } + } + } else { + var parseLength = number.length - start; + for (i = parseLength % 2 === 0 ? start + 1 : start; i < number.length; i += 2) { + w = parseHexByte(number, start, i) << off; + this.words[j] |= w & 0x3ffffff; + if (off >= 18) { + off -= 18; + j += 1; + this.words[j] |= w >>> 26; + } else { + off += 8; + } } } - if (i + 6 !== start) { - w = parseHex(number, start, i + 6); - this.words[j] |= (w << off) & 0x3ffffff; - this.words[j + 1] |= w >>> (26 - off) & 0x3fffff; - } - this.strip(); + + this._strip(); }; function parseBase (str, start, end, mul) { var r = 0; + var b = 0; var len = Math.min(str.length, end); for (var i = start; i < len; i++) { var c = str.charCodeAt(i) - 48; @@ -247,23 +261,25 @@ // 'a' if (c >= 49) { - r += c - 49 + 0xa; + b = c - 49 + 0xa; // 'A' } else if (c >= 17) { - r += c - 17 + 0xa; + b = c - 17 + 0xa; // '0' - '9' } else { - r += c; + b = c; } + assert(c >= 0 && b < mul, 'Invalid character'); + r += b; } return r; } BN.prototype._parseBase = function _parseBase (number, base, start) { // Initialize as zero - this.words = [ 0 ]; + this.words = [0]; this.length = 1; // Find length of limb in base @@ -304,6 +320,8 @@ this._iaddn(word); } } + + this._strip(); }; BN.prototype.copy = function copy (dest) { @@ -316,6 +334,17 @@ dest.red = this.red; }; + function move (dest, src) { + dest.words = src.words; + dest.length = src.length; + dest.negative = src.negative; + dest.red = src.red; + } + + BN.prototype._move = function _move (dest) { + move(dest, this); + }; + BN.prototype.clone = function clone () { var r = new BN(null); this.copy(r); @@ -330,7 +359,7 @@ }; // Remove leading `0` from `this` - BN.prototype.strip = function strip () { + BN.prototype._strip = function strip () { while (this.length > 1 && this.words[this.length - 1] === 0) { this.length--; } @@ -345,9 +374,21 @@ return this; }; - BN.prototype.inspect = function inspect () { + // Check Symbol.for because not everywhere where Symbol defined + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#Browser_compatibility + if (typeof Symbol !== 'undefined' && typeof Symbol.for === 'function') { + try { + BN.prototype[Symbol.for('nodejs.util.inspect.custom')] = inspect; + } catch (e) { + BN.prototype.inspect = inspect; + } + } else { + BN.prototype.inspect = inspect; + } + + function inspect () { return (this.red ? ''; - }; + } /* @@ -439,16 +480,16 @@ var w = this.words[i]; var word = (((w << off) | carry) & 0xffffff).toString(16); carry = (w >>> (24 - off)) & 0xffffff; - if (carry !== 0 || i !== this.length - 1) { - out = zeros[6 - word.length] + word + out; - } else { - out = word + out; - } off += 2; if (off >= 26) { off -= 26; i--; } + if (carry !== 0 || i !== this.length - 1) { + out = zeros[6 - word.length] + word + out; + } else { + out = word + out; + } } if (carry !== 0) { out = carry.toString(16) + out; @@ -471,7 +512,7 @@ var c = this.clone(); c.negative = 0; while (!c.isZero()) { - var r = c.modn(groupBase).toString(base); + var r = c.modrn(groupBase).toString(base); c = c.idivn(groupBase); if (!c.isZero()) { @@ -509,56 +550,110 @@ }; BN.prototype.toJSON = function toJSON () { - return this.toString(16); + return this.toString(16, 2); }; - BN.prototype.toBuffer = function toBuffer (endian, length) { - assert(typeof Buffer !== 'undefined'); - return this.toArrayLike(Buffer, endian, length); - }; + if (Buffer) { + BN.prototype.toBuffer = function toBuffer (endian, length) { + return this.toArrayLike(Buffer, endian, length); + }; + } BN.prototype.toArray = function toArray (endian, length) { return this.toArrayLike(Array, endian, length); }; + var allocate = function allocate (ArrayType, size) { + if (ArrayType.allocUnsafe) { + return ArrayType.allocUnsafe(size); + } + return new ArrayType(size); + }; + BN.prototype.toArrayLike = function toArrayLike (ArrayType, endian, length) { + this._strip(); + var byteLength = this.byteLength(); var reqLength = length || Math.max(1, byteLength); assert(byteLength <= reqLength, 'byte array longer than desired length'); assert(reqLength > 0, 'Requested array length <= 0'); - this.strip(); - var littleEndian = endian === 'le'; - var res = new ArrayType(reqLength); + var res = allocate(ArrayType, reqLength); + var postfix = endian === 'le' ? 'LE' : 'BE'; + this['_toArrayLike' + postfix](res, byteLength); + return res; + }; + + BN.prototype._toArrayLikeLE = function _toArrayLikeLE (res, byteLength) { + var position = 0; + var carry = 0; + + for (var i = 0, shift = 0; i < this.length; i++) { + var word = (this.words[i] << shift) | carry; - var b, i; - var q = this.clone(); - if (!littleEndian) { - // Assume big-endian - for (i = 0; i < reqLength - byteLength; i++) { - res[i] = 0; + res[position++] = word & 0xff; + if (position < res.length) { + res[position++] = (word >> 8) & 0xff; + } + if (position < res.length) { + res[position++] = (word >> 16) & 0xff; } - for (i = 0; !q.isZero(); i++) { - b = q.andln(0xff); - q.iushrn(8); + if (shift === 6) { + if (position < res.length) { + res[position++] = (word >> 24) & 0xff; + } + carry = 0; + shift = 0; + } else { + carry = word >>> 24; + shift += 2; + } + } - res[reqLength - i - 1] = b; + if (position < res.length) { + res[position++] = carry; + + while (position < res.length) { + res[position++] = 0; } - } else { - for (i = 0; !q.isZero(); i++) { - b = q.andln(0xff); - q.iushrn(8); + } + }; + + BN.prototype._toArrayLikeBE = function _toArrayLikeBE (res, byteLength) { + var position = res.length - 1; + var carry = 0; + + for (var i = 0, shift = 0; i < this.length; i++) { + var word = (this.words[i] << shift) | carry; - res[i] = b; + res[position--] = word & 0xff; + if (position >= 0) { + res[position--] = (word >> 8) & 0xff; + } + if (position >= 0) { + res[position--] = (word >> 16) & 0xff; } - for (; i < reqLength; i++) { - res[i] = 0; + if (shift === 6) { + if (position >= 0) { + res[position--] = (word >> 24) & 0xff; + } + carry = 0; + shift = 0; + } else { + carry = word >>> 24; + shift += 2; } } - return res; + if (position >= 0) { + res[position--] = carry; + + while (position >= 0) { + res[position--] = 0; + } + } }; if (Math.clz32) { @@ -631,7 +726,7 @@ var off = (bit / 26) | 0; var wbit = bit % 26; - w[bit] = (num.words[off] & (1 << wbit)) >>> wbit; + w[bit] = (num.words[off] >>> wbit) & 0x01; } return w; @@ -695,7 +790,7 @@ this.words[i] = this.words[i] | num.words[i]; } - return this.strip(); + return this._strip(); }; BN.prototype.ior = function ior (num) { @@ -730,7 +825,7 @@ this.length = b.length; - return this.strip(); + return this._strip(); }; BN.prototype.iand = function iand (num) { @@ -774,7 +869,7 @@ this.length = a.length; - return this.strip(); + return this._strip(); }; BN.prototype.ixor = function ixor (num) { @@ -818,7 +913,7 @@ } // And remove leading zeroes - return this.strip(); + return this._strip(); }; BN.prototype.notn = function notn (width) { @@ -840,7 +935,7 @@ this.words[off] = this.words[off] & ~(1 << wbit); } - return this.strip(); + return this._strip(); }; // Add `num` to `this` in-place @@ -981,7 +1076,7 @@ this.negative = 1; } - return this.strip(); + return this._strip(); }; // Subtract `num` from `this` @@ -1027,7 +1122,7 @@ out.length--; } - return out.strip(); + return out._strip(); } // TODO(indutny): it may be reasonable to omit it for users who don't need @@ -1649,12 +1744,14 @@ out.length--; } - return out.strip(); + return out._strip(); } function jumboMulTo (self, num, out) { - var fftm = new FFTM(); - return fftm.mulp(self, num, out); + // Temporary disable, see https://github.com/indutny/bn.js/issues/211 + // var fftm = new FFTM(); + // return fftm.mulp(self, num, out); + return bigMulTo(self, num, out); } BN.prototype.mulTo = function mulTo (num, out) { @@ -1866,7 +1963,7 @@ out.negative = x.negative ^ y.negative; out.length = x.length + y.length; - return out.strip(); + return out._strip(); }; // Multiply `this` by `num` @@ -1889,6 +1986,9 @@ }; BN.prototype.imuln = function imuln (num) { + var isNegNum = num < 0; + if (isNegNum) num = -num; + assert(typeof num === 'number'); assert(num < 0x4000000); @@ -1908,8 +2008,9 @@ this.words[i] = carry; this.length++; } + this.length = num === 0 ? 1 : this.length; - return this; + return isNegNum ? this.ineg() : this; }; BN.prototype.muln = function muln (num) { @@ -1984,7 +2085,7 @@ this.length += s; } - return this.strip(); + return this._strip(); }; BN.prototype.ishln = function ishln (bits) { @@ -2050,7 +2151,7 @@ this.length = 1; } - return this.strip(); + return this._strip(); }; BN.prototype.ishrn = function ishrn (bits, hint, extended) { @@ -2115,7 +2216,7 @@ this.words[this.length - 1] &= mask; } - return this.strip(); + return this._strip(); }; // Return only lowers bits of number @@ -2131,7 +2232,7 @@ // Possible sign change if (this.negative !== 0) { - if (this.length === 1 && (this.words[0] | 0) < num) { + if (this.length === 1 && (this.words[0] | 0) <= num) { this.words[0] = num - (this.words[0] | 0); this.negative = 0; return this; @@ -2190,7 +2291,7 @@ } } - return this.strip(); + return this._strip(); }; BN.prototype.addn = function addn (num) { @@ -2232,7 +2333,7 @@ this.words[i + shift] = w & 0x3ffffff; } - if (carry === 0) return this.strip(); + if (carry === 0) return this._strip(); // Subtraction overflow assert(carry === -1); @@ -2244,7 +2345,7 @@ } this.negative = 1; - return this.strip(); + return this._strip(); }; BN.prototype._wordDiv = function _wordDiv (num, mode) { @@ -2306,9 +2407,9 @@ } } if (q) { - q.strip(); + q._strip(); } - a.strip(); + a._strip(); // Denormalize if (mode !== 'div' && shift !== 0) { @@ -2407,13 +2508,13 @@ if (mode === 'mod') { return { div: null, - mod: new BN(this.modn(num.words[0])) + mod: new BN(this.modrn(num.words[0])) }; } return { div: this.divn(num.words[0]), - mod: new BN(this.modn(num.words[0])) + mod: new BN(this.modrn(num.words[0])) }; } @@ -2448,13 +2549,16 @@ var cmp = mod.cmp(half); // Round down - if (cmp < 0 || r2 === 1 && cmp === 0) return dm.div; + if (cmp < 0 || (r2 === 1 && cmp === 0)) return dm.div; // Round up return dm.div.negative !== 0 ? dm.div.isubn(1) : dm.div.iaddn(1); }; - BN.prototype.modn = function modn (num) { + BN.prototype.modrn = function modrn (num) { + var isNegNum = num < 0; + if (isNegNum) num = -num; + assert(num <= 0x3ffffff); var p = (1 << 26) % num; @@ -2463,11 +2567,19 @@ acc = (p * acc + (this.words[i] | 0)) % num; } - return acc; + return isNegNum ? -acc : acc; + }; + + // WARNING: DEPRECATED + BN.prototype.modn = function modn (num) { + return this.modrn(num); }; // In-place division by number BN.prototype.idivn = function idivn (num) { + var isNegNum = num < 0; + if (isNegNum) num = -num; + assert(num <= 0x3ffffff); var carry = 0; @@ -2477,7 +2589,8 @@ carry = w % num; } - return this.strip(); + this._strip(); + return isNegNum ? this.ineg() : this; }; BN.prototype.divn = function divn (num) { @@ -2729,7 +2842,7 @@ if (this.negative !== 0 && !negative) return -1; if (this.negative === 0 && negative) return 1; - this.strip(); + this._strip(); var res; if (this.length > 1) { @@ -2972,7 +3085,13 @@ } else if (cmp > 0) { r.isub(this.p); } else { - r.strip(); + if (r.strip !== undefined) { + // r is a BN v4 instance + r.strip(); + } else { + // r is a BN v5 instance + r._strip(); + } } return r; @@ -3145,7 +3264,9 @@ Red.prototype.imod = function imod (a) { if (this.prime) return this.prime.ireduce(a)._forceRed(this); - return a.umod(this.m)._forceRed(this); + + move(a, a.umod(this.m)._forceRed(this)); + return a; }; Red.prototype.neg = function neg (a) { @@ -3287,7 +3408,7 @@ }; Red.prototype.pow = function pow (a, num) { - if (num.isZero()) return new BN(1); + if (num.isZero()) return new BN(1).toRed(this); if (num.cmpn(1) === 0) return a.clone(); var windowSize = 4; diff --git a/package.json b/package.json index 88352136..792c0fbf 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,39 @@ { "name": "bn.js", - "version": "4.11.6", + "version": "5.2.2", "description": "Big number implementation in pure javascript", - "main": "lib/bn.js", - "scripts": { - "lint": "semistandard", - "unit": "mocha --reporter=spec test/*-test.js", - "test": "npm run lint && npm run unit" - }, - "repository": { - "type": "git", - "url": "git@github.com:indutny/bn.js" - }, "keywords": [ "BN", - "BigNum", "Big number", + "BigNum", "Modulo", "Montgomery" ], - "author": "Fedor Indutny ", - "license": "MIT", + "homepage": "https://github.com/indutny/bn.js", "bugs": { "url": "https://github.com/indutny/bn.js/issues" }, - "homepage": "https://github.com/indutny/bn.js", + "repository": { + "type": "git", + "url": "git@github.com:indutny/bn.js" + }, + "license": "MIT", + "author": "Fedor Indutny ", + "files": [ + "lib/bn.js" + ], + "main": "lib/bn.js", + "browser": { + "buffer": false + }, + "scripts": { + "lint": "standardx", + "test": "npm run lint && npm run unit", + "unit": "mocha --reporter=spec test/*-test.js" + }, "devDependencies": { - "istanbul": "^0.3.5", - "mocha": "^2.1.0", - "semistandard": "^7.0.4" + "eslint-plugin-es5": "^1.5.0", + "mocha": "^8.3.0", + "standardx": "^7.0.0" } } diff --git a/sponsors/scout-apm.png b/sponsors/scout-apm.png new file mode 100644 index 00000000..26ccbd09 Binary files /dev/null and b/sponsors/scout-apm.png differ diff --git a/test/arithmetic-test.js b/test/arithmetic-test.js index c3a0d7d5..eade1533 100644 --- a/test/arithmetic-test.js +++ b/test/arithmetic-test.js @@ -1,6 +1,6 @@ /* global describe, it */ -var assert = require('assert'); +var assert = require('assert').strict; var BN = require('../').BN; var fixtures = require('./fixtures'); @@ -83,6 +83,11 @@ describe('BN.js/Arithmetic', function () { new BN(0).iaddn(0x4000000); }, /^Error: Assertion failed$/); }); + + it('should reset sign if value equal to value in instance', function () { + var a = new BN(-1); + assert.equal(a.addn(1).toString(), '0'); + }); }); describe('.sub()', function () { @@ -236,11 +241,11 @@ describe('BN.js/Arithmetic', function () { } testMethod('.mul()', function (x, y) { - return BN.prototype.mul.apply(x, [ y ]); + return BN.prototype.mul.apply(x, [y]); }); testMethod('.mulf()', function (x, y) { - return BN.prototype.mulf.apply(x, [ y ]); + return BN.prototype.mulf.apply(x, [y]); }); describe('.imul()', function () { @@ -290,6 +295,26 @@ describe('BN.js/Arithmetic', function () { new BN(0).imuln(0x4000000); }, /^Error: Assertion failed$/); }); + + it('should negate number if number is negative', function () { + var a = new BN('dead', 16); + assert.equal(a.clone().imuln(-1).toString(16), a.clone().neg().toString(16)); + assert.equal(a.clone().muln(-1).toString(16), a.clone().neg().toString(16)); + + var b = new BN('dead', 16); + assert.equal(b.clone().imuln(-42).toString(16), b.clone().neg().muln(42).toString(16)); + assert.equal(b.clone().muln(-42).toString(16), b.clone().neg().muln(42).toString(16)); + }); + + it('should strip length on zero multiplication', function () { + var a = new BN(42); + assert.equal(a.clone().imuln(0).isZero(), true); + assert.equal(a.clone().muln(0).isZero(), true); + + var b = new BN('1222222225255589'); + assert.equal(b.clone().imuln(0).isZero(), true); + assert.equal(b.clone().muln(0).isZero(), true); + }); }); describe('.pow()', function () { @@ -392,6 +417,7 @@ describe('BN.js/Arithmetic', function () { describe('.idivn()', function () { it('should divide numbers in-place', function () { assert.equal(new BN('10', 16).idivn(3).toString(16), '5'); + assert.equal(new BN('10', 16).idivn(-3).toString(16), '-5'); assert.equal(new BN('12', 16).idivn(3).toString(16), '6'); assert.equal(new BN('10000000000000000').idivn(3).toString(10), '3333333333333333'); @@ -527,15 +553,16 @@ describe('BN.js/Arithmetic', function () { }); }); - describe('.modn()', function () { + describe('.modrn()', function () { it('should act like .mod() on small numbers', function () { - assert.equal(new BN('10', 16).modn(256).toString(16), '10'); - assert.equal(new BN('100', 16).modn(256).toString(16), '0'); - assert.equal(new BN('1001', 16).modn(256).toString(16), '1'); - assert.equal(new BN('100000000001', 16).modn(256).toString(16), '1'); - assert.equal(new BN('100000000001', 16).modn(257).toString(16), + assert.equal(new BN('10', 16).modrn(256).toString(16), '10'); + assert.equal(new BN('10', 16).modrn(-256).toString(16), '-10'); + assert.equal(new BN('100', 16).modrn(256).toString(16), '0'); + assert.equal(new BN('1001', 16).modrn(256).toString(16), '1'); + assert.equal(new BN('100000000001', 16).modrn(256).toString(16), '1'); + assert.equal(new BN('100000000001', 16).modrn(257).toString(16), new BN('100000000001', 16).mod(new BN(257)).toString(16)); - assert.equal(new BN('123456789012', 16).modn(3).toString(16), + assert.equal(new BN('123456789012', 16).modrn(3).toString(16), new BN('123456789012', 16).mod(new BN(3)).toString(16)); }); }); diff --git a/test/binary-test.js b/test/binary-test.js index 37b6421d..75b8f4ca 100644 --- a/test/binary-test.js +++ b/test/binary-test.js @@ -1,6 +1,6 @@ /* global describe, it */ -var assert = require('assert'); +var assert = require('assert').strict; var BN = require('../').BN; describe('BN.js/Binary', function () { diff --git a/test/constructor-test.js b/test/constructor-test.js index 11c7df08..0bd256b8 100644 --- a/test/constructor-test.js +++ b/test/constructor-test.js @@ -1,6 +1,6 @@ /* global describe, it */ -var assert = require('assert'); +var assert = require('assert').strict; var BN = require('../').BN; describe('BN.js/Constructor', function () { @@ -90,6 +90,45 @@ describe('BN.js/Constructor', function () { assert.equal(new BN('1A6B765D8CDF', 16, 'le').toString(16), 'df8c5d766b1a'); }); + + it('should accept base-16 LE integer with leading zeros', function () { + assert.equal(new BN('0010', 16, 'le').toNumber(), 4096); + assert.equal(new BN('-010', 16, 'le').toNumber(), -4096); + assert.equal(new BN('010', 16, 'le').toNumber(), 4096); + }); + + it('should not accept wrong characters for base', function () { + assert.throws(function () { + return new BN('01FF'); + }, /^Error: Invalid character$/); + }); + + it('should not accept decimal', function () { + assert.throws(function () { + new BN('10.00', 10); // eslint-disable-line no-new + }, /Invalid character/); + + assert.throws(function () { + new BN('16.00', 16); // eslint-disable-line no-new + }, /Invalid character/); + }); + + it('should not accept non-hex characters', function () { + [ + '0000000z', + '000000gg', + '0000gg00', + 'fffggfff', + '/0000000', + '0-000000', // if -, is first, that is OK + 'ff.fffff', + 'hexadecimal' + ].forEach(function (str) { + assert.throws(function () { + new BN(str, 16); // eslint-disable-line no-new + }, /Invalid character in /); + }); + }); }); describe('with Array input', function () { @@ -98,45 +137,47 @@ describe('BN.js/Constructor', function () { }); it('should import/export big endian', function () { - assert.equal(new BN([ 1, 2, 3 ]).toString(16), '10203'); - assert.equal(new BN([ 1, 2, 3, 4 ]).toString(16), '1020304'); - assert.equal(new BN([ 1, 2, 3, 4, 5 ]).toString(16), '102030405'); - assert.equal(new BN([ 1, 2, 3, 4, 5, 6, 7, 8 ]).toString(16), + assert.equal(new BN([0, 1], 16).toString(16), '1'); + assert.equal(new BN([1, 2, 3]).toString(16), '10203'); + assert.equal(new BN([1, 2, 3, 4]).toString(16), '1020304'); + assert.equal(new BN([1, 2, 3, 4, 5]).toString(16), '102030405'); + assert.equal(new BN([1, 2, 3, 4, 5, 6, 7, 8]).toString(16), '102030405060708'); - assert.equal(new BN([ 1, 2, 3, 4 ]).toArray().join(','), '1,2,3,4'); - assert.equal(new BN([ 1, 2, 3, 4, 5, 6, 7, 8 ]).toArray().join(','), + assert.equal(new BN([1, 2, 3, 4]).toArray().join(','), '1,2,3,4'); + assert.equal(new BN([1, 2, 3, 4, 5, 6, 7, 8]).toArray().join(','), '1,2,3,4,5,6,7,8'); }); it('should import little endian', function () { - assert.equal(new BN([ 1, 2, 3 ], 10, 'le').toString(16), '30201'); - assert.equal(new BN([ 1, 2, 3, 4 ], 10, 'le').toString(16), '4030201'); - assert.equal(new BN([ 1, 2, 3, 4, 5 ], 10, 'le').toString(16), + assert.equal(new BN([0, 1], 16, 'le').toString(16), '100'); + assert.equal(new BN([1, 2, 3], 16, 'le').toString(16), '30201'); + assert.equal(new BN([1, 2, 3, 4], 16, 'le').toString(16), '4030201'); + assert.equal(new BN([1, 2, 3, 4, 5], 16, 'le').toString(16), '504030201'); - assert.equal(new BN([ 1, 2, 3, 4, 5, 6, 7, 8 ], 'le').toString(16), + assert.equal(new BN([1, 2, 3, 4, 5, 6, 7, 8], 'le').toString(16), '807060504030201'); - assert.equal(new BN([ 1, 2, 3, 4 ]).toArray('le').join(','), '4,3,2,1'); - assert.equal(new BN([ 1, 2, 3, 4, 5, 6, 7, 8 ]).toArray('le').join(','), + assert.equal(new BN([1, 2, 3, 4]).toArray('le').join(','), '4,3,2,1'); + assert.equal(new BN([1, 2, 3, 4, 5, 6, 7, 8]).toArray('le').join(','), '8,7,6,5,4,3,2,1'); }); it('should import big endian with implicit base', function () { - assert.equal(new BN([ 1, 2, 3, 4, 5 ], 'le').toString(16), '504030201'); + assert.equal(new BN([1, 2, 3, 4, 5], 'le').toString(16), '504030201'); }); }); // the Array code is able to handle Buffer describe('with Buffer input', function () { it('should not fail on empty Buffer', function () { - assert.equal(new BN(new Buffer(0)).toString(16), '0'); + assert.equal(new BN(Buffer.alloc(0)).toString(16), '0'); }); it('should import/export big endian', function () { - assert.equal(new BN(new Buffer('010203', 'hex')).toString(16), '10203'); + assert.equal(new BN(Buffer.from('010203', 'hex')).toString(16), '10203'); }); it('should import little endian', function () { - assert.equal(new BN(new Buffer('010203', 'hex'), 'le').toString(16), '30201'); + assert.equal(new BN(Buffer.from('010203', 'hex'), 'le').toString(16), '30201'); }); }); diff --git a/test/pummel/dh-group-test.js b/test/pummel/dh-group-test.js index 37a259ff..31395da7 100644 --- a/test/pummel/dh-group-test.js +++ b/test/pummel/dh-group-test.js @@ -1,6 +1,6 @@ /* global describe, it */ -var assert = require('assert'); +var assert = require('assert').strict; var BN = require('../../').BN; var fixtures = require('../fixtures'); @@ -16,7 +16,7 @@ describe('BN.js/Slow DH test', function () { var mont = BN.red(new BN(group.prime, 16)); var priv = new BN(group.priv, 16); var multed = base.toRed(mont).redPow(priv).fromRed(); - var actual = new Buffer(multed.toArray()); + var actual = Buffer.from(multed.toArray()); assert.equal(actual.toString('hex'), group.pub); }); }); diff --git a/test/red-test.js b/test/red-test.js index fc2498c9..38e1f2c9 100644 --- a/test/red-test.js +++ b/test/red-test.js @@ -1,6 +1,6 @@ /* global describe, it */ -var assert = require('assert'); +var assert = require('assert').strict; var BN = require('../').BN; describe('BN.js/Reduction context', function () { @@ -31,6 +31,8 @@ describe('BN.js/Reduction context', function () { var c = a.toRed(m).redMul(b.toRed(m)).fromRed(); assert(c.cmp(a.mul(b).mod(p192)) === 0); + assert.equal(a.toRed(m).redPow(new BN(0)).fromRed() + .cmp(new BN(1)), 0); assert.equal(a.toRed(m).redPow(new BN(3)).fromRed() .cmp(a.sqr().mul(a)), 0); assert.equal(a.toRed(m).redPow(new BN(4)).fromRed() @@ -168,16 +170,18 @@ describe('BN.js/Reduction context', function () { assert.equal(p.ireduce(new BN(0xdead)).toString(16), 'dead'); assert.equal(p.ireduce(new BN('deadbeef', 16)).toString(16), 'deadbeef'); - var num = new BN('fedcba9876543210fedcba9876543210dead' + - 'fedcba9876543210fedcba9876543210dead', + var num = new BN( + 'fedcba9876543210fedcba9876543210dead' + + 'fedcba9876543210fedcba9876543210dead', 16); var exp = num.mod(p.p).toString(16); assert.equal(p.ireduce(num).toString(16), exp); - var regr = new BN('f7e46df64c1815962bf7bc9c56128798' + - '3f4fcef9cb1979573163b477eab93959' + - '335dfb29ef07a4d835d22aa3b6797760' + - '70a8b8f59ba73d56d01a79af9', + var regr = new BN( + 'f7e46df64c1815962bf7bc9c56128798' + + '3f4fcef9cb1979573163b477eab93959' + + '335dfb29ef07a4d835d22aa3b6797760' + + '70a8b8f59ba73d56d01a79af9', 16); exp = regr.mod(p.p).toString(16); @@ -195,8 +199,9 @@ describe('BN.js/Reduction context', function () { var p = BN._prime('k256').p; var red = BN.red('k256'); - var n = new BN('9cd8cb48c3281596139f147c1364a3ed' + - 'e88d3f310fdb0eb98c924e599ca1b3c9', + var n = new BN( + '9cd8cb48c3281596139f147c1364a3ed' + + 'e88d3f310fdb0eb98c924e599ca1b3c9', 16); var expected = n.sqr().mod(p); var actual = n.toRed(red).redSqr().fromRed(); @@ -220,28 +225,30 @@ describe('BN.js/Reduction context', function () { } return bits; } - var t = new Buffer('aff1651e4cd6036d57aa8b2a05ccf1a9d5a40166340ecbbdc55' + + var t = Buffer.from('aff1651e4cd6036d57aa8b2a05ccf1a9d5a40166340ecbbdc55' + 'be10b568aa0aa3d05ce9a2fcec9df8ed018e29683c6051cb83e' + '46ce31ba4edb045356a8d0d80b', 'hex'); - var g = new BN('5c7ff6b06f8f143fe8288433493e4769c4d988ace5be25a0e24809670' + - '716c613d7b0cee6932f8faa7c44d2cb24523da53fbe4f6ec3595892d1' + - 'aa58c4328a06c46a15662e7eaa703a1decf8bbb2d05dbe2eb956c142a' + - '338661d10461c0d135472085057f3494309ffa73c611f78b32adbb574' + - '0c361c9f35be90997db2014e2ef5aa61782f52abeb8bd6432c4dd097b' + - 'c5423b285dafb60dc364e8161f4a2a35aca3a10b1c4d203cc76a470a3' + - '3afdcbdd92959859abd8b56e1725252d78eac66e71ba9ae3f1dd24871' + - '99874393cd4d832186800654760e1e34c09e4d155179f9ec0dc4473f9' + - '96bdce6eed1cabed8b6f116f7ad9cf505df0f998e34ab27514b0ffe7', + var g = new BN( + '5c7ff6b06f8f143fe8288433493e4769c4d988ace5be25a0e24809670' + + '716c613d7b0cee6932f8faa7c44d2cb24523da53fbe4f6ec3595892d1' + + 'aa58c4328a06c46a15662e7eaa703a1decf8bbb2d05dbe2eb956c142a' + + '338661d10461c0d135472085057f3494309ffa73c611f78b32adbb574' + + '0c361c9f35be90997db2014e2ef5aa61782f52abeb8bd6432c4dd097b' + + 'c5423b285dafb60dc364e8161f4a2a35aca3a10b1c4d203cc76a470a3' + + '3afdcbdd92959859abd8b56e1725252d78eac66e71ba9ae3f1dd24871' + + '99874393cd4d832186800654760e1e34c09e4d155179f9ec0dc4473f9' + + '96bdce6eed1cabed8b6f116f7ad9cf505df0f998e34ab27514b0ffe7', 16); - var p = new BN('9db6fb5951b66bb6fe1e140f1d2ce5502374161fd6538df1648218642' + - 'f0b5c48c8f7a41aadfa187324b87674fa1822b00f1ecf8136943d7c55' + - '757264e5a1a44ffe012e9936e00c1d3e9310b01c7d179805d3058b2a9' + - 'f4bb6f9716bfe6117c6b5b3cc4d9be341104ad4a80ad6c94e005f4b99' + - '3e14f091eb51743bf33050c38de235567e1b34c3d6a5c0ceaa1a0f368' + - '213c3d19843d0b4b09dcb9fc72d39c8de41f1bf14d4bb4563ca283716' + - '21cad3324b6a2d392145bebfac748805236f5ca2fe92b871cd8f9c36d' + - '3292b5509ca8caa77a2adfc7bfd77dda6f71125a7456fea153e433256' + - 'a2261c6a06ed3693797e7995fad5aabbcfbe3eda2741e375404ae25b', + var p = new BN( + '9db6fb5951b66bb6fe1e140f1d2ce5502374161fd6538df1648218642' + + 'f0b5c48c8f7a41aadfa187324b87674fa1822b00f1ecf8136943d7c55' + + '757264e5a1a44ffe012e9936e00c1d3e9310b01c7d179805d3058b2a9' + + 'f4bb6f9716bfe6117c6b5b3cc4d9be341104ad4a80ad6c94e005f4b99' + + '3e14f091eb51743bf33050c38de235567e1b34c3d6a5c0ceaa1a0f368' + + '213c3d19843d0b4b09dcb9fc72d39c8de41f1bf14d4bb4563ca283716' + + '21cad3324b6a2d392145bebfac748805236f5ca2fe92b871cd8f9c36d' + + '3292b5509ca8caa77a2adfc7bfd77dda6f71125a7456fea153e433256' + + 'a2261c6a06ed3693797e7995fad5aabbcfbe3eda2741e375404ae25b', 16); var q = new BN('f2c3119374ce76c9356990b465374a17f23f9ed35089bd969f61c6dde' + '9998c1f', 16); @@ -260,4 +267,13 @@ describe('BN.js/Reduction context', function () { red.prime.split(input, output); assert.equal(input.cmp(output), 0); }); + + it('imod should change host object', function () { + var red = BN.red(new BN(13)); + var a = new BN(2).toRed(red); + var b = new BN(7).toRed(red); + var c = a.redIMul(b); + assert.equal(a.toNumber(), 1); + assert.equal(c.toNumber(), 1); + }); }); diff --git a/test/utils-test.js b/test/utils-test.js index 8571905a..6ff1f939 100644 --- a/test/utils-test.js +++ b/test/utils-test.js @@ -1,10 +1,20 @@ /* global describe, it */ -var assert = require('assert'); +var assert = require('assert').strict; var BN = require('../').BN; describe('BN.js/Utils', function () { describe('.toString()', function () { + describe('hex no padding', function () { + it('should have same length as input', function () { + var hex = '1'; + for (var i = 1; i <= 128; i++) { + var n = new BN(hex, 16); + assert.equal(n.toString(16).length, i); + hex = hex + '0'; + } + }); + }); describe('binary padding', function () { it('should have a length of 256', function () { var a = new BN(0); @@ -123,14 +133,14 @@ describe('BN.js/Utils', function () { describe('.toArray()', function () { it('should return [ 0 ] for `0`', function () { var n = new BN(0); - assert.deepEqual(n.toArray('be'), [ 0 ]); - assert.deepEqual(n.toArray('le'), [ 0 ]); + assert.deepEqual(n.toArray('be'), [0]); + assert.deepEqual(n.toArray('le'), [0]); }); it('should zero pad to desired lengths', function () { var n = new BN(0x123456); - assert.deepEqual(n.toArray('be', 5), [ 0x00, 0x00, 0x12, 0x34, 0x56 ]); - assert.deepEqual(n.toArray('le', 5), [ 0x56, 0x34, 0x12, 0x00, 0x00 ]); + assert.deepEqual(n.toArray('be', 5), [0x00, 0x00, 0x12, 0x34, 0x56]); + assert.deepEqual(n.toArray('le', 5), [0x56, 0x34, 0x12, 0x00, 0x00]); }); it('should throw when naturally larger than desired length', function () { @@ -144,8 +154,16 @@ describe('BN.js/Utils', function () { describe('.toBuffer', function () { it('should return proper Buffer', function () { var n = new BN(0x123456); - assert.deepEqual(n.toBuffer('be', 5).toString('hex'), '0000123456'); + assert.equal(n.toBuffer('be', 5).toString('hex'), '0000123456'); assert.deepEqual(n.toBuffer('le', 5).toString('hex'), '5634120000'); + + var s = '211e1566be78319bb949470577c2d4ac7e800a90d5104379478d8039451a8efe'; + for (var i = 1; i <= s.length; i++) { + var slice = (i % 2 === 0 ? '' : '0') + s.slice(0, i); + var bn = new BN(slice, 16); + assert.equal(bn.toBuffer('be').toString('hex'), slice); + assert.equal(bn.toBuffer('le').toString('hex'), Buffer.from(slice, 'hex').reverse().toString('hex')); + } }); }); @@ -188,7 +206,11 @@ describe('BN.js/Utils', function () { describe('.toJSON', function () { it('should return hex string', function () { - assert.equal(new BN(0x123).toJSON(), '123'); + assert.equal(new BN(0x123).toJSON(), '0123'); + }); + + it('should be padded to multiple of 2 bytes for interop', function () { + assert.equal(new BN(0x1).toJSON(), '01'); }); }); @@ -295,14 +317,16 @@ describe('BN.js/Utils', function () { 'fffffffffffffffffffffffffffffffe', 16).fromTwos(256).toNumber(), -2); assert.equal(new BN('ffffffffffffffffffffffffffffffff' + 'ffffffffffffffffffffffffffffffff', 16).fromTwos(256).toNumber(), -1); - assert.equal(new BN('7fffffffffffffffffffffffffffffff' + + assert.equal( + new BN('7fffffffffffffffffffffffffffffff' + 'ffffffffffffffffffffffffffffffff', 16).fromTwos(256).toString(10), new BN('5789604461865809771178549250434395392663499' + - '2332820282019728792003956564819967', 10).toString(10)); - assert.equal(new BN('80000000000000000000000000000000' + + '2332820282019728792003956564819967', 10).toString(10)); + assert.equal( + new BN('80000000000000000000000000000000' + '00000000000000000000000000000000', 16).fromTwos(256).toString(10), new BN('-578960446186580977117854925043439539266349' + - '92332820282019728792003956564819968', 10).toString(10)); + '92332820282019728792003956564819968', 10).toString(10)); }); }); @@ -322,10 +346,12 @@ describe('BN.js/Utils', function () { 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe'); assert.equal(new BN('-1', 10).toTwos(256).toString(16), 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); - assert.equal(new BN('5789604461865809771178549250434395392663' + + assert.equal( + new BN('5789604461865809771178549250434395392663' + '4992332820282019728792003956564819967', 10).toTwos(256).toString(16), '7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'); - assert.equal(new BN('-578960446186580977117854925043439539266' + + assert.equal( + new BN('-578960446186580977117854925043439539266' + '34992332820282019728792003956564819968', 10).toTwos(256).toString(16), '8000000000000000000000000000000000000000000000000000000000000000'); }); diff --git a/util/genCombMulTo.js b/util/genCombMulTo.js index 8b456c7c..859857f9 100644 --- a/util/genCombMulTo.js +++ b/util/genCombMulTo.js @@ -30,7 +30,7 @@ function genCombMulTo (alen, blen) { var minJ = Math.max(0, k - alen + 1); var maxJ = Math.min(k, blen - 1); - src.push('\/* k = ' + k + ' *\/'); + src.push('/* k = ' + k + ' */'); src.push('var w' + k + ' = c;'); src.push('c = 0;'); for (var j = minJ; j <= maxJ; j++) { @@ -53,11 +53,11 @@ function genCombMulTo (alen, blen) { for (k = 0; k < len; k++) { src.push('o[' + k + '] = w' + k + ';'); } - src.push('if (c !== 0) {', - ' o[' + k + '] = c;', - ' out.length++;', - '}', - 'return out;'); + src.push('if (c !== 0) {'); + src.push(' o[' + k + '] = c;'); + src.push(' out.length++;'); + src.push('}'); + src.push('return out;'); return src.join('\n'); } diff --git a/util/genCombMulTo10.js b/util/genCombMulTo10.js index cf2e6e80..7baf82cb 100644 --- a/util/genCombMulTo10.js +++ b/util/genCombMulTo10.js @@ -29,11 +29,10 @@ function genCombMulTo (alen, blen) { var minJ = Math.max(0, k - alen + 1); var maxJ = Math.min(k, blen - 1); - src.push('\/* k = ' + k + ' *\/'); + src.push('/* k = ' + k + ' */'); src.push('lo = Math.imul(al' + (k - minJ) + ', bl' + minJ + ');'); src.push('mid = Math.imul(al' + (k - minJ) + ', bh' + minJ + ');'); - src.push( - 'mid = (mid + Math.imul(ah' + (k - minJ) + ', bl' + minJ + ')) | 0;'); + src.push('mid = (mid + Math.imul(ah' + (k - minJ) + ', bl' + minJ + ')) | 0;'); src.push('hi = Math.imul(ah' + (k - minJ) + ', bh' + minJ + ');'); for (var j = minJ + 1; j <= maxJ; j++) { @@ -53,11 +52,11 @@ function genCombMulTo (alen, blen) { for (k = 0; k < len; k++) { src.push('o[' + k + '] = w' + k + ';'); } - src.push('if (c !== 0) {', - ' o[' + k + '] = c;', - ' out.length++;', - '}', - 'return out;'); + src.push('if (c !== 0) {'); + src.push(' o[' + k + '] = c;'); + src.push(' out.length++;'); + src.push('}'); + src.push('return out;'); return src.join('\n'); }