Skip to content

Commit cda64b6

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 fef2128 commit cda64b6

File tree

7 files changed

+511
-0
lines changed

7 files changed

+511
-0
lines changed

doc/api/crypto.md

+65
Original file line numberDiff line numberDiff line change
@@ -1961,6 +1961,25 @@ 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])`
1965+
<!-- YAML
1966+
added: REPLACEME
1967+
-->
1968+
1969+
* `candidate` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView} A
1970+
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.
1974+
**Defaults**: `1`
1975+
* `fast` {boolean} `true` to use the fast check algorithm.
1976+
* `trialDivision` {boolean} When using the fast check algorithm, `true`
1977+
to enable trial division.
1978+
* Returns: {boolean} `true` if the number is prime with an error probability
1979+
less than `0.25^options.checks`.
1980+
1981+
Checks the primality of the `candidate`.
1982+
19641983
### `crypto.createCipher(algorithm, password[, options])`
19651984
<!-- YAML
19661985
added: v0.1.94
@@ -3432,6 +3451,52 @@ const n = crypto.randomInt(1, 7);
34323451
console.log(`The dice rolled: ${n}`);
34333452
```
34343453

3454+
### `crypto.randomPrime(size[, options[, callback]])`
3455+
<!-- YAML
3456+
added: REPLACEME
3457+
-->
3458+
3459+
* `size` {number} The size (in bytes) of the prime to generate.
3460+
* `options` {Object}
3461+
* `add` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
3462+
* `rem` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
3463+
* `safe` {boolean}
3464+
* `callback` {Function}
3465+
* `err` {Error}
3466+
* `prime` {ArrayBuffer}
3467+
3468+
Generates a pseudo-random prime of `size` bytes.
3469+
3470+
If `options.safe` is true, the prime will be a safe prime -- that is,
3471+
prime - 1 / 2 will also be a prime.
3472+
3473+
If `options.add` and `options.rem` are set, the prime will satisfy the
3474+
condition that prime % add = rem.
3475+
3476+
The prime is encoded as a big-endian sequence of octets in an {ArrayBuffer}.
3477+
3478+
### `crypto.randomPrimeSync(size[, options])`
3479+
<!-- YAML
3480+
added: REPLACEME
3481+
-->
3482+
3483+
* `size` {number} The size (in bytes) of the prime to generate.
3484+
* `options` {Object}
3485+
* `add` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
3486+
* `rem` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
3487+
* `safe` {boolean}
3488+
* Returns: {ArrayBuffer}
3489+
3490+
Generates a pseudo-random prime of `size` bytes.
3491+
3492+
If `options.safe` is true, the prime will be a safe prime -- that is,
3493+
prime - 1 / 2 will also be a prime.
3494+
3495+
If `options.add` and `options.rem` are set, the prime will satisfy the
3496+
condition that prime % add = rem.
3497+
3498+
The prime is encoded as a big-endian sequence of octets in an {ArrayBuffer}.
3499+
34353500
### `crypto.randomUUID([options])`
34363501
<!-- YAML
34373502
added: v15.6.0

lib/crypto.js

+6
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,13 @@ const {
5050
timingSafeEqual,
5151
} = internalBinding('crypto');
5252
const {
53+
checkPrime,
5354
randomBytes,
5455
randomFill,
5556
randomFillSync,
5657
randomInt,
58+
randomPrime,
59+
randomPrimeSync,
5760
randomUUID,
5861
} = require('internal/crypto/random');
5962
const {
@@ -170,6 +173,7 @@ function createVerify(algorithm, options) {
170173

171174
module.exports = {
172175
// Methods
176+
checkPrime,
173177
createCipheriv,
174178
createDecipheriv,
175179
createDiffieHellman,
@@ -204,6 +208,8 @@ module.exports = {
204208
randomFill,
205209
randomFillSync,
206210
randomInt,
211+
randomPrime,
212+
randomPrimeSync,
207213
randomUUID,
208214
scrypt,
209215
scryptSync,

lib/internal/crypto/random.js

+117
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ const {
1010

1111
const {
1212
RandomBytesJob,
13+
RandomPrimeJob,
1314
kCryptoJobAsync,
1415
kCryptoJobSync,
1516
secureBuffer,
17+
checkPrime: _checkPrime,
1618
} = internalBinding('crypto');
1719

1820
const {
@@ -34,6 +36,7 @@ const {
3436
validateBoolean,
3537
validateCallback,
3638
validateObject,
39+
validateUint32,
3740
} = require('internal/validators');
3841

3942
const {
@@ -387,11 +390,125 @@ function randomUUID(options) {
387390
return uuid.latin1Slice(0, 36);
388391
}
389392

393+
function randomPrime(size, options, callback) {
394+
validateUint32(size, 'size', true);
395+
if (typeof options === 'function') {
396+
callback = options;
397+
options = {};
398+
}
399+
validateCallback(callback);
400+
validateObject(options, 'options');
401+
const {
402+
safe = false,
403+
add,
404+
rem,
405+
} = options;
406+
validateBoolean(safe, 'options.safe');
407+
408+
if (add !== undefined && !isAnyArrayBuffer(add) && !isArrayBufferView(add)) {
409+
throw new ERR_INVALID_ARG_TYPE(
410+
'options.add',
411+
[
412+
'ArrayBuffer',
413+
'TypedArray',
414+
'Buffer',
415+
'DataView'
416+
],
417+
add);
418+
}
419+
420+
if (rem !== undefined && !isAnyArrayBuffer(rem) && !isArrayBufferView(rem)) {
421+
throw new ERR_INVALID_ARG_TYPE(
422+
'options.rem',
423+
[
424+
'ArrayBuffer',
425+
'TypedArray',
426+
'Buffer',
427+
'DataView'
428+
],
429+
rem);
430+
}
431+
432+
const job = new RandomPrimeJob(kCryptoJobAsync, size, safe, add, rem);
433+
job.ondone = callback;
434+
job.run();
435+
}
436+
437+
function randomPrimeSync(size, options = {}) {
438+
validateUint32(size, 'size', true);
439+
validateObject(options, 'options');
440+
const {
441+
safe = false,
442+
add,
443+
rem,
444+
} = options;
445+
validateBoolean(safe, 'options.safe');
446+
447+
if (add !== undefined && !isAnyArrayBuffer(add) && !isArrayBufferView(add)) {
448+
throw new ERR_INVALID_ARG_TYPE(
449+
'options.add',
450+
[
451+
'ArrayBuffer',
452+
'TypedArray',
453+
'Buffer',
454+
'DataView'
455+
],
456+
add);
457+
}
458+
459+
if (rem !== undefined && !isAnyArrayBuffer(rem) && !isArrayBufferView(rem)) {
460+
throw new ERR_INVALID_ARG_TYPE(
461+
'options.rem',
462+
[
463+
'ArrayBuffer',
464+
'TypedArray',
465+
'Buffer',
466+
'DataView'
467+
],
468+
rem);
469+
}
470+
471+
const job = new RandomPrimeJob(kCryptoJobSync, size, safe, add, rem);
472+
const [err, prime] = job.run();
473+
if (err)
474+
throw err;
475+
476+
return prime;
477+
}
478+
479+
function checkPrime(candidate, options = {}) {
480+
if (!isAnyArrayBuffer(candidate) && !isArrayBufferView(candidate)) {
481+
throw new ERR_INVALID_ARG_TYPE(
482+
'candidate',
483+
[
484+
'ArrayBuffer',
485+
'TypedArray',
486+
'Buffer',
487+
'DataView'
488+
],
489+
candidate
490+
);
491+
}
492+
validateObject(options, 'options');
493+
const {
494+
fast = false,
495+
checks = 1,
496+
trialDivision = false
497+
} = options;
498+
validateBoolean(fast, 'options.fast');
499+
validateBoolean(trialDivision, 'options.trialDivision');
500+
validateUint32(checks, 'options.checks', true);
501+
return _checkPrime(candidate, fast, checks, trialDivision);
502+
}
503+
390504
module.exports = {
505+
checkPrime,
391506
randomBytes,
392507
randomFill,
393508
randomFillSync,
394509
randomInt,
395510
getRandomValues,
396511
randomUUID,
512+
randomPrime,
513+
randomPrimeSync,
397514
};

src/crypto/crypto_random.cc

+120
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
#include "threadpoolwork-inl.h"
88
#include "v8.h"
99

10+
#include <openssl/bn.h>
11+
1012
namespace node {
1113

14+
using v8::ArrayBuffer;
15+
using v8::BackingStore;
1216
using v8::FunctionCallbackInfo;
1317
using v8::Just;
1418
using v8::Local;
@@ -64,9 +68,125 @@ bool RandomBytesTraits::DeriveBits(
6468
return RAND_bytes(params.buffer, params.size) != 0;
6569
}
6670

71+
void RandomPrimeConfig::MemoryInfo(MemoryTracker* tracker) const {
72+
tracker->TrackFieldWithSize("prime", prime ? bits * 8 : 0);
73+
}
74+
75+
Maybe<bool> RandomPrimeTraits::EncodeOutput(
76+
Environment* env,
77+
const RandomPrimeConfig& params,
78+
ByteSource* unused,
79+
v8::Local<v8::Value>* result) {
80+
size_t size = BN_num_bytes(params.prime.get());
81+
std::shared_ptr<BackingStore> store =
82+
ArrayBuffer::NewBackingStore(env->isolate(), size);
83+
BN_bn2binpad(
84+
params.prime.get(),
85+
reinterpret_cast<unsigned char*>(store->Data()),
86+
size);
87+
*result = ArrayBuffer::New(env->isolate(), store);
88+
return Just(true);
89+
}
90+
91+
Maybe<bool> RandomPrimeTraits::AdditionalConfig(
92+
CryptoJobMode mode,
93+
const FunctionCallbackInfo<Value>& args,
94+
unsigned int offset,
95+
RandomPrimeConfig* params) {
96+
Environment* env = Environment::GetCurrent(args);
97+
CHECK(args[offset]->IsUint32()); // Size
98+
CHECK(args[offset + 1]->IsBoolean()); // Safe
99+
100+
const uint32_t size = args[offset].As<Uint32>()->Value();
101+
bool safe = args[offset + 1]->IsTrue();
102+
103+
if (!args[offset + 2]->IsUndefined()) {
104+
params->add.reset(BN_new());
105+
ArrayBufferOrViewContents<unsigned char> add(args[offset + 2]);
106+
BN_bin2bn(add.data(), add.size(), params->add.get());
107+
if (!params->add) {
108+
THROW_ERR_INVALID_ARG_VALUE(env, "invalid options.add");
109+
return Nothing<bool>();
110+
}
111+
}
112+
113+
if (!args[offset + 3]->IsUndefined()) {
114+
params->rem.reset(BN_new());
115+
ArrayBufferOrViewContents<unsigned char> rem(args[offset + 3]);
116+
BN_bin2bn(rem.data(), rem.size(), params->rem.get());
117+
if (!params->rem) {
118+
THROW_ERR_INVALID_ARG_VALUE(env, "invalid options.rem");
119+
return Nothing<bool>();
120+
}
121+
}
122+
123+
int bits = static_cast<int>(size * 8);
124+
if (bits < 0) {
125+
THROW_ERR_OUT_OF_RANGE(env, "invalid size");
126+
return Nothing<bool>();
127+
}
128+
129+
params->bits = bits;
130+
params->safe = safe;
131+
params->prime.reset(BN_new());
132+
133+
return Just(true);
134+
}
135+
136+
bool RandomPrimeTraits::DeriveBits(
137+
Environment* env,
138+
const RandomPrimeConfig& params,
139+
ByteSource* unused) {
140+
141+
CheckEntropy();
142+
143+
if (BN_generate_prime_ex(
144+
params.prime.get(),
145+
params.bits,
146+
params.safe ? 1 : 0,
147+
params.add.get(),
148+
params.rem.get(),
149+
nullptr) == 0) {
150+
return false;
151+
}
152+
153+
return true;
154+
}
155+
156+
void CheckPrime(const FunctionCallbackInfo<Value>& args) {
157+
Environment* env = Environment::GetCurrent(args);
158+
ArrayBufferOrViewContents<unsigned char> candidate(args[0]);
159+
bool fast = args[1]->IsTrue();
160+
int checks = static_cast<int>(args[2].As<Uint32>()->Value());
161+
CHECK_GE(checks, 1);
162+
bool trial_division = args[3]->IsTrue();
163+
164+
BignumCtxPointer ctx(BN_CTX_new());
165+
BignumPointer check(BN_bin2bn(candidate.data(), candidate.size(), nullptr));
166+
167+
int ret = fast
168+
? BN_is_prime_fasttest_ex(
169+
check.get(),
170+
checks,
171+
ctx.get(),
172+
trial_division ? 1 : 0,
173+
nullptr)
174+
: BN_is_prime_ex(check.get(), checks, ctx.get(), nullptr);
175+
switch (ret) {
176+
case -1:
177+
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Error checking the prime");
178+
case 0:
179+
return args.GetReturnValue().Set(false);
180+
default:
181+
return args.GetReturnValue().Set(true);
182+
}
183+
}
184+
67185
namespace Random {
68186
void Initialize(Environment* env, Local<Object> target) {
69187
RandomBytesJob::Initialize(env, target);
188+
RandomPrimeJob::Initialize(env, target);
189+
env->SetMethod(target, "checkPrime", CheckPrime);
70190
}
71191
} // namespace Random
72192
} // namespace crypto

0 commit comments

Comments
 (0)