Skip to content

Commit 0df5e6b

Browse files
committed
crypto: update randomUUIDv7() to accept options for disabling entropy cache
1 parent b1d3efc commit 0df5e6b

File tree

3 files changed

+76
-47
lines changed

3 files changed

+76
-47
lines changed

doc/api/crypto.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5826,12 +5826,18 @@ added:
58265826
Generates a random [RFC 4122][] version 4 UUID. The UUID is generated using a
58275827
cryptographic pseudorandom number generator.
58285828

5829-
### `crypto.randomUUIDv7()`
5829+
### `crypto.randomUUIDv7([options])`
58305830

58315831
<!-- YAML
58325832
added: REPLACEME
58335833
-->
58345834

5835+
* `options` {Object}
5836+
* `disableEntropyCache` {boolean} By default, to improve performance,
5837+
Node.js generates and caches enough
5838+
random data to generate up to 128 random UUIDs. To generate a UUID
5839+
without using the cache, set `disableEntropyCache` to `true`.
5840+
**Default:** `false`.
58355841
* Returns: {string}
58365842

58375843
Generates a random [RFC 9562][] version 7 UUID. The UUID contains a millisecond

lib/internal/crypto/random.js

Lines changed: 41 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ const {
1414
DateNow,
1515
FunctionPrototypeBind,
1616
FunctionPrototypeCall,
17-
MathFloor,
1817
MathMin,
1918
NumberIsNaN,
2019
NumberIsSafeInteger,
@@ -361,7 +360,7 @@ function getHexBytes() {
361360
return hexBytesCache;
362361
}
363362

364-
function serializeUUID(buf, offset = 0) {
363+
function serializeUUID(buf, offset = 0, version = 0x40, variant = 0x80) {
365364
const kHexBytes = getHexBytes();
366365
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
367366
return kHexBytes[buf[offset]] +
@@ -372,10 +371,10 @@ function serializeUUID(buf, offset = 0) {
372371
kHexBytes[buf[offset + 4]] +
373372
kHexBytes[buf[offset + 5]] +
374373
'-' +
375-
kHexBytes[(buf[offset + 6] & 0x0f) | 0x40] +
374+
kHexBytes[(buf[offset + 6] & 0x0f) | version] +
376375
kHexBytes[buf[offset + 7]] +
377376
'-' +
378-
kHexBytes[(buf[offset + 8] & 0x3f) | 0x80] +
377+
kHexBytes[(buf[offset + 8] & 0x3f) | variant] +
379378
kHexBytes[buf[offset + 9]] +
380379
'-' +
381380
kHexBytes[buf[offset + 10]] +
@@ -416,56 +415,52 @@ function randomUUID(options) {
416415
return disableEntropyCache ? getUnbufferedUUID() : getBufferedUUID();
417416
}
418417

419-
// Implements an RFC 9562 version 7 UUID (time-ordered).
420-
// Layout (128 bits total):
421-
// 48 bits - unix_ts_ms: UTC timestamp in milliseconds
422-
// 4 bits - version: 0b0111 (7)
423-
// 12 bits - rand_a: random
424-
// 2 bits - variant: 0b10
425-
// 62 bits - rand_b: random
418+
let uuidV7Data;
419+
let uuidV7NotBuffered;
420+
let uuidV7Batch = 0;
426421

427-
let uuidV7Buffer;
422+
function writeTimestamp(buf, offset) {
423+
const now = DateNow();
424+
const msb = now / (2 ** 32);
425+
buf[offset] = msb >>> 8;
426+
buf[offset + 1] = msb;
427+
buf[offset + 2] = now >>> 24;
428+
buf[offset + 3] = now >>> 16;
429+
buf[offset + 4] = now >>> 8;
430+
buf[offset + 5] = now;
431+
}
428432

429-
function randomUUIDv7() {
430-
uuidV7Buffer ??= secureBuffer(16);
431-
if (uuidV7Buffer === undefined)
433+
function getBufferedUUIDv7() {
434+
uuidV7Data ??= secureBuffer(16 * kBatchSize);
435+
if (uuidV7Data === undefined)
432436
throw new ERR_OPERATION_FAILED('Out of memory');
433437

434-
randomFillSync(uuidV7Buffer);
438+
if (uuidV7Batch === 0) randomFillSync(uuidV7Data);
439+
uuidV7Batch = (uuidV7Batch + 1) % kBatchSize;
440+
const offset = uuidV7Batch * 16;
441+
writeTimestamp(uuidV7Data, offset);
442+
return serializeUUID(uuidV7Data, offset, 0x70);
443+
}
435444

436-
const now = DateNow();
437-
uuidV7Buffer[0] = MathFloor(now / 0x10000000000) & 0xff;
438-
uuidV7Buffer[1] = MathFloor(now / 0x100000000) & 0xff;
439-
uuidV7Buffer[2] = MathFloor(now / 0x1000000) & 0xff;
440-
uuidV7Buffer[3] = MathFloor(now / 0x10000) & 0xff;
441-
uuidV7Buffer[4] = MathFloor(now / 0x100) & 0xff;
442-
uuidV7Buffer[5] = now & 0xff;
445+
function getUnbufferedUUIDv7() {
446+
uuidV7NotBuffered ??= secureBuffer(16);
447+
if (uuidV7NotBuffered === undefined)
448+
throw new ERR_OPERATION_FAILED('Out of memory');
449+
randomFillSync(uuidV7NotBuffered, 6);
450+
writeTimestamp(uuidV7NotBuffered, 0);
451+
return serializeUUID(uuidV7NotBuffered, 0, 0x70);
452+
}
443453

444-
uuidV7Buffer[6] = (uuidV7Buffer[6] & 0x0f) | 0x70;
454+
function randomUUIDv7(options) {
455+
if (options !== undefined)
456+
validateObject(options, 'options');
457+
const {
458+
disableEntropyCache = false,
459+
} = options || kEmptyObject;
445460

446-
uuidV7Buffer[8] = (uuidV7Buffer[8] & 0x3f) | 0x80;
461+
validateBoolean(disableEntropyCache, 'options.disableEntropyCache');
447462

448-
const kHexBytes = getHexBytes();
449-
return kHexBytes[uuidV7Buffer[0]] +
450-
kHexBytes[uuidV7Buffer[1]] +
451-
kHexBytes[uuidV7Buffer[2]] +
452-
kHexBytes[uuidV7Buffer[3]] +
453-
'-' +
454-
kHexBytes[uuidV7Buffer[4]] +
455-
kHexBytes[uuidV7Buffer[5]] +
456-
'-' +
457-
kHexBytes[uuidV7Buffer[6]] +
458-
kHexBytes[uuidV7Buffer[7]] +
459-
'-' +
460-
kHexBytes[uuidV7Buffer[8]] +
461-
kHexBytes[uuidV7Buffer[9]] +
462-
'-' +
463-
kHexBytes[uuidV7Buffer[10]] +
464-
kHexBytes[uuidV7Buffer[11]] +
465-
kHexBytes[uuidV7Buffer[12]] +
466-
kHexBytes[uuidV7Buffer[13]] +
467-
kHexBytes[uuidV7Buffer[14]] +
468-
kHexBytes[uuidV7Buffer[15]];
463+
return disableEntropyCache ? getUnbufferedUUIDv7() : getBufferedUUIDv7();
469464
}
470465

471466
function createRandomPrimeJob(type, size, options) {

test/parallel/test-crypto-randomuuidv7.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,31 @@ const {
8282
/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
8383
);
8484
}
85+
86+
{
87+
const uuidv7Regex =
88+
/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
89+
90+
assert.match(randomUUIDv7({ disableEntropyCache: true }), uuidv7Regex);
91+
assert.match(randomUUIDv7({ disableEntropyCache: true }), uuidv7Regex);
92+
assert.match(randomUUIDv7({ disableEntropyCache: true }), uuidv7Regex);
93+
assert.match(randomUUIDv7({ disableEntropyCache: true }), uuidv7Regex);
94+
95+
assert.throws(() => randomUUIDv7(1), {
96+
code: 'ERR_INVALID_ARG_TYPE',
97+
});
98+
99+
assert.throws(() => randomUUIDv7({ disableEntropyCache: '' }), {
100+
code: 'ERR_INVALID_ARG_TYPE',
101+
});
102+
}
103+
104+
{
105+
for (let n = 0; n < 130; n++) {
106+
const uuid = randomUUIDv7();
107+
assert.match(
108+
uuid,
109+
/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
110+
);
111+
}
112+
}

0 commit comments

Comments
 (0)