Skip to content

Commit cacf92a

Browse files
authored
feat: add randomUUIDv7 (RFC 9562 §5.7) and disableEntropyCache option (#1033)
1 parent 8254643 commit cacf92a

5 files changed

Lines changed: 435 additions & 46 deletions

File tree

.docs/implementation-coverage.md

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ These algorithms provide quantum-resistant cryptography.
156156
-`crypto.randomFillSync(buffer[, offset][, size])`
157157
-`crypto.randomInt([min, ]max[, callback])`
158158
-`crypto.randomUUID([options])`
159+
-`crypto.randomUUIDv7([options])`
159160
-`crypto.scrypt(password, salt, keylen[, options], callback)`
160161
-`crypto.scryptSync(password, salt, keylen[, options])`
161162
- `-` `crypto.secureHeapUsed()` not applicable to RN
@@ -306,6 +307,7 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
306307
-`crypto.subtle`
307308
-`crypto.getRandomValues(typedArray)`
308309
-`crypto.randomUUID()`
310+
-`crypto.randomUUIDv7()` _(extension; not in WebCrypto spec)_
309311
- ✅ Class: `CryptoKey`
310312
-`cryptoKey.algorithm`
311313
-`cryptoKey.extractable`
@@ -378,19 +380,25 @@ These ciphers are **not available in Node.js** but are provided by RNQC via libs
378380

379381
## `subtle.digest`
380382

381-
| Algorithm | Status |
382-
| ----------- | :----: |
383-
| `cSHAKE128` ||
384-
| `cSHAKE256` ||
385-
| `SHA-1` ||
386-
| `SHA-256` ||
387-
| `SHA-384` ||
388-
| `SHA-512` ||
389-
| `SHA3-256` ||
390-
| `SHA3-384` ||
391-
| `SHA3-512` ||
392-
393-
> **Note:** `cSHAKE128` and `cSHAKE256` provide SHAKE128/SHAKE256 (XOF) functionality with empty customization, matching Node.js behavior. The `length` parameter (in bytes, must be a multiple of 8) is required to specify the output length.
383+
| Algorithm | Status |
384+
| --------------- | :----: |
385+
| `cSHAKE128` ||
386+
| `cSHAKE256` ||
387+
| `KT128` ||
388+
| `KT256` ||
389+
| `SHA-1` ||
390+
| `SHA-256` ||
391+
| `SHA-384` ||
392+
| `SHA-512` ||
393+
| `SHA3-256` ||
394+
| `SHA3-384` ||
395+
| `SHA3-512` ||
396+
| `TurboSHAKE128` ||
397+
| `TurboSHAKE256` ||
398+
399+
> **Note:** `cSHAKE128` and `cSHAKE256` provide SHAKE128/SHAKE256 (XOF) functionality with empty customization, matching Node.js behavior. The `outputLength` parameter (in bytes, must be a multiple of 8) is required to specify the output length.
400+
>
401+
> **TurboSHAKE128/256** (RFC 9861) and **KangarooTwelve** (`KT128`, `KT256`) are extendable-output functions (XOFs) requiring an `outputLength` parameter. TurboSHAKE additionally accepts a `domainSeparation` byte; KangarooTwelve accepts a `customization` byte string.
394402
395403
## `subtle.encrypt`
396404

docs/content/docs/api/random.mdx

Lines changed: 81 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ Standard random number generators (like `Math.random()`) are **Pseudo-Random Num
2222
Cryptographically secure systems require **CSPRNGs (Cryptographically Strong PRNGs)**. These are designed to be unpredictable even if an attacker knows the algorithm.
2323

2424
RNQC delegates randomness to the underlying Operating System's entropy pool:
25-
* **iOS/macOS**: `SecRandomCopyBytes`
26-
* **Android**: `SecureRandom`
25+
26+
- **iOS/macOS**: `SecRandomCopyBytes`
27+
- **Android**: `SecureRandom`
2728

2829
This ensures that generated keys, salts, and nonces are secure.
2930

@@ -40,7 +41,10 @@ Generates cryptographically strong pseudo-random data.
4041
<TypeTable
4142
type={{
4243
size: { description: 'The number of bytes to generate.', type: 'number' },
43-
callback: { description: 'Optional. If provided, generation is async.', type: 'Function' }
44+
callback: {
45+
description: 'Optional. If provided, generation is async.',
46+
type: 'Function',
47+
},
4448
}}
4549
/>
4650

@@ -66,17 +70,27 @@ randomBytes(256, (err, buf) => {
6670
---
6771

6872
### randomFill(buffer[, offset][, size], callback)
73+
6974
### randomFillSync(buffer[, offset][, size])
7075

71-
Populates an *existing* buffer with random data. Works correctly with TypedArray views over larger ArrayBuffers — `offset` and `size` are relative to the view, not the underlying buffer.
76+
Populates an _existing_ buffer with random data. Works correctly with TypedArray views over larger ArrayBuffers — `offset` and `size` are relative to the view, not the underlying buffer.
7277

7378
**Parameters:**
7479

7580
<TypeTable
7681
type={{
77-
buffer: { description: 'Buffer or TypedArray view to fill.', type: 'Buffer | TypedArray' },
78-
offset: { description: 'Start position within the view. Default: 0', type: 'number' },
79-
size: { description: 'Bytes to fill. Default: buffer.byteLength - offset', type: 'number' }
82+
buffer: {
83+
description: 'Buffer or TypedArray view to fill.',
84+
type: 'Buffer | TypedArray',
85+
},
86+
offset: {
87+
description: 'Start position within the view. Default: 0',
88+
type: 'number',
89+
},
90+
size: {
91+
description: 'Bytes to fill. Default: buffer.byteLength - offset',
92+
type: 'number',
93+
},
8094
}}
8195
/>
8296

@@ -90,9 +104,12 @@ Returns a random integer `n` such that `min <= n < max`. The implementation avoi
90104

91105
<TypeTable
92106
type={{
93-
min: { description: 'Lower bound (inclusive). Default: 0.', type: 'number' },
107+
min: {
108+
description: 'Lower bound (inclusive). Default: 0.',
109+
type: 'number',
110+
},
94111
max: { description: 'Upper bound (exclusive).', type: 'number' },
95-
callback: { description: 'Optional callback.', type: 'Function' }
112+
callback: { description: 'Optional callback.', type: 'Function' },
96113
}}
97114
/>
98115

@@ -114,21 +131,58 @@ const m = randomInt(10, 50);
114131

115132
### randomUUID([options])
116133

117-
Generates a random RFC 4122 Version 4 UUID.
134+
Generates a random RFC 9562 Version 4 UUID.
118135

119136
**Parameters:**
120137

121138
<TypeTable
122139
type={{
123140
options: { description: 'Configuration.', type: 'Object' },
124-
'options.disableEntropyCache': { description: 'Disable internal buffering.', type: 'boolean' }
141+
'options.disableEntropyCache': {
142+
description:
143+
'Accepted for Node.js parity. RNQC pulls fresh OS entropy on every call, so this is a no-op.',
144+
type: 'boolean',
145+
},
125146
}}
126147
/>
127148

128149
**Returns:** `string` e.g. `'f47ac10b-58cc-4372-a567-0e02b2c3d479'`
129150

130151
---
131152

153+
### randomUUIDv7([options])
154+
155+
Generates a random RFC 9562 §5.7 Version 7 UUID. Layout: 48-bit big-endian Unix-ms timestamp prefix, 4-bit version (`7`), 2-bit variant (`10`), and 74 bits of CSPRNG output.
156+
157+
The timestamp prefix makes v7 UUIDs **lexicographically sortable by creation time**, which makes them well-suited as primary keys, idempotency tokens, and ordered identifiers.
158+
159+
**Parameters:**
160+
161+
<TypeTable
162+
type={{
163+
options: { description: 'Configuration.', type: 'Object' },
164+
'options.disableEntropyCache': {
165+
description:
166+
'Accepted for Node.js parity. RNQC pulls fresh OS entropy on every call, so this is a no-op.',
167+
type: 'boolean',
168+
},
169+
}}
170+
/>
171+
172+
**Returns:** `string` e.g. `'017f22e2-79b0-7cc3-98c4-dc0c0c07398f'`
173+
174+
**Example:**
175+
176+
```ts twoslash
177+
// @noErrors
178+
import { randomUUIDv7 } from 'react-native-quick-crypto';
179+
180+
const id = randomUUIDv7();
181+
// '0193b6f6-a8d0-7abc-8def-0123456789ab'
182+
```
183+
184+
---
185+
132186
## Real-World Examples
133187

134188
### API Key Generation
@@ -139,11 +193,12 @@ Generating a URL-safe random string.
139193
import { randomBytes } from 'react-native-quick-crypto';
140194

141195
function generateApiKey(lengthBytes = 32): string {
142-
const buffer = randomBytes(lengthBytes);
143-
return buffer.toString('base64')
144-
.replace(/\+/g, '-')
145-
.replace(/\//g, '_')
146-
.replace(/=/g, '');
196+
const buffer = randomBytes(lengthBytes);
197+
return buffer
198+
.toString('base64')
199+
.replace(/\+/g, '-')
200+
.replace(/\//g, '_')
201+
.replace(/=/g, '');
147202
}
148203
```
149204

@@ -157,7 +212,7 @@ import { randomBytes } from 'react-native-quick-crypto';
157212
const NONCE_SIZE = 12;
158213

159214
function generateNonce(): Buffer {
160-
return randomBytes(NONCE_SIZE);
215+
return randomBytes(NONCE_SIZE);
161216
}
162217
```
163218

@@ -169,14 +224,14 @@ Shuffling an array using the Fisher-Yates algorithm with CSPRNG.
169224
import { randomInt } from 'react-native-quick-crypto';
170225

171226
async function secureShuffle<T>(array: T[]): Promise<T[]> {
172-
const arr = [...array];
173-
for (let i = arr.length - 1; i > 0; i--) {
174-
const j = await new Promise<number>((resolve, reject) => {
175-
randomInt(0, i + 1, (err, n) => err ? reject(err) : resolve(n));
176-
});
177-
[arr[i], arr[j]] = [arr[j], arr[i]];
178-
}
179-
return arr;
227+
const arr = [...array];
228+
for (let i = arr.length - 1; i > 0; i--) {
229+
const j = await new Promise<number>((resolve, reject) => {
230+
randomInt(0, i + 1, (err, n) => (err ? reject(err) : resolve(n)));
231+
});
232+
[arr[i], arr[j]] = [arr[j], arr[i]];
233+
}
234+
return arr;
180235
}
181236
```
182237

@@ -185,4 +240,5 @@ async function secureShuffle<T>(array: T[]): Promise<T[]> {
185240
## Security Considerations
186241

187242
### Blocking the Event Loop
243+
188244
`randomBytes` (synchronous) taps into system sources. While generally fast, requesting large amounts of entropy on a constrained device could potentially block the Main/UI thread. For generating 4KB or less (keys, nonces), sync is fine. For larger buffers, use the asynchronous version or `randomUUID`.

0 commit comments

Comments
 (0)