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.
		
		
		
		
		
			
		
			
				
					298 lines
				
				8.6 KiB
			
		
		
			
		
	
	
					298 lines
				
				8.6 KiB
			| 
								 
											2 years ago
										 
									 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * 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;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								})();
							 |