From f2d6043ee7369cc122a57b34226e7f437ed2a660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Hern=C3=A1ndez=20Serrano?= Date: Sat, 20 Apr 2019 22:11:44 +0200 Subject: [PATCH] ready for node worker threads --- README.md | 22 +- dist/bigint-crypto-utils-latest.browser.js | 140 ++++++----- .../bigint-crypto-utils-latest.browser.min.js | 2 +- .../bigint-crypto-utils-latest.browser.mod.js | 141 ++++++----- ...int-crypto-utils-latest.browser.mod.min.js | 2 +- dist/bigint-crypto-utils-latest.node.js | 165 ++++++++++--- package.json | 4 +- src/main.js | 219 +++++++++++++----- 8 files changed, 487 insertions(+), 208 deletions(-) diff --git a/README.md b/README.md index faa7c6c..b19cc6c 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,8 @@ Take positive integers a, b as input, and return a triple (g, x, y), such that a

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

isProbablyPrime(w, iterations)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)

+

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)

lcm(a, b)bigint

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

@@ -110,6 +111,9 @@ main process, and it can be much faster (if several cores or cpu are available).
randBetween(max, min)Promise

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

+
randBits(bitLength, forceLength)Promise
+

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

+
randBytes(byteLength, forceLength)Promise

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

@@ -167,7 +171,8 @@ Greatest-common divisor of two integers based on the iterative binary algorithm. ## isProbablyPrime(w, iterations) ⇒ 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) +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 function **Returns**: Promise - A promise that resolve to a boolean that is either true (a probably prime number) or false (definitely composite) @@ -245,6 +250,19 @@ Returns a cryptographically secure random integer between [min,max] | max | bigint | Returned value will be <= max | | min | bigint | Returned value will be >= min | + + +## randBits(bitLength, forceLength) ⇒ Promise +Secure random bits for both node and browsers. Node version uses crypto.randomFill() and browser one self.crypto.getRandomValues() + +**Kind**: global function +**Returns**: Promise - A promise that resolves to a Buffer/UInt8Array filled with cryptographically secure random bits + +| Param | Type | Description | +| --- | --- | --- | +| bitLength | number | The desired number of random bits | +| forceLength | boolean | If we want to force the output to have a specific bit length. It basically forces the msb to be 1 | + ## randBytes(byteLength, forceLength) ⇒ Promise diff --git a/dist/bigint-crypto-utils-latest.browser.js b/dist/bigint-crypto-utils-latest.browser.js index 0200bdf..315e8e0 100644 --- a/dist/bigint-crypto-utils-latest.browser.js +++ b/dist/bigint-crypto-utils-latest.browser.js @@ -88,7 +88,8 @@ var bigintCryptoUtils = (function (exports) { } /** - * 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) + * 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 @@ -96,8 +97,8 @@ var bigintCryptoUtils = (function (exports) { * @return {Promise} A promise that resolve to a boolean that is either true (a probably prime number) or false (definitely composite) */ async function isProbablyPrime(w, iterations = 16) { - { - return new Promise(resolve => { + { // browser + return new Promise((resolve, reject) => { let worker = new Worker(_isProbablyPrimeWorkerURL()); worker.onmessage = (event) => { @@ -105,9 +106,14 @@ var bigintCryptoUtils = (function (exports) { resolve(event.data.isPrime); }; + worker.onmessageerror = (event) => { + reject(event); + }; + worker.postMessage({ 'rnd': w, - 'iterations': iterations + 'iterations': iterations, + 'id': 0 }); }); } @@ -185,43 +191,50 @@ var bigintCryptoUtils = (function (exports) { * @returns {Promise} A promise that resolves to a bigint probable prime of bitLength bits */ async function prime(bitLength, iterations = 16) { - return new Promise(async (resolve) => { - { - let workerList = []; + return new Promise((resolve) => { + let workerList = []; + const _onmessage = (msg, newWorker) => { + if (msg.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(msg.value); + } else { // if a composite is found, make the worker test another random number + randBits(bitLength, true).then((buf) => { + let rnd = fromBuffer(buf); + try { + newWorker.postMessage({ + 'rnd': rnd, + 'iterations': iterations, + 'id': msg.id + }); + } catch (error) { + // The worker has already terminated. There is nothing to handle here + } + }); + } + }; + { //browser let workerURL = _isProbablyPrimeWorkerURL(); for (let i = 0; i < self.navigator.hardwareConcurrency; i++) { let newWorker = new Worker(workerURL); - 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 - }); - } - }; + newWorker.onmessage = (event) => _onmessage(event.data, newWorker); workerList.push(newWorker); } - - for (const worker of workerList) { - let rnd = BigInt(0); - rnd = fromBuffer(await randBytes(bitLength / 8, true)); - worker.postMessage({ + } + for (let i = 0; i < workerList.length; i++) { + randBits(bitLength, true).then((buf) => { + let rnd = fromBuffer(buf); + workerList[i].postMessage({ 'rnd': rnd, - 'iterations': iterations + 'iterations': iterations, + 'id': i }); - } - + }); } }); } @@ -233,25 +246,36 @@ var bigintCryptoUtils = (function (exports) { * * @returns {Promise} A promise that resolves to a cryptographically secure random bigint between [min,max] */ - async function randBetween(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; - } - + async function randBetween(max, min = BigInt(1)) { + if (max <= min) throw new Error('max must be > min'); + const interval = max - min; + let bitLen = bitLength(interval); let rnd; do { - let buf = await randBytes(byteLength); - // remove extra bits - if (remaining > 0) - buf[0] = buf[0] & extraBits; + let buf = await randBits(bitLen); rnd = fromBuffer(buf); - } while (rnd > max || rnd < min); - return rnd; + } while (rnd > interval); + return rnd + min; + } + + /** + * Secure random bits for both node and browsers. Node version uses crypto.randomFill() and browser one self.crypto.getRandomValues() + * + * @param {number} bitLength The desired number of random bits + * @param {boolean} forceLength If we want to force the output to have a specific bit length. It basically forces the msb to be 1 + * + * @returns {Promise} A promise that resolves to a Buffer/UInt8Array filled with cryptographically secure random bits + */ + async function randBits(bitLength, forceLength = false) { + const byteLength = Math.ceil(bitLength / 8); + let rndBytes = await randBytes(byteLength, false); + // Fill with 0's the extra birs + rndBytes[0] = rndBytes[0] & (2 ** (bitLength % 8) - 1); + if (forceLength) { + let mask = (bitLength % 8) ? 2 ** ((bitLength % 8) - 1) : 128; + rndBytes[0] = rndBytes[0] | mask; + } + return rndBytes; } /** @@ -312,16 +336,21 @@ var bigintCryptoUtils = (function (exports) { } function _isProbablyPrimeWorkerURL() { - async function _onmessage(event) { // Let's start once we are called + // Let's us first add all the required functions + let workerCode = `'use strict';const eGcd = ${eGcd.toString()};const modInv = ${modInv.toString()};const modPow = ${modPow.toString()};const toZn = ${toZn.toString()};const randBits = ${randBits.toString()};const randBytes = ${randBytes.toString()};const randBetween = ${randBetween.toString()};const isProbablyPrime = ${_isProbablyPrime.toString()};${bitLength.toString()}${fromBuffer.toString()}`; + + const _onmessage = async function (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 + 'value': event.data.rnd, + 'id': event.data.id }); - } + }; + workerCode += `onmessage = ${_onmessage.toString()};`; - 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()};})()`; + workerCode = `(() => {${workerCode}})()`; // encapsulate IIFE var _blob = new Blob([workerCode], { type: 'text/javascript' }); @@ -629,7 +658,7 @@ var bigintCryptoUtils = (function (exports) { let m = (w - BigInt(1)) / (BigInt(2) ** a); loop: do { - let b = await randBetween(w - BigInt(1), 2); + let b = await randBetween(w - BigInt(1), BigInt(2)); let z = modPow(b, m, w); if (z === BigInt(1) || z === w - BigInt(1)) continue; @@ -656,6 +685,7 @@ var bigintCryptoUtils = (function (exports) { exports.modPow = modPow; exports.prime = prime; exports.randBetween = randBetween; + exports.randBits = randBits; exports.randBytes = randBytes; exports.toZn = toZn; diff --git a/dist/bigint-crypto-utils-latest.browser.min.js b/dist/bigint-crypto-utils-latest.browser.min.js index c497ece..67b8cd7 100644 --- a/dist/bigint-crypto-utils-latest.browser.min.js +++ b/dist/bigint-crypto-utils-latest.browser.min.js @@ -1 +1 @@ -var bigintCryptoUtils=function(a){'use strict';function c(b){return b=BigInt(b),b>=BigInt(0)?b:-b}function d(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}}function e(d,e){d=c(d),e=c(e);let f=BigInt(0);for(;!((d|e)&BigInt(1));)d>>=BigInt(1),e>>=BigInt(1),f++;for(;!(d&BigInt(1));)d>>=BigInt(1);do{for(;!(e&BigInt(1));)e>>=BigInt(1);if(d>e){let a=d;d=e,e=a}e-=d}while(e);return d<{let d=new Worker(n());d.onmessage=a=>{d.terminate(),c(a.data.isPrime)},d.postMessage({rnd:a,iterations:b})})}function g(b,a){let c=d(b,a);return c.b===BigInt(1)?k(c.x,a):null}function h(d,e,f){if(f=BigInt(f),d=k(d,f),e=BigInt(e),e>3,f=d-8*e;0a||g{let d;d=new Uint8Array(a),self.crypto.getRandomValues(d),b&&(d[0]|=128),c(d)})}function k(b,c){return c=BigInt(c),b=BigInt(b)%c,0>b?b+c:b}function l(a){let b=BigInt(0);for(let c of a.values()){let a=BigInt(c);b=(b<>=BigInt(1))>BigInt(1));return c}function n(){let a=`(() => {'use strict';const eGcd = ${d.toString()};const modInv = ${g.toString()};const modPow = ${h.toString()};const toZn = ${k.toString()};const randBytes = ${j.toString()};const randBetween = ${i.toString()};const isProbablyPrime = ${o.toString()};${m.toString()}${l.toString()}onmessage = ${async function(a){const b=await f(a.data.rnd,a.data.iterations);postMessage({isPrime:b,value:a.data.rnd})}.toString()};})()`;var b=new Blob([a],{type:"text/javascript"});return window.URL.createObjectURL(b)}async function o(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=[],e=n();for(let f,g=0;g{if(e.data.isPrime){for(let a=0;a=BigInt(0)?b:-b}function d(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}}function e(d,e){d=c(d),e=c(e);let f=BigInt(0);for(;!((d|e)&BigInt(1));)d>>=BigInt(1),e>>=BigInt(1),f++;for(;!(d&BigInt(1));)d>>=BigInt(1);do{for(;!(e&BigInt(1));)e>>=BigInt(1);if(d>e){let a=d;d=e,e=a}e-=d}while(e);return d<{let e=new Worker(o());e.onmessage=a=>{e.terminate(),c(a.data.isPrime)},e.onmessageerror=a=>{d(a)},e.postMessage({rnd:a,iterations:b,id:0})})}function g(b,a){let c=d(b,a);return c.b===BigInt(1)?l(c.x,a):null}function h(d,e,f){if(f=BigInt(f),d=l(d,f),e=BigInt(e),e min");const c=a-b;let d,e=n(c);do{let a=await j(e);d=m(a)}while(d>c);return d+b}async function j(a,b=!1){var c=Math.ceil;const d=c(a/8);let e=await k(d,!1);if(e[0]&=2**(a%8)-1,b){let b=a%8?2**(a%8-1):128;e[0]|=b}return e}async function k(a,b=!1){return new Promise(c=>{let d;d=new Uint8Array(a),self.crypto.getRandomValues(d),b&&(d[0]|=128),c(d)})}function l(b,c){return c=BigInt(c),b=BigInt(b)%c,0>b?b+c:b}function m(a){let b=BigInt(0);for(let c of a.values()){let a=BigInt(c);b=(b<>=BigInt(1))>BigInt(1));return c}function o(){let a=`'use strict';const eGcd = ${d.toString()};const modInv = ${g.toString()};const modPow = ${h.toString()};const toZn = ${l.toString()};const randBits = ${j.toString()};const randBytes = ${k.toString()};const randBetween = ${i.toString()};const isProbablyPrime = ${p.toString()};${n.toString()}${m.toString()}`;a+=`onmessage = ${async function(a){const b=await f(a.data.rnd,a.data.iterations);postMessage({isPrime:b,value:a.data.rnd,id:a.data.id})}.toString()};`,a=`(() => {${a}})()`;var b=new Blob([a],{type:"text/javascript"});return window.URL.createObjectURL(b)}async function p(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=[];const e=(e,f)=>{if(e.isPrime){for(let a=0;a{let c=m(a);try{f.postMessage({rnd:c,iterations:b,id:e.id})}catch(a){}})};{let a=o();for(let b,c=0;ce(a.data,b),d.push(b)}for(let e=0;e{let c=m(a);d[e].postMessage({rnd:c,iterations:b,id:e})})})},a.randBetween=i,a.randBits=j,a.randBytes=k,a.toZn=l,a}({}); diff --git a/dist/bigint-crypto-utils-latest.browser.mod.js b/dist/bigint-crypto-utils-latest.browser.mod.js index 7eaafab..556e6a0 100644 --- a/dist/bigint-crypto-utils-latest.browser.mod.js +++ b/dist/bigint-crypto-utils-latest.browser.mod.js @@ -85,7 +85,8 @@ function gcd(a, b) { } /** - * 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) + * 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 @@ -93,8 +94,8 @@ function gcd(a, b) { * @return {Promise} A promise that resolve to a boolean that is either true (a probably prime number) or false (definitely composite) */ async function isProbablyPrime(w, iterations = 16) { - { - return new Promise(resolve => { + { // browser + return new Promise((resolve, reject) => { let worker = new Worker(_isProbablyPrimeWorkerURL()); worker.onmessage = (event) => { @@ -102,9 +103,14 @@ async function isProbablyPrime(w, iterations = 16) { resolve(event.data.isPrime); }; + worker.onmessageerror = (event) => { + reject(event); + }; + worker.postMessage({ 'rnd': w, - 'iterations': iterations + 'iterations': iterations, + 'id': 0 }); }); } @@ -182,43 +188,50 @@ function modPow(a, b, n) { * @returns {Promise} A promise that resolves to a bigint probable prime of bitLength bits */ async function prime(bitLength, iterations = 16) { - return new Promise(async (resolve) => { - { - let workerList = []; + return new Promise((resolve) => { + let workerList = []; + const _onmessage = (msg, newWorker) => { + if (msg.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(msg.value); + } else { // if a composite is found, make the worker test another random number + randBits(bitLength, true).then((buf) => { + let rnd = fromBuffer(buf); + try { + newWorker.postMessage({ + 'rnd': rnd, + 'iterations': iterations, + 'id': msg.id + }); + } catch (error) { + // The worker has already terminated. There is nothing to handle here + } + }); + } + }; + { //browser let workerURL = _isProbablyPrimeWorkerURL(); for (let i = 0; i < self.navigator.hardwareConcurrency; i++) { let newWorker = new Worker(workerURL); - 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 - }); - } - }; + newWorker.onmessage = (event) => _onmessage(event.data, newWorker); workerList.push(newWorker); } - - for (const worker of workerList) { - let rnd = BigInt(0); - rnd = fromBuffer(await randBytes(bitLength / 8, true)); - worker.postMessage({ + } + for (let i = 0; i < workerList.length; i++) { + randBits(bitLength, true).then((buf) => { + let rnd = fromBuffer(buf); + workerList[i].postMessage({ 'rnd': rnd, - 'iterations': iterations + 'iterations': iterations, + 'id': i }); - } - + }); } }); } @@ -230,25 +243,36 @@ async function prime(bitLength, iterations = 16) { * * @returns {Promise} A promise that resolves to a cryptographically secure random bigint between [min,max] */ -async function randBetween(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; - } - +async function randBetween(max, min = BigInt(1)) { + if (max <= min) throw new Error('max must be > min'); + const interval = max - min; + let bitLen = bitLength(interval); let rnd; do { - let buf = await randBytes(byteLength); - // remove extra bits - if (remaining > 0) - buf[0] = buf[0] & extraBits; + let buf = await randBits(bitLen); rnd = fromBuffer(buf); - } while (rnd > max || rnd < min); - return rnd; + } while (rnd > interval); + return rnd + min; +} + +/** + * Secure random bits for both node and browsers. Node version uses crypto.randomFill() and browser one self.crypto.getRandomValues() + * + * @param {number} bitLength The desired number of random bits + * @param {boolean} forceLength If we want to force the output to have a specific bit length. It basically forces the msb to be 1 + * + * @returns {Promise} A promise that resolves to a Buffer/UInt8Array filled with cryptographically secure random bits + */ +async function randBits(bitLength, forceLength = false) { + const byteLength = Math.ceil(bitLength / 8); + let rndBytes = await randBytes(byteLength, false); + // Fill with 0's the extra birs + rndBytes[0] = rndBytes[0] & (2 ** (bitLength % 8) - 1); + if (forceLength) { + let mask = (bitLength % 8) ? 2 ** ((bitLength % 8) - 1) : 128; + rndBytes[0] = rndBytes[0] | mask; + } + return rndBytes; } /** @@ -309,16 +333,21 @@ function bitLength(a) { } function _isProbablyPrimeWorkerURL() { - async function _onmessage(event) { // Let's start once we are called + // Let's us first add all the required functions + let workerCode = `'use strict';const eGcd = ${eGcd.toString()};const modInv = ${modInv.toString()};const modPow = ${modPow.toString()};const toZn = ${toZn.toString()};const randBits = ${randBits.toString()};const randBytes = ${randBytes.toString()};const randBetween = ${randBetween.toString()};const isProbablyPrime = ${_isProbablyPrime.toString()};${bitLength.toString()}${fromBuffer.toString()}`; + + const _onmessage = async function (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 + 'value': event.data.rnd, + 'id': event.data.id }); - } + }; + workerCode += `onmessage = ${_onmessage.toString()};`; - 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()};})()`; + workerCode = `(() => {${workerCode}})()`; // encapsulate IIFE var _blob = new Blob([workerCode], { type: 'text/javascript' }); @@ -626,7 +655,7 @@ async function _isProbablyPrime(w, iterations = 16) { let m = (w - BigInt(1)) / (BigInt(2) ** a); loop: do { - let b = await randBetween(w - BigInt(1), 2); + let b = await randBetween(w - BigInt(1), BigInt(2)); let z = modPow(b, m, w); if (z === BigInt(1) || z === w - BigInt(1)) continue; @@ -644,4 +673,4 @@ async function _isProbablyPrime(w, iterations = 16) { return true; } -export { abs, eGcd, gcd, isProbablyPrime, lcm, modInv, modPow, prime, randBetween, randBytes, toZn }; +export { abs, eGcd, gcd, isProbablyPrime, lcm, modInv, modPow, prime, randBetween, randBits, randBytes, toZn }; diff --git a/dist/bigint-crypto-utils-latest.browser.mod.min.js b/dist/bigint-crypto-utils-latest.browser.mod.min.js index dc2396b..fc9237d 100644 --- a/dist/bigint-crypto-utils-latest.browser.mod.min.js +++ b/dist/bigint-crypto-utils-latest.browser.mod.min.js @@ -1 +1 @@ -function abs(b){return b=BigInt(b),b>=BigInt(0)?b:-b}function eGcd(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}}function gcd(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<{let d=new Worker(_isProbablyPrimeWorkerURL());d.onmessage=a=>{d.terminate(),c(a.data.isPrime)},d.postMessage({rnd:a,iterations:b})})}function lcm(c,d){return c=BigInt(c),d=BigInt(d),abs(c*d)/gcd(c,d)}function modInv(b,a){let c=eGcd(b,a);return c.b===BigInt(1)?toZn(c.x,a):null}function modPow(c,d,e){if(e=BigInt(e),c=toZn(c,e),d=BigInt(d),d{{let d=[],e=_isProbablyPrimeWorkerURL();for(let f,g=0;g{if(e.data.isPrime){for(let a=0;a>3,f=d-8*e;0a||g{let d;d=new Uint8Array(a),self.crypto.getRandomValues(d),b&&(d[0]|=128),c(d)})}function toZn(b,c){return c=BigInt(c),b=BigInt(b)%c,0>b?b+c:b}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}function _isProbablyPrimeWorkerURL(){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);postMessage({isPrime:b,value:a.data.rnd})}.toString()};})()`;var b=new Blob([a],{type:"text/javascript"});return 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=BigInt(0)?b:-b}function eGcd(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}}function gcd(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<{let e=new Worker(_isProbablyPrimeWorkerURL());e.onmessage=a=>{e.terminate(),c(a.data.isPrime)},e.onmessageerror=a=>{d(a)},e.postMessage({rnd:a,iterations:b,id:0})})}function lcm(c,d){return c=BigInt(c),d=BigInt(d),abs(c*d)/gcd(c,d)}function modInv(b,a){let c=eGcd(b,a);return c.b===BigInt(1)?toZn(c.x,a):null}function modPow(c,d,e){if(e=BigInt(e),c=toZn(c,e),d=BigInt(d),d{let d=[];const e=(e,f)=>{if(e.isPrime){for(let a=0;a{let c=fromBuffer(a);try{f.postMessage({rnd:c,iterations:b,id:e.id})}catch(a){}})};{let a=_isProbablyPrimeWorkerURL();for(let b,c=0;ce(a.data,b),d.push(b)}for(let e=0;e{let c=fromBuffer(a);d[e].postMessage({rnd:c,iterations:b,id:e})})})}async function randBetween(a,b=BigInt(1)){if(a<=b)throw new Error("max must be > min");const c=a-b;let d,e=bitLength(c);do{let a=await randBits(e);d=fromBuffer(a)}while(d>c);return d+b}async function randBits(a,b=!1){var c=Math.ceil;const d=c(a/8);let e=await randBytes(d,!1);if(e[0]&=2**(a%8)-1,b){let b=a%8?2**(a%8-1):128;e[0]|=b}return e}async function randBytes(a,b=!1){return new Promise(c=>{let d;d=new Uint8Array(a),self.crypto.getRandomValues(d),b&&(d[0]|=128),c(d)})}function toZn(b,c){return c=BigInt(c),b=BigInt(b)%c,0>b?b+c:b}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}function _isProbablyPrimeWorkerURL(){let a=`'use strict';const eGcd = ${eGcd.toString()};const modInv = ${modInv.toString()};const modPow = ${modPow.toString()};const toZn = ${toZn.toString()};const randBits = ${randBits.toString()};const randBytes = ${randBytes.toString()};const randBetween = ${randBetween.toString()};const isProbablyPrime = ${_isProbablyPrime.toString()};${bitLength.toString()}${fromBuffer.toString()}`;a+=`onmessage = ${async function(a){const b=await isProbablyPrime(a.data.rnd,a.data.iterations);postMessage({isPrime:b,value:a.data.rnd,id:a.data.id})}.toString()};`,a=`(() => {${a}})()`;var b=new Blob([a],{type:"text/javascript"});return 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 worker = new Worker(__filename); + + worker.on('message', (data) => { + worker.terminate(); + resolve(data.isPrime); + }); + + worker.on('error', reject); + + worker.postMessage({ + 'rnd': w, + 'iterations': iterations, + 'id': 0 + + }); + }); + } else { + return _isProbablyPrime(w, iterations); + } } } @@ -174,13 +196,58 @@ function modPow(a, b, n) { * @returns {Promise} A promise that resolves to a bigint probable prime of bitLength bits */ async function prime(bitLength, iterations = 16) { - return new Promise(async (resolve) => { - { - let rnd = BigInt(0); - do { - rnd = fromBuffer(await randBytes(bitLength / 8, true)); - } while (! await isProbablyPrime(rnd, iterations)); - resolve(rnd); + if (!_useWorkers) { + let rnd = BigInt(0); + do { + rnd = fromBuffer(await randBytes(bitLength / 8, true)); + } while (! await _isProbablyPrime(rnd, iterations)); + return rnd; + } + return new Promise((resolve) => { + let workerList = []; + const _onmessage = (msg, newWorker) => { + if (msg.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(msg.value); + } else { // if a composite is found, make the worker test another random number + randBits(bitLength, true).then((buf) => { + let rnd = fromBuffer(buf); + try { + newWorker.postMessage({ + 'rnd': rnd, + 'iterations': iterations, + 'id': msg.id + }); + } catch (error) { + // The worker has already terminated. There is nothing to handle here + } + }); + } + }; + { // Node.js + const { cpus } = require('os'); + const { Worker } = require('worker_threads'); + for (let i = 0; i < cpus().length; i++) { + let newWorker = new Worker(__filename); + newWorker.on('message', (msg) => _onmessage(msg, newWorker)); + workerList.push(newWorker); + } + } + for (let i = 0; i < workerList.length; i++) { + randBits(bitLength, true).then((buf) => { + let rnd = fromBuffer(buf); + workerList[i].postMessage({ + 'rnd': rnd, + 'iterations': iterations, + 'id': i + }); + }); } }); } @@ -192,25 +259,36 @@ async function prime(bitLength, iterations = 16) { * * @returns {Promise} A promise that resolves to a cryptographically secure random bigint between [min,max] */ -async function randBetween(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; - } - +async function randBetween(max, min = BigInt(1)) { + if (max <= min) throw new Error('max must be > min'); + const interval = max - min; + let bitLen = bitLength(interval); let rnd; do { - let buf = await randBytes(byteLength); - // remove extra bits - if (remaining > 0) - buf[0] = buf[0] & extraBits; + let buf = await randBits(bitLen); rnd = fromBuffer(buf); - } while (rnd > max || rnd < min); - return rnd; + } while (rnd > interval); + return rnd + min; +} + +/** + * Secure random bits for both node and browsers. Node version uses crypto.randomFill() and browser one self.crypto.getRandomValues() + * + * @param {number} bitLength The desired number of random bits + * @param {boolean} forceLength If we want to force the output to have a specific bit length. It basically forces the msb to be 1 + * + * @returns {Promise} A promise that resolves to a Buffer/UInt8Array filled with cryptographically secure random bits + */ +async function randBits(bitLength, forceLength = false) { + const byteLength = Math.ceil(bitLength / 8); + let rndBytes = await randBytes(byteLength, false); + // Fill with 0's the extra birs + rndBytes[0] = rndBytes[0] & (2 ** (bitLength % 8) - 1); + if (forceLength) { + let mask = (bitLength % 8) ? 2 ** ((bitLength % 8) - 1) : 128; + rndBytes[0] = rndBytes[0] | mask; + } + return rndBytes; } /** @@ -574,7 +652,7 @@ async function _isProbablyPrime(w, iterations = 16) { let m = (w - BigInt(1)) / (BigInt(2) ** a); loop: do { - let b = await randBetween(w - BigInt(1), 2); + let b = await randBetween(w - BigInt(1), BigInt(2)); let z = modPow(b, m, w); if (z === BigInt(1) || z === w - BigInt(1)) continue; @@ -592,6 +670,38 @@ async function _isProbablyPrime(w, iterations = 16) { return true; } +let _useWorkers = true; + +{ + _useWorkers = (function _workers() { + try { + require.resolve('worker_threads'); + return true; + } catch (e) { + console.log(`[bigint-crypto-utils] WARNING: +This node version doesn't support worker_threads. You should enable them in order to greately speedup the generation of big prime numbers. + · With Node 11 it is enabled by default (consider upgrading). + · With Node 10, starting with 10.5.0, you can enable worker_threads at runtime executing node --experimental-worker `); + return false; + } + })(); +} + +if (_useWorkers) { // node.js + const { parentPort, isMainThread } = require('worker_threads'); + if (!isMainThread) { // worker + parentPort.on('message', async function (data) { // Let's start once we are called + // data = {rnd: , iterations: } + const isPrime = await _isProbablyPrime(data.rnd, data.iterations); + parentPort.postMessage({ + 'isPrime': isPrime, + 'value': data.rnd, + 'id': data.id + }); + }); + } +} + exports.abs = abs; exports.eGcd = eGcd; exports.gcd = gcd; @@ -601,5 +711,6 @@ exports.modInv = modInv; exports.modPow = modPow; exports.prime = prime; exports.randBetween = randBetween; +exports.randBits = randBits; exports.randBytes = randBytes; exports.toZn = toZn; diff --git a/package.json b/package.json index 36ee81c..777689f 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "test": "./test" }, "scripts": { - "test": "mocha --timeout 600000", + "test": "node --experimental-worker node_modules/mocha/bin/_mocha --timeout 600000", "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", @@ -46,4 +46,4 @@ "rollup-plugin-node-resolve": "^4.2.3", "rollup-plugin-replace": "^2.2.0" } -} +} \ No newline at end of file diff --git a/src/main.js b/src/main.js index 951d4ae..8ec9e15 100644 --- a/src/main.js +++ b/src/main.js @@ -87,7 +87,8 @@ export function gcd(a, b) { } /** - * 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) + * 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 @@ -95,8 +96,31 @@ export function gcd(a, b) { * @return {Promise} A promise that resolve to a boolean that is either true (a probably prime number) or false (definitely composite) */ export async function isProbablyPrime(w, iterations = 16) { - if (process.browser) { - return new Promise(resolve => { + if (!process.browser) { // Node.js + if (_useWorkers) { + const { Worker } = require('worker_threads'); + return new Promise((resolve, reject) => { + let worker = new Worker(__filename); + + worker.on('message', (data) => { + worker.terminate(); + resolve(data.isPrime); + }); + + worker.on('error', reject); + + worker.postMessage({ + 'rnd': w, + 'iterations': iterations, + 'id': 0 + + }); + }); + } else { + return _isProbablyPrime(w, iterations); + } + } else { // browser + return new Promise((resolve, reject) => { let worker = new Worker(_isProbablyPrimeWorkerURL()); worker.onmessage = (event) => { @@ -104,13 +128,16 @@ export async function isProbablyPrime(w, iterations = 16) { resolve(event.data.isPrime); }; + worker.onmessageerror = (event) => { + reject(event); + }; + worker.postMessage({ 'rnd': w, - 'iterations': iterations + 'iterations': iterations, + 'id': 0 }); }); - } else { - return _isProbablyPrime(w, iterations); } } @@ -186,49 +213,65 @@ export function modPow(a, b, n) { * @returns {Promise} A promise that resolves to a bigint probable prime of bitLength bits */ export async function prime(bitLength, iterations = 16) { - return new Promise(async (resolve) => { - if (process.browser) { - let workerList = []; + if (!process.browser && !_useWorkers) { + let rnd = BigInt(0); + do { + rnd = fromBuffer(await randBytes(bitLength / 8, true)); + } while (! await _isProbablyPrime(rnd, iterations)); + return rnd; + } + return new Promise((resolve) => { + let workerList = []; + const _onmessage = (msg, newWorker) => { + if (msg.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(msg.value); + } else { // if a composite is found, make the worker test another random number + randBits(bitLength, true).then((buf) => { + let rnd = fromBuffer(buf); + try { + newWorker.postMessage({ + 'rnd': rnd, + 'iterations': iterations, + 'id': msg.id + }); + } catch (error) { + // The worker has already terminated. There is nothing to handle here + } + }); + } + }; + if (process.browser) { //browser let workerURL = _isProbablyPrimeWorkerURL(); for (let i = 0; i < self.navigator.hardwareConcurrency; i++) { let newWorker = new Worker(workerURL); - 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 - }); - } - }; + newWorker.onmessage = (event) => _onmessage(event.data, newWorker); 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 { // Node.js + const { cpus } = require('os'); + const { Worker } = require('worker_threads'); + for (let i = 0; i < cpus().length; i++) { + let newWorker = new Worker(__filename); + newWorker.on('message', (msg) => _onmessage(msg, newWorker)); + workerList.push(newWorker); } - - } else { - let rnd = BigInt(0); - do { - rnd = fromBuffer(await randBytes(bitLength / 8, true)); - } while (! await isProbablyPrime(rnd, iterations)); - resolve(rnd); + } + for (let i = 0; i < workerList.length; i++) { + randBits(bitLength, true).then((buf) => { + let rnd = fromBuffer(buf); + workerList[i].postMessage({ + 'rnd': rnd, + 'iterations': iterations, + 'id': i + }); + }); } }); } @@ -240,25 +283,36 @@ export async function prime(bitLength, iterations = 16) { * * @returns {Promise} A promise that resolves to a cryptographically secure random bigint between [min,max] */ -export async function randBetween(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; - } - +export async function randBetween(max, min = BigInt(1)) { + if (max <= min) throw new Error('max must be > min'); + const interval = max - min; + let bitLen = bitLength(interval); let rnd; do { - let buf = await randBytes(byteLength); - // remove extra bits - if (remaining > 0) - buf[0] = buf[0] & extraBits; + let buf = await randBits(bitLen); rnd = fromBuffer(buf); - } while (rnd > max || rnd < min); - return rnd; + } while (rnd > interval); + return rnd + min; +} + +/** + * Secure random bits for both node and browsers. Node version uses crypto.randomFill() and browser one self.crypto.getRandomValues() + * + * @param {number} bitLength The desired number of random bits + * @param {boolean} forceLength If we want to force the output to have a specific bit length. It basically forces the msb to be 1 + * + * @returns {Promise} A promise that resolves to a Buffer/UInt8Array filled with cryptographically secure random bits + */ +export async function randBits(bitLength, forceLength = false) { + const byteLength = Math.ceil(bitLength / 8); + let rndBytes = await randBytes(byteLength, false); + // Fill with 0's the extra birs + rndBytes[0] = rndBytes[0] & (2 ** (bitLength % 8) - 1); + if (forceLength) { + let mask = (bitLength % 8) ? 2 ** ((bitLength % 8) - 1) : 128; + rndBytes[0] = rndBytes[0] | mask; + } + return rndBytes; } /** @@ -329,16 +383,21 @@ function bitLength(a) { } function _isProbablyPrimeWorkerURL() { - async function _onmessage(event) { // Let's start once we are called + // Let's us first add all the required functions + let workerCode = `'use strict';const eGcd = ${eGcd.toString()};const modInv = ${modInv.toString()};const modPow = ${modPow.toString()};const toZn = ${toZn.toString()};const randBits = ${randBits.toString()};const randBytes = ${randBytes.toString()};const randBetween = ${randBetween.toString()};const isProbablyPrime = ${_isProbablyPrime.toString()};${bitLength.toString()}${fromBuffer.toString()}`; + + const _onmessage = async function (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 + 'value': event.data.rnd, + 'id': event.data.id }); - } + }; + workerCode += `onmessage = ${_onmessage.toString()};`; - 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()};})()`; + workerCode = `(() => {${workerCode}})()`; // encapsulate IIFE var _blob = new Blob([workerCode], { type: 'text/javascript' }); @@ -646,7 +705,7 @@ async function _isProbablyPrime(w, iterations = 16) { let m = (w - BigInt(1)) / (BigInt(2) ** a); loop: do { - let b = await randBetween(w - BigInt(1), 2); + let b = await randBetween(w - BigInt(1), BigInt(2)); let z = modPow(b, m, w); if (z === BigInt(1) || z === w - BigInt(1)) continue; @@ -662,4 +721,36 @@ async function _isProbablyPrime(w, iterations = 16) { } while (--iterations); return true; +} + +let _useWorkers = true; + +if (!process.browser) { + _useWorkers = (function _workers() { + try { + require.resolve('worker_threads'); + return true; + } catch (e) { + console.log(`[bigint-crypto-utils] WARNING: +This node version doesn't support worker_threads. You should enable them in order to greately speedup the generation of big prime numbers. + · With Node 11 it is enabled by default (consider upgrading). + · With Node 10, starting with 10.5.0, you can enable worker_threads at runtime executing node --experimental-worker `); + return false; + } + })(); +} + +if (!process.browser && _useWorkers) { // node.js + const { parentPort, isMainThread } = require('worker_threads'); + if (!isMainThread) { // worker + parentPort.on('message', async function (data) { // Let's start once we are called + // data = {rnd: , iterations: } + const isPrime = await _isProbablyPrime(data.rnd, data.iterations); + parentPort.postMessage({ + 'isPrime': isPrime, + 'value': data.rnd, + 'id': data.id + }); + }); + } } \ No newline at end of file