crypto: add randomInt function · nodejs/node@53c9975 · GitHub
Skip to content

Commit 53c9975

Browse files
olalondeaddaleax
authored andcommitted
crypto: add randomInt function
PR-URL: #34600 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
1 parent 2c409a2 commit 53c9975

4 files changed

Lines changed: 301 additions & 2 deletions

File tree

doc/api/crypto.md

Lines changed: 39 additions & 0 deletions

lib/crypto.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ const {
5252
const {
5353
randomBytes,
5454
randomFill,
55-
randomFillSync
55+
randomFillSync,
56+
randomInt
5657
} = require('internal/crypto/random');
5758
const {
5859
pbkdf2,
@@ -184,6 +185,7 @@ module.exports = {
184185
randomBytes,
185186
randomFill,
186187
randomFillSync,
188+
randomInt,
187189
scrypt,
188190
scryptSync,
189191
sign: signOneShot,

lib/internal/crypto/random.js

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const {
44
MathMin,
55
NumberIsNaN,
6+
NumberIsSafeInteger
67
} = primordials;
78

89
const { AsyncWrap, Providers } = internalBinding('async_wrap');
@@ -119,6 +120,82 @@ function randomFill(buf, offset, size, cb) {
119120
_randomBytes(buf, offset, size, wrap);
120121
}
121122

123+
// Largest integer we can read from a buffer.
124+
// e.g.: Buffer.from("ff".repeat(6), "hex").readUIntBE(0, 6);
125+
const RAND_MAX = 0xFFFF_FFFF_FFFF;
126+
127+
// Generates an integer in [min, max) range where min is inclusive and max is
128+
// exclusive.
129+
function randomInt(min, max, cb) {
130+
// Detect optional min syntax
131+
// randomInt(max)
132+
// randomInt(max, cb)
133+
const minNotSpecified = typeof max === 'undefined' ||
134+
typeof max === 'function';
135+
136+
if (minNotSpecified) {
137+
cb = max;
138+
max = min;
139+
min = 0;
140+
}
141+
142+
const isSync = typeof cb === 'undefined';
143+
if (!isSync && typeof cb !== 'function') {
144+
throw new ERR_INVALID_CALLBACK(cb);
145+
}
146+
if (!NumberIsSafeInteger(min)) {
147+
throw new ERR_INVALID_ARG_TYPE('min', 'safe integer', min);
148+
}
149+
if (!NumberIsSafeInteger(max)) {
150+
throw new ERR_INVALID_ARG_TYPE('max', 'safe integer', max);
151+
}
152+
if (!(max >= min)) {
153+
throw new ERR_OUT_OF_RANGE('max', `>= ${min}`, max);
154+
}
155+
156+
// First we generate a random int between [0..range)
157+
const range = max - min;
158+
159+
if (!(range <= RAND_MAX)) {
160+
throw new ERR_OUT_OF_RANGE(`max${minNotSpecified ? '' : ' - min'}`,
161+
`<= ${RAND_MAX}`, range);
162+
}
163+
164+
const excess = RAND_MAX % range;
165+
const randLimit = RAND_MAX - excess;
166+
167+
if (isSync) {
168+
// Sync API
169+
while (true) {
170+
const x = randomBytes(6).readUIntBE(0, 6);
171+
// If x > (maxVal - (maxVal % range)), we will get "modulo bias"
172+
if (x > randLimit) {
173+
// Try again
174+
continue;
175+
}
176+
const n = (x % range) + min;
177+
return n;
178+
}
179+
} else {
180+
// Async API
181+
const pickAttempt = () => {
182+
randomBytes(6, (err, bytes) => {
183+
if (err) return cb(err);
184+
const x = bytes.readUIntBE(0, 6);
185+
// If x > (maxVal - (maxVal % range)), we will get "modulo bias"
186+
if (x > randLimit) {
187+
// Try again
188+
return pickAttempt();
189+
}
190+
const n = (x % range) + min;
191+
cb(null, n);
192+
});
193+
};
194+
195+
pickAttempt();
196+
}
197+
}
198+
122199
function handleError(ex, buf) {
123200
if (ex) throw ex;
124201
return buf;
@@ -127,5 +204,6 @@ function handleError(ex, buf) {
127204
module.exports = {
128205
randomBytes,
129206
randomFill,
130-
randomFillSync
207+
randomFillSync,
208+
randomInt
131209
};

test/parallel/test-crypto-random.js

Lines changed: 180 additions & 0 deletions

0 commit comments

Comments
 (0)