|
|
/** * 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; }
})();
|