Skip to content

Commit c068dc4

Browse files
committed
crypto(randomInt): make max arg inclusive
1 parent c7e3d0b commit c068dc4

File tree

3 files changed

+57
-27
lines changed

3 files changed

+57
-27
lines changed

doc/api/crypto.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2803,18 +2803,19 @@ request.
28032803
### `crypto.randomInt([min, ]max[, callback])`
28042804
<!-- YAML
28052805
added:
2806-
- v14.8.0
2806+
- CHANGEME
28072807
-->
28082808

2809-
* `min` {integer} Start of random range. **Default**: `0`.
2810-
* `max` {integer} End of random range (non-inclusive).
2809+
* `min` {integer} Start of random range (inclusive). **Default**: `0`.
2810+
* `max` {integer} End of random range (inclusive).
28112811
* `callback` {Function} `function(err, n) {}`.
28122812

2813-
Return a random integer `n` such that `min <= n < max`. This
2813+
Return a random integer `n` such that `min <= n <= max`. This
28142814
implementation avoids [modulo
28152815
bias](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#Modulo_bias).
28162816

2817-
The maximum supported range value (`max - min`) is `2^48 - 1`.
2817+
The cardinality of the range (`max - min + 1`) must be at most `2^48 -
2818+
1`. `min` and `max` must be safe integers.
28182819

28192820
If the `callback` function is not provided, the random integer is generated
28202821
synchronously.
@@ -2823,18 +2824,18 @@ synchronously.
28232824
// Asynchronous
28242825
crypto.randomInt(3, (err, n) => {
28252826
if (err) throw err;
2826-
console.log(`Random number chosen from (0, 1, 2): ${n}`);
2827+
console.log(`Random number chosen from (0, 1, 2, 3): ${n}`);
28272828
});
28282829
```
28292830

28302831
```js
28312832
// Synchronous
28322833
const n = crypto.randomInt(3);
2833-
console.log(`Random number chosen from (0, 1, 2): ${n}`);
2834+
console.log(`Random number chosen from (0, 1, 2, 3): ${n}`);
28342835
```
28352836

28362837
```js
2837-
crypto.randomInt(1, 7, (err, n) => {
2838+
crypto.randomInt(1, 6, (err, n) => {
28382839
if (err) throw err;
28392840
console.log(`The dice rolled: ${n}`);
28402841
});

lib/internal/crypto/random.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const {
44
MathMin,
55
NumberIsNaN,
6-
NumberIsInteger
6+
NumberIsSafeInteger
77
} = primordials;
88

99
const { AsyncWrap, Providers } = internalBinding('async_wrap');
@@ -124,6 +124,8 @@ function randomFill(buf, offset, size, cb) {
124124
// e.g.: Buffer.from("ff".repeat(6), "hex").readUIntBE(0, 6);
125125
const RAND_MAX = 281474976710655;
126126

127+
// Generates an integer in [min, max] range where both min and max are
128+
// inclusive.
127129
function randomInt(min, max, cb) {
128130
// randomInt(max, cb)
129131
// randomInt(max)
@@ -136,18 +138,18 @@ function randomInt(min, max, cb) {
136138
if (!isSync && typeof cb !== 'function') {
137139
throw new ERR_INVALID_CALLBACK(cb);
138140
}
139-
if (!NumberIsInteger(min)) {
140-
throw new ERR_INVALID_ARG_TYPE('min', 'integer', min);
141+
if (!NumberIsSafeInteger(min)) {
142+
throw new ERR_INVALID_ARG_TYPE('min', 'safe integer', min);
141143
}
142-
if (!NumberIsInteger(max)) {
143-
throw new ERR_INVALID_ARG_TYPE('max', 'integer', max);
144+
if (!NumberIsSafeInteger(max)) {
145+
throw new ERR_INVALID_ARG_TYPE('max', 'safe integer', max);
144146
}
145147
if (!(max > min)) {
146148
throw new ERR_OUT_OF_RANGE('max', `> ${min}`, max);
147149
}
148150

149-
// First we generate a random int between [0..range)
150-
const range = max - min;
151+
// First we generate a random int between [0..range]
152+
const range = max - min + 1;
151153

152154
if (!(range <= RAND_MAX)) {
153155
throw new ERR_OUT_OF_RANGE('max - min', `<= ${RAND_MAX}`, range);

test/parallel/test-crypto-random.js

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -320,10 +320,10 @@ assert.throws(
320320
{
321321
const randomInts = [];
322322
for (let i = 0; i < 100; i++) {
323-
crypto.randomInt(1, 4, common.mustCall((err, n) => {
323+
crypto.randomInt(1, 3, common.mustCall((err, n) => {
324324
assert.ifError(err);
325325
assert.ok(n >= 1);
326-
assert.ok(n < 4);
326+
assert.ok(n <= 3);
327327
randomInts.push(n);
328328
if (randomInts.length === 100) {
329329
assert.ok(!randomInts.includes(0));
@@ -338,10 +338,10 @@ assert.throws(
338338
{
339339
const randomInts = [];
340340
for (let i = 0; i < 100; i++) {
341-
crypto.randomInt(-10, -7, common.mustCall((err, n) => {
341+
crypto.randomInt(-10, -8, common.mustCall((err, n) => {
342342
assert.ifError(err);
343343
assert.ok(n >= -10);
344-
assert.ok(n < -7);
344+
assert.ok(n <= -8);
345345
randomInts.push(n);
346346
if (randomInts.length === 100) {
347347
assert.ok(!randomInts.includes(-11));
@@ -359,12 +359,15 @@ assert.throws(
359359
crypto.randomInt(3, common.mustCall((err, n) => {
360360
assert.ifError(err);
361361
assert.ok(n >= 0);
362-
assert.ok(n < 3);
362+
assert.ok(n <= 3);
363363
randomInts.push(n);
364364
if (randomInts.length === 100) {
365+
assert.ok(!randomInts.includes(-1));
365366
assert.ok(randomInts.includes(0));
366367
assert.ok(randomInts.includes(1));
367368
assert.ok(randomInts.includes(2));
369+
assert.ok(randomInts.includes(3));
370+
assert.ok(!randomInts.includes(4));
368371
}
369372
}));
370373
}
@@ -375,21 +378,24 @@ assert.throws(
375378
for (let i = 0; i < 100; i++) {
376379
const n = crypto.randomInt(3);
377380
assert.ok(n >= 0);
378-
assert.ok(n < 3);
381+
assert.ok(n <= 3);
379382
randomInts.push(n);
380383
}
381384

385+
assert.ok(!randomInts.includes(-1));
382386
assert.ok(randomInts.includes(0));
383387
assert.ok(randomInts.includes(1));
384388
assert.ok(randomInts.includes(2));
389+
assert.ok(randomInts.includes(3));
390+
assert.ok(!randomInts.includes(4));
385391
}
386392
{
387393
// Synchronous API with min
388394
const randomInts = [];
389395
for (let i = 0; i < 100; i++) {
390-
const n = crypto.randomInt(3, 6);
396+
const n = crypto.randomInt(3, 5);
391397
assert.ok(n >= 3);
392-
assert.ok(n < 6);
398+
assert.ok(n <= 5);
393399
randomInts.push(n);
394400
}
395401

@@ -403,25 +409,46 @@ assert.throws(
403409
assert.throws(() => crypto.randomInt(i, 100, common.mustNotCall()), {
404410
code: 'ERR_INVALID_ARG_TYPE',
405411
name: 'TypeError',
406-
message: 'The "min" argument must be integer.' +
412+
message: 'The "min" argument must be safe integer.' +
407413
`${common.invalidArgTypeHelper(i)}`,
408414
});
409415

410416
assert.throws(() => crypto.randomInt(0, i, common.mustNotCall()), {
411417
code: 'ERR_INVALID_ARG_TYPE',
412418
name: 'TypeError',
413-
message: 'The "max" argument must be integer.' +
419+
message: 'The "max" argument must be safe integer.' +
414420
`${common.invalidArgTypeHelper(i)}`,
415421
});
416422

417423
assert.throws(() => crypto.randomInt(i, common.mustNotCall()), {
418424
code: 'ERR_INVALID_ARG_TYPE',
419425
name: 'TypeError',
420-
message: 'The "max" argument must be integer.' +
426+
message: 'The "max" argument must be safe integer.' +
421427
`${common.invalidArgTypeHelper(i)}`,
422428
});
423429
});
424430

431+
const minInt = Number.MIN_SAFE_INTEGER;
432+
const maxInt = Number.MAX_SAFE_INTEGER;
433+
434+
crypto.randomInt(minInt, minInt + 5, common.mustCall());
435+
crypto.randomInt(maxInt - 5, maxInt, common.mustCall());
436+
437+
assert.throws(() => crypto.randomInt(minInt - 1, minInt + 5, common.mustNotCall()), {
438+
code: 'ERR_INVALID_ARG_TYPE',
439+
name: 'TypeError',
440+
message: 'The "min" argument must be safe integer.' +
441+
`${common.invalidArgTypeHelper(minInt - 1)}`,
442+
});
443+
444+
assert.throws(() => crypto.randomInt(maxInt - 5, maxInt + 1, common.mustNotCall()), {
445+
code: 'ERR_INVALID_ARG_TYPE',
446+
name: 'TypeError',
447+
message: 'The "max" argument must be safe integer.' +
448+
`${common.invalidArgTypeHelper(maxInt + 1)}`,
449+
});
450+
451+
425452
assert.throws(() => crypto.randomInt(0, 0, common.mustNotCall()), {
426453
code: 'ERR_OUT_OF_RANGE',
427454
name: 'RangeError',
@@ -445,7 +472,7 @@ assert.throws(
445472
name: 'RangeError',
446473
message: 'The value of "max - min" is out of range. ' +
447474
`It must be <= ${(2 ** 48) - 1}. ` +
448-
'Received 281_474_976_710_656'
475+
'Received 281_474_976_710_657'
449476
});
450477

451478
[1, true, NaN, null, {}, []].forEach((i) => {

0 commit comments

Comments
 (0)