You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
297 lines
8.6 KiB
297 lines
8.6 KiB
/** |
|
* 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; |
|
} |
|
|
|
})();
|
|
|