1 | /**
|
---|
2 | * Prime number generation API.
|
---|
3 | *
|
---|
4 | * @author Dave Longley
|
---|
5 | *
|
---|
6 | * Copyright (c) 2014 Digital Bazaar, Inc.
|
---|
7 | */
|
---|
8 | var forge = require('./forge');
|
---|
9 | require('./util');
|
---|
10 | require('./jsbn');
|
---|
11 | require('./random');
|
---|
12 |
|
---|
13 | (function() {
|
---|
14 |
|
---|
15 | // forge.prime already defined
|
---|
16 | if(forge.prime) {
|
---|
17 | module.exports = forge.prime;
|
---|
18 | return;
|
---|
19 | }
|
---|
20 |
|
---|
21 | /* PRIME API */
|
---|
22 | var prime = module.exports = forge.prime = forge.prime || {};
|
---|
23 |
|
---|
24 | var BigInteger = forge.jsbn.BigInteger;
|
---|
25 |
|
---|
26 | // primes are 30k+i for i = 1, 7, 11, 13, 17, 19, 23, 29
|
---|
27 | var GCD_30_DELTA = [6, 4, 2, 4, 2, 4, 6, 2];
|
---|
28 | var THIRTY = new BigInteger(null);
|
---|
29 | THIRTY.fromInt(30);
|
---|
30 | var op_or = function(x, y) {return x|y;};
|
---|
31 |
|
---|
32 | /**
|
---|
33 | * Generates a random probable prime with the given number of bits.
|
---|
34 | *
|
---|
35 | * Alternative algorithms can be specified by name as a string or as an
|
---|
36 | * object with custom options like so:
|
---|
37 | *
|
---|
38 | * {
|
---|
39 | * name: 'PRIMEINC',
|
---|
40 | * options: {
|
---|
41 | * maxBlockTime: <the maximum amount of time to block the main
|
---|
42 | * thread before allowing I/O other JS to run>,
|
---|
43 | * millerRabinTests: <the number of miller-rabin tests to run>,
|
---|
44 | * workerScript: <the worker script URL>,
|
---|
45 | * workers: <the number of web workers (if supported) to use,
|
---|
46 | * -1 to use estimated cores minus one>.
|
---|
47 | * workLoad: the size of the work load, ie: number of possible prime
|
---|
48 | * numbers for each web worker to check per work assignment,
|
---|
49 | * (default: 100).
|
---|
50 | * }
|
---|
51 | * }
|
---|
52 | *
|
---|
53 | * @param bits the number of bits for the prime number.
|
---|
54 | * @param options the options to use.
|
---|
55 | * [algorithm] the algorithm to use (default: 'PRIMEINC').
|
---|
56 | * [prng] a custom crypto-secure pseudo-random number generator to use,
|
---|
57 | * that must define "getBytesSync".
|
---|
58 | *
|
---|
59 | * @return callback(err, num) called once the operation completes.
|
---|
60 | */
|
---|
61 | prime.generateProbablePrime = function(bits, options, callback) {
|
---|
62 | if(typeof options === 'function') {
|
---|
63 | callback = options;
|
---|
64 | options = {};
|
---|
65 | }
|
---|
66 | options = options || {};
|
---|
67 |
|
---|
68 | // default to PRIMEINC algorithm
|
---|
69 | var algorithm = options.algorithm || 'PRIMEINC';
|
---|
70 | if(typeof algorithm === 'string') {
|
---|
71 | algorithm = {name: algorithm};
|
---|
72 | }
|
---|
73 | algorithm.options = algorithm.options || {};
|
---|
74 |
|
---|
75 | // create prng with api that matches BigInteger secure random
|
---|
76 | var prng = options.prng || forge.random;
|
---|
77 | var rng = {
|
---|
78 | // x is an array to fill with bytes
|
---|
79 | nextBytes: function(x) {
|
---|
80 | var b = prng.getBytesSync(x.length);
|
---|
81 | for(var i = 0; i < x.length; ++i) {
|
---|
82 | x[i] = b.charCodeAt(i);
|
---|
83 | }
|
---|
84 | }
|
---|
85 | };
|
---|
86 |
|
---|
87 | if(algorithm.name === 'PRIMEINC') {
|
---|
88 | return primeincFindPrime(bits, rng, algorithm.options, callback);
|
---|
89 | }
|
---|
90 |
|
---|
91 | throw new Error('Invalid prime generation algorithm: ' + algorithm.name);
|
---|
92 | };
|
---|
93 |
|
---|
94 | function primeincFindPrime(bits, rng, options, callback) {
|
---|
95 | if('workers' in options) {
|
---|
96 | return primeincFindPrimeWithWorkers(bits, rng, options, callback);
|
---|
97 | }
|
---|
98 | return primeincFindPrimeWithoutWorkers(bits, rng, options, callback);
|
---|
99 | }
|
---|
100 |
|
---|
101 | function primeincFindPrimeWithoutWorkers(bits, rng, options, callback) {
|
---|
102 | // initialize random number
|
---|
103 | var num = generateRandom(bits, rng);
|
---|
104 |
|
---|
105 | /* Note: All primes are of the form 30k+i for i < 30 and gcd(30, i)=1. The
|
---|
106 | number we are given is always aligned at 30k + 1. Each time the number is
|
---|
107 | determined not to be prime we add to get to the next 'i', eg: if the number
|
---|
108 | was at 30k + 1 we add 6. */
|
---|
109 | var deltaIdx = 0;
|
---|
110 |
|
---|
111 | // get required number of MR tests
|
---|
112 | var mrTests = getMillerRabinTests(num.bitLength());
|
---|
113 | if('millerRabinTests' in options) {
|
---|
114 | mrTests = options.millerRabinTests;
|
---|
115 | }
|
---|
116 |
|
---|
117 | // find prime nearest to 'num' for maxBlockTime ms
|
---|
118 | // 10 ms gives 5ms of leeway for other calculations before dropping
|
---|
119 | // below 60fps (1000/60 == 16.67), but in reality, the number will
|
---|
120 | // likely be higher due to an 'atomic' big int modPow
|
---|
121 | var maxBlockTime = 10;
|
---|
122 | if('maxBlockTime' in options) {
|
---|
123 | maxBlockTime = options.maxBlockTime;
|
---|
124 | }
|
---|
125 |
|
---|
126 | _primeinc(num, bits, rng, deltaIdx, mrTests, maxBlockTime, callback);
|
---|
127 | }
|
---|
128 |
|
---|
129 | function _primeinc(num, bits, rng, deltaIdx, mrTests, maxBlockTime, callback) {
|
---|
130 | var start = +new Date();
|
---|
131 | do {
|
---|
132 | // overflow, regenerate random number
|
---|
133 | if(num.bitLength() > bits) {
|
---|
134 | num = generateRandom(bits, rng);
|
---|
135 | }
|
---|
136 | // do primality test
|
---|
137 | if(num.isProbablePrime(mrTests)) {
|
---|
138 | return callback(null, num);
|
---|
139 | }
|
---|
140 | // get next potential prime
|
---|
141 | num.dAddOffset(GCD_30_DELTA[deltaIdx++ % 8], 0);
|
---|
142 | } while(maxBlockTime < 0 || (+new Date() - start < maxBlockTime));
|
---|
143 |
|
---|
144 | // keep trying later
|
---|
145 | forge.util.setImmediate(function() {
|
---|
146 | _primeinc(num, bits, rng, deltaIdx, mrTests, maxBlockTime, callback);
|
---|
147 | });
|
---|
148 | }
|
---|
149 |
|
---|
150 | // NOTE: This algorithm is indeterminate in nature because workers
|
---|
151 | // run in parallel looking at different segments of numbers. Even if this
|
---|
152 | // algorithm is run twice with the same input from a predictable RNG, it
|
---|
153 | // may produce different outputs.
|
---|
154 | function primeincFindPrimeWithWorkers(bits, rng, options, callback) {
|
---|
155 | // web workers unavailable
|
---|
156 | if(typeof Worker === 'undefined') {
|
---|
157 | return primeincFindPrimeWithoutWorkers(bits, rng, options, callback);
|
---|
158 | }
|
---|
159 |
|
---|
160 | // initialize random number
|
---|
161 | var num = generateRandom(bits, rng);
|
---|
162 |
|
---|
163 | // use web workers to generate keys
|
---|
164 | var numWorkers = options.workers;
|
---|
165 | var workLoad = options.workLoad || 100;
|
---|
166 | var range = workLoad * 30 / 8;
|
---|
167 | var workerScript = options.workerScript || 'forge/prime.worker.js';
|
---|
168 | if(numWorkers === -1) {
|
---|
169 | return forge.util.estimateCores(function(err, cores) {
|
---|
170 | if(err) {
|
---|
171 | // default to 2
|
---|
172 | cores = 2;
|
---|
173 | }
|
---|
174 | numWorkers = cores - 1;
|
---|
175 | generate();
|
---|
176 | });
|
---|
177 | }
|
---|
178 | generate();
|
---|
179 |
|
---|
180 | function generate() {
|
---|
181 | // require at least 1 worker
|
---|
182 | numWorkers = Math.max(1, numWorkers);
|
---|
183 |
|
---|
184 | // TODO: consider optimizing by starting workers outside getPrime() ...
|
---|
185 | // note that in order to clean up they will have to be made internally
|
---|
186 | // asynchronous which may actually be slower
|
---|
187 |
|
---|
188 | // start workers immediately
|
---|
189 | var workers = [];
|
---|
190 | for(var i = 0; i < numWorkers; ++i) {
|
---|
191 | // FIXME: fix path or use blob URLs
|
---|
192 | workers[i] = new Worker(workerScript);
|
---|
193 | }
|
---|
194 | var running = numWorkers;
|
---|
195 |
|
---|
196 | // listen for requests from workers and assign ranges to find prime
|
---|
197 | for(var i = 0; i < numWorkers; ++i) {
|
---|
198 | workers[i].addEventListener('message', workerMessage);
|
---|
199 | }
|
---|
200 |
|
---|
201 | /* Note: The distribution of random numbers is unknown. Therefore, each
|
---|
202 | web worker is continuously allocated a range of numbers to check for a
|
---|
203 | random number until one is found.
|
---|
204 |
|
---|
205 | Every 30 numbers will be checked just 8 times, because prime numbers
|
---|
206 | have the form:
|
---|
207 |
|
---|
208 | 30k+i, for i < 30 and gcd(30, i)=1 (there are 8 values of i for this)
|
---|
209 |
|
---|
210 | Therefore, if we want a web worker to run N checks before asking for
|
---|
211 | a new range of numbers, each range must contain N*30/8 numbers.
|
---|
212 |
|
---|
213 | For 100 checks (workLoad), this is a range of 375. */
|
---|
214 |
|
---|
215 | var found = false;
|
---|
216 | function workerMessage(e) {
|
---|
217 | // ignore message, prime already found
|
---|
218 | if(found) {
|
---|
219 | return;
|
---|
220 | }
|
---|
221 |
|
---|
222 | --running;
|
---|
223 | var data = e.data;
|
---|
224 | if(data.found) {
|
---|
225 | // terminate all workers
|
---|
226 | for(var i = 0; i < workers.length; ++i) {
|
---|
227 | workers[i].terminate();
|
---|
228 | }
|
---|
229 | found = true;
|
---|
230 | return callback(null, new BigInteger(data.prime, 16));
|
---|
231 | }
|
---|
232 |
|
---|
233 | // overflow, regenerate random number
|
---|
234 | if(num.bitLength() > bits) {
|
---|
235 | num = generateRandom(bits, rng);
|
---|
236 | }
|
---|
237 |
|
---|
238 | // assign new range to check
|
---|
239 | var hex = num.toString(16);
|
---|
240 |
|
---|
241 | // start prime search
|
---|
242 | e.target.postMessage({
|
---|
243 | hex: hex,
|
---|
244 | workLoad: workLoad
|
---|
245 | });
|
---|
246 |
|
---|
247 | num.dAddOffset(range, 0);
|
---|
248 | }
|
---|
249 | }
|
---|
250 | }
|
---|
251 |
|
---|
252 | /**
|
---|
253 | * Generates a random number using the given number of bits and RNG.
|
---|
254 | *
|
---|
255 | * @param bits the number of bits for the number.
|
---|
256 | * @param rng the random number generator to use.
|
---|
257 | *
|
---|
258 | * @return the random number.
|
---|
259 | */
|
---|
260 | function generateRandom(bits, rng) {
|
---|
261 | var num = new BigInteger(bits, rng);
|
---|
262 | // force MSB set
|
---|
263 | var bits1 = bits - 1;
|
---|
264 | if(!num.testBit(bits1)) {
|
---|
265 | num.bitwiseTo(BigInteger.ONE.shiftLeft(bits1), op_or, num);
|
---|
266 | }
|
---|
267 | // align number on 30k+1 boundary
|
---|
268 | num.dAddOffset(31 - num.mod(THIRTY).byteValue(), 0);
|
---|
269 | return num;
|
---|
270 | }
|
---|
271 |
|
---|
272 | /**
|
---|
273 | * Returns the required number of Miller-Rabin tests to generate a
|
---|
274 | * prime with an error probability of (1/2)^80.
|
---|
275 | *
|
---|
276 | * See Handbook of Applied Cryptography Chapter 4, Table 4.4.
|
---|
277 | *
|
---|
278 | * @param bits the bit size.
|
---|
279 | *
|
---|
280 | * @return the required number of iterations.
|
---|
281 | */
|
---|
282 | function getMillerRabinTests(bits) {
|
---|
283 | if(bits <= 100) return 27;
|
---|
284 | if(bits <= 150) return 18;
|
---|
285 | if(bits <= 200) return 15;
|
---|
286 | if(bits <= 250) return 12;
|
---|
287 | if(bits <= 300) return 9;
|
---|
288 | if(bits <= 350) return 8;
|
---|
289 | if(bits <= 400) return 7;
|
---|
290 | if(bits <= 500) return 6;
|
---|
291 | if(bits <= 600) return 5;
|
---|
292 | if(bits <= 800) return 4;
|
---|
293 | if(bits <= 1250) return 3;
|
---|
294 | return 2;
|
---|
295 | }
|
---|
296 |
|
---|
297 | })();
|
---|