Skip to content

Commit 17e8dd6

Browse files
committed
crypto: add randomPrime/randomPrimeSync/checkPrime
APIs for generating and checking pseudo-random primes Signed-off-by: James M Snell <jasnell@gmail.com>
1 parent 9da3f21 commit 17e8dd6

File tree

9 files changed

+728
-0
lines changed

9 files changed

+728
-0
lines changed

doc/api/crypto.md

+107
Original file line numberDiff line numberDiff line change
@@ -1961,6 +1961,48 @@ is currently in use. Setting to true requires a FIPS build of Node.js.
19611961
This property is deprecated. Please use `crypto.setFips()` and
19621962
`crypto.getFips()` instead.
19631963

1964+
### `crypto.checkPrime(candidate[, options, [callback]])`
1965+
<!-- YAML
1966+
added: REPLACEME
1967+
-->
1968+
1969+
* `candidate` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView|bigint}
1970+
A possible prime encoded as a sequence of big endian octets of arbitrary
1971+
length.
1972+
* `options` {Object}
1973+
* `checks` {number} The number of primality checks to perform. When the
1974+
value is `0` (zero), a number of checks is used that yields a false
1975+
positive rate of at most 2^-64 for random input. Care must be used
1976+
when selecting a number of checks. Refer to the OpenSSL documentation
1977+
for the [`BN_is_prime_ex`][] function `nchecks` options for more details.
1978+
**Defaults**: `0`
1979+
* `callback` {Function}
1980+
* `err` {Error} Set to an {Error} object if an error occured during check.
1981+
* `result` {boolean} `true` if the candidate is a prime with an error
1982+
probability less than `0.25^options.checks`.
1983+
1984+
Checks the primality of the `candidate`.
1985+
1986+
### `crypto.checkPrimeSync(candidate[, options])`
1987+
<!-- YAML
1988+
added: REPLACEME
1989+
-->
1990+
1991+
* `candidate` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView|bigint}
1992+
A possible prime encoded as a sequence of big endian octets of arbitrary
1993+
length.
1994+
* `options` {Object}
1995+
* `checks` {number} The number of primality checks to perform. When the
1996+
value is `0` (zero), a number of checks is used that yields a false
1997+
positive rate of at most 2^-64 for random input. Care must be used
1998+
when selecting a number of checks. Refer to the OpenSSL documentation
1999+
for the [`BN_is_prime_ex`][] function `nchecks` options for more details.
2000+
**Defaults**: `0`
2001+
* Returns: {boolean} `true` if the candidate is a prime with an error
2002+
probability less than `0.25^options.checks`.
2003+
2004+
Checks the primality of the `candidate`.
2005+
19642006
### `crypto.createCipher(algorithm, password[, options])`
19652007
<!-- YAML
19662008
added: v0.1.94
@@ -3432,6 +3474,70 @@ const n = crypto.randomInt(1, 7);
34323474
console.log(`The dice rolled: ${n}`);
34333475
```
34343476

3477+
### `crypto.randomPrime(size[, options[, callback]])`
3478+
<!-- YAML
3479+
added: REPLACEME
3480+
-->
3481+
3482+
* `size` {number} The size (in bits) of the prime to generate.
3483+
* `options` {Object}
3484+
* `add` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
3485+
* `rem` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
3486+
* `safe` {boolean}
3487+
* `bigint` {boolean} When `true`, the generated prime is returned
3488+
as a `bigint`.
3489+
* `callback` {Function}
3490+
* `err` {Error}
3491+
* `prime` {ArrayBuffer|bigint}
3492+
3493+
Generates a pseudo-random prime of `size` bits.
3494+
3495+
If `options.safe` is true, the prime will be a safe prime -- that is,
3496+
`(prime - 1) / 2` will also be a prime.
3497+
3498+
If `options.add` and `options.rem` are set, the prime will satisfy the
3499+
condition that prime % add = rem. The `options.rem` is ignored if
3500+
`options.add` is not given. If `options.safe` is `true`, `options.add`
3501+
is given, and `options.rem` is `undefined`, then th prime generated
3502+
will satisfy the condition `prime % add = 3`. Otherwise if `options.safe`
3503+
is `false` and `options.rem` is `undefined`, `options.add` will be
3504+
ignored.
3505+
3506+
By default, the prime is encoded as a big-endian sequence of octets
3507+
in an {ArrayBuffer}. If the `bigint` option is `true`, then a {bigint}
3508+
is provided.
3509+
3510+
### `crypto.randomPrimeSync(size[, options])`
3511+
<!-- YAML
3512+
added: REPLACEME
3513+
-->
3514+
3515+
* `size` {number} The size (in bits) of the prime to generate.
3516+
* `options` {Object}
3517+
* `add` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
3518+
* `rem` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
3519+
* `safe` {boolean}
3520+
* `bigint` {boolean} When `true`, the generated prime is returned
3521+
as a `bigint`.
3522+
* Returns: {ArrayBuffer|bigint}
3523+
3524+
Generates a pseudo-random prime of `size` bits.
3525+
3526+
If `options.safe` is true, the prime will be a safe prime -- that is,
3527+
`(prime - 1)` / 2 will also be a prime.
3528+
3529+
If `options.add` and `options.rem` are set, the prime will satisfy the
3530+
condition that prime % add = rem. The `options.rem` is ignored if
3531+
`options.add` is not given. If `options.safe` is `true`, `options.add`
3532+
is given, and `options.rem` is `undefined`, then th prime generated
3533+
will satisfy the condition `prime % add = 3`. Otherwise if `options.safe`
3534+
is `false` and `options.rem` is `undefined`, `options.add` will be
3535+
ignored.
3536+
3537+
By default, the prime is encoded as a big-endian sequence of octets
3538+
in an {ArrayBuffer}. If the `bigint` option is `true`, then a {bigint}
3539+
is provided.
3540+
34353541
### `crypto.randomUUID([options])`
34363542
<!-- YAML
34373543
added: v15.6.0
@@ -4234,6 +4340,7 @@ See the [list of SSL OP Flags][] for details.
42344340
[RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt
42354341
[RFC 5208]: https://www.rfc-editor.org/rfc/rfc5208.txt
42364342
[Web Crypto API documentation]: webcrypto.md
4343+
[`BN_is_prime_ex`]: https://www.openssl.org/docs/man1.1.1/man3/BN_is_prime_ex.html
42374344
[`Buffer`]: buffer.md
42384345
[`EVP_BytesToKey`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_BytesToKey.html
42394346
[`KeyObject`]: #crypto_class_keyobject

lib/crypto.js

+8
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,14 @@ const {
5050
timingSafeEqual,
5151
} = internalBinding('crypto');
5252
const {
53+
checkPrime,
54+
checkPrimeSync,
5355
randomBytes,
5456
randomFill,
5557
randomFillSync,
5658
randomInt,
59+
randomPrime,
60+
randomPrimeSync,
5761
randomUUID,
5862
} = require('internal/crypto/random');
5963
const {
@@ -170,6 +174,8 @@ function createVerify(algorithm, options) {
170174

171175
module.exports = {
172176
// Methods
177+
checkPrime,
178+
checkPrimeSync,
173179
createCipheriv,
174180
createDecipheriv,
175181
createDiffieHellman,
@@ -204,6 +210,8 @@ module.exports = {
204210
randomFill,
205211
randomFillSync,
206212
randomInt,
213+
randomPrime,
214+
randomPrimeSync,
207215
randomUUID,
208216
scrypt,
209217
scryptSync,

lib/internal/crypto/random.js

+178
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const {
4+
BigInt,
45
FunctionPrototypeBind,
56
FunctionPrototypeCall,
67
MathMin,
@@ -10,6 +11,8 @@ const {
1011

1112
const {
1213
RandomBytesJob,
14+
RandomPrimeJob,
15+
CheckPrimeJob,
1316
kCryptoJobAsync,
1417
kCryptoJobSync,
1518
secureBuffer,
@@ -34,6 +37,7 @@ const {
3437
validateBoolean,
3538
validateCallback,
3639
validateObject,
40+
validateUint32,
3741
} = require('internal/validators');
3842

3943
const {
@@ -387,11 +391,185 @@ function randomUUID(options) {
387391
return uuid.latin1Slice(0, 36);
388392
}
389393

394+
function randomPrime(size, options, callback) {
395+
validateUint32(size, 'size', true);
396+
if (typeof options === 'function') {
397+
callback = options;
398+
options = {};
399+
}
400+
validateCallback(callback);
401+
validateObject(options, 'options');
402+
const {
403+
safe = false,
404+
add,
405+
rem,
406+
bigint = false,
407+
} = options;
408+
validateBoolean(safe, 'options.safe');
409+
validateBoolean(bigint, 'options.bigint');
410+
411+
if (add !== undefined && !isAnyArrayBuffer(add) && !isArrayBufferView(add)) {
412+
throw new ERR_INVALID_ARG_TYPE(
413+
'options.add',
414+
[
415+
'ArrayBuffer',
416+
'TypedArray',
417+
'Buffer',
418+
'DataView'
419+
],
420+
add);
421+
}
422+
423+
if (rem !== undefined && !isAnyArrayBuffer(rem) && !isArrayBufferView(rem)) {
424+
throw new ERR_INVALID_ARG_TYPE(
425+
'options.rem',
426+
[
427+
'ArrayBuffer',
428+
'TypedArray',
429+
'Buffer',
430+
'DataView'
431+
],
432+
rem);
433+
}
434+
435+
const job = new RandomPrimeJob(kCryptoJobAsync, size, safe, add, rem);
436+
job.ondone = (err, prime) => {
437+
if (err) {
438+
callback(err);
439+
return;
440+
}
441+
442+
callback(
443+
undefined,
444+
bigint ?
445+
BigInt(`0x${Buffer.from(prime).toString('hex')}`) :
446+
prime);
447+
};
448+
job.run();
449+
}
450+
451+
function randomPrimeSync(size, options = {}) {
452+
validateUint32(size, 'size', true);
453+
validateObject(options, 'options');
454+
const {
455+
safe = false,
456+
add,
457+
rem,
458+
bigint = false,
459+
} = options;
460+
validateBoolean(safe, 'options.safe');
461+
validateBoolean(bigint, 'options.bigint');
462+
463+
if (add !== undefined && !isAnyArrayBuffer(add) && !isArrayBufferView(add)) {
464+
throw new ERR_INVALID_ARG_TYPE(
465+
'options.add',
466+
[
467+
'ArrayBuffer',
468+
'TypedArray',
469+
'Buffer',
470+
'DataView'
471+
],
472+
add);
473+
}
474+
475+
if (rem !== undefined && !isAnyArrayBuffer(rem) && !isArrayBufferView(rem)) {
476+
throw new ERR_INVALID_ARG_TYPE(
477+
'options.rem',
478+
[
479+
'ArrayBuffer',
480+
'TypedArray',
481+
'Buffer',
482+
'DataView'
483+
],
484+
rem);
485+
}
486+
487+
const job = new RandomPrimeJob(kCryptoJobSync, size, safe, add, rem);
488+
const [err, prime] = job.run();
489+
if (err)
490+
throw err;
491+
492+
return bigint ?
493+
BigInt(`0x${Buffer.from(prime).toString('hex')}`) :
494+
prime;
495+
}
496+
497+
function toHexPadded(bigint) {
498+
const hex = bigint.toString(16);
499+
return hex.padStart(hex.length + (hex.length % 2), 0);
500+
}
501+
502+
function checkPrime(candidate, options = {}, callback) {
503+
if (typeof candidate === 'bigint')
504+
candidate = Buffer.from(toHexPadded(candidate), 'hex');
505+
if (!isAnyArrayBuffer(candidate) && !isArrayBufferView(candidate)) {
506+
throw new ERR_INVALID_ARG_TYPE(
507+
'candidate',
508+
[
509+
'ArrayBuffer',
510+
'TypedArray',
511+
'Buffer',
512+
'DataView'
513+
],
514+
candidate
515+
);
516+
}
517+
if (typeof options === 'function') {
518+
callback = options;
519+
options = {};
520+
}
521+
validateCallback(callback);
522+
validateObject(options, 'options');
523+
const {
524+
checks = 0,
525+
} = options;
526+
527+
validateUint32(checks, 'options.checks');
528+
529+
const job = new CheckPrimeJob(kCryptoJobAsync, candidate, checks);
530+
job.ondone = callback;
531+
job.run();
532+
}
533+
534+
function checkPrimeSync(candidate, options = {}) {
535+
if (typeof candidate === 'bigint')
536+
candidate = Buffer.from(toHexPadded(candidate), 'hex');
537+
if (!isAnyArrayBuffer(candidate) && !isArrayBufferView(candidate)) {
538+
throw new ERR_INVALID_ARG_TYPE(
539+
'candidate',
540+
[
541+
'ArrayBuffer',
542+
'TypedArray',
543+
'Buffer',
544+
'DataView'
545+
],
546+
candidate
547+
);
548+
}
549+
validateObject(options, 'options');
550+
const {
551+
checks = 0,
552+
} = options;
553+
554+
validateUint32(checks, 'options.checks');
555+
556+
const job = new CheckPrimeJob(kCryptoJobSync, candidate, checks);
557+
const [err, result] = job.run();
558+
if (err)
559+
throw err;
560+
561+
return result;
562+
}
563+
390564
module.exports = {
565+
checkPrime,
566+
checkPrimeSync,
391567
randomBytes,
392568
randomFill,
393569
randomFillSync,
394570
randomInt,
395571
getRandomValues,
396572
randomUUID,
573+
randomPrime,
574+
randomPrimeSync,
397575
};

src/async_wrap.h

+2
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ namespace node {
8484

8585
#if HAVE_OPENSSL
8686
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) \
87+
V(CHECKPRIMEREQUEST) \
8788
V(PBKDF2REQUEST) \
8889
V(KEYPAIRGENREQUEST) \
8990
V(KEYGENREQUEST) \
@@ -92,6 +93,7 @@ namespace node {
9293
V(DERIVEBITSREQUEST) \
9394
V(HASHREQUEST) \
9495
V(RANDOMBYTESREQUEST) \
96+
V(RANDOMPRIMEREQUEST) \
9597
V(SCRYPTREQUEST) \
9698
V(SIGNREQUEST) \
9799
V(TLSWRAP) \

0 commit comments

Comments
 (0)