ready for node worker threads

This commit is contained in:
Juan Hernández Serrano 2019-04-20 22:11:44 +02:00
parent de00327fd8
commit f2d6043ee7
8 changed files with 487 additions and 208 deletions

View File

@ -91,7 +91,8 @@ Take positive integers a, b as input, and return a triple (g, x, y), such that a
<dd><p>Greatest-common divisor of two integers based on the iterative binary algorithm.</p> <dd><p>Greatest-common divisor of two integers based on the iterative binary algorithm.</p>
</dd> </dd>
<dt><a href="#isProbablyPrime">isProbablyPrime(w, iterations)</a><code>Promise</code></dt> <dt><a href="#isProbablyPrime">isProbablyPrime(w, iterations)</a><code>Promise</code></dt>
<dd><p>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)</p> <dd><p>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)</p>
</dd> </dd>
<dt><a href="#lcm">lcm(a, b)</a><code>bigint</code></dt> <dt><a href="#lcm">lcm(a, b)</a><code>bigint</code></dt>
<dd><p>The least common multiple computed as abs(a*b)/gcd(a,b)</p> <dd><p>The least common multiple computed as abs(a*b)/gcd(a,b)</p>
@ -110,6 +111,9 @@ main process, and it can be much faster (if several cores or cpu are available).
<dt><a href="#randBetween">randBetween(max, min)</a><code>Promise</code></dt> <dt><a href="#randBetween">randBetween(max, min)</a><code>Promise</code></dt>
<dd><p>Returns a cryptographically secure random integer between [min,max]</p> <dd><p>Returns a cryptographically secure random integer between [min,max]</p>
</dd> </dd>
<dt><a href="#randBits">randBits(bitLength, forceLength)</a><code>Promise</code></dt>
<dd><p>Secure random bits for both node and browsers. Node version uses crypto.randomFill() and browser one self.crypto.getRandomValues()</p>
</dd>
<dt><a href="#randBytes">randBytes(byteLength, forceLength)</a><code>Promise</code></dt> <dt><a href="#randBytes">randBytes(byteLength, forceLength)</a><code>Promise</code></dt>
<dd><p>Secure random bytes for both node and browsers. Node version uses crypto.randomFill() and browser one self.crypto.getRandomValues()</p> <dd><p>Secure random bytes for both node and browsers. Node version uses crypto.randomFill() and browser one self.crypto.getRandomValues()</p>
</dd> </dd>
@ -167,7 +171,8 @@ Greatest-common divisor of two integers based on the iterative binary algorithm.
<a name="isProbablyPrime"></a> <a name="isProbablyPrime"></a>
## isProbablyPrime(w, iterations) ⇒ <code>Promise</code> ## isProbablyPrime(w, iterations) ⇒ <code>Promise</code>
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 **Kind**: global function
**Returns**: <code>Promise</code> - A promise that resolve to a boolean that is either true (a probably prime number) or false (definitely composite) **Returns**: <code>Promise</code> - 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 | <code>bigint</code> | Returned value will be <= max | | max | <code>bigint</code> | Returned value will be <= max |
| min | <code>bigint</code> | Returned value will be >= min | | min | <code>bigint</code> | Returned value will be >= min |
<a name="randBits"></a>
## randBits(bitLength, forceLength) ⇒ <code>Promise</code>
Secure random bits for both node and browsers. Node version uses crypto.randomFill() and browser one self.crypto.getRandomValues()
**Kind**: global function
**Returns**: <code>Promise</code> - A promise that resolves to a Buffer/UInt8Array filled with cryptographically secure random bits
| Param | Type | Description |
| --- | --- | --- |
| bitLength | <code>number</code> | The desired number of random bits |
| forceLength | <code>boolean</code> | If we want to force the output to have a specific bit length. It basically forces the msb to be 1 |
<a name="randBytes"></a> <a name="randBytes"></a>
## randBytes(byteLength, forceLength) ⇒ <code>Promise</code> ## randBytes(byteLength, forceLength) ⇒ <code>Promise</code>

View File

@ -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 {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 * @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) * @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) { async function isProbablyPrime(w, iterations = 16) {
{ { // browser
return new Promise(resolve => { return new Promise((resolve, reject) => {
let worker = new Worker(_isProbablyPrimeWorkerURL()); let worker = new Worker(_isProbablyPrimeWorkerURL());
worker.onmessage = (event) => { worker.onmessage = (event) => {
@ -105,9 +106,14 @@ var bigintCryptoUtils = (function (exports) {
resolve(event.data.isPrime); resolve(event.data.isPrime);
}; };
worker.onmessageerror = (event) => {
reject(event);
};
worker.postMessage({ worker.postMessage({
'rnd': w, '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 * @returns {Promise} A promise that resolves to a bigint probable prime of bitLength bits
*/ */
async function prime(bitLength, iterations = 16) { async function prime(bitLength, iterations = 16) {
return new Promise(async (resolve) => { return new Promise((resolve) => {
{ let workerList = [];
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(); let workerURL = _isProbablyPrimeWorkerURL();
for (let i = 0; i < self.navigator.hardwareConcurrency; i++) { for (let i = 0; i < self.navigator.hardwareConcurrency; i++) {
let newWorker = new Worker(workerURL); let newWorker = new Worker(workerURL);
newWorker.onmessage = async (event) => { newWorker.onmessage = (event) => _onmessage(event.data, newWorker);
if (event.data.isPrime) {
// if a prime number has been found, stop all the workers, and return it
for (let j = 0; j < workerList.length; j++) {
workerList[j].terminate();
}
while (workerList.length) {
workerList.pop();
}
resolve(event.data.value);
} else { // if a composite is found, make the worker test another random number
let rnd = BigInt(0);
rnd = fromBuffer(await randBytes(bitLength / 8, true));
newWorker.postMessage({
'rnd': rnd,
'iterations': iterations
});
}
};
workerList.push(newWorker); workerList.push(newWorker);
} }
}
for (const worker of workerList) { for (let i = 0; i < workerList.length; i++) {
let rnd = BigInt(0); randBits(bitLength, true).then((buf) => {
rnd = fromBuffer(await randBytes(bitLength / 8, true)); let rnd = fromBuffer(buf);
worker.postMessage({ workerList[i].postMessage({
'rnd': rnd, '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] * @returns {Promise} A promise that resolves to a cryptographically secure random bigint between [min,max]
*/ */
async function randBetween(max, min = 1) { async function randBetween(max, min = BigInt(1)) {
let bitLen = bitLength(max); if (max <= min) throw new Error('max must be > min');
let byteLength = bitLen >> 3; const interval = max - min;
let remaining = bitLen - (byteLength * 8); let bitLen = bitLength(interval);
let extraBits;
if (remaining > 0) {
byteLength++;
extraBits = 2 ** remaining - 1;
}
let rnd; let rnd;
do { do {
let buf = await randBytes(byteLength); let buf = await randBits(bitLen);
// remove extra bits
if (remaining > 0)
buf[0] = buf[0] & extraBits;
rnd = fromBuffer(buf); rnd = fromBuffer(buf);
} while (rnd > max || rnd < min); } while (rnd > interval);
return rnd; 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() { 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: <bigint>, iterations: <number>} // event.data = {rnd: <bigint>, iterations: <number>}
const isPrime = await isProbablyPrime(event.data.rnd, event.data.iterations); const isPrime = await isProbablyPrime(event.data.rnd, event.data.iterations);
postMessage({ postMessage({
'isPrime': isPrime, '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' }); var _blob = new Blob([workerCode], { type: 'text/javascript' });
@ -629,7 +658,7 @@ var bigintCryptoUtils = (function (exports) {
let m = (w - BigInt(1)) / (BigInt(2) ** a); let m = (w - BigInt(1)) / (BigInt(2) ** a);
loop: do { 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); let z = modPow(b, m, w);
if (z === BigInt(1) || z === w - BigInt(1)) if (z === BigInt(1) || z === w - BigInt(1))
continue; continue;
@ -656,6 +685,7 @@ var bigintCryptoUtils = (function (exports) {
exports.modPow = modPow; exports.modPow = modPow;
exports.prime = prime; exports.prime = prime;
exports.randBetween = randBetween; exports.randBetween = randBetween;
exports.randBits = randBits;
exports.randBytes = randBytes; exports.randBytes = randBytes;
exports.toZn = toZn; exports.toZn = toZn;

View File

@ -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<<f}async function f(a,b=16){return new Promise(c=>{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<BigInt(0))return g(h(d,c(e),f),f);let i=BigInt(1),j=d;for(;0<e;){var l=e%BigInt(2);e/=BigInt(2),l==BigInt(1)&&(i*=j,i%=f),j*=j,j%=f}return i}async function i(a,b=1){let c,d=m(a),e=d>>3,f=d-8*e;0<f&&(e++,c=2**f-1);let g;do{let a=await j(e);0<f&&(a[0]&=c),g=l(a)}while(g>a||g<b);return g}async function j(a,b=!1){return new Promise(c=>{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(8))+a}return b}function m(b){let c=1;do c++;while((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<e.length&&BigInt(e[a])<=c;a++){const b=BigInt(e[a]);if(c===b)return!0;if(c%b===BigInt(0))return!1}let f=BigInt(0),g=c-BigInt(1);for(;g%BigInt(2)===BigInt(0);)g/=BigInt(2),++f;let j=(c-BigInt(1))/BigInt(2)**f;loop:do{let a=await i(c-BigInt(1),2),b=h(a,j,c);if(b===BigInt(1)||b===c-BigInt(1))continue;for(let a=1;a<f;a++){if(b=h(b,BigInt(2),c),b===c-BigInt(1))continue loop;if(b===BigInt(1))break}return!1}while(--b);return!0}return a.abs=c,a.eGcd=d,a.gcd=e,a.isProbablyPrime=f,a.lcm=function(d,f){return d=BigInt(d),f=BigInt(f),c(d*f)/e(d,f)},a.modInv=g,a.modPow=h,a.prime=async function(a,b=16){return new Promise(async c=>{{let d=[],e=n();for(let f,g=0;g<self.navigator.hardwareConcurrency;g++)f=new Worker(e),f.onmessage=async e=>{if(e.data.isPrime){for(let a=0;a<d.length;a++)d[a].terminate();for(;d.length;)d.pop();c(e.data.value)}else{let c=BigInt(0);c=l((await j(a/8,!0))),f.postMessage({rnd:c,iterations:b})}},d.push(f);for(const c of d){let d=BigInt(0);d=l((await j(a/8,!0))),c.postMessage({rnd:d,iterations:b})}}})},a.randBetween=i,a.randBytes=j,a.toZn=k,a}({}); 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<<f}async function f(a,b=16){return new Promise((c,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<BigInt(0))return g(h(d,c(e),f),f);let i=BigInt(1),j=d;for(;0<e;){var k=e%BigInt(2);e/=BigInt(2),k==BigInt(1)&&(i*=j,i%=f),j*=j,j%=f}return i}async function i(a,b=BigInt(1)){if(a<=b)throw new Error("max must be > 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(8))+a}return b}function n(b){let c=1;do c++;while((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<e.length&&BigInt(e[a])<=c;a++){const b=BigInt(e[a]);if(c===b)return!0;if(c%b===BigInt(0))return!1}let f=BigInt(0),g=c-BigInt(1);for(;g%BigInt(2)===BigInt(0);)g/=BigInt(2),++f;let j=(c-BigInt(1))/BigInt(2)**f;loop:do{let a=await i(c-BigInt(1),BigInt(2)),b=h(a,j,c);if(b===BigInt(1)||b===c-BigInt(1))continue;for(let a=1;a<f;a++){if(b=h(b,BigInt(2),c),b===c-BigInt(1))continue loop;if(b===BigInt(1))break}return!1}while(--b);return!0}return a.abs=c,a.eGcd=d,a.gcd=e,a.isProbablyPrime=f,a.lcm=function(d,f){return d=BigInt(d),f=BigInt(f),c(d*f)/e(d,f)},a.modInv=g,a.modPow=h,a.prime=async function(a,b=16){return new Promise(c=>{let d=[];const e=(e,f)=>{if(e.isPrime){for(let a=0;a<d.length;a++)d[a].terminate();for(;d.length;)d.pop();c(e.value)}else j(a,!0).then(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;c<self.navigator.hardwareConcurrency;c++)b=new Worker(a),b.onmessage=a=>e(a.data,b),d.push(b)}for(let e=0;e<d.length;e++)j(a,!0).then(a=>{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}({});

View File

@ -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 {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 * @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) * @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) { async function isProbablyPrime(w, iterations = 16) {
{ { // browser
return new Promise(resolve => { return new Promise((resolve, reject) => {
let worker = new Worker(_isProbablyPrimeWorkerURL()); let worker = new Worker(_isProbablyPrimeWorkerURL());
worker.onmessage = (event) => { worker.onmessage = (event) => {
@ -102,9 +103,14 @@ async function isProbablyPrime(w, iterations = 16) {
resolve(event.data.isPrime); resolve(event.data.isPrime);
}; };
worker.onmessageerror = (event) => {
reject(event);
};
worker.postMessage({ worker.postMessage({
'rnd': w, '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 * @returns {Promise} A promise that resolves to a bigint probable prime of bitLength bits
*/ */
async function prime(bitLength, iterations = 16) { async function prime(bitLength, iterations = 16) {
return new Promise(async (resolve) => { return new Promise((resolve) => {
{ let workerList = [];
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(); let workerURL = _isProbablyPrimeWorkerURL();
for (let i = 0; i < self.navigator.hardwareConcurrency; i++) { for (let i = 0; i < self.navigator.hardwareConcurrency; i++) {
let newWorker = new Worker(workerURL); let newWorker = new Worker(workerURL);
newWorker.onmessage = async (event) => { newWorker.onmessage = (event) => _onmessage(event.data, newWorker);
if (event.data.isPrime) {
// if a prime number has been found, stop all the workers, and return it
for (let j = 0; j < workerList.length; j++) {
workerList[j].terminate();
}
while (workerList.length) {
workerList.pop();
}
resolve(event.data.value);
} else { // if a composite is found, make the worker test another random number
let rnd = BigInt(0);
rnd = fromBuffer(await randBytes(bitLength / 8, true));
newWorker.postMessage({
'rnd': rnd,
'iterations': iterations
});
}
};
workerList.push(newWorker); workerList.push(newWorker);
} }
}
for (const worker of workerList) { for (let i = 0; i < workerList.length; i++) {
let rnd = BigInt(0); randBits(bitLength, true).then((buf) => {
rnd = fromBuffer(await randBytes(bitLength / 8, true)); let rnd = fromBuffer(buf);
worker.postMessage({ workerList[i].postMessage({
'rnd': rnd, '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] * @returns {Promise} A promise that resolves to a cryptographically secure random bigint between [min,max]
*/ */
async function randBetween(max, min = 1) { async function randBetween(max, min = BigInt(1)) {
let bitLen = bitLength(max); if (max <= min) throw new Error('max must be > min');
let byteLength = bitLen >> 3; const interval = max - min;
let remaining = bitLen - (byteLength * 8); let bitLen = bitLength(interval);
let extraBits;
if (remaining > 0) {
byteLength++;
extraBits = 2 ** remaining - 1;
}
let rnd; let rnd;
do { do {
let buf = await randBytes(byteLength); let buf = await randBits(bitLen);
// remove extra bits
if (remaining > 0)
buf[0] = buf[0] & extraBits;
rnd = fromBuffer(buf); rnd = fromBuffer(buf);
} while (rnd > max || rnd < min); } while (rnd > interval);
return rnd; 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() { 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: <bigint>, iterations: <number>} // event.data = {rnd: <bigint>, iterations: <number>}
const isPrime = await isProbablyPrime(event.data.rnd, event.data.iterations); const isPrime = await isProbablyPrime(event.data.rnd, event.data.iterations);
postMessage({ postMessage({
'isPrime': isPrime, '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' }); 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); let m = (w - BigInt(1)) / (BigInt(2) ** a);
loop: do { 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); let z = modPow(b, m, w);
if (z === BigInt(1) || z === w - BigInt(1)) if (z === BigInt(1) || z === w - BigInt(1))
continue; continue;
@ -644,4 +673,4 @@ async function _isProbablyPrime(w, iterations = 16) {
return true; 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 };

View File

@ -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<<e}async function isProbablyPrime(a,b=16){return new Promise(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<BigInt(0))return modInv(modPow(c,abs(d),e),e);let f=BigInt(1),g=c;for(;0<d;){var h=d%BigInt(2);d/=BigInt(2),h==BigInt(1)&&(f*=g,f%=e),g*=g,g%=e}return f}async function prime(a,b=16){return new Promise(async c=>{{let d=[],e=_isProbablyPrimeWorkerURL();for(let f,g=0;g<self.navigator.hardwareConcurrency;g++)f=new Worker(e),f.onmessage=async e=>{if(e.data.isPrime){for(let a=0;a<d.length;a++)d[a].terminate();for(;d.length;)d.pop();c(e.data.value)}else{let c=BigInt(0);c=fromBuffer((await randBytes(a/8,!0))),f.postMessage({rnd:c,iterations:b})}},d.push(f);for(const c of d){let d=BigInt(0);d=fromBuffer((await randBytes(a/8,!0))),c.postMessage({rnd:d,iterations:b})}}})}async function randBetween(a,b=1){let c,d=bitLength(a),e=d>>3,f=d-8*e;0<f&&(e++,c=2**f-1);let g;do{let a=await randBytes(e);0<f&&(a[0]&=c),g=fromBuffer(a)}while(g>a||g<b);return g}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(8))+a}return b}function bitLength(b){let c=1;do c++;while((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<e.length&&BigInt(e[a])<=c;a++){const b=BigInt(e[a]);if(c===b)return!0;if(c%b===BigInt(0))return!1}let f=BigInt(0),g=c-BigInt(1);for(;g%BigInt(2)===BigInt(0);)g/=BigInt(2),++f;let h=(c-BigInt(1))/BigInt(2)**f;loop:do{let a=await randBetween(c-BigInt(1),2),b=modPow(a,h,c);if(b===BigInt(1)||b===c-BigInt(1))continue;for(let a=1;a<f;a++){if(b=modPow(b,BigInt(2),c),b===c-BigInt(1))continue loop;if(b===BigInt(1))break}return!1}while(--b);return!0}export{abs,eGcd,gcd,isProbablyPrime,lcm,modInv,modPow,prime,randBetween,randBytes,toZn}; 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<<e}async function isProbablyPrime(a,b=16){return new Promise((c,d)=>{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<BigInt(0))return modInv(modPow(c,abs(d),e),e);let f=BigInt(1),g=c;for(;0<d;){var h=d%BigInt(2);d/=BigInt(2),h==BigInt(1)&&(f*=g,f%=e),g*=g,g%=e}return f}async function prime(a,b=16){return new Promise(c=>{let d=[];const e=(e,f)=>{if(e.isPrime){for(let a=0;a<d.length;a++)d[a].terminate();for(;d.length;)d.pop();c(e.value)}else randBits(a,!0).then(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;c<self.navigator.hardwareConcurrency;c++)b=new Worker(a),b.onmessage=a=>e(a.data,b),d.push(b)}for(let e=0;e<d.length;e++)randBits(a,!0).then(a=>{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(8))+a}return b}function bitLength(b){let c=1;do c++;while((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<e.length&&BigInt(e[a])<=c;a++){const b=BigInt(e[a]);if(c===b)return!0;if(c%b===BigInt(0))return!1}let f=BigInt(0),g=c-BigInt(1);for(;g%BigInt(2)===BigInt(0);)g/=BigInt(2),++f;let h=(c-BigInt(1))/BigInt(2)**f;loop:do{let a=await randBetween(c-BigInt(1),BigInt(2)),b=modPow(a,h,c);if(b===BigInt(1)||b===c-BigInt(1))continue;for(let a=1;a<f;a++){if(b=modPow(b,BigInt(2),c),b===c-BigInt(1))continue loop;if(b===BigInt(1))break}return!1}while(--b);return!0}export{abs,eGcd,gcd,isProbablyPrime,lcm,modInv,modPow,prime,randBetween,randBits,randBytes,toZn};

View File

@ -89,7 +89,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 {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 * @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
@ -97,8 +98,29 @@ 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) * @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) { async function isProbablyPrime(w, iterations = 16) {
{ { // Node.js
return _isProbablyPrime(w, iterations); 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);
}
} }
} }
@ -174,13 +196,58 @@ function modPow(a, b, n) {
* @returns {Promise} A promise that resolves to a bigint probable prime of bitLength bits * @returns {Promise} A promise that resolves to a bigint probable prime of bitLength bits
*/ */
async function prime(bitLength, iterations = 16) { async function prime(bitLength, iterations = 16) {
return new Promise(async (resolve) => { if (!_useWorkers) {
{ let rnd = BigInt(0);
let rnd = BigInt(0); do {
do { rnd = fromBuffer(await randBytes(bitLength / 8, true));
rnd = fromBuffer(await randBytes(bitLength / 8, true)); } while (! await _isProbablyPrime(rnd, iterations));
} while (! await isProbablyPrime(rnd, iterations)); return rnd;
resolve(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] * @returns {Promise} A promise that resolves to a cryptographically secure random bigint between [min,max]
*/ */
async function randBetween(max, min = 1) { async function randBetween(max, min = BigInt(1)) {
let bitLen = bitLength(max); if (max <= min) throw new Error('max must be > min');
let byteLength = bitLen >> 3; const interval = max - min;
let remaining = bitLen - (byteLength * 8); let bitLen = bitLength(interval);
let extraBits;
if (remaining > 0) {
byteLength++;
extraBits = 2 ** remaining - 1;
}
let rnd; let rnd;
do { do {
let buf = await randBytes(byteLength); let buf = await randBits(bitLen);
// remove extra bits
if (remaining > 0)
buf[0] = buf[0] & extraBits;
rnd = fromBuffer(buf); rnd = fromBuffer(buf);
} while (rnd > max || rnd < min); } while (rnd > interval);
return rnd; 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); let m = (w - BigInt(1)) / (BigInt(2) ** a);
loop: do { 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); let z = modPow(b, m, w);
if (z === BigInt(1) || z === w - BigInt(1)) if (z === BigInt(1) || z === w - BigInt(1))
continue; continue;
@ -592,6 +670,38 @@ async function _isProbablyPrime(w, iterations = 16) {
return true; 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: <bigint>, iterations: <number>}
const isPrime = await _isProbablyPrime(data.rnd, data.iterations);
parentPort.postMessage({
'isPrime': isPrime,
'value': data.rnd,
'id': data.id
});
});
}
}
exports.abs = abs; exports.abs = abs;
exports.eGcd = eGcd; exports.eGcd = eGcd;
exports.gcd = gcd; exports.gcd = gcd;
@ -601,5 +711,6 @@ exports.modInv = modInv;
exports.modPow = modPow; exports.modPow = modPow;
exports.prime = prime; exports.prime = prime;
exports.randBetween = randBetween; exports.randBetween = randBetween;
exports.randBits = randBits;
exports.randBytes = randBytes; exports.randBytes = randBytes;
exports.toZn = toZn; exports.toZn = toZn;

View File

@ -28,7 +28,7 @@
"test": "./test" "test": "./test"
}, },
"scripts": { "scripts": {
"test": "mocha --timeout 600000", "test": "node --experimental-worker node_modules/mocha/bin/_mocha --timeout 600000",
"build": "node build/build.rollup.js", "build": "node build/build.rollup.js",
"build:browserTests": "node build/build.browser.tests.js", "build:browserTests": "node build/build.browser.tests.js",
"build:docs": "jsdoc2md --template=README.hbs --files ./src/main.js > README.md", "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-node-resolve": "^4.2.3",
"rollup-plugin-replace": "^2.2.0" "rollup-plugin-replace": "^2.2.0"
} }
} }

View File

@ -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 {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 * @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) * @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) { export async function isProbablyPrime(w, iterations = 16) {
if (process.browser) { if (!process.browser) { // Node.js
return new Promise(resolve => { 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()); let worker = new Worker(_isProbablyPrimeWorkerURL());
worker.onmessage = (event) => { worker.onmessage = (event) => {
@ -104,13 +128,16 @@ export async function isProbablyPrime(w, iterations = 16) {
resolve(event.data.isPrime); resolve(event.data.isPrime);
}; };
worker.onmessageerror = (event) => {
reject(event);
};
worker.postMessage({ worker.postMessage({
'rnd': w, '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 * @returns {Promise} A promise that resolves to a bigint probable prime of bitLength bits
*/ */
export async function prime(bitLength, iterations = 16) { export async function prime(bitLength, iterations = 16) {
return new Promise(async (resolve) => { if (!process.browser && !_useWorkers) {
if (process.browser) { let rnd = BigInt(0);
let workerList = []; 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(); let workerURL = _isProbablyPrimeWorkerURL();
for (let i = 0; i < self.navigator.hardwareConcurrency; i++) { for (let i = 0; i < self.navigator.hardwareConcurrency; i++) {
let newWorker = new Worker(workerURL); let newWorker = new Worker(workerURL);
newWorker.onmessage = async (event) => { newWorker.onmessage = (event) => _onmessage(event.data, newWorker);
if (event.data.isPrime) {
// if a prime number has been found, stop all the workers, and return it
for (let j = 0; j < workerList.length; j++) {
workerList[j].terminate();
}
while (workerList.length) {
workerList.pop();
}
resolve(event.data.value);
} else { // if a composite is found, make the worker test another random number
let rnd = BigInt(0);
rnd = fromBuffer(await randBytes(bitLength / 8, true));
newWorker.postMessage({
'rnd': rnd,
'iterations': iterations
});
}
};
workerList.push(newWorker); workerList.push(newWorker);
} }
} else { // Node.js
for (const worker of workerList) { const { cpus } = require('os');
let rnd = BigInt(0); const { Worker } = require('worker_threads');
rnd = fromBuffer(await randBytes(bitLength / 8, true)); for (let i = 0; i < cpus().length; i++) {
worker.postMessage({ let newWorker = new Worker(__filename);
'rnd': rnd, newWorker.on('message', (msg) => _onmessage(msg, newWorker));
'iterations': iterations workerList.push(newWorker);
});
} }
}
} else { for (let i = 0; i < workerList.length; i++) {
let rnd = BigInt(0); randBits(bitLength, true).then((buf) => {
do { let rnd = fromBuffer(buf);
rnd = fromBuffer(await randBytes(bitLength / 8, true)); workerList[i].postMessage({
} while (! await isProbablyPrime(rnd, iterations)); 'rnd': rnd,
resolve(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] * @returns {Promise} A promise that resolves to a cryptographically secure random bigint between [min,max]
*/ */
export async function randBetween(max, min = 1) { export async function randBetween(max, min = BigInt(1)) {
let bitLen = bitLength(max); if (max <= min) throw new Error('max must be > min');
let byteLength = bitLen >> 3; const interval = max - min;
let remaining = bitLen - (byteLength * 8); let bitLen = bitLength(interval);
let extraBits;
if (remaining > 0) {
byteLength++;
extraBits = 2 ** remaining - 1;
}
let rnd; let rnd;
do { do {
let buf = await randBytes(byteLength); let buf = await randBits(bitLen);
// remove extra bits
if (remaining > 0)
buf[0] = buf[0] & extraBits;
rnd = fromBuffer(buf); rnd = fromBuffer(buf);
} while (rnd > max || rnd < min); } while (rnd > interval);
return rnd; 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() { 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: <bigint>, iterations: <number>} // event.data = {rnd: <bigint>, iterations: <number>}
const isPrime = await isProbablyPrime(event.data.rnd, event.data.iterations); const isPrime = await isProbablyPrime(event.data.rnd, event.data.iterations);
postMessage({ postMessage({
'isPrime': isPrime, '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' }); 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); let m = (w - BigInt(1)) / (BigInt(2) ** a);
loop: do { 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); let z = modPow(b, m, w);
if (z === BigInt(1) || z === w - BigInt(1)) if (z === BigInt(1) || z === w - BigInt(1))
continue; continue;
@ -662,4 +721,36 @@ async function _isProbablyPrime(w, iterations = 16) {
} while (--iterations); } while (--iterations);
return true; 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: <bigint>, iterations: <number>}
const isPrime = await _isProbablyPrime(data.rnd, data.iterations);
parentPort.postMessage({
'isPrime': isPrime,
'value': data.rnd,
'id': data.id
});
});
}
} }