From 628715c073b145de7f24bd2e9b600b0a00e29f42 Mon Sep 17 00:00:00 2001 From: nabeel378 Date: Thu, 2 Apr 2026 07:43:12 +0500 Subject: [PATCH 1/7] crypto: implement randomUUIDv7() Signed-off-by: nabeel378 --- doc/api/crypto.md | 28 +++++++ lib/crypto.js | 2 + lib/internal/crypto/random.js | 58 +++++++++++++++ test/parallel/test-crypto-randomuuidv7.js | 91 +++++++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 test/parallel/test-crypto-randomuuidv7.js diff --git a/doc/api/crypto.md b/doc/api/crypto.md index fb80671b8bd8a6..12a81e0d27fb03 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -5826,6 +5826,33 @@ added: Generates a random [RFC 4122][] version 4 UUID. The UUID is generated using a cryptographic pseudorandom number generator. +### `crypto.randomUUIDv7()` + + + +* Returns: {string} + +Generates a random [RFC 9562][] version 7 UUID. The UUID contains a millisecond +precision Unix timestamp in the most significant 48 bits, followed by +cryptographically secure random bits for the remaining fields, making it +suitable for use as a database key with time-based sorting. + +```mjs +import { randomUUIDv7 } from 'node:crypto'; + +console.log(randomUUIDv7()); +// e.g. '019d45ea-151f-780b-82a6-d097b39db62a' +``` + +```cjs +const { randomUUIDv7 } = require('node:crypto'); + +console.log(randomUUIDv7()); +// e.g. '019d45ea-151f-780b-82a6-d097b39db62a' +``` + ### `crypto.scrypt(password, salt, keylen[, options], callback)` +* `options` {Object} + * `disableEntropyCache` {boolean} By default, to improve performance, + Node.js generates and caches enough + random data to generate up to 128 random UUIDs. To generate a UUID + without using the cache, set `disableEntropyCache` to `true`. + **Default:** `false`. * Returns: {string} Generates a random [RFC 9562][] version 7 UUID. The UUID contains a millisecond diff --git a/lib/internal/crypto/random.js b/lib/internal/crypto/random.js index 0c5a94fe3d3d9b..748add2f711295 100644 --- a/lib/internal/crypto/random.js +++ b/lib/internal/crypto/random.js @@ -14,7 +14,6 @@ const { DateNow, FunctionPrototypeBind, FunctionPrototypeCall, - MathFloor, MathMin, NumberIsNaN, NumberIsSafeInteger, @@ -361,7 +360,7 @@ function getHexBytes() { return hexBytesCache; } -function serializeUUID(buf, offset = 0) { +function serializeUUID(buf, offset = 0, version = 0x40, variant = 0x80) { const kHexBytes = getHexBytes(); // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx return kHexBytes[buf[offset]] + @@ -372,10 +371,10 @@ function serializeUUID(buf, offset = 0) { kHexBytes[buf[offset + 4]] + kHexBytes[buf[offset + 5]] + '-' + - kHexBytes[(buf[offset + 6] & 0x0f) | 0x40] + + kHexBytes[(buf[offset + 6] & 0x0f) | version] + kHexBytes[buf[offset + 7]] + '-' + - kHexBytes[(buf[offset + 8] & 0x3f) | 0x80] + + kHexBytes[(buf[offset + 8] & 0x3f) | variant] + kHexBytes[buf[offset + 9]] + '-' + kHexBytes[buf[offset + 10]] + @@ -416,56 +415,52 @@ function randomUUID(options) { return disableEntropyCache ? getUnbufferedUUID() : getBufferedUUID(); } -// Implements an RFC 9562 version 7 UUID (time-ordered). -// Layout (128 bits total): -// 48 bits - unix_ts_ms: UTC timestamp in milliseconds -// 4 bits - version: 0b0111 (7) -// 12 bits - rand_a: random -// 2 bits - variant: 0b10 -// 62 bits - rand_b: random +let uuidV7Data; +let uuidV7NotBuffered; +let uuidV7Batch = 0; -let uuidV7Buffer; +function writeTimestamp(buf, offset) { + const now = DateNow(); + const msb = now / (2 ** 32); + buf[offset] = msb >>> 8; + buf[offset + 1] = msb; + buf[offset + 2] = now >>> 24; + buf[offset + 3] = now >>> 16; + buf[offset + 4] = now >>> 8; + buf[offset + 5] = now; +} -function randomUUIDv7() { - uuidV7Buffer ??= secureBuffer(16); - if (uuidV7Buffer === undefined) +function getBufferedUUIDv7() { + uuidV7Data ??= secureBuffer(16 * kBatchSize); + if (uuidV7Data === undefined) throw new ERR_OPERATION_FAILED('Out of memory'); - randomFillSync(uuidV7Buffer); + if (uuidV7Batch === 0) randomFillSync(uuidV7Data); + uuidV7Batch = (uuidV7Batch + 1) % kBatchSize; + const offset = uuidV7Batch * 16; + writeTimestamp(uuidV7Data, offset); + return serializeUUID(uuidV7Data, offset, 0x70); +} - const now = DateNow(); - uuidV7Buffer[0] = MathFloor(now / 0x10000000000) & 0xff; - uuidV7Buffer[1] = MathFloor(now / 0x100000000) & 0xff; - uuidV7Buffer[2] = MathFloor(now / 0x1000000) & 0xff; - uuidV7Buffer[3] = MathFloor(now / 0x10000) & 0xff; - uuidV7Buffer[4] = MathFloor(now / 0x100) & 0xff; - uuidV7Buffer[5] = now & 0xff; +function getUnbufferedUUIDv7() { + uuidV7NotBuffered ??= secureBuffer(16); + if (uuidV7NotBuffered === undefined) + throw new ERR_OPERATION_FAILED('Out of memory'); + randomFillSync(uuidV7NotBuffered, 6); + writeTimestamp(uuidV7NotBuffered, 0); + return serializeUUID(uuidV7NotBuffered, 0, 0x70); +} - uuidV7Buffer[6] = (uuidV7Buffer[6] & 0x0f) | 0x70; +function randomUUIDv7(options) { + if (options !== undefined) + validateObject(options, 'options'); + const { + disableEntropyCache = false, + } = options || kEmptyObject; - uuidV7Buffer[8] = (uuidV7Buffer[8] & 0x3f) | 0x80; + validateBoolean(disableEntropyCache, 'options.disableEntropyCache'); - const kHexBytes = getHexBytes(); - return kHexBytes[uuidV7Buffer[0]] + - kHexBytes[uuidV7Buffer[1]] + - kHexBytes[uuidV7Buffer[2]] + - kHexBytes[uuidV7Buffer[3]] + - '-' + - kHexBytes[uuidV7Buffer[4]] + - kHexBytes[uuidV7Buffer[5]] + - '-' + - kHexBytes[uuidV7Buffer[6]] + - kHexBytes[uuidV7Buffer[7]] + - '-' + - kHexBytes[uuidV7Buffer[8]] + - kHexBytes[uuidV7Buffer[9]] + - '-' + - kHexBytes[uuidV7Buffer[10]] + - kHexBytes[uuidV7Buffer[11]] + - kHexBytes[uuidV7Buffer[12]] + - kHexBytes[uuidV7Buffer[13]] + - kHexBytes[uuidV7Buffer[14]] + - kHexBytes[uuidV7Buffer[15]]; + return disableEntropyCache ? getUnbufferedUUIDv7() : getBufferedUUIDv7(); } function createRandomPrimeJob(type, size, options) { diff --git a/test/parallel/test-crypto-randomuuidv7.js b/test/parallel/test-crypto-randomuuidv7.js index d7316d37d630f2..99d052f356721c 100644 --- a/test/parallel/test-crypto-randomuuidv7.js +++ b/test/parallel/test-crypto-randomuuidv7.js @@ -82,3 +82,31 @@ const { /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/, ); } + +{ + const uuidv7Regex = + /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/; + + assert.match(randomUUIDv7({ disableEntropyCache: true }), uuidv7Regex); + assert.match(randomUUIDv7({ disableEntropyCache: true }), uuidv7Regex); + assert.match(randomUUIDv7({ disableEntropyCache: true }), uuidv7Regex); + assert.match(randomUUIDv7({ disableEntropyCache: true }), uuidv7Regex); + + assert.throws(() => randomUUIDv7(1), { + code: 'ERR_INVALID_ARG_TYPE', + }); + + assert.throws(() => randomUUIDv7({ disableEntropyCache: '' }), { + code: 'ERR_INVALID_ARG_TYPE', + }); +} + +{ + for (let n = 0; n < 130; n++) { + const uuid = randomUUIDv7(); + assert.match( + uuid, + /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/, + ); + } +} From 8ddc406af3592e5db8c65888657ef8529408560a Mon Sep 17 00:00:00 2001 From: nabeel378 Date: Thu, 2 Apr 2026 17:18:50 +0500 Subject: [PATCH 6/7] crypto: address review feedback for randomUUIDv7 - Make version/variant required params in serializeUUID - Share buffer pools (uuidData/uuidNotBuffered) between v4 and v7 Signed-off-by: nabeel378 --- doc/api/crypto.md | 14 -------------- lib/internal/crypto/random.js | 36 ++++++++++++++++------------------- 2 files changed, 16 insertions(+), 34 deletions(-) diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 1fdb86c59f634d..f0d295901ac59d 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -5845,20 +5845,6 @@ precision Unix timestamp in the most significant 48 bits, followed by cryptographically secure random bits for the remaining fields, making it suitable for use as a database key with time-based sorting. -```mjs -import { randomUUIDv7 } from 'node:crypto'; - -console.log(randomUUIDv7()); -// e.g. '019d45ea-151f-780b-82a6-d097b39db62a' -``` - -```cjs -const { randomUUIDv7 } = require('node:crypto'); - -console.log(randomUUIDv7()); -// e.g. '019d45ea-151f-780b-82a6-d097b39db62a' -``` - ### `crypto.scrypt(password, salt, keylen[, options], callback)`