From 788bcd219f48acc8eea43cd7baeff5786992d445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Hern=C3=A1ndez=20Serrano?= Date: Fri, 19 Apr 2019 12:04:06 +0200 Subject: [PATCH] Working! --- README.hbs | 47 ++- README.md | 169 ++++----- build/build.browser.tests.js | 2 +- build/build.rollup.js | 81 ++--- dist/bigint-utils-latest.browser.js | 50 ++- dist/bigint-utils-latest.browser.min.js | 16 +- dist/bigint-utils-latest.browser.mod.js | 50 ++- dist/bigint-utils-latest.browser.mod.min.js | 16 +- dist/bigint-utils-latest.node.js | 8 +- dist/workerPrimalityTest.js | 14 - package.json | 13 +- src/browser-test-template.html | 2 +- src/main.js | 378 +++++++++++--------- src/workerPrimalityTest.js | 14 - test/01_PrimeValidation.js | 4 +- test/02_PrimeGeneration.js | 11 +- test/browser/index.html | 6 +- test/browser/tests.js | 11 +- 18 files changed, 505 insertions(+), 387 deletions(-) delete mode 100644 dist/workerPrimalityTest.js delete mode 100644 src/workerPrimalityTest.js diff --git a/README.hbs b/README.hbs index e7de3ab..a3ab912 100644 --- a/README.hbs +++ b/README.hbs @@ -1,30 +1,27 @@ -# bigint-utils +# bigint-crypto-utils -Some extra functions to work with modular arithmetics along with secure random numbers and probable prime (Miller-Rabin primality test) generation/testing using native JS (stage 3) implementation of BigInt. It can be used with Node.js (>=10.4.0) and [Web Browsers supporting BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility). +Utils for working with cryptography using native JS (stage 3) implementation of BigInt. It includes some extra functions to work with modular arithmetics along with secure random numbers and a very fast strong probable prime generation/testing (parallelised multi-threaded Miller-Rabin primality test). It can be used with Node.js (>=10.4.0) and [Web Browsers supporting BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility). _The operations supported on BigInts are not constant time. BigInt can be therefore **[unsuitable for use in cryptography](https://www.chosenplaintext.ca/articles/beginners-guide-constant-time-cryptography.html)**_ Many platforms provide native support for cryptography, such as [webcrypto](https://w3c.github.io/webcrypto/Overview.html) or [node crypto](https://nodejs.org/dist/latest/docs/api/crypto.html). ## Installation -bigint-utils is distributed as both an ES6 and a CJS module. +bigint-crypto-utils is distributed for [web browsers supporting BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility) as an ES6 module or a IIFE file, and for Node.js (>=10.4.0) as a CJS module. -The ES6 module is built for any [web browser supporting BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility). The module only uses native javascript implementations and no polyfills had been applied. - -The CJS module is built as a standard node module. - -bigint-utils can be imported to your project with `npm`: +bigint-crypto-utils can be imported to your project with `npm`: ```bash -npm install bigint-utils +npm install bigint-crypto-utils ``` +NPM installation defaults to the ES6 module for browsers and the CJS for Node.js. -For web browsers, you can also [download the bundle from GitHub](https://raw.githubusercontent.com/juanelas/bigint-utils/master/dist/bigint-utils-latest.browser.mod.min.js). +For web browsers, you can also directly download the [IIFE file](https://raw.githubusercontent.com/juanelas/bigint-crypto-utils/master/dist/bigint-crypto-utils-latest.browser.min.js) or the [ES6 module](https://raw.githubusercontent.com/juanelas/bigint-crypto-utils/master/dist/bigint-crypto-utils-latest.browser.mod.min.js) from GitHub. ## Usage example With node js: ```javascript -const bigintUtils = require('bigint-utils'); +const bigintCryptoUtils = require('bigint-crypto-utils'); // Stage 3 BigInts with value 666 can be declared as BigInt('666') // or the shorter new no-so-linter-friendly syntax 666n @@ -32,55 +29,55 @@ let a = BigInt('5'); let b = BigInt('2'); let n = BigInt('19'); -console.log(bigintModArith.modPow(a, b, n)); // prints 6 +console.log(bigintCryptoUtils.modPow(a, b, n)); // prints 6 -console.log(bigintModArith.modInv(BigInt('2'), BigInt('5'))); // prints 3 +console.log(bigintCryptoUtils.modInv(BigInt('2'), BigInt('5'))); // prints 3 -console.log(bigintModArith.modInv(BigInt('3'), BigInt('5'))); // prints 2 +console.log(bigintCryptoUtils.modInv(BigInt('3'), BigInt('5'))); // prints 2 // Generation of a probable prime of 2048 bits -const prime = await bigintUtils.prime(2048); +const prime = await bigintCryptoUtils.prime(2048); // Testing if a prime is a probable prime (Miller-Rabin) -if ( await bigintUtils.isProbablyPrime(prime) ) +if ( await bigintCryptoUtils.isProbablyPrime(prime) ) // code if is prime // Get a cryptographically secure random number between 1 and 2**256 bits. -const rnd = bigintUtils.randBetween(BigInt(2)**256); +const rnd = bigintCryptoUtils.randBetween(BigInt(2)**256); ``` From a browser, you can just load the module in a html page as: ```html ``` -# bigint-utils JS Doc +# bigint-crypto-utils JS Doc {{>main}} diff --git a/README.md b/README.md index c021819..9b0f402 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,27 @@ -# bigint-utils +# bigint-crypto-utils -Some extra functions to work with modular arithmetics along with secure random numbers and probable prime (Miller-Rabin primality test) generation/testing using native JS (stage 3) implementation of BigInt. It can be used with Node.js (>=10.4.0) and [Web Browsers supporting BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility). +Utils for working with cryptography using native JS (stage 3) implementation of BigInt. It includes some extra functions to work with modular arithmetics along with secure random numbers and a very fast strong probable prime generation/testing (parallelised multi-threaded Miller-Rabin primality test). It can be used with Node.js (>=10.4.0) and [Web Browsers supporting BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility). _The operations supported on BigInts are not constant time. BigInt can be therefore **[unsuitable for use in cryptography](https://www.chosenplaintext.ca/articles/beginners-guide-constant-time-cryptography.html)**_ Many platforms provide native support for cryptography, such as [webcrypto](https://w3c.github.io/webcrypto/Overview.html) or [node crypto](https://nodejs.org/dist/latest/docs/api/crypto.html). ## Installation -bigint-utils is distributed as both an ES6 and a CJS module. +bigint-crypto-utils is distributed for [web browsers supporting BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility) as an ES6 module or a IIFE file, and for Node.js (>=10.4.0) as a CJS module. -The ES6 module is built for any [web browser supporting BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility). The module only uses native javascript implementations and no polyfills had been applied. - -The CJS module is built as a standard node module. - -bigint-utils can be imported to your project with `npm`: +bigint-crypto-utils can be imported to your project with `npm`: ```bash -npm install bigint-utils +npm install bigint-crypto-utils ``` +NPM installation defaults to the ES6 module for browsers and the CJS for Node.js. -For web browsers, you can also [download the bundle from GitHub](https://raw.githubusercontent.com/juanelas/bigint-utils/master/dist/bigint-utils-latest.browser.mod.min.js). +For web browsers, you can also directly download the [IIFE file](https://raw.githubusercontent.com/juanelas/bigint-crypto-utils/master/dist/bigint-crypto-utils-latest.browser.min.js) or the [ES6 module](https://raw.githubusercontent.com/juanelas/bigint-crypto-utils/master/dist/bigint-crypto-utils-latest.browser.mod.min.js) from GitHub. ## Usage example With node js: ```javascript -const bigintUtils = require('bigint-utils'); +const bigintCryptoUtils = require('bigint-crypto-utils'); // Stage 3 BigInts with value 666 can be declared as BigInt('666') // or the shorter new no-so-linter-friendly syntax 666n @@ -32,55 +29,55 @@ let a = BigInt('5'); let b = BigInt('2'); let n = BigInt('19'); -console.log(bigintModArith.modPow(a, b, n)); // prints 6 +console.log(bigintCryptoUtils.modPow(a, b, n)); // prints 6 -console.log(bigintModArith.modInv(BigInt('2'), BigInt('5'))); // prints 3 +console.log(bigintCryptoUtils.modInv(BigInt('2'), BigInt('5'))); // prints 3 -console.log(bigintModArith.modInv(BigInt('3'), BigInt('5'))); // prints 2 +console.log(bigintCryptoUtils.modInv(BigInt('3'), BigInt('5'))); // prints 2 // Generation of a probable prime of 2048 bits -const prime = await bigintUtils.prime(2048); +const prime = await bigintCryptoUtils.prime(2048); // Testing if a prime is a probable prime (Miller-Rabin) -if ( await bigintUtils.isProbablyPrime(prime) ) +if ( await bigintCryptoUtils.isProbablyPrime(prime) ) // code if is prime // Get a cryptographically secure random number between 1 and 2**256 bits. -const rnd = bigintUtils.randBetween(BigInt(2)**256); +const rnd = bigintCryptoUtils.randBetween(BigInt(2)**256); ``` From a browser, you can just load the module in a html page as: ```html ``` -# bigint-utils JS Doc +# bigint-crypto-utils JS Doc ## Constants @@ -88,36 +85,38 @@ From a browser, you can just load the module in a html page as:
absbigint

Absolute value. abs(a)==a if a>=0. abs(a)==-a if a<0

-
gcdbigint
-

Greatest-common divisor of two integers based on the iterative binary algorithm.

-
-
lcmbigint
-

The least common multiple computed as abs(a*b)/gcd(a,b)

-
-
toZnbigint
-

Finds the smallest positive element that is congruent to a in modulo n

-
eGcdegcdReturn

An iterative implementation of the extended euclidean algorithm or extended greatest common divisor algorithm. Take positive integers a, b as input, and return a triple (g, x, y), such that ax + by = g = gcd(a, b).

+
gcdbigint
+

Greatest-common divisor of two integers based on the iterative binary algorithm.

+
+
isProbablyPrimePromise
+

The test first tries if any of the first 250 small primes are a factor of the input number and then passes several iterations of Miller-Rabin Probabilistic Primality Test (FIPS 186-4 C.3.1)

+
+
lcmbigint
+

The least common multiple computed as abs(a*b)/gcd(a,b)

+
modInvbigint

Modular inverse.

modPowbigint

Modular exponentiation a**b mod n

-
randBytesPromise
-

Secure random bytes for both node and browsers. Browser implementation uses WebWorkers in order to not lock the main process

+
primePromise
+

A probably-prime (Miller-Rabin), cryptographically-secure, random-number generator. +The browser version uses web workers to parallelise prime look up. Therefore, it does not lock the UI +main process, and it can be much faster (if several cores or cpu are available).

randBetweenPromise

Returns a cryptographically secure random integer between [min,max]

-
isProbablyPrimePromise
-

The test first tries if any of the first 250 small primes are a factor of the input number and then passes several iterations of Miller-Rabin Probabilistic Primality Test (FIPS 186-4 C.3.1)

+
randBytesPromise
+

Secure random bytes for both node and browsers. Node version uses crypto.randomFill() and browser one self.crypto.getRandomValues()

-
primePromise
-

A probably-prime (Miller-Rabin), cryptographically-secure, random-number generator

+
toZnbigint
+

Finds the smallest positive element that is congruent to a in modulo n

@@ -141,6 +140,19 @@ Absolute value. abs(a)==a if a>=0. abs(a)==-a if a<0 | --- | --- | | a | number \| bigint | + + +## eGcd ⇒ [egcdReturn](#egcdReturn) +An iterative implementation of the extended euclidean algorithm or extended greatest common divisor algorithm. +Take positive integers a, b as input, and return a triple (g, x, y), such that ax + by = g = gcd(a, b). + +**Kind**: global constant + +| Param | Type | +| --- | --- | +| a | number \| bigint | +| b | number \| bigint | + ## gcd ⇒ bigint @@ -154,6 +166,19 @@ Greatest-common divisor of two integers based on the iterative binary algorithm. | a | number \| bigint | | b | number \| bigint | + + +## isProbablyPrime ⇒ Promise +The test first tries if any of the first 250 small primes are a factor of the input number and then passes several iterations of Miller-Rabin Probabilistic Primality Test (FIPS 186-4 C.3.1) + +**Kind**: global constant +**Returns**: Promise - A promise that resolve to a boolean that is either true (a probably prime number) or false (definitely composite) + +| Param | Type | Description | +| --- | --- | --- | +| w | bigint | An integer to be tested for primality | +| iterations | number | The number of iterations for the primality test. The value shall be consistent with Table C.1, C.2 or C.3 | + ## lcm ⇒ bigint @@ -167,32 +192,6 @@ The least common multiple computed as abs(a*b)/gcd(a,b) | a | number \| bigint | | b | number \| bigint | - - -## toZn ⇒ bigint -Finds the smallest positive element that is congruent to a in modulo n - -**Kind**: global constant -**Returns**: bigint - The smallest positive representation of a in modulo n - -| Param | Type | Description | -| --- | --- | --- | -| a | number \| bigint | An integer | -| n | number \| bigint | The modulo | - - - -## eGcd ⇒ [egcdReturn](#egcdReturn) -An iterative implementation of the extended euclidean algorithm or extended greatest common divisor algorithm. -Take positive integers a, b as input, and return a triple (g, x, y), such that ax + by = g = gcd(a, b). - -**Kind**: global constant - -| Param | Type | -| --- | --- | -| a | number \| bigint | -| b | number \| bigint | - ## modInv ⇒ bigint @@ -220,18 +219,20 @@ Modular exponentiation a**b mod n | b | number \| bigint | exponent | | n | number \| bigint | modulo | - + -## randBytes ⇒ Promise -Secure random bytes for both node and browsers. Browser implementation uses WebWorkers in order to not lock the main process +## prime ⇒ Promise +A probably-prime (Miller-Rabin), cryptographically-secure, random-number generator. +The browser version uses web workers to parallelise prime look up. Therefore, it does not lock the UI +main process, and it can be much faster (if several cores or cpu are available). **Kind**: global constant -**Returns**: Promise - A promise that resolves to a Buffer/UInt8Array filled with cryptographically secure random bytes +**Returns**: Promise - A promise that resolves to a bigint probable prime of bitLength bits | Param | Type | Description | | --- | --- | --- | -| byteLength | number | The desired number of random bytes | -| forceLength | boolean | If we want to force the output to have a bit length of 8*byteLength. It basically forces the msb to be 1 | +| bitLength | number | The required bit length for the generated prime | +| iterations | number | The number of iterations for the Miller-Rabin Probabilistic Primality Test | @@ -246,31 +247,31 @@ Returns a cryptographically secure random integer between [min,max] | max | bigint | Returned value will be <= max | | min | bigint | Returned value will be >= min | - + -## isProbablyPrime ⇒ Promise -The test first tries if any of the first 250 small primes are a factor of the input number and then passes several iterations of Miller-Rabin Probabilistic Primality Test (FIPS 186-4 C.3.1) +## randBytes ⇒ Promise +Secure random bytes for both node and browsers. Node version uses crypto.randomFill() and browser one self.crypto.getRandomValues() **Kind**: global constant -**Returns**: Promise - A promise that resolve to a boolean that is either true (a probably prime number) or false (definitely composite) +**Returns**: Promise - A promise that resolves to a Buffer/UInt8Array filled with cryptographically secure random bytes | Param | Type | Description | | --- | --- | --- | -| w | bigint | An integer to be tested for primality | -| iterations | number | The number of iterations for the primality test. The value shall be consistent with Table C.1, C.2 or C.3 | +| byteLength | number | The desired number of random bytes | +| forceLength | boolean | If we want to force the output to have a bit length of 8*byteLength. It basically forces the msb to be 1 | - + -## prime ⇒ Promise -A probably-prime (Miller-Rabin), cryptographically-secure, random-number generator +## toZn ⇒ bigint +Finds the smallest positive element that is congruent to a in modulo n **Kind**: global constant -**Returns**: Promise - A promise that resolves to a bigint probable prime of bitLength bits +**Returns**: bigint - The smallest positive representation of a in modulo n | Param | Type | Description | | --- | --- | --- | -| bitLength | number | The required bit length for the generated prime | -| iterations | number | The number of iterations for the Miller-Rabin Probabilistic Primality Test | +| a | number \| bigint | An integer | +| n | number \| bigint | The modulo | diff --git a/build/build.browser.tests.js b/build/build.browser.tests.js index 4fac1e9..7c8309e 100644 --- a/build/build.browser.tests.js +++ b/build/build.browser.tests.js @@ -27,7 +27,7 @@ const testsJs = ` mocha.run(); `; -fs.writeFileSync(dstFileName, template.replace('{{TESTS}}', testsJs)); +fs.writeFileSync(dstFileName, template.replace('{{TESTS}}', testsJs).replace('{{PKG_NAME}}', pkgJson.name)); /* Now we create a bundle of all the tests called test.js diff --git a/build/build.rollup.js b/build/build.rollup.js index ecb5921..b1c74b2 100644 --- a/build/build.rollup.js +++ b/build/build.rollup.js @@ -27,24 +27,24 @@ const buildOptions = [ name: camelise(pkgJson.name) } }, - // { // Browser minified - // input: { - // input: path.join(srcDir, 'main.js'), - // plugins: [ - // replace({ - // 'process.browser': true - // }), - // minify({ - // 'comments': false - // }) - // ], - // }, - // output: { - // file: path.join(dstDir, `${pkgJson.name}-${pkgJson.version}.browser.min.js`), - // format: 'iife', - // name: camelise(pkgJson.name) - // } - // }, + { // Browser minified + input: { + input: path.join(srcDir, 'main.js'), + plugins: [ + replace({ + 'process.browser': true + }), + minify({ + 'comments': false + }) + ], + }, + output: { + file: path.join(dstDir, `${pkgJson.name}-${pkgJson.version}.browser.min.js`), + format: 'iife', + name: camelise(pkgJson.name) + } + }, { // Browser esm input: { input: path.join(srcDir, 'main.js'), @@ -59,23 +59,23 @@ const buildOptions = [ format: 'esm' } }, - // { // Browser esm minified - // input: { - // input: path.join(srcDir, 'main.js'), - // plugins: [ - // replace({ - // 'process.browser': true - // }), - // minify({ - // 'comments': false - // }) - // ], - // }, - // output: { - // file: path.join(dstDir, `${pkgJson.name}-${pkgJson.version}.browser.mod.min.js`), - // format: 'esm' - // } - // }, + { // Browser esm minified + input: { + input: path.join(srcDir, 'main.js'), + plugins: [ + replace({ + 'process.browser': true + }), + minify({ + 'comments': false + }) + ], + }, + output: { + file: path.join(dstDir, `${pkgJson.name}-${pkgJson.version}.browser.mod.min.js`), + format: 'esm' + } + }, { // Node input: { input: path.join(srcDir, 'main.js'), @@ -97,17 +97,6 @@ for (const options of buildOptions) { } - -// Let's manually build the worker file -const workerFilename = path.join(srcDir, 'workerPrimalityTest.js'); -const dstFileName = path.join(dstDir, 'workerPrimalityTest.js'); - -const workerFile = fs.readFileSync(workerFilename, 'utf-8'); - -fs.writeFileSync(dstFileName, workerFile.replace('{{IIFE}}', `./${pkgJson.name}-latest.browser.js`)); - - - /* --- HELPLER FUNCTIONS --- */ async function build(options) { diff --git a/dist/bigint-utils-latest.browser.js b/dist/bigint-utils-latest.browser.js index 1413386..eb16d95 100644 --- a/dist/bigint-utils-latest.browser.js +++ b/dist/bigint-utils-latest.browser.js @@ -222,6 +222,51 @@ var bigintUtils = (function (exports) { * @return {Promise} A promise that resolve to a boolean that is either true (a probably prime number) or false (definitely composite) */ const isProbablyPrime = async function (w, iterations = 16) { + { + return new Promise(resolve => { + let worker = _isProbablyPrimeWorker(); + + worker.onmessage = (event) => { + resolve(event.data.isPrime); + }; + worker.postMessage({ + 'rnd': w, + 'iterations': iterations + }); + }); + } + }; + function _isProbablyPrimeWorker() { + async function _onmessage(event) { // Let's start once we are called + // event.data = {rnd: , iterations: } + const isPrime = await isProbablyPrime(event.data.rnd, event.data.iterations, false); + postMessage({ + 'isPrime': isPrime, + 'value': event.data.rnd + }); + } + + let workerCode = `(() => { + 'use strict'; + + const eGcd = ${eGcd.toString()}; + const modInv = ${modInv.toString()}; + const modPow = ${modPow.toString()}; + const toZn = ${toZn.toString()}; + const randBytes = ${randBytes.toString()}; + const randBetween = ${randBetween.toString()}; + const isProbablyPrime = ${_isProbablyPrime.toString()}; + ${bitLength.toString()} + ${fromBuffer.toString()} + + onmessage = ${_onmessage.toString()}; + })()`; + var _blob = new Blob([workerCode], { type: 'text/javascript' }); + + return new Worker(window.URL.createObjectURL(_blob)); + } + + async function _isProbablyPrime(w, iterations = 16) { /* PREFILTERING. Even values but 2 are not primes, so don't test. 1 is not a prime and the M-R algorithm needs w>1. @@ -538,7 +583,7 @@ var bigintUtils = (function (exports) { } while (--iterations); return true; - }; + } /** * A probably-prime (Miller-Rabin), cryptographically-secure, random-number generator @@ -553,8 +598,7 @@ var bigintUtils = (function (exports) { { let workerList = []; for (let i = 0; i < self.navigator.hardwareConcurrency; i++) { - const moduleDir = new URL('./', (document.currentScript && document.currentScript.src || new URL('bigint-utils-0.9.0.browser.js', document.baseURI).href)).pathname; - let newWorker = new Worker(`${moduleDir}/workerPrimalityTest.js`); + let newWorker = _isProbablyPrimeWorker(); newWorker.onmessage = async (event) => { if (event.data.isPrime) { // if a prime number has been found, stop all the workers, and return it diff --git a/dist/bigint-utils-latest.browser.min.js b/dist/bigint-utils-latest.browser.min.js index b581ef7..2e89ca3 100644 --- a/dist/bigint-utils-latest.browser.min.js +++ b/dist/bigint-utils-latest.browser.min.js @@ -1 +1,15 @@ -var bigintUtils=function(a){'use strict';function b(a){let b=BigInt(0);for(let c of a.values()){let a=BigInt(c);b=(b<>=BigInt(1))>BigInt(1));return c}const d=function(b){return b=BigInt(b),b>=BigInt(0)?b:-b},e=function(c,e){c=d(c),e=d(e);let f=BigInt(0);for(;!((c|e)&BigInt(1));)c>>=BigInt(1),e>>=BigInt(1),f++;for(;!(c&BigInt(1));)c>>=BigInt(1);do{for(;!(e&BigInt(1));)e>>=BigInt(1);if(c>e){let a=c;c=e,e=a}e-=c}while(e);return c<b?b+c:b},g=function(c,d){c=BigInt(c),d=BigInt(d);let e=BigInt(0),f=BigInt(1),g=BigInt(1),h=BigInt(0);for(;c!==BigInt(0);){let a=d/c,b=d%c,i=e-g*a,j=f-h*a;d=c,c=b,e=g,f=h,g=i,h=j}return{b:d,x:e,y:f}},h=function(b,a){let c=g(b,a);return c.b===BigInt(1)?f(c.x,a):null},i=function(c,e,g){if(g=BigInt(g),c=f(c,g),e=BigInt(e),e{let d;const e=(a,d)=>{b&&(d[0]|=128),c(d)};d=new Uint8Array(a),l(d,e)})},k=async function(a,d=1){let e,f=c(a),g=f>>3,h=f-8*g;0a||i{onmessage=function(a){const b=self.crypto.getRandomValues(a.data.buf);self.postMessage({buf:b,id:a.data.id})}});return a}();return a.abs=d,a.eGcd=g,a.gcd=e,a.isProbablyPrime=async function(c,b=16){if(c===BigInt(2))return!0;if((c&BigInt(1))===BigInt(0)||c===BigInt(1))return!1;const e=[3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063,1069,1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,1153,1163,1171,1181,1187,1193,1201,1213,1217,1223,1229,1231,1237,1249,1259,1277,1279,1283,1289,1291,1297,1301,1303,1307,1319,1321,1327,1361,1367,1373,1381,1399,1409,1423,1427,1429,1433,1439,1447,1451,1453,1459,1471,1481,1483,1487,1489,1493,1499,1511,1523,1531,1543,1549,1553,1559,1567,1571,1579,1583,1597];for(let a=0;a{if(e.data.isPrime){for(let a=0;a { + 'use strict'; + + const eGcd = ${i.toString()}; + const modInv = ${j.toString()}; + const modPow = ${k.toString()}; + const toZn = ${h.toString()}; + const randBytes = ${l.toString()}; + const randBetween = ${n.toString()}; + const isProbablyPrime = ${c.toString()}; + ${e.toString()} + ${d.toString()} + + onmessage = ${async function(a){const b=await o(a.data.rnd,a.data.iterations,!1);postMessage({isPrime:b,value:a.data.rnd})}.toString()}; + })()`;var b=new Blob([a],{type:"text/javascript"});return new Worker(window.URL.createObjectURL(b))}async function c(c,b=16){if(c===BigInt(2))return!0;if((c&BigInt(1))===BigInt(0)||c===BigInt(1))return!1;const e=[3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063,1069,1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,1153,1163,1171,1181,1187,1193,1201,1213,1217,1223,1229,1231,1237,1249,1259,1277,1279,1283,1289,1291,1297,1301,1303,1307,1319,1321,1327,1361,1367,1373,1381,1399,1409,1423,1427,1429,1433,1439,1447,1451,1453,1459,1471,1481,1483,1487,1489,1493,1499,1511,1523,1531,1543,1549,1553,1559,1567,1571,1579,1583,1597];for(let a=0;a>=BigInt(1))>BigInt(1));return c}const f=function(b){return b=BigInt(b),b>=BigInt(0)?b:-b},g=function(c,d){c=f(c),d=f(d);let e=BigInt(0);for(;!((c|d)&BigInt(1));)c>>=BigInt(1),d>>=BigInt(1),e++;for(;!(c&BigInt(1));)c>>=BigInt(1);do{for(;!(d&BigInt(1));)d>>=BigInt(1);if(c>d){let a=c;c=d,d=a}d-=c}while(d);return c<b?b+c:b},i=function(c,d){c=BigInt(c),d=BigInt(d);let e=BigInt(0),f=BigInt(1),g=BigInt(1),h=BigInt(0);for(;c!==BigInt(0);){let a=d/c,b=d%c,i=e-g*a,j=f-h*a;d=c,c=b,e=g,f=h,g=i,h=j}return{b:d,x:e,y:f}},j=function(b,a){let c=i(b,a);return c.b===BigInt(1)?h(c.x,a):null},k=function(c,d,e){if(e=BigInt(e),c=h(c,e),d=BigInt(d),d{let d;d=new Uint8Array(a),self.crypto.getRandomValues(d),b&&(d[0]|=128),c(d)})},n=async function(a,b=1){let c,f=e(a),g=f>>3,h=f-8*g;0a||i{let e=b();e.onmessage=a=>{d(a.data.isPrime)},e.postMessage({rnd:a,iterations:c})})};return a.abs=f,a.eGcd=i,a.gcd=g,a.isProbablyPrime=o,a.lcm=function(c,d){return c=BigInt(c),d=BigInt(d),f(c*d)/g(c,d)},a.modInv=j,a.modPow=k,a.prime=async function(a,c=16){return new Promise(async e=>{{let f=[];for(let g,h=0;h{if(b.data.isPrime){for(let a=0;a { + let worker = _isProbablyPrimeWorker(); + + worker.onmessage = (event) => { + resolve(event.data.isPrime); + }; + worker.postMessage({ + 'rnd': w, + 'iterations': iterations + }); + }); + } +}; +function _isProbablyPrimeWorker() { + async function _onmessage(event) { // Let's start once we are called + // event.data = {rnd: , iterations: } + const isPrime = await isProbablyPrime(event.data.rnd, event.data.iterations, false); + postMessage({ + 'isPrime': isPrime, + 'value': event.data.rnd + }); + } + + let workerCode = `(() => { + 'use strict'; + + const eGcd = ${eGcd.toString()}; + const modInv = ${modInv.toString()}; + const modPow = ${modPow.toString()}; + const toZn = ${toZn.toString()}; + const randBytes = ${randBytes.toString()}; + const randBetween = ${randBetween.toString()}; + const isProbablyPrime = ${_isProbablyPrime.toString()}; + ${bitLength.toString()} + ${fromBuffer.toString()} + + onmessage = ${_onmessage.toString()}; + })()`; + var _blob = new Blob([workerCode], { type: 'text/javascript' }); + + return new Worker(window.URL.createObjectURL(_blob)); +} + +async function _isProbablyPrime(w, iterations = 16) { /* PREFILTERING. Even values but 2 are not primes, so don't test. 1 is not a prime and the M-R algorithm needs w>1. @@ -535,7 +580,7 @@ const isProbablyPrime = async function (w, iterations = 16) { } while (--iterations); return true; -}; +} /** * A probably-prime (Miller-Rabin), cryptographically-secure, random-number generator @@ -550,8 +595,7 @@ const prime = async function (bitLength, iterations = 16) { { let workerList = []; for (let i = 0; i < self.navigator.hardwareConcurrency; i++) { - const moduleDir = new URL('./', import.meta.url).pathname; - let newWorker = new Worker(`${moduleDir}/workerPrimalityTest.js`); + let newWorker = _isProbablyPrimeWorker(); newWorker.onmessage = async (event) => { if (event.data.isPrime) { // if a prime number has been found, stop all the workers, and return it diff --git a/dist/bigint-utils-latest.browser.mod.min.js b/dist/bigint-utils-latest.browser.mod.min.js index a5b6532..36e0845 100644 --- a/dist/bigint-utils-latest.browser.mod.min.js +++ b/dist/bigint-utils-latest.browser.mod.min.js @@ -1 +1,15 @@ -const abs=function(b){return b=BigInt(b),b>=BigInt(0)?b:-b},gcd=function(c,d){c=abs(c),d=abs(d);let e=BigInt(0);for(;!((c|d)&BigInt(1));)c>>=BigInt(1),d>>=BigInt(1),e++;for(;!(c&BigInt(1));)c>>=BigInt(1);do{for(;!(d&BigInt(1));)d>>=BigInt(1);if(c>d){let a=c;c=d,d=a}d-=c}while(d);return c<b?b+c:b},eGcd=function(c,d){c=BigInt(c),d=BigInt(d);let e=BigInt(0),f=BigInt(1),g=BigInt(1),h=BigInt(0);for(;c!==BigInt(0);){let a=d/c,b=d%c,i=e-g*a,j=f-h*a;d=c,c=b,e=g,f=h,g=i,h=j}return{b:d,x:e,y:f}},modInv=function(b,a){let c=eGcd(b,a);return c.b===BigInt(1)?toZn(c.x,a):null},modPow=function(c,d,e){if(e=BigInt(e),c=toZn(c,e),d=BigInt(d),d{let d;const e=(a,d)=>{b&&(d[0]|=128),c(d)};d=new Uint8Array(a),getRandomValuesWorker(d,e)})},randBetween=async function(a,b=1){let c,d=bitLength(a),e=d>>3,f=d-8*e;0a||g{if(e.data.isPrime){for(let a=0;a{onmessage=function(a){const b=self.crypto.getRandomValues(a.data.buf);self.postMessage({buf:b,id:a.data.id})}});return a}();function fromBuffer(a){let b=BigInt(0);for(let c of a.values()){let a=BigInt(c);b=(b<>=BigInt(1))>BigInt(1));return c}export{abs,eGcd,gcd,isProbablyPrime,lcm,modInv,modPow,prime,randBetween,randBytes,toZn}; +const abs=function(b){return b=BigInt(b),b>=BigInt(0)?b:-b},gcd=function(c,d){c=abs(c),d=abs(d);let e=BigInt(0);for(;!((c|d)&BigInt(1));)c>>=BigInt(1),d>>=BigInt(1),e++;for(;!(c&BigInt(1));)c>>=BigInt(1);do{for(;!(d&BigInt(1));)d>>=BigInt(1);if(c>d){let a=c;c=d,d=a}d-=c}while(d);return c<b?b+c:b},eGcd=function(c,d){c=BigInt(c),d=BigInt(d);let e=BigInt(0),f=BigInt(1),g=BigInt(1),h=BigInt(0);for(;c!==BigInt(0);){let a=d/c,b=d%c,i=e-g*a,j=f-h*a;d=c,c=b,e=g,f=h,g=i,h=j}return{b:d,x:e,y:f}},modInv=function(b,a){let c=eGcd(b,a);return c.b===BigInt(1)?toZn(c.x,a):null},modPow=function(c,d,e){if(e=BigInt(e),c=toZn(c,e),d=BigInt(d),d{let d;d=new Uint8Array(a),self.crypto.getRandomValues(d),b&&(d[0]|=128),c(d)})},randBetween=async function(a,b=1){let c,d=bitLength(a),e=d>>3,f=d-8*e;0a||g{let d=_isProbablyPrimeWorker();d.onmessage=a=>{c(a.data.isPrime)},d.postMessage({rnd:a,iterations:b})})};function _isProbablyPrimeWorker(){let a=`(() => { + 'use strict'; + + const eGcd = ${eGcd.toString()}; + const modInv = ${modInv.toString()}; + const modPow = ${modPow.toString()}; + const toZn = ${toZn.toString()}; + const randBytes = ${randBytes.toString()}; + const randBetween = ${randBetween.toString()}; + const isProbablyPrime = ${_isProbablyPrime.toString()}; + ${bitLength.toString()} + ${fromBuffer.toString()} + + onmessage = ${async function(a){const b=await isProbablyPrime(a.data.rnd,a.data.iterations,!1);postMessage({isPrime:b,value:a.data.rnd})}.toString()}; + })()`;var b=new Blob([a],{type:"text/javascript"});return new Worker(window.URL.createObjectURL(b))}async function _isProbablyPrime(c,b=16){if(c===BigInt(2))return!0;if((c&BigInt(1))===BigInt(0)||c===BigInt(1))return!1;const e=[3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063,1069,1087,1091,1093,1097,1103,1109,1117,1123,1129,1151,1153,1163,1171,1181,1187,1193,1201,1213,1217,1223,1229,1231,1237,1249,1259,1277,1279,1283,1289,1291,1297,1301,1303,1307,1319,1321,1327,1361,1367,1373,1381,1399,1409,1423,1427,1429,1433,1439,1447,1451,1453,1459,1471,1481,1483,1487,1489,1493,1499,1511,1523,1531,1543,1549,1553,1559,1567,1571,1579,1583,1597];for(let a=0;a{{let d=[];for(let e,f=0;f{if(f.data.isPrime){for(let a=0;a>=BigInt(1))>BigInt(1));return c}export{abs,eGcd,gcd,isProbablyPrime,lcm,modInv,modPow,prime,randBetween,randBytes,toZn}; diff --git a/dist/bigint-utils-latest.node.js b/dist/bigint-utils-latest.node.js index 51363a5..cfac845 100644 --- a/dist/bigint-utils-latest.node.js +++ b/dist/bigint-utils-latest.node.js @@ -226,6 +226,12 @@ const randBetween = async function (max, min = 1) { * @return {Promise} A promise that resolve to a boolean that is either true (a probably prime number) or false (definitely composite) */ const isProbablyPrime = async function (w, iterations = 16) { + { + return _isProbablyPrime(w, iterations); + } +}; + +async function _isProbablyPrime(w, iterations = 16) { /* PREFILTERING. Even values but 2 are not primes, so don't test. 1 is not a prime and the M-R algorithm needs w>1. @@ -542,7 +548,7 @@ const isProbablyPrime = async function (w, iterations = 16) { } while (--iterations); return true; -}; +} /** * A probably-prime (Miller-Rabin), cryptographically-secure, random-number generator diff --git a/dist/workerPrimalityTest.js b/dist/workerPrimalityTest.js deleted file mode 100644 index 31057f8..0000000 --- a/dist/workerPrimalityTest.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -//const window = self; - -importScripts('./bigint-utils-latest.browser.js'); // to be replaced during build with rollup replace - -onmessage = async function(event) { // Let's start once we are called - // event.data = {rnd: , iterations: } - const isPrime = await bigintUtils.isProbablyPrime(event.data.rnd, event.data.iterations); - postMessage({ - 'isPrime': isPrime, - 'value' : event.data.rnd - }); -}; \ No newline at end of file diff --git a/package.json b/package.json index e8bd3d1..36ee81c 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,12 @@ { - "name": "bigint-utils", + "name": "bigint-crypto-utils", "version": "0.9.0", - "description": "Arbitrary precision modular arithmetics, cryptographically secure random numbers and prime generation/testing using native JS (stage 3) implementation of BigInt", + "description": "Utils for working with cryptography using native JS (stage 3) implementation of BigInt. It includes arbitrary precision modular arithmetics, cryptographically secure random numbers and strong probable prime generation/testing ", "keywords": [ "modular arithmetics", + "crypto", "prime", + "random", "rng", "prng", "primality test", @@ -16,9 +18,9 @@ "email": "jserrano@entel.upc.edu", "url": "https://github.com/juanelas" }, - "repository": "github:juanelas/bigint-utils", - "main": "./dist/bigint-utils-latest.node.js", - "browser": "./dist/bigint-utils-latest.browser.mod.js", + "repository": "github:juanelas/bigint-crypto-utils", + "main": "./dist/bigint-crypto-utils-latest.node.js", + "browser": "./dist/bigint-crypto-utils-latest.browser.mod.js", "directories": { "build": "./build", "dist": "./dist", @@ -30,6 +32,7 @@ "build": "node build/build.rollup.js", "build:browserTests": "node build/build.browser.tests.js", "build:docs": "jsdoc2md --template=README.hbs --files ./src/main.js > README.md", + "build:all": "npm run build && npm run build:browserTests && npm run build:docs", "prepublishOnly": "npm run build && npm run build:docs" }, "devDependencies": { diff --git a/src/browser-test-template.html b/src/browser-test-template.html index 04ec5a4..3cb28da 100644 --- a/src/browser-test-template.html +++ b/src/browser-test-template.html @@ -1,7 +1,7 @@ - Mocha Tests + {{PKG_NAME}} - Mocha Tests diff --git a/src/main.js b/src/main.js index 7179c2a..0450479 100644 --- a/src/main.js +++ b/src/main.js @@ -12,64 +12,6 @@ export const abs = function (a) { return (a >= BigInt(0)) ? a : -a; }; -/** - * Greatest-common divisor of two integers based on the iterative binary algorithm. - * - * @param {number|bigint} a - * @param {number|bigint} b - * - * @returns {bigint} The greatest common divisor of a and b - */ -export const gcd = function (a, b) { - a = abs(a); - b = abs(b); - let shift = BigInt(0); - while (!((a | b) & BigInt(1))) { - a >>= BigInt(1); - b >>= BigInt(1); - shift++; - } - while (!(a & BigInt(1))) a >>= BigInt(1); - do { - while (!(b & BigInt(1))) b >>= BigInt(1); - if (a > b) { - let x = a; - a = b; - b = x; - } - b -= a; - } while (b); - - // rescale - return a << shift; -}; - -/** - * The least common multiple computed as abs(a*b)/gcd(a,b) - * @param {number|bigint} a - * @param {number|bigint} b - * - * @returns {bigint} The least common multiple of a and b - */ -export const lcm = function (a, b) { - a = BigInt(a); - b = BigInt(b); - return abs(a * b) / gcd(a, b); -}; - -/** - * Finds the smallest positive element that is congruent to a in modulo n - * @param {number|bigint} a An integer - * @param {number|bigint} n The modulo - * - * @returns {bigint} The smallest positive representation of a in modulo n - */ -export const toZn = function (a, n) { - n = BigInt(n); - a = BigInt(a) % n; - return (a < 0) ? a + n : a; -}; - /** * @typedef {Object} egcdReturn A triple (g, x, y), such that ax + by = g = gcd(a, b). * @property {bigint} g @@ -112,6 +54,79 @@ export const eGcd = function (a, b) { }; }; +/** + * Greatest-common divisor of two integers based on the iterative binary algorithm. + * + * @param {number|bigint} a + * @param {number|bigint} b + * + * @returns {bigint} The greatest common divisor of a and b + */ +export const gcd = function (a, b) { + a = abs(a); + b = abs(b); + let shift = BigInt(0); + while (!((a | b) & BigInt(1))) { + a >>= BigInt(1); + b >>= BigInt(1); + shift++; + } + while (!(a & BigInt(1))) a >>= BigInt(1); + do { + while (!(b & BigInt(1))) b >>= BigInt(1); + if (a > b) { + let x = a; + a = b; + b = x; + } + b -= a; + } while (b); + + // rescale + return a << shift; +}; + +/** + * The test first tries if any of the first 250 small primes are a factor of the input number and then passes several iterations of Miller-Rabin Probabilistic Primality Test (FIPS 186-4 C.3.1) + * + * @param {bigint} w An integer to be tested for primality + * @param {number} iterations The number of iterations for the primality test. The value shall be consistent with Table C.1, C.2 or C.3 + * + * @return {Promise} A promise that resolve to a boolean that is either true (a probably prime number) or false (definitely composite) + */ +export const isProbablyPrime = async function (w, iterations = 16) { + if (process.browser) { + return new Promise(resolve => { + let worker = _isProbablyPrimeWorker(); + + worker.onmessage = (event) => { + worker.terminate(); + resolve(event.data.isPrime); + }; + + worker.postMessage({ + 'rnd': w, + 'iterations': iterations + }); + }); + } else { + return _isProbablyPrime(w, iterations); + } +}; + +/** + * The least common multiple computed as abs(a*b)/gcd(a,b) + * @param {number|bigint} a + * @param {number|bigint} b + * + * @returns {bigint} The least common multiple of a and b + */ +export const lcm = function (a, b) { + a = BigInt(a); + b = BigInt(b); + return abs(a * b) / gcd(a, b); +}; + /** * Modular inverse. * @@ -161,7 +176,92 @@ export const modPow = function (a, b, n) { }; /** - * Secure random bytes for both node and browsers. Browser implementation uses WebWorkers in order to not lock the main process + * A probably-prime (Miller-Rabin), cryptographically-secure, random-number generator. + * The browser version uses web workers to parallelise prime look up. Therefore, it does not lock the UI + * main process, and it can be much faster (if several cores or cpu are available). + * + * @param {number} bitLength The required bit length for the generated prime + * @param {number} iterations The number of iterations for the Miller-Rabin Probabilistic Primality Test + * + * @returns {Promise} A promise that resolves to a bigint probable prime of bitLength bits + */ +export const prime = async function (bitLength, iterations = 16) { + return new Promise(async (resolve) => { + if (process.browser) { + let workerList = []; + for (let i = 0; i < self.navigator.hardwareConcurrency; i++) { + let newWorker = _isProbablyPrimeWorker(); + newWorker.onmessage = async (event) => { + if (event.data.isPrime) { + // if a prime number has been found, stop all the workers, and return it + for (let j = 0; j < workerList.length; j++) { + workerList[j].terminate(); + } + while (workerList.length) { + workerList.pop(); + } + resolve(event.data.value); + } else { // if a composite is found, make the worker test another random number + let rnd = BigInt(0); + rnd = fromBuffer(await randBytes(bitLength / 8, true)); + newWorker.postMessage({ + 'rnd': rnd, + 'iterations': iterations + }); + } + }; + workerList.push(newWorker); + } + + for (const worker of workerList) { + let rnd = BigInt(0); + rnd = fromBuffer(await randBytes(bitLength / 8, true)); + worker.postMessage({ + 'rnd': rnd, + 'iterations': iterations + }); + } + + } else { + let rnd = BigInt(0); + do { + rnd = fromBuffer(await randBytes(bitLength / 8, true)); + } while (! await isProbablyPrime(rnd, iterations)); + resolve(rnd); + } + }); +}; + +/** + * Returns a cryptographically secure random integer between [min,max] + * @param {bigint} max Returned value will be <= max + * @param {bigint} min Returned value will be >= min + * + * @returns {Promise} A promise that resolves to a cryptographically secure random bigint between [min,max] + */ +export const randBetween = async function (max, min = 1) { + let bitLen = bitLength(max); + let byteLength = bitLen >> 3; + let remaining = bitLen - (byteLength * 8); + let extraBits; + if (remaining > 0) { + byteLength++; + extraBits = 2 ** remaining - 1; + } + + let rnd; + do { + let buf = await randBytes(byteLength); + // remove extra bits + if (remaining > 0) + buf[0] = buf[0] & extraBits; + rnd = fromBuffer(buf); + } while (rnd > max || rnd < min); + return rnd; +}; + +/** + * Secure random bytes for both node and browsers. Node version uses crypto.randomFill() and browser one self.crypto.getRandomValues() * * @param {number} byteLength The desired number of random bytes * @param {boolean} forceLength If we want to force the output to have a bit length of 8*byteLength. It basically forces the msb to be 1 @@ -193,44 +293,58 @@ export const randBytes = async function (byteLength, forceLength = false) { }); }; - /** - * Returns a cryptographically secure random integer between [min,max] - * @param {bigint} max Returned value will be <= max - * @param {bigint} min Returned value will be >= min + * Finds the smallest positive element that is congruent to a in modulo n + * @param {number|bigint} a An integer + * @param {number|bigint} n The modulo * - * @returns {Promise} A promise that resolves to a cryptographically secure random bigint between [min,max] + * @returns {bigint} The smallest positive representation of a in modulo n */ -export const randBetween = async function (max, min = 1) { - let bitLen = bitLength(max); - let byteLength = bitLen >> 3; - let remaining = bitLen - (byteLength * 8); - let extraBits; - if (remaining > 0) { - byteLength++; - extraBits = 2 ** remaining - 1; - } - - let rnd; - do { - let buf = await randBytes(byteLength); - // remove extra bits - if (remaining > 0) - buf[0] = buf[0] & extraBits; - rnd = fromBuffer(buf); - } while (rnd > max || rnd < min); - return rnd; +export const toZn = function (a, n) { + n = BigInt(n); + a = BigInt(a) % n; + return (a < 0) ? a + n : a; }; -/** - * The test first tries if any of the first 250 small primes are a factor of the input number and then passes several iterations of Miller-Rabin Probabilistic Primality Test (FIPS 186-4 C.3.1) - * - * @param {bigint} w An integer to be tested for primality - * @param {number} iterations The number of iterations for the primality test. The value shall be consistent with Table C.1, C.2 or C.3 - * - * @return {Promise} A promise that resolve to a boolean that is either true (a probably prime number) or false (definitely composite) - */ -export const isProbablyPrime = async function (w, iterations = 16) { + + +/* HELPER FUNCTIONS */ + +function fromBuffer(buf) { + let ret = BigInt(0); + for (let i of buf.values()) { + let bi = BigInt(i); + ret = (ret << BigInt(8)) + bi; + } + return ret; +} + +function bitLength(a) { + let bits = 1; + do { + bits++; + } while ((a >>= BigInt(1)) > BigInt(1)); + return bits; +} + +function _isProbablyPrimeWorker() { + async function _onmessage(event) { // Let's start once we are called + // event.data = {rnd: , iterations: } + const isPrime = await isProbablyPrime(event.data.rnd, event.data.iterations); + postMessage({ + 'isPrime': isPrime, + 'value': event.data.rnd + }); + } + + let workerCode = `(() => {'use strict';const eGcd = ${eGcd.toString()};const modInv = ${modInv.toString()};const modPow = ${modPow.toString()};const toZn = ${toZn.toString()};const randBytes = ${randBytes.toString()};const randBetween = ${randBetween.toString()};const isProbablyPrime = ${_isProbablyPrime.toString()};${bitLength.toString()}${fromBuffer.toString()}onmessage = ${_onmessage.toString()};})()`; + + var _blob = new Blob([workerCode], { type: 'text/javascript' }); + + return new Worker(window.URL.createObjectURL(_blob)); +} + +async function _isProbablyPrime(w, iterations = 16) { /* PREFILTERING. Even values but 2 are not primes, so don't test. 1 is not a prime and the M-R algorithm needs w>1. @@ -547,82 +661,4 @@ export const isProbablyPrime = async function (w, iterations = 16) { } while (--iterations); return true; -}; - -/** - * A probably-prime (Miller-Rabin), cryptographically-secure, random-number generator - * - * @param {number} bitLength The required bit length for the generated prime - * @param {number} iterations The number of iterations for the Miller-Rabin Probabilistic Primality Test - * - * @returns {Promise} A promise that resolves to a bigint probable prime of bitLength bits - */ -export const prime = async function (bitLength, iterations = 16) { - return new Promise(async (resolve) => { - if (process.browser) { - let workerList = []; - for (let i = 0; i < self.navigator.hardwareConcurrency; i++) { - const moduleDir = new URL('./', import.meta.url).pathname; - let newWorker = new Worker(`${moduleDir}/workerPrimalityTest.js`); - newWorker.onmessage = async (event) => { - if (event.data.isPrime) { - // if a prime number has been found, stop all the workers, and return it - for (let j = 0; j < workerList.length; j++) { - workerList[j].terminate(); - } - while (workerList.length) { - workerList.pop(); - } - resolve(event.data.value); - } else { // if a composite is found, make the worker test another random number - let rnd = BigInt(0); - rnd = fromBuffer(await randBytes(bitLength / 8, true)); - newWorker.postMessage({ - 'rnd': rnd, - 'iterations': iterations - }); - } - }; - workerList.push(newWorker); - } - - for (const worker of workerList) { - let rnd = BigInt(0); - rnd = fromBuffer(await randBytes(bitLength / 8, true)); - worker.postMessage({ - 'rnd': rnd, - 'iterations': iterations - }); - } - - } else { - let rnd = BigInt(0); - do { - rnd = fromBuffer(await randBytes(bitLength / 8, true)); - } while (! await isProbablyPrime(rnd, iterations)); - resolve(rnd); - } - }); -}; - - - - -/* HELPER FUNCTIONS */ - -function fromBuffer(buf) { - let ret = BigInt(0); - for (let i of buf.values()) { - let bi = BigInt(i); - ret = (ret << BigInt(8)) + bi; - } - return ret; -} - -function bitLength(a) { - let bits = 1; - do { - bits++; - } while ((a >>= BigInt(1)) > BigInt(1)); - return bits; -} +} \ No newline at end of file diff --git a/src/workerPrimalityTest.js b/src/workerPrimalityTest.js deleted file mode 100644 index 1d45592..0000000 --- a/src/workerPrimalityTest.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -//const window = self; - -importScripts('{{IIFE}}'); // to be replaced during build with rollup replace - -onmessage = async function(event) { // Let's start once we are called - // event.data = {rnd: , iterations: } - const isPrime = await bigintUtils.isProbablyPrime(event.data.rnd, event.data.iterations); - postMessage({ - 'isPrime': isPrime, - 'value' : event.data.rnd - }); -}; \ No newline at end of file diff --git a/test/01_PrimeValidation.js b/test/01_PrimeValidation.js index 0cdc0e7..00114c3 100644 --- a/test/01_PrimeValidation.js +++ b/test/01_PrimeValidation.js @@ -2,7 +2,7 @@ // For the browser test builder to work you MUST import them module in a variable that // is the camelised version of the package name. -const bigintUtils = require('../dist/bigint-utils-latest.node'); +const bigintCryptoUtils = require('../dist/bigint-crypto-utils-latest.node'); const chai = require('chai'); const numbers = [ @@ -52,7 +52,7 @@ describe('Testing validation of prime numbers', function () { for (const num of numbers) { describe(`isProbablyPrime(${num.value})`, function () { it(`should return ${num.prime}`, async function () { - const ret = await bigintUtils.isProbablyPrime(num.value); + const ret = await bigintCryptoUtils.isProbablyPrime(num.value); chai.expect(ret).to.equal(num.prime); }); }); diff --git a/test/02_PrimeGeneration.js b/test/02_PrimeGeneration.js index e6c13ed..6f2f631 100644 --- a/test/02_PrimeGeneration.js +++ b/test/02_PrimeGeneration.js @@ -2,23 +2,22 @@ // For the browser test builder to work you MUST import them module in a variable that // is the camelised version of the package name. -const bigintUtils = require('../dist/bigint-utils-latest.node'); +const bigintCryptoUtils = require('../dist/bigint-crypto-utils-latest.node'); const chai = require('chai'); const bitLengths = [ - 256, - 512, 1024, 2048, - 3072 + 3072, + 4096 ]; describe('Testing generation of prime numbers', function () { for (const bitLength of bitLengths) { describe(`Executing prime(${bitLength})`, function () { it(`should return a random ${bitLength}-bits probable prime`, async function () { - let prime = await bigintUtils.prime(bitLength); - const ret = await bigintUtils.isProbablyPrime(prime); + let prime = await bigintCryptoUtils.prime(bitLength); + const ret = await bigintCryptoUtils.isProbablyPrime(prime); chai.expect(ret).to.equal(true); let bits = 1; do { diff --git a/test/browser/index.html b/test/browser/index.html index 3ed7ace..1517f11 100644 --- a/test/browser/index.html +++ b/test/browser/index.html @@ -1,7 +1,7 @@ - Mocha Tests + bigint-crypto-utils - Mocha Tests @@ -12,8 +12,8 @@ diff --git a/test/browser/tests.js b/test/browser/tests.js index aa9cbd4..b80e88d 100644 --- a/test/browser/tests.js +++ b/test/browser/tests.js @@ -50,7 +50,7 @@ describe('Testing validation of prime numbers', function () { for (const num of numbers) { describe(`isProbablyPrime(${num.value})`, function () { it(`should return ${num.prime}`, async function () { - const ret = await bigintUtils.isProbablyPrime(num.value); + const ret = await bigintCryptoUtils.isProbablyPrime(num.value); chai.expect(ret).to.equal(num.prime); }); }); @@ -63,19 +63,18 @@ describe('Testing validation of prime numbers', function () { const bitLengths = [ - 256, - 512, 1024, 2048, - 3072 + 3072, + 4096 ]; describe('Testing generation of prime numbers', function () { for (const bitLength of bitLengths) { describe(`Executing prime(${bitLength})`, function () { it(`should return a random ${bitLength}-bits probable prime`, async function () { - let prime = await bigintUtils.prime(bitLength); - const ret = await bigintUtils.isProbablyPrime(prime); + let prime = await bigintCryptoUtils.prime(bitLength); + const ret = await bigintCryptoUtils.isProbablyPrime(prime); chai.expect(ret).to.equal(true); let bits = 1; do {