| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 1bf5d1a commit 44d89a9
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2793,6 +2793,44 @@ threadpool request. To minimize threadpool task length variation, partition | |||
| 2793 | 2793 | large `randomFill` requests when doing so as part of fulfilling a client | |
| 2794 | 2794 | request. | |
| 2795 | 2795 | ||
| 2796 | + ### `crypto.randomInt([min, ]max[, callback])` | ||
| 2797 | + <!-- YAML | ||
| 2798 | + added: REPLACEME | ||
| 2799 | + --> | ||
| 2800 | + | ||
| 2801 | + * `min` {integer} Start of random range (inclusive). **Default**: `0`. | ||
| 2802 | + * `max` {integer} End of random range (exclusive). | ||
| 2803 | + * `callback` {Function} `function(err, n) {}`. | ||
| 2804 | + | ||
| 2805 | + Return a random integer `n` such that `min <= n < max`. This | ||
| 2806 | + implementation avoids [modulo bias][]. | ||
| 2807 | + | ||
| 2808 | + The range (`max - min`) must be less than `2^48`. `min` and `max` must | ||
| 2809 | + be safe integers. | ||
| 2810 | + | ||
| 2811 | + If the `callback` function is not provided, the random integer is | ||
| 2812 | + generated synchronously. | ||
| 2813 | + | ||
| 2814 | + ```js | ||
| 2815 | + // Asynchronous | ||
| 2816 | + crypto.randomInt(3, (err, n) => { | ||
| 2817 | + if (err) throw err; | ||
| 2818 | + console.log(`Random number chosen from (0, 1, 2): ${n}`); | ||
| 2819 | + }); | ||
| 2820 | + ``` | ||
| 2821 | + | ||
| 2822 | + ```js | ||
| 2823 | + // Synchronous | ||
| 2824 | + const n = crypto.randomInt(3); | ||
| 2825 | + console.log(`Random number chosen from (0, 1, 2): ${n}`); | ||
| 2826 | + ``` | ||
| 2827 | + | ||
| 2828 | + ```js | ||
| 2829 | + // With `min` argument | ||
| 2830 | + const n = crypto.randomInt(1, 7); | ||
| 2831 | + console.log(`The dice rolled: ${n}`); | ||
| 2832 | + ``` | ||
| 2833 | + | ||
| 2796 | 2834 | ### `crypto.scrypt(password, salt, keylen[, options], callback)` | |
| 2797 | 2835 | <!-- YAML | |
| 2798 | 2836 | added: v10.5.0 | |
@@ -3565,6 +3603,7 @@ See the [list of SSL OP Flags][] for details. | |||
| 3565 | 3603 | [NIST SP 800-131A]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf | |
| 3566 | 3604 | [NIST SP 800-132]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf | |
| 3567 | 3605 | [NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf | |
| 3606 | + [modulo bias]: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#Modulo_bias | ||
| 3568 | 3607 | [Nonce-Disrespecting Adversaries]: https://github.com/nonce-disrespect/nonce-disrespect | |
| 3569 | 3608 | [OpenSSL's SPKAC implementation]: https://www.openssl.org/docs/man1.1.0/apps/openssl-spkac.html | |
| 3570 | 3609 | [RFC 1421]: https://www.rfc-editor.org/rfc/rfc1421.txt | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -52,7 +52,8 @@ const { | |||
| 52 | 52 | const { | |
| 53 | 53 | randomBytes, | |
| 54 | 54 | randomFill, | |
| 55 | - randomFillSync | ||
| 55 | + randomFillSync, | ||
| 56 | + randomInt | ||
| 56 | 57 | } = require('internal/crypto/random'); | |
| 57 | 58 | const { | |
| 58 | 59 | pbkdf2, | |
@@ -184,6 +185,7 @@ module.exports = { | |||
| 184 | 185 | randomBytes, | |
| 185 | 186 | randomFill, | |
| 186 | 187 | randomFillSync, | |
| 188 | + randomInt, | ||
| 187 | 189 | scrypt, | |
| 188 | 190 | scryptSync, | |
| 189 | 191 | sign: signOneShot, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,6 +3,7 @@ | |||
| 3 | 3 | const { | |
| 4 | 4 | MathMin, | |
| 5 | 5 | NumberIsNaN, | |
| 6 | + NumberIsSafeInteger | ||
| 6 | 7 | } = primordials; | |
| 7 | 8 | ||
| 8 | 9 | const { AsyncWrap, Providers } = internalBinding('async_wrap'); | |
@@ -119,6 +120,82 @@ function randomFill(buf, offset, size, cb) { | |||
| 119 | 120 | _randomBytes(buf, offset, size, wrap); | |
| 120 | 121 | } | |
| 121 | 122 | ||
| 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 | + | ||
| 122 | 199 | function handleError(ex, buf) { | |
| 123 | 200 | if (ex) throw ex; | |
| 124 | 201 | return buf; | |
@@ -127,5 +204,6 @@ function handleError(ex, buf) { | |||
| 127 | 204 | module.exports = { | |
| 128 | 205 | randomBytes, | |
| 129 | 206 | randomFill, | |
| 130 | - randomFillSync | ||
| 207 | + randomFillSync, | ||
| 208 | + randomInt | ||
| 131 | 209 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -315,3 +315,183 @@ assert.throws( | |||
| 315 | 315 | assert.strictEqual(desc.writable, true); | |
| 316 | 316 | assert.strictEqual(desc.enumerable, false); | |
| 317 | 317 | }); | |
| 318 | + | ||
| 319 | + | ||
| 320 | + { | ||
| 321 | + // Asynchronous API | ||
| 322 | + const randomInts = []; | ||
| 323 | + for (let i = 0; i < 100; i++) { | ||
| 324 | + crypto.randomInt(3, common.mustCall((err, n) => { | ||
| 325 | + assert.ifError(err); | ||
| 326 | + assert.ok(n >= 0); | ||
| 327 | + assert.ok(n < 3); | ||
| 328 | + randomInts.push(n); | ||
| 329 | + if (randomInts.length === 100) { | ||
| 330 | + assert.ok(!randomInts.includes(-1)); | ||
| 331 | + assert.ok(randomInts.includes(0)); | ||
| 332 | + assert.ok(randomInts.includes(1)); | ||
| 333 | + assert.ok(randomInts.includes(2)); | ||
| 334 | + assert.ok(!randomInts.includes(3)); | ||
| 335 | + } | ||
| 336 | + })); | ||
| 337 | + } | ||
| 338 | + } | ||
| 339 | + { | ||
| 340 | + // Synchronous API | ||
| 341 | + const randomInts = []; | ||
| 342 | + for (let i = 0; i < 100; i++) { | ||
| 343 | + const n = crypto.randomInt(3); | ||
| 344 | + assert.ok(n >= 0); | ||
| 345 | + assert.ok(n < 3); | ||
| 346 | + randomInts.push(n); | ||
| 347 | + } | ||
| 348 | + | ||
| 349 | + assert.ok(!randomInts.includes(-1)); | ||
| 350 | + assert.ok(randomInts.includes(0)); | ||
| 351 | + assert.ok(randomInts.includes(1)); | ||
| 352 | + assert.ok(randomInts.includes(2)); | ||
| 353 | + assert.ok(!randomInts.includes(3)); | ||
| 354 | + } | ||
| 355 | + { | ||
| 356 | + // Positive range | ||
| 357 | + const randomInts = []; | ||
| 358 | + for (let i = 0; i < 100; i++) { | ||
| 359 | + crypto.randomInt(1, 3, common.mustCall((err, n) => { | ||
| 360 | + assert.ifError(err); | ||
| 361 | + assert.ok(n >= 1); | ||
| 362 | + assert.ok(n < 3); | ||
| 363 | + randomInts.push(n); | ||
| 364 | + if (randomInts.length === 100) { | ||
| 365 | + assert.ok(!randomInts.includes(0)); | ||
| 366 | + assert.ok(randomInts.includes(1)); | ||
| 367 | + assert.ok(randomInts.includes(2)); | ||
| 368 | + assert.ok(!randomInts.includes(3)); | ||
| 369 | + } | ||
| 370 | + })); | ||
| 371 | + } | ||
| 372 | + } | ||
| 373 | + { | ||
| 374 | + // Negative range | ||
| 375 | + const randomInts = []; | ||
| 376 | + for (let i = 0; i < 100; i++) { | ||
| 377 | + crypto.randomInt(-10, -8, common.mustCall((err, n) => { | ||
| 378 | + assert.ifError(err); | ||
| 379 | + assert.ok(n >= -10); | ||
| 380 | + assert.ok(n < -8); | ||
| 381 | + randomInts.push(n); | ||
| 382 | + if (randomInts.length === 100) { | ||
| 383 | + assert.ok(!randomInts.includes(-11)); | ||
| 384 | + assert.ok(randomInts.includes(-10)); | ||
| 385 | + assert.ok(randomInts.includes(-9)); | ||
| 386 | + assert.ok(!randomInts.includes(-8)); | ||
| 387 | + } | ||
| 388 | + })); | ||
| 389 | + } | ||
| 390 | + } | ||
| 391 | + { | ||
| 392 | + | ||
| 393 | + ['10', true, NaN, null, {}, []].forEach((i) => { | ||
| 394 | + const invalidMinError = { | ||
| 395 | + code: 'ERR_INVALID_ARG_TYPE', | ||
| 396 | + name: 'TypeError', | ||
| 397 | + message: 'The "min" argument must be safe integer.' + | ||
| 398 | + `${common.invalidArgTypeHelper(i)}`, | ||
| 399 | + }; | ||
| 400 | + const invalidMaxError = { | ||
| 401 | + code: 'ERR_INVALID_ARG_TYPE', | ||
| 402 | + name: 'TypeError', | ||
| 403 | + message: 'The "max" argument must be safe integer.' + | ||
| 404 | + `${common.invalidArgTypeHelper(i)}`, | ||
| 405 | + }; | ||
| 406 | + | ||
| 407 | + assert.throws( | ||
| 408 | + () => crypto.randomInt(i, 100), | ||
| 409 | + invalidMinError | ||
| 410 | + ); | ||
| 411 | + assert.throws( | ||
| 412 | + () => crypto.randomInt(i, 100, common.mustNotCall()), | ||
| 413 | + invalidMinError | ||
| 414 | + ); | ||
| 415 | + assert.throws( | ||
| 416 | + () => crypto.randomInt(i), | ||
| 417 | + invalidMaxError | ||
| 418 | + ); | ||
| 419 | + assert.throws( | ||
| 420 | + () => crypto.randomInt(i, common.mustNotCall()), | ||
| 421 | + invalidMaxError | ||
| 422 | + ); | ||
| 423 | + assert.throws( | ||
| 424 | + () => crypto.randomInt(0, i, common.mustNotCall()), | ||
| 425 | + invalidMaxError | ||
| 426 | + ); | ||
| 427 | + assert.throws( | ||
| 428 | + () => crypto.randomInt(0, i), | ||
| 429 | + invalidMaxError | ||
| 430 | + ); | ||
| 431 | + }); | ||
| 432 | + | ||
| 433 | + const maxInt = Number.MAX_SAFE_INTEGER; | ||
| 434 | + const minInt = Number.MIN_SAFE_INTEGER; | ||
| 435 | + | ||
| 436 | + crypto.randomInt(minInt, minInt + 5, common.mustCall()); | ||
| 437 | + crypto.randomInt(maxInt - 5, maxInt, common.mustCall()); | ||
| 438 | + | ||
| 439 | + assert.throws( | ||
| 440 | + () => crypto.randomInt(minInt - 1, minInt + 5, common.mustNotCall()), | ||
| 441 | + { | ||
| 442 | + code: 'ERR_INVALID_ARG_TYPE', | ||
| 443 | + name: 'TypeError', | ||
| 444 | + message: 'The "min" argument must be safe integer.' + | ||
| 445 | + `${common.invalidArgTypeHelper(minInt - 1)}`, | ||
| 446 | + } | ||
| 447 | + ); | ||
| 448 | + | ||
| 449 | + assert.throws( | ||
| 450 | + () => crypto.randomInt(maxInt + 1, common.mustNotCall()), | ||
| 451 | + { | ||
| 452 | + code: 'ERR_INVALID_ARG_TYPE', | ||
| 453 | + name: 'TypeError', | ||
| 454 | + message: 'The "max" argument must be safe integer.' + | ||
| 455 | + `${common.invalidArgTypeHelper(maxInt + 1)}`, | ||
| 456 | + } | ||
| 457 | + ); | ||
| 458 | + | ||
| 459 | + crypto.randomInt(0, common.mustCall()); | ||
| 460 | + crypto.randomInt(0, 0, common.mustCall()); | ||
| 461 | + assert.throws(() => crypto.randomInt(-1, common.mustNotCall()), { | ||
| 462 | + code: 'ERR_OUT_OF_RANGE', | ||
| 463 | + name: 'RangeError', | ||
| 464 | + message: 'The value of "max" is out of range. It must be >= 0. Received -1' | ||
| 465 | + }); | ||
| 466 | + | ||
| 467 | + const MAX_RANGE = 0xFFFF_FFFF_FFFF; | ||
| 468 | + crypto.randomInt(MAX_RANGE, common.mustCall()); | ||
| 469 | + crypto.randomInt(1, MAX_RANGE + 1, common.mustCall()); | ||
| 470 | + assert.throws( | ||
| 471 | + () => crypto.randomInt(1, MAX_RANGE + 2, common.mustNotCall()), | ||
| 472 | + { | ||
| 473 | + code: 'ERR_OUT_OF_RANGE', | ||
| 474 | + name: 'RangeError', | ||
| 475 | + message: 'The value of "max - min" is out of range. ' + | ||
| 476 | + `It must be <= ${MAX_RANGE}. ` + | ||
| 477 | + 'Received 281_474_976_710_656' | ||
| 478 | + } | ||
| 479 | + ); | ||
| 480 | + | ||
| 481 | + assert.throws(() => crypto.randomInt(MAX_RANGE + 1, common.mustNotCall()), { | ||
| 482 | + code: 'ERR_OUT_OF_RANGE', | ||
| 483 | + name: 'RangeError', | ||
| 484 | + message: 'The value of "max" is out of range. ' + | ||
| 485 | + `It must be <= ${MAX_RANGE}. ` + | ||
| 486 | + 'Received 281_474_976_710_656' | ||
| 487 | + }); | ||
| 488 | + | ||
| 489 | + [true, NaN, null, {}, [], 10].forEach((i) => { | ||
| 490 | + const cbError = { | ||
| 491 | + code: 'ERR_INVALID_CALLBACK', | ||
| 492 | + name: 'TypeError', | ||
| 493 | + message: `Callback must be a function. Received ${inspect(i)}` | ||
| 494 | + }; | ||
| 495 | + assert.throws(() => crypto.randomInt(0, 1, i), cbError); | ||
| 496 | + }); | ||
| 497 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments