Spaces:
Configuration error
Configuration error
/** | |
* Prime number generation API. | |
* | |
* @author Dave Longley | |
* | |
* Copyright (c) 2014 Digital Bazaar, Inc. | |
*/ | |
var forge = require('./forge'); | |
require('./util'); | |
require('./jsbn'); | |
require('./random'); | |
(function() { | |
// forge.prime already defined | |
if(forge.prime) { | |
module.exports = forge.prime; | |
return; | |
} | |
/* PRIME API */ | |
var prime = module.exports = forge.prime = forge.prime || {}; | |
var BigInteger = forge.jsbn.BigInteger; | |
// primes are 30k+i for i = 1, 7, 11, 13, 17, 19, 23, 29 | |
var GCD_30_DELTA = [6, 4, 2, 4, 2, 4, 6, 2]; | |
var THIRTY = new BigInteger(null); | |
THIRTY.fromInt(30); | |
var op_or = function(x, y) {return x|y;}; | |
/** | |
* Generates a random probable prime with the given number of bits. | |
* | |
* Alternative algorithms can be specified by name as a string or as an | |
* object with custom options like so: | |
* | |
* { | |
* name: 'PRIMEINC', | |
* options: { | |
* maxBlockTime: <the maximum amount of time to block the main | |
* thread before allowing I/O other JS to run>, | |
* millerRabinTests: <the number of miller-rabin tests to run>, | |
* workerScript: <the worker script URL>, | |
* workers: <the number of web workers (if supported) to use, | |
* -1 to use estimated cores minus one>. | |
* workLoad: the size of the work load, ie: number of possible prime | |
* numbers for each web worker to check per work assignment, | |
* (default: 100). | |
* } | |
* } | |
* | |
* @param bits the number of bits for the prime number. | |
* @param options the options to use. | |
* [algorithm] the algorithm to use (default: 'PRIMEINC'). | |
* [prng] a custom crypto-secure pseudo-random number generator to use, | |
* that must define "getBytesSync". | |
* | |
* @return callback(err, num) called once the operation completes. | |
*/ | |
prime.generateProbablePrime = function(bits, options, callback) { | |
if(typeof options === 'function') { | |
callback = options; | |
options = {}; | |
} | |
options = options || {}; | |
// default to PRIMEINC algorithm | |
var algorithm = options.algorithm || 'PRIMEINC'; | |
if(typeof algorithm === 'string') { | |
algorithm = {name: algorithm}; | |
} | |
algorithm.options = algorithm.options || {}; | |
// create prng with api that matches BigInteger secure random | |
var prng = options.prng || forge.random; | |
var rng = { | |
// x is an array to fill with bytes | |
nextBytes: function(x) { | |
var b = prng.getBytesSync(x.length); | |
for(var i = 0; i < x.length; ++i) { | |
x[i] = b.charCodeAt(i); | |
} | |
} | |
}; | |
if(algorithm.name === 'PRIMEINC') { | |
return primeincFindPrime(bits, rng, algorithm.options, callback); | |
} | |
throw new Error('Invalid prime generation algorithm: ' + algorithm.name); | |
}; | |
function primeincFindPrime(bits, rng, options, callback) { | |
if('workers' in options) { | |
return primeincFindPrimeWithWorkers(bits, rng, options, callback); | |
} | |
return primeincFindPrimeWithoutWorkers(bits, rng, options, callback); | |
} | |
function primeincFindPrimeWithoutWorkers(bits, rng, options, callback) { | |
// initialize random number | |
var num = generateRandom(bits, rng); | |
/* Note: All primes are of the form 30k+i for i < 30 and gcd(30, i)=1. The | |
number we are given is always aligned at 30k + 1. Each time the number is | |
determined not to be prime we add to get to the next 'i', eg: if the number | |
was at 30k + 1 we add 6. */ | |
var deltaIdx = 0; | |
// get required number of MR tests | |
var mrTests = getMillerRabinTests(num.bitLength()); | |
if('millerRabinTests' in options) { | |
mrTests = options.millerRabinTests; | |
} | |
// find prime nearest to 'num' for maxBlockTime ms | |
// 10 ms gives 5ms of leeway for other calculations before dropping | |
// below 60fps (1000/60 == 16.67), but in reality, the number will | |
// likely be higher due to an 'atomic' big int modPow | |
var maxBlockTime = 10; | |
if('maxBlockTime' in options) { | |
maxBlockTime = options.maxBlockTime; | |
} | |
_primeinc(num, bits, rng, deltaIdx, mrTests, maxBlockTime, callback); | |
} | |
function _primeinc(num, bits, rng, deltaIdx, mrTests, maxBlockTime, callback) { | |
var start = +new Date(); | |
do { | |
// overflow, regenerate random number | |
if(num.bitLength() > bits) { | |
num = generateRandom(bits, rng); | |
} | |
// do primality test | |
if(num.isProbablePrime(mrTests)) { | |
return callback(null, num); | |
} | |
// get next potential prime | |
num.dAddOffset(GCD_30_DELTA[deltaIdx++ % 8], 0); | |
} while(maxBlockTime < 0 || (+new Date() - start < maxBlockTime)); | |
// keep trying later | |
forge.util.setImmediate(function() { | |
_primeinc(num, bits, rng, deltaIdx, mrTests, maxBlockTime, callback); | |
}); | |
} | |
// NOTE: This algorithm is indeterminate in nature because workers | |
// run in parallel looking at different segments of numbers. Even if this | |
// algorithm is run twice with the same input from a predictable RNG, it | |
// may produce different outputs. | |
function primeincFindPrimeWithWorkers(bits, rng, options, callback) { | |
// web workers unavailable | |
if(typeof Worker === 'undefined') { | |
return primeincFindPrimeWithoutWorkers(bits, rng, options, callback); | |
} | |
// initialize random number | |
var num = generateRandom(bits, rng); | |
// use web workers to generate keys | |
var numWorkers = options.workers; | |
var workLoad = options.workLoad || 100; | |
var range = workLoad * 30 / 8; | |
var workerScript = options.workerScript || 'forge/prime.worker.js'; | |
if(numWorkers === -1) { | |
return forge.util.estimateCores(function(err, cores) { | |
if(err) { | |
// default to 2 | |
cores = 2; | |
} | |
numWorkers = cores - 1; | |
generate(); | |
}); | |
} | |
generate(); | |
function generate() { | |
// require at least 1 worker | |
numWorkers = Math.max(1, numWorkers); | |
// TODO: consider optimizing by starting workers outside getPrime() ... | |
// note that in order to clean up they will have to be made internally | |
// asynchronous which may actually be slower | |
// start workers immediately | |
var workers = []; | |
for(var i = 0; i < numWorkers; ++i) { | |
// FIXME: fix path or use blob URLs | |
workers[i] = new Worker(workerScript); | |
} | |
var running = numWorkers; | |
// listen for requests from workers and assign ranges to find prime | |
for(var i = 0; i < numWorkers; ++i) { | |
workers[i].addEventListener('message', workerMessage); | |
} | |
/* Note: The distribution of random numbers is unknown. Therefore, each | |
web worker is continuously allocated a range of numbers to check for a | |
random number until one is found. | |
Every 30 numbers will be checked just 8 times, because prime numbers | |
have the form: | |
30k+i, for i < 30 and gcd(30, i)=1 (there are 8 values of i for this) | |
Therefore, if we want a web worker to run N checks before asking for | |
a new range of numbers, each range must contain N*30/8 numbers. | |
For 100 checks (workLoad), this is a range of 375. */ | |
var found = false; | |
function workerMessage(e) { | |
// ignore message, prime already found | |
if(found) { | |
return; | |
} | |
--running; | |
var data = e.data; | |
if(data.found) { | |
// terminate all workers | |
for(var i = 0; i < workers.length; ++i) { | |
workers[i].terminate(); | |
} | |
found = true; | |
return callback(null, new BigInteger(data.prime, 16)); | |
} | |
// overflow, regenerate random number | |
if(num.bitLength() > bits) { | |
num = generateRandom(bits, rng); | |
} | |
// assign new range to check | |
var hex = num.toString(16); | |
// start prime search | |
e.target.postMessage({ | |
hex: hex, | |
workLoad: workLoad | |
}); | |
num.dAddOffset(range, 0); | |
} | |
} | |
} | |
/** | |
* Generates a random number using the given number of bits and RNG. | |
* | |
* @param bits the number of bits for the number. | |
* @param rng the random number generator to use. | |
* | |
* @return the random number. | |
*/ | |
function generateRandom(bits, rng) { | |
var num = new BigInteger(bits, rng); | |
// force MSB set | |
var bits1 = bits - 1; | |
if(!num.testBit(bits1)) { | |
num.bitwiseTo(BigInteger.ONE.shiftLeft(bits1), op_or, num); | |
} | |
// align number on 30k+1 boundary | |
num.dAddOffset(31 - num.mod(THIRTY).byteValue(), 0); | |
return num; | |
} | |
/** | |
* Returns the required number of Miller-Rabin tests to generate a | |
* prime with an error probability of (1/2)^80. | |
* | |
* See Handbook of Applied Cryptography Chapter 4, Table 4.4. | |
* | |
* @param bits the bit size. | |
* | |
* @return the required number of iterations. | |
*/ | |
function getMillerRabinTests(bits) { | |
if(bits <= 100) return 27; | |
if(bits <= 150) return 18; | |
if(bits <= 200) return 15; | |
if(bits <= 250) return 12; | |
if(bits <= 300) return 9; | |
if(bits <= 350) return 8; | |
if(bits <= 400) return 7; | |
if(bits <= 500) return 6; | |
if(bits <= 600) return 5; | |
if(bits <= 800) return 4; | |
if(bits <= 1250) return 3; | |
return 2; | |
} | |
})(); | |