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