2020-04-07 15:03:30 +00:00
import { bitLength , eGcd , modInv , modPow , toZn } from 'bigint-mod-arith'
export { abs , bitLength , eGcd , gcd , lcm , max , min , modInv , modPow , toZn } from 'bigint-mod-arith'
2020-04-06 11:17:22 +00:00
/ * *
* 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 )
*
2020-04-07 15:03:30 +00:00
* @ param { number | bigint } w An integer to be tested for primality
2020-04-06 11:17:22 +00:00
* @ param { number } [ iterations = 16 ] The number of iterations for the primality test . The value shall be consistent with Table C . 1 , C . 2 or C . 3
*
2020-04-07 17:36:14 +00:00
* @ returns { Promise < boolean > } A promise that resolves to a boolean that is either true ( a probably prime number ) or false ( definitely composite )
2020-04-06 11:17:22 +00:00
* /
2020-04-07 17:36:14 +00:00
function isProbablyPrime ( w , iterations = 16 ) {
2020-04-06 11:17:22 +00:00
if ( typeof w === 'number' ) {
w = BigInt ( w )
}
/* eslint-disable no-lone-blocks */
{ // browser
return new Promise ( ( resolve , reject ) => {
const worker = new Worker ( _isProbablyPrimeWorkerUrl ( ) )
worker . onmessage = ( event ) => {
worker . terminate ( )
resolve ( event . data . isPrime )
}
worker . onmessageerror = ( event ) => {
reject ( event )
}
worker . postMessage ( {
rnd : w ,
iterations : iterations ,
id : 0
} )
} )
}
/* eslint-enable no-lone-blocks */
}
/ * *
* A probably - prime ( Miller - Rabin ) , cryptographically - secure , random - number generator .
* The browser version uses web workers to parallelise prime look up . Therefore , it does not lock the UI
* main process , and it can be much faster ( if several cores or cpu are available ) .
* The node version can also use worker _threads if they are available ( enabled by default with Node 11 and
* and can be enabled at runtime executing node -- experimental - worker with node >= 10.5 . 0 ) .
*
* @ param { number } bitLength The required bit length for the generated prime
* @ param { number } [ iterations = 16 ] The number of iterations for the Miller - Rabin Probabilistic Primality Test
*
2020-04-07 15:03:30 +00:00
* @ returns { Promise < bigint > } A promise that resolves to a bigint probable prime of bitLength bits .
2020-04-06 11:17:22 +00:00
* /
function prime ( bitLength , iterations = 16 ) {
if ( bitLength < 1 ) { throw new RangeError ( ` bitLength MUST be > 0 and it is ${ bitLength } ` ) }
return new Promise ( ( resolve ) => {
const 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
2020-04-07 17:29:23 +00:00
const buf = randBitsSync ( bitLength , true )
2020-04-06 11:17:22 +00:00
const 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
}
}
}
/* eslint-disable no-lone-blocks */
{ // browser
const workerURL = _isProbablyPrimeWorkerUrl ( )
2020-04-07 15:03:30 +00:00
for ( let i = 0 ; i < self . navigator . hardwareConcurrency - 1 ; i ++ ) {
2020-04-06 11:17:22 +00:00
const newWorker = new Worker ( workerURL )
newWorker . onmessage = ( event ) => _onmessage ( event . data , newWorker )
workerList . push ( newWorker )
}
}
/* eslint-enable no-lone-blocks */
for ( let i = 0 ; i < workerList . length ; i ++ ) {
2020-04-07 17:29:23 +00:00
const buf = randBitsSync ( bitLength , true )
2020-04-06 11:17:22 +00:00
const rnd = fromBuffer ( buf )
workerList [ i ] . postMessage ( {
rnd : rnd ,
iterations : iterations ,
id : i
} )
}
} )
}
/ * *
* A probably - prime ( Miller - Rabin ) , cryptographically - secure , random - number generator .
* The sync version is NOT RECOMMENDED since it won 't use workers and thus it' ll be slower and may freeze thw window in browser ' s javascript . Please consider using prime ( ) instead .
*
* @ param { number } bitLength The required bit length for the generated prime
* @ param { number } [ iterations = 16 ] The number of iterations for the Miller - Rabin Probabilistic Primality Test
*
* @ returns { bigint } A bigint probable prime of bitLength bits .
* /
function primeSync ( bitLength , iterations = 16 ) {
if ( bitLength < 1 ) { throw new RangeError ( ` bitLength MUST be > 0 and it is ${ bitLength } ` ) }
2020-04-07 15:03:30 +00:00
let rnd = 0 n
2020-04-06 11:17:22 +00:00
do {
2020-04-07 17:41:29 +00:00
rnd = fromBuffer ( randBitsSync ( bitLength , true ) )
2020-04-06 11:17:22 +00:00
} while ( ! _isProbablyPrime ( rnd , iterations ) )
return rnd
}
/ * *
* Returns a cryptographically secure random integer between [ min , max ]
* @ param { bigint } max Returned value will be <= max
* @ param { bigint } [ min = BigInt ( 1 ) ] Returned value will be >= min
*
* @ returns { bigint } A cryptographically secure random bigint between [ min , max ]
* /
2020-04-07 15:03:30 +00:00
function randBetween ( max , min = 1 n ) {
2020-04-06 11:17:22 +00:00
if ( max <= min ) throw new Error ( 'max must be > min' )
const interval = max - min
const bitLen = bitLength ( interval )
let rnd
do {
2020-04-07 17:29:23 +00:00
const buf = randBitsSync ( bitLen )
2020-04-06 11:17:22 +00:00
rnd = fromBuffer ( buf )
} while ( rnd > interval )
return rnd + min
}
2020-04-07 17:29:23 +00:00
/ * *
* 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 = false ] If we want to force the output to have a specific bit length . It basically forces the msb to be 1
*
* @ returns { Promise < Buffer | Uint8Array > } A Promise that resolves to a Buffer / UInt8Array ( Node . js / Browser ) filled with cryptographically secure random bits
* /
async function randBits ( bitLength , forceLength = false ) {
if ( bitLength < 1 ) {
throw new RangeError ( ` bitLength MUST be > 0 and it is ${ bitLength } ` )
}
const byteLength = Math . ceil ( bitLength / 8 )
const bitLengthMod8 = bitLength % 8
const rndBytes = await randBytes ( byteLength , false )
if ( bitLengthMod8 ) {
// Fill with 0's the extra bits
rndBytes [ 0 ] = rndBytes [ 0 ] & ( 2 * * bitLengthMod8 - 1 )
}
if ( forceLength ) {
const mask = bitLengthMod8 ? 2 * * ( bitLengthMod8 - 1 ) : 128
rndBytes [ 0 ] = rndBytes [ 0 ] | mask
}
return rndBytes
}
2020-04-06 11:17:22 +00:00
/ * *
* 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 = false ] If we want to force the output to have a specific bit length . It basically forces the msb to be 1
*
2020-04-07 15:03:30 +00:00
* @ returns { Buffer | Uint8Array } A Buffer / UInt8Array ( Node . js / Browser ) filled with cryptographically secure random bits
2020-04-06 11:17:22 +00:00
* /
2020-04-07 17:29:23 +00:00
function randBitsSync ( bitLength , forceLength = false ) {
2020-04-06 11:34:03 +00:00
if ( bitLength < 1 ) {
throw new RangeError ( ` bitLength MUST be > 0 and it is ${ bitLength } ` )
}
2020-04-06 11:17:22 +00:00
const byteLength = Math . ceil ( bitLength / 8 )
const rndBytes = randBytesSync ( byteLength , false )
2020-04-06 11:34:03 +00:00
const bitLengthMod8 = bitLength % 8
if ( bitLengthMod8 ) {
// Fill with 0's the extra bits
rndBytes [ 0 ] = rndBytes [ 0 ] & ( 2 * * bitLengthMod8 - 1 )
}
2020-04-06 11:17:22 +00:00
if ( forceLength ) {
2020-04-06 11:34:03 +00:00
const mask = bitLengthMod8 ? 2 * * ( bitLengthMod8 - 1 ) : 128
2020-04-06 11:17:22 +00:00
rndBytes [ 0 ] = rndBytes [ 0 ] | mask
}
return rndBytes
}
/ * *
* Secure random bytes for both node and browsers . Node version uses crypto . randomFill ( ) and browser one self . crypto . getRandomValues ( )
*
* @ param { number } byteLength The desired number of random bytes
* @ param { boolean } [ forceLength = false ] If we want to force the output to have a bit length of 8 * byteLength . It basically forces the msb to be 1
*
2020-04-07 15:03:30 +00:00
* @ returns { Promise < Buffer | Uint8Array > } A promise that resolves to a Buffer / UInt8Array ( Node . js / Browser ) filled with cryptographically secure random bytes
2020-04-06 11:17:22 +00:00
* /
function randBytes ( byteLength , forceLength = false ) {
if ( byteLength < 1 ) { throw new RangeError ( ` byteLength MUST be > 0 and it is ${ byteLength } ` ) }
/* eslint-disable no-lone-blocks */
{ // browser
return new Promise ( function ( resolve ) {
2020-04-07 17:29:23 +00:00
const buf = new Uint8Array ( byteLength )
2020-04-06 11:17:22 +00:00
self . crypto . getRandomValues ( buf )
// If fixed length is required we put the first bit to 1 -> to get the necessary bitLength
if ( forceLength ) { buf [ 0 ] = buf [ 0 ] | 128 }
resolve ( buf )
} )
}
2020-04-07 17:29:23 +00:00
/* eslint-enable no-lone-blocks */
2020-04-06 11:17:22 +00:00
}
/ * *
* Secure random bytes for both node and browsers . Node version uses crypto . randomFill ( ) and browser one self . crypto . getRandomValues ( )
*
* @ param { number } byteLength The desired number of random bytes
* @ param { boolean } [ forceLength = false ] If we want to force the output to have a bit length of 8 * byteLength . It basically forces the msb to be 1
*
2020-04-07 15:03:30 +00:00
* @ returns { Buffer | Uint8Array } A Buffer / UInt8Array ( Node . js / Browser ) filled with cryptographically secure random bytes
2020-04-06 11:17:22 +00:00
* /
function randBytesSync ( byteLength , forceLength = false ) {
if ( byteLength < 1 ) { throw new RangeError ( ` byteLength MUST be > 0 and it is ${ byteLength } ` ) }
2020-04-07 17:29:23 +00:00
/* eslint-disable no-lone-blocks */
2020-04-06 11:17:22 +00:00
{ // browser
2020-04-07 17:29:23 +00:00
const buf = new Uint8Array ( byteLength )
2020-04-06 11:17:22 +00:00
self . crypto . getRandomValues ( buf )
2020-04-07 17:29:23 +00:00
// If fixed length is required we put the first bit to 1 -> to get the necessary bitLength
if ( forceLength ) { buf [ 0 ] = buf [ 0 ] | 128 }
return buf
2020-04-06 11:17:22 +00:00
}
2020-04-07 17:29:23 +00:00
/* eslint-enable no-lone-blocks */
2020-04-06 11:17:22 +00:00
}
/* HELPER FUNCTIONS */
function fromBuffer ( buf ) {
2020-04-07 15:03:30 +00:00
let ret = 0 n
2020-04-06 11:17:22 +00:00
for ( const i of buf . values ( ) ) {
const bi = BigInt ( i )
ret = ( ret << BigInt ( 8 ) ) + bi
}
return ret
}
function _isProbablyPrimeWorkerUrl ( ) {
// Let's us first add all the required functions
2020-04-07 17:29:23 +00:00
let workerCode = ` 'use strict';const ${ eGcd . name } = ${ eGcd . toString ( ) } ;const ${ modInv . name } = ${ modInv . toString ( ) } ;const ${ modPow . name } = ${ modPow . toString ( ) } ;const ${ toZn . name } = ${ toZn . toString ( ) } ;const ${ randBitsSync . name } = ${ randBitsSync . toString ( ) } ;const ${ randBytesSync . name } = ${ randBytesSync . toString ( ) } ;const ${ randBetween . name } = ${ randBetween . toString ( ) } ;const ${ isProbablyPrime . name } = ${ _isProbablyPrime . toString ( ) } ; ${ bitLength . toString ( ) } ${ fromBuffer . toString ( ) } `
2020-04-06 11:17:22 +00:00
const onmessage = async function ( event ) { // Let's start once we are called
// event.data = {rnd: <bigint>, iterations: <number>}
const isPrime = await isProbablyPrime ( event . data . rnd , event . data . iterations )
postMessage ( {
isPrime : isPrime ,
value : event . data . rnd ,
id : event . data . id
} )
}
workerCode += ` onmessage = ${ onmessage . toString ( ) } ; `
return _workerUrl ( workerCode )
}
function _workerUrl ( workerCode ) {
workerCode = ` (() => { ${ workerCode } })() ` // encapsulate IIFE
const _blob = new Blob ( [ workerCode ] , { type : 'text/javascript' } )
return window . URL . createObjectURL ( _blob )
}
function _isProbablyPrime ( w , iterations = 16 ) {
/ *
PREFILTERING . Even values but 2 are not primes , so don ' t test .
1 is not a prime and the M - R algorithm needs w > 1.
* /
2020-04-07 15:03:30 +00:00
if ( w === 2 n ) { return true } else if ( ( w & 1 n ) === 0 n || w === 1 n ) { return false }
2020-04-06 11:17:22 +00:00
/ *
Test if any of the first 250 small primes are a factor of w . 2 is not tested because it was already tested above .
* /
const firstPrimes = [
2020-04-07 15:03:30 +00:00
3 n ,
5 n ,
7 n ,
11 n ,
13 n ,
17 n ,
19 n ,
23 n ,
29 n ,
31 n ,
37 n ,
41 n ,
43 n ,
47 n ,
53 n ,
59 n ,
61 n ,
67 n ,
71 n ,
73 n ,
79 n ,
83 n ,
89 n ,
97 n ,
101 n ,
103 n ,
107 n ,
109 n ,
113 n ,
127 n ,
131 n ,
137 n ,
139 n ,
149 n ,
151 n ,
157 n ,
163 n ,
167 n ,
173 n ,
179 n ,
181 n ,
191 n ,
193 n ,
197 n ,
199 n ,
211 n ,
223 n ,
227 n ,
229 n ,
233 n ,
239 n ,
241 n ,
251 n ,
257 n ,
263 n ,
269 n ,
271 n ,
277 n ,
281 n ,
283 n ,
293 n ,
307 n ,
311 n ,
313 n ,
317 n ,
331 n ,
337 n ,
347 n ,
349 n ,
353 n ,
359 n ,
367 n ,
373 n ,
379 n ,
383 n ,
389 n ,
397 n ,
401 n ,
409 n ,
419 n ,
421 n ,
431 n ,
433 n ,
439 n ,
443 n ,
449 n ,
457 n ,
461 n ,
463 n ,
467 n ,
479 n ,
487 n ,
491 n ,
499 n ,
503 n ,
509 n ,
521 n ,
523 n ,
541 n ,
547 n ,
557 n ,
563 n ,
569 n ,
571 n ,
577 n ,
587 n ,
593 n ,
599 n ,
601 n ,
607 n ,
613 n ,
617 n ,
619 n ,
631 n ,
641 n ,
643 n ,
647 n ,
653 n ,
659 n ,
661 n ,
673 n ,
677 n ,
683 n ,
691 n ,
701 n ,
709 n ,
719 n ,
727 n ,
733 n ,
739 n ,
743 n ,
751 n ,
757 n ,
761 n ,
769 n ,
773 n ,
787 n ,
797 n ,
809 n ,
811 n ,
821 n ,
823 n ,
827 n ,
829 n ,
839 n ,
853 n ,
857 n ,
859 n ,
863 n ,
877 n ,
881 n ,
883 n ,
887 n ,
907 n ,
911 n ,
919 n ,
929 n ,
937 n ,
941 n ,
947 n ,
953 n ,
967 n ,
971 n ,
977 n ,
983 n ,
991 n ,
997 n ,
1009 n ,
1013 n ,
1019 n ,
1021 n ,
1031 n ,
1033 n ,
1039 n ,
1049 n ,
1051 n ,
1061 n ,
1063 n ,
1069 n ,
1087 n ,
1091 n ,
1093 n ,
1097 n ,
1103 n ,
1109 n ,
1117 n ,
1123 n ,
1129 n ,
1151 n ,
1153 n ,
1163 n ,
1171 n ,
1181 n ,
1187 n ,
1193 n ,
1201 n ,
1213 n ,
1217 n ,
1223 n ,
1229 n ,
1231 n ,
1237 n ,
1249 n ,
1259 n ,
1277 n ,
1279 n ,
1283 n ,
1289 n ,
1291 n ,
1297 n ,
1301 n ,
1303 n ,
1307 n ,
1319 n ,
1321 n ,
1327 n ,
1361 n ,
1367 n ,
1373 n ,
1381 n ,
1399 n ,
1409 n ,
1423 n ,
1427 n ,
1429 n ,
1433 n ,
1439 n ,
1447 n ,
1451 n ,
1453 n ,
1459 n ,
1471 n ,
1481 n ,
1483 n ,
1487 n ,
1489 n ,
1493 n ,
1499 n ,
1511 n ,
1523 n ,
1531 n ,
1543 n ,
1549 n ,
1553 n ,
1559 n ,
1567 n ,
1571 n ,
1579 n ,
1583 n ,
1597 n
2020-04-06 11:17:22 +00:00
]
2020-04-07 15:03:30 +00:00
for ( let i = 0 ; i < firstPrimes . length && ( firstPrimes [ i ] <= w ) ; i ++ ) {
const p = firstPrimes [ i ]
2020-04-06 17:01:44 +00:00
if ( w === p ) {
return true
2020-04-07 15:03:30 +00:00
} else if ( w % p === 0 n ) {
2020-04-06 17:01:44 +00:00
return false
}
2020-04-06 11:17:22 +00:00
}
/ *
1. Let a be the largest integer such that 2 * * a divides w − 1.
2. m = ( w − 1 ) / 2 * * a .
3. wlen = len ( w ) .
4. For i = 1 to iterations do
4.1 Obtain a string b of wlen bits from an RBG .
Comment : Ensure that 1 < b < w − 1.
4.2 If ( ( b ≤ 1 ) or ( b ≥ w − 1 ) ) , then go to step 4.1 .
4.3 z = b * * m mod w .
4.4 If ( ( z = 1 ) or ( z = w − 1 ) ) , then go to step 4.7 .
4.5 For j = 1 to a − 1 do .
4.5 . 1 z = z * * 2 mod w .
4.5 . 2 If ( z = w − 1 ) , then go to step 4.7 .
4.5 . 3 If ( z = 1 ) , then go to step 4.6 .
4.6 Return COMPOSITE .
4.7 Continue .
Comment : Increment i for the do - loop in step 4.
5. Return PROBABLY PRIME .
* /
2020-04-07 15:03:30 +00:00
let a = 0 n
const d = w - 1 n
let aux = d
while ( aux % 2 n === 0 n ) {
aux /= 2 n
2020-04-06 11:17:22 +00:00
++ a
}
2020-04-07 15:03:30 +00:00
const m = d / ( 2 n * * a )
2020-04-06 11:17:22 +00:00
2020-04-07 15:03:30 +00:00
// /* eslint-disable no-labels */
// loop: do {
// const b = randBetween(w - 1n, 2n)
// let z = modPow(b, m, w)
// if (z === 1n || z === w - 1n) { continue }
// for (let j = 1; j < a; j++) {
// z = modPow(z, 2n, w)
// if (z === w - 1n) { continue loop }
// if (z === 1n) { break }
// }
// return false
// } while (--iterations)
// /* eslint-enable no-labels */
// return true
2020-04-06 11:17:22 +00:00
2020-04-07 15:03:30 +00:00
do {
const b = randBetween ( d , 2 n )
let z = modPow ( b , m , w )
if ( z === 1 n || z === d ) { continue }
let j = 1
while ( j < a ) {
z = modPow ( z , 2 n , w )
if ( z === d ) { break }
if ( z === 1 n ) { return false }
j ++
}
if ( z !== d ) {
return false
2020-04-06 11:17:22 +00:00
}
} while ( -- iterations )
return true
}
2020-04-07 17:29:23 +00:00
export { isProbablyPrime , prime , primeSync , randBetween , randBits , randBitsSync , randBytes , randBytesSync }