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