@bcts/rand 1.0.0-alpha.8 → 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,46 +1,34 @@
1
+ /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ */
6
+
1
7
  // Ported from bc-rand-rust/src/random_number_generator.rs
2
8
 
3
- import { wideMulU32, wideMulU64 } from "./widening.js";
4
- import {
5
- toMagnitude,
6
- toMagnitude64 as _toMagnitude64,
7
- fromMagnitude as _fromMagnitude,
8
- fromMagnitude64 as _fromMagnitude64,
9
- } from "./magnitude.js";
9
+ import { wideMulU8, wideMulU16, wideMulU32, wideMulU64 } from "./widening.js";
10
+ import { toMagnitude, toMagnitude64, fromMagnitude64 } from "./magnitude.js";
10
11
 
11
12
  /**
12
13
  * Interface for random number generators.
13
- * This is the TypeScript equivalent of Rust's RandomNumberGenerator trait
14
- * which extends RngCore + CryptoRng.
15
14
  *
16
- * This is compatible with the RandomNumberGenerator Swift protocol used
17
- * in MacOS and iOS, which is important for cross-platform testing.
15
+ * The TypeScript equivalent of Rust's `RandomNumberGenerator` trait
16
+ * (which extends `RngCore + CryptoRng`).
18
17
  */
19
18
  export interface RandomNumberGenerator {
20
- /**
21
- * Returns the next random 32-bit unsigned integer.
22
- */
19
+ /** Returns the next random 32-bit unsigned integer. */
23
20
  nextU32(): number;
24
21
 
25
- /**
26
- * Returns the next random 64-bit unsigned integer as a bigint.
27
- */
22
+ /** Returns the next random 64-bit unsigned integer as a bigint. */
28
23
  nextU64(): bigint;
29
24
 
30
- /**
31
- * Fills the given Uint8Array with random bytes.
32
- */
25
+ /** Fills the given Uint8Array with random bytes. */
33
26
  fillBytes(dest: Uint8Array): void;
34
27
 
35
- /**
36
- * Returns a Uint8Array of random bytes of the given size.
37
- */
28
+ /** Returns a Uint8Array of random bytes of the given size. */
38
29
  randomData(size: number): Uint8Array;
39
30
 
40
- /**
41
- * Fills the given Uint8Array with random bytes.
42
- * Alias for fillBytes for compatibility.
43
- */
31
+ /** Fills the given Uint8Array with random bytes. Alias for fillBytes. */
44
32
  fillRandomData(data: Uint8Array): void;
45
33
  }
46
34
 
@@ -60,197 +48,364 @@ export function rngFillRandomData(rng: RandomNumberGenerator, data: Uint8Array):
60
48
  rng.fillRandomData(data);
61
49
  }
62
50
 
51
+ // =====================================================================
52
+ // Lemire's "nearly divisionless" upper-bound sampler
53
+ // (https://arxiv.org/abs/1805.10941)
54
+ //
55
+ // Rust exposes a single generic `rng_next_with_upper_bound<T>`. JavaScript
56
+ // has no integer-width generics, so we provide one specialization per
57
+ // width — `U8`, `U16`, `U32`, `U64` — exactly mirroring the Rust dispatch.
58
+ // =====================================================================
59
+
63
60
  /**
64
- * Returns a random value that is less than the given upper bound.
65
- *
66
- * Uses Lemire's "nearly divisionless" method for generating random
67
- * integers in an interval. For a detailed explanation, see:
68
- * https://arxiv.org/abs/1805.10941
69
- *
70
- * @param rng - The random number generator to use
71
- * @param upperBound - The upper bound for the randomly generated value. Must be non-zero.
72
- * @returns A random value in the range [0, upperBound). Every value in the range is equally likely.
61
+ * Returns a random `u8` value strictly less than `upperBound`.
73
62
  */
74
- export function rngNextWithUpperBound(rng: RandomNumberGenerator, upperBound: bigint): bigint {
75
- if (upperBound === 0n) {
76
- throw new Error("upperBound must be non-zero");
63
+ export function rngNextWithUpperBoundU8(rng: RandomNumberGenerator, upperBound: number): number {
64
+ if (upperBound === 0) throw new Error("upperBound must be non-zero");
65
+ const ub = upperBound & 0xff;
66
+ let random = Number(rng.nextU64() & 0xffn);
67
+ let m = wideMulU8(random, ub);
68
+ if (m[0] < ub) {
69
+ const t = ((0x100 - ub) & 0xff) % ub;
70
+ while (m[0] < t) {
71
+ random = Number(rng.nextU64() & 0xffn);
72
+ m = wideMulU8(random, ub);
73
+ }
77
74
  }
75
+ return m[1];
76
+ }
78
77
 
79
- // We use Lemire's "nearly divisionless" method for generating random
80
- // integers in an interval. For a detailed explanation, see:
81
- // https://arxiv.org/abs/1805.10941
78
+ /**
79
+ * Returns a random `u16` value strictly less than `upperBound`.
80
+ */
81
+ export function rngNextWithUpperBoundU16(rng: RandomNumberGenerator, upperBound: number): number {
82
+ if (upperBound === 0) throw new Error("upperBound must be non-zero");
83
+ const ub = upperBound & 0xffff;
84
+ let random = Number(rng.nextU64() & 0xffffn);
85
+ let m = wideMulU16(random, ub);
86
+ if (m[0] < ub) {
87
+ const t = ((0x10000 - ub) & 0xffff) % ub;
88
+ while (m[0] < t) {
89
+ random = Number(rng.nextU64() & 0xffffn);
90
+ m = wideMulU16(random, ub);
91
+ }
92
+ }
93
+ return m[1];
94
+ }
82
95
 
83
- const bitmask = 0xffffffffffffffffn; // u64 max
84
- let random = rng.nextU64() & bitmask;
85
- let m = wideMulU64(random, upperBound);
96
+ /**
97
+ * Returns a random `u32` value strictly less than `upperBound`.
98
+ */
99
+ export function rngNextWithUpperBoundU32(rng: RandomNumberGenerator, upperBound: number): number {
100
+ if (upperBound === 0) throw new Error("upperBound must be non-zero");
101
+ const ub = upperBound >>> 0;
102
+ let random = Number(rng.nextU64() & 0xffffffffn);
103
+ let m = wideMulU32(random, ub);
104
+ if (Number(m[0]) < ub) {
105
+ const t = ((0x100000000 - ub) >>> 0) % ub;
106
+ while (Number(m[0]) < t) {
107
+ random = Number(rng.nextU64() & 0xffffffffn);
108
+ m = wideMulU32(random, ub);
109
+ }
110
+ }
111
+ return Number(m[1]);
112
+ }
86
113
 
87
- if (m[0] < upperBound) {
88
- // t = (0 - upperBound) % upperBound
89
- const negUpperBound = (bitmask + 1n - upperBound) & bitmask;
90
- const t = negUpperBound % upperBound;
114
+ /**
115
+ * Returns a random `u64` value strictly less than `upperBound`.
116
+ */
117
+ export function rngNextWithUpperBoundU64(rng: RandomNumberGenerator, upperBound: bigint): bigint {
118
+ if (upperBound === 0n) throw new Error("upperBound must be non-zero");
119
+ const mask64 = 0xffffffffffffffffn;
120
+ const ub = upperBound & mask64;
121
+ let random = rng.nextU64() & mask64;
122
+ let m = wideMulU64(random, ub);
123
+ if (m[0] < ub) {
124
+ // wrapping_sub(0, ub) within u64
125
+ const t = ((mask64 + 1n - ub) & mask64) % ub;
91
126
  while (m[0] < t) {
92
- random = rng.nextU64() & bitmask;
93
- m = wideMulU64(random, upperBound);
127
+ random = rng.nextU64() & mask64;
128
+ m = wideMulU64(random, ub);
94
129
  }
95
130
  }
96
-
97
131
  return m[1];
98
132
  }
99
133
 
100
134
  /**
101
- * Returns a random 32-bit value that is less than the given upper bound.
102
- * This matches Rust's behavior when called with u32 type.
103
- *
104
- * Uses Lemire's "nearly divisionless" method with 32-bit arithmetic.
135
+ * Alias of `rngNextWithUpperBoundU64`. Kept for API backwards compatibility.
105
136
  *
106
- * @param rng - The random number generator to use
107
- * @param upperBound - The upper bound for the randomly generated value. Must be non-zero and fit in u32.
108
- * @returns A random u32 value in the range [0, upperBound).
137
+ * @deprecated Prefer the explicit-width name `rngNextWithUpperBoundU64`.
109
138
  */
110
- export function rngNextWithUpperBoundU32(rng: RandomNumberGenerator, upperBound: number): number {
111
- if (upperBound === 0) {
112
- throw new Error("upperBound must be non-zero");
139
+ export const rngNextWithUpperBound = rngNextWithUpperBoundU64;
140
+
141
+ // =====================================================================
142
+ // Range / closed-range samplers
143
+ //
144
+ // Mirrors Rust `rng_next_in_range<T>` and `rng_next_in_closed_range<T>`.
145
+ // `from_u64(...).unwrap()` in the early-return branch panics in Rust when
146
+ // the random `u64` does not fit in `T`. We mirror that with a thrown
147
+ // `Error` so cross-platform behavior matches exactly.
148
+ // =====================================================================
149
+
150
+ function fromU64ThrowsIfAbove(value: bigint, max: bigint): bigint {
151
+ if (value > max) {
152
+ throw new Error("from_u64 conversion overflow");
113
153
  }
154
+ return value;
155
+ }
114
156
 
115
- const upperBoundU32 = upperBound >>> 0;
116
- const bitmask = 0xffffffff;
157
+ /** Random `u8` in the half-open range [start, end). */
158
+ export function rngNextInRangeU8(rng: RandomNumberGenerator, start: number, end: number): number {
159
+ if (start >= end) throw new Error("start must be less than end");
160
+ const lo = start & 0xff;
161
+ const hi = end & 0xff;
162
+ const delta = (hi - lo) & 0xff;
163
+ if (delta === 0xff) {
164
+ return Number(fromU64ThrowsIfAbove(rng.nextU64(), 0xffn));
165
+ }
166
+ return (lo + rngNextWithUpperBoundU8(rng, delta)) & 0xff;
167
+ }
117
168
 
118
- // Get random and mask to 32 bits (matches Rust behavior)
119
- let random = Number(rng.nextU64() & BigInt(bitmask));
120
- let m = wideMulU32(random, upperBoundU32);
169
+ /** Random `u16` in the half-open range [start, end). */
170
+ export function rngNextInRangeU16(rng: RandomNumberGenerator, start: number, end: number): number {
171
+ if (start >= end) throw new Error("start must be less than end");
172
+ const lo = start & 0xffff;
173
+ const hi = end & 0xffff;
174
+ const delta = (hi - lo) & 0xffff;
175
+ if (delta === 0xffff) {
176
+ return Number(fromU64ThrowsIfAbove(rng.nextU64(), 0xffffn));
177
+ }
178
+ return (lo + rngNextWithUpperBoundU16(rng, delta)) & 0xffff;
179
+ }
121
180
 
122
- if (Number(m[0]) < upperBoundU32) {
123
- // t = (0 - upperBound) % upperBound (wrapping subtraction)
124
- const t = ((bitmask + 1 - upperBoundU32) >>> 0) % upperBoundU32;
125
- while (Number(m[0]) < t) {
126
- random = Number(rng.nextU64() & BigInt(bitmask));
127
- m = wideMulU32(random, upperBoundU32);
128
- }
181
+ /** Random `u32` in the half-open range [start, end). */
182
+ export function rngNextInRangeU32(rng: RandomNumberGenerator, start: number, end: number): number {
183
+ if (start >= end) throw new Error("start must be less than end");
184
+ const lo = start >>> 0;
185
+ const hi = end >>> 0;
186
+ const delta = (hi - lo) >>> 0;
187
+ if (delta === 0xffffffff) {
188
+ return Number(fromU64ThrowsIfAbove(rng.nextU64(), 0xffffffffn));
129
189
  }
190
+ return (lo + rngNextWithUpperBoundU32(rng, delta)) >>> 0;
191
+ }
130
192
 
131
- return Number(m[1]);
193
+ /** Random `u64` in the half-open range [start, end). */
194
+ export function rngNextInRangeU64(rng: RandomNumberGenerator, start: bigint, end: bigint): bigint {
195
+ if (start >= end) throw new Error("start must be less than end");
196
+ const mask64 = 0xffffffffffffffffn;
197
+ const delta = (end - start) & mask64;
198
+ if (delta === mask64) {
199
+ // Every u64 fits in u64 — `from_u64::<u64>` is total — no throw.
200
+ return rng.nextU64();
201
+ }
202
+ return (start + rngNextWithUpperBoundU64(rng, delta)) & mask64;
132
203
  }
133
204
 
134
205
  /**
135
- * Returns a random value within the specified range [start, end) using 32-bit arithmetic.
136
- * This matches Rust's behavior when called with i32 types.
206
+ * Alias of `rngNextInRangeU64`. Kept for API backwards compatibility.
137
207
  *
138
- * @param rng - The random number generator to use
139
- * @param start - The lower bound (inclusive) as i32
140
- * @param end - The upper bound (exclusive) as i32
141
- * @returns A random i32 value within the bounds
208
+ * @deprecated Prefer the explicit-width name `rngNextInRangeU64`.
142
209
  */
143
- export function rngNextInRangeI32(rng: RandomNumberGenerator, start: number, end: number): number {
144
- if (start >= end) {
145
- throw new Error("start must be less than end");
210
+ export const rngNextInRange = rngNextInRangeU64;
211
+
212
+ /** Random `i8` in the half-open range [start, end). */
213
+ export function rngNextInRangeI8(rng: RandomNumberGenerator, start: number, end: number): number {
214
+ if (start >= end) throw new Error("start must be less than end");
215
+ const lo = (start << 24) >> 24;
216
+ const hi = (end << 24) >> 24;
217
+ const delta = toMagnitude(hi - lo, 8);
218
+ if (delta === 0xff) {
219
+ // i8::from_u64 succeeds only when value <= i8::MAX = 127
220
+ return Number(fromU64ThrowsIfAbove(rng.nextU64(), 0x7fn));
146
221
  }
222
+ const random = rngNextWithUpperBoundU8(rng, delta);
223
+ return ((lo + random) << 24) >> 24;
224
+ }
147
225
 
148
- const startI32 = start | 0;
149
- const endI32 = end | 0;
150
- const delta = toMagnitude(endI32 - startI32, 32);
226
+ /** Random `i16` in the half-open range [start, end). */
227
+ export function rngNextInRangeI16(rng: RandomNumberGenerator, start: number, end: number): number {
228
+ if (start >= end) throw new Error("start must be less than end");
229
+ const lo = (start << 16) >> 16;
230
+ const hi = (end << 16) >> 16;
231
+ const delta = toMagnitude(hi - lo, 16);
232
+ if (delta === 0xffff) {
233
+ // i16::from_u64 succeeds only when value <= i16::MAX = 32767
234
+ return Number(fromU64ThrowsIfAbove(rng.nextU64(), 0x7fffn));
235
+ }
236
+ const random = rngNextWithUpperBoundU16(rng, delta);
237
+ return ((lo + random) << 16) >> 16;
238
+ }
151
239
 
240
+ /** Random `i32` in the half-open range [start, end). */
241
+ export function rngNextInRangeI32(rng: RandomNumberGenerator, start: number, end: number): number {
242
+ if (start >= end) throw new Error("start must be less than end");
243
+ const lo = start | 0;
244
+ const hi = end | 0;
245
+ const delta = toMagnitude(hi - lo, 32);
152
246
  if (delta === 0xffffffff) {
153
- return rng.nextU32() | 0;
247
+ // i32::from_u64 succeeds only when value <= i32::MAX = 2147483647
248
+ return Number(fromU64ThrowsIfAbove(rng.nextU64(), 0x7fffffffn));
154
249
  }
155
-
156
250
  const random = rngNextWithUpperBoundU32(rng, delta);
157
- return (startI32 + random) | 0;
251
+ return (lo + random) | 0;
158
252
  }
159
253
 
160
- /**
161
- * Returns a random value within the specified range [start, end).
162
- *
163
- * @param rng - The random number generator to use
164
- * @param start - The lower bound (inclusive)
165
- * @param end - The upper bound (exclusive)
166
- * @returns A random value within the bounds of the range
167
- */
168
- export function rngNextInRange(rng: RandomNumberGenerator, start: bigint, end: bigint): bigint {
169
- if (start >= end) {
170
- throw new Error("start must be less than end");
254
+ /** Random `i64` in the half-open range [start, end). */
255
+ export function rngNextInRangeI64(rng: RandomNumberGenerator, start: bigint, end: bigint): bigint {
256
+ if (start >= end) throw new Error("start must be less than end");
257
+ const delta = toMagnitude64(end - start);
258
+ const mask64 = 0xffffffffffffffffn;
259
+ if (delta === mask64) {
260
+ // i64::from_u64 succeeds only when value <= i64::MAX = (2^63 - 1)
261
+ return fromU64ThrowsIfAbove(rng.nextU64(), 0x7fffffffffffffffn);
171
262
  }
263
+ const random = rngNextWithUpperBoundU64(rng, delta);
264
+ return fromMagnitude64((toMagnitude64(start) + random) & mask64);
265
+ }
172
266
 
173
- const delta = end - start;
267
+ /** Random `u8` in the closed range [start, end]. */
268
+ export function rngNextInClosedRangeU8(
269
+ rng: RandomNumberGenerator,
270
+ start: number,
271
+ end: number,
272
+ ): number {
273
+ if (start > end) throw new Error("start must be less than or equal to end");
274
+ const lo = start & 0xff;
275
+ const hi = end & 0xff;
276
+ const delta = (hi - lo) & 0xff;
277
+ if (delta === 0xff) {
278
+ return Number(fromU64ThrowsIfAbove(rng.nextU64(), 0xffn));
279
+ }
280
+ return (lo + rngNextWithUpperBoundU8(rng, delta + 1)) & 0xff;
281
+ }
174
282
 
175
- // If delta covers the entire range, just return a random value
176
- const maxU64 = 0xffffffffffffffffn;
177
- if (delta === maxU64) {
178
- return rng.nextU64();
283
+ /** Random `u16` in the closed range [start, end]. */
284
+ export function rngNextInClosedRangeU16(
285
+ rng: RandomNumberGenerator,
286
+ start: number,
287
+ end: number,
288
+ ): number {
289
+ if (start > end) throw new Error("start must be less than or equal to end");
290
+ const lo = start & 0xffff;
291
+ const hi = end & 0xffff;
292
+ const delta = (hi - lo) & 0xffff;
293
+ if (delta === 0xffff) {
294
+ return Number(fromU64ThrowsIfAbove(rng.nextU64(), 0xffffn));
179
295
  }
296
+ return (lo + rngNextWithUpperBoundU16(rng, delta + 1)) & 0xffff;
297
+ }
180
298
 
181
- const random = rngNextWithUpperBound(rng, delta);
182
- return start + random;
299
+ /** Random `u32` in the closed range [start, end]. */
300
+ export function rngNextInClosedRangeU32(
301
+ rng: RandomNumberGenerator,
302
+ start: number,
303
+ end: number,
304
+ ): number {
305
+ if (start > end) throw new Error("start must be less than or equal to end");
306
+ const lo = start >>> 0;
307
+ const hi = end >>> 0;
308
+ const delta = (hi - lo) >>> 0;
309
+ if (delta === 0xffffffff) {
310
+ return Number(fromU64ThrowsIfAbove(rng.nextU64(), 0xffffffffn));
311
+ }
312
+ return (lo + rngNextWithUpperBoundU32(rng, delta + 1)) >>> 0;
183
313
  }
184
314
 
185
- /**
186
- * Returns a random value within the specified closed range [start, end].
187
- *
188
- * @param rng - The random number generator to use
189
- * @param start - The lower bound (inclusive)
190
- * @param end - The upper bound (inclusive)
191
- * @returns A random value within the bounds of the range
192
- */
193
- export function rngNextInClosedRange(
315
+ /** Random `u64` in the closed range [start, end]. */
316
+ export function rngNextInClosedRangeU64(
194
317
  rng: RandomNumberGenerator,
195
318
  start: bigint,
196
319
  end: bigint,
197
320
  ): bigint {
198
- if (start > end) {
199
- throw new Error("start must be less than or equal to end");
200
- }
201
-
202
- const delta = end - start;
203
-
204
- // If delta covers the entire range, just return a random value
205
- const maxU64 = 0xffffffffffffffffn;
206
- if (delta === maxU64) {
321
+ if (start > end) throw new Error("start must be less than or equal to end");
322
+ const mask64 = 0xffffffffffffffffn;
323
+ const delta = (end - start) & mask64;
324
+ if (delta === mask64) {
207
325
  return rng.nextU64();
208
326
  }
209
-
210
- const random = rngNextWithUpperBound(rng, delta + 1n);
211
- return start + random;
327
+ return (start + rngNextWithUpperBoundU64(rng, delta + 1n)) & mask64;
212
328
  }
213
329
 
214
330
  /**
215
- * Returns a random value within the specified closed range [start, end] for i32 values.
216
- * Convenience function that handles signed 32-bit integers.
331
+ * Alias of `rngNextInClosedRangeU64`. Kept for API backwards compatibility.
217
332
  *
218
- * @param rng - The random number generator to use
219
- * @param start - The lower bound (inclusive) as i32
220
- * @param end - The upper bound (inclusive) as i32
221
- * @returns A random i32 value within the bounds of the range
333
+ * @deprecated Prefer the explicit-width name `rngNextInClosedRangeU64`.
222
334
  */
223
- export function rngNextInClosedRangeI32(
335
+ export const rngNextInClosedRange = rngNextInClosedRangeU64;
336
+
337
+ /** Random `i8` in the closed range [start, end]. */
338
+ export function rngNextInClosedRangeI8(
224
339
  rng: RandomNumberGenerator,
225
340
  start: number,
226
341
  end: number,
227
342
  ): number {
228
- if (start > end) {
229
- throw new Error("start must be less than or equal to end");
343
+ if (start > end) throw new Error("start must be less than or equal to end");
344
+ const lo = (start << 24) >> 24;
345
+ const hi = (end << 24) >> 24;
346
+ const delta = toMagnitude(hi - lo, 8);
347
+ if (delta === 0xff) {
348
+ return Number(fromU64ThrowsIfAbove(rng.nextU64(), 0x7fn));
230
349
  }
350
+ const random = rngNextWithUpperBoundU8(rng, delta + 1);
351
+ return ((lo + random) << 24) >> 24;
352
+ }
231
353
 
232
- // Convert to i32
233
- const startI32 = start | 0;
234
- const endI32 = end | 0;
235
-
236
- // Calculate delta as u32 magnitude
237
- const delta = toMagnitude(endI32 - startI32, 32);
354
+ /** Random `i16` in the closed range [start, end]. */
355
+ export function rngNextInClosedRangeI16(
356
+ rng: RandomNumberGenerator,
357
+ start: number,
358
+ end: number,
359
+ ): number {
360
+ if (start > end) throw new Error("start must be less than or equal to end");
361
+ const lo = (start << 16) >> 16;
362
+ const hi = (end << 16) >> 16;
363
+ const delta = toMagnitude(hi - lo, 16);
364
+ if (delta === 0xffff) {
365
+ return Number(fromU64ThrowsIfAbove(rng.nextU64(), 0x7fffn));
366
+ }
367
+ const random = rngNextWithUpperBoundU16(rng, delta + 1);
368
+ return ((lo + random) << 16) >> 16;
369
+ }
238
370
 
239
- // If delta covers the entire u32 range, just return a random value
371
+ /** Random `i32` in the closed range [start, end]. */
372
+ export function rngNextInClosedRangeI32(
373
+ rng: RandomNumberGenerator,
374
+ start: number,
375
+ end: number,
376
+ ): number {
377
+ if (start > end) throw new Error("start must be less than or equal to end");
378
+ const lo = start | 0;
379
+ const hi = end | 0;
380
+ const delta = toMagnitude(hi - lo, 32);
240
381
  if (delta === 0xffffffff) {
241
- return rng.nextU32() | 0;
382
+ return Number(fromU64ThrowsIfAbove(rng.nextU64(), 0x7fffffffn));
242
383
  }
243
-
244
384
  const random = rngNextWithUpperBoundU32(rng, delta + 1);
245
- return (startI32 + random) | 0;
385
+ return (lo + random) | 0;
386
+ }
387
+
388
+ /** Random `i64` in the closed range [start, end]. */
389
+ export function rngNextInClosedRangeI64(
390
+ rng: RandomNumberGenerator,
391
+ start: bigint,
392
+ end: bigint,
393
+ ): bigint {
394
+ if (start > end) throw new Error("start must be less than or equal to end");
395
+ const delta = toMagnitude64(end - start);
396
+ const mask64 = 0xffffffffffffffffn;
397
+ if (delta === mask64) {
398
+ return fromU64ThrowsIfAbove(rng.nextU64(), 0x7fffffffffffffffn);
399
+ }
400
+ const random = rngNextWithUpperBoundU64(rng, delta + 1n);
401
+ return fromMagnitude64((toMagnitude64(start) + random) & mask64);
246
402
  }
247
403
 
248
404
  /**
249
- * Returns a fixed-size array of random bytes.
405
+ * Returns a random fixed-size byte array.
250
406
  *
251
- * @param rng - The random number generator to use
252
- * @param size - The size of the array to return
253
- * @returns A Uint8Array of the specified size filled with random bytes
407
+ * Mirrors Rust's `rng_random_array<const N: usize>()` but takes the size at
408
+ * runtime since JavaScript lacks const generics.
254
409
  */
255
410
  export function rngRandomArray(rng: RandomNumberGenerator, size: number): Uint8Array {
256
411
  const data = new Uint8Array(size);
@@ -259,10 +414,8 @@ export function rngRandomArray(rng: RandomNumberGenerator, size: number): Uint8A
259
414
  }
260
415
 
261
416
  /**
262
- * Returns a random boolean value.
263
- *
264
- * @param rng - The random number generator to use
265
- * @returns A random boolean
417
+ * Returns a random boolean. Mirrors Rust's `rng_random_bool` which tests
418
+ * whether `next_u32()` is a multiple of 2.
266
419
  */
267
420
  export function rngRandomBool(rng: RandomNumberGenerator): boolean {
268
421
  return (rng.nextU32() & 1) === 0;
@@ -270,9 +423,6 @@ export function rngRandomBool(rng: RandomNumberGenerator): boolean {
270
423
 
271
424
  /**
272
425
  * Returns a random 32-bit unsigned integer.
273
- *
274
- * @param rng - The random number generator to use
275
- * @returns A random u32 value
276
426
  */
277
427
  export function rngRandomU32(rng: RandomNumberGenerator): number {
278
428
  return rng.nextU32();
@@ -1,22 +1,19 @@
1
+ /**
2
+ * Copyright © 2023-2026 Blockchain Commons, LLC
3
+ * Copyright © 2025-2026 Parity Technologies
4
+ *
5
+ */
6
+
1
7
  // Ported from bc-rand-rust/src/secure_random.rs
2
8
 
3
9
  import type { RandomNumberGenerator } from "./random-number-generator.js";
4
10
 
5
11
  /**
6
- * Detects the crypto API available in the current environment.
7
- * Works in both Node.js and browser environments.
12
+ * Returns the Web Crypto API for the current environment. Available natively
13
+ * in browsers and in Node.js >= 15 via `globalThis.crypto`.
8
14
  */
9
15
  function getCrypto(): Crypto {
10
- // Browser environment
11
- if (
12
- typeof globalThis !== "undefined" &&
13
- globalThis.crypto !== null &&
14
- globalThis.crypto !== undefined
15
- ) {
16
- return globalThis.crypto;
17
- }
18
- // Node.js environment - globalThis.crypto is also available in Node.js >= 15
19
- if (typeof globalThis.crypto !== "undefined") {
16
+ if (typeof globalThis !== "undefined" && globalThis.crypto != null) {
20
17
  return globalThis.crypto;
21
18
  }
22
19
  throw new Error("No crypto API available in this environment");
@@ -36,13 +33,16 @@ export function randomData(size: number): Uint8Array {
36
33
  */
37
34
  export function fillRandomData(data: Uint8Array): void {
38
35
  const crypto = getCrypto();
39
- crypto.getRandomValues(data);
36
+ crypto.getRandomValues(data as Uint8Array<ArrayBuffer>);
40
37
  }
41
38
 
42
39
  /**
43
40
  * Returns the next cryptographically strong random 64-bit unsigned integer.
41
+ *
42
+ * This mirrors Rust's module-private `secure_random::next_u64()` and is not
43
+ * re-exported from the package surface (matches Rust `lib.rs` behavior).
44
44
  */
45
- export function nextU64(): bigint {
45
+ function nextU64(): bigint {
46
46
  const data = new Uint8Array(8);
47
47
  fillRandomData(data);
48
48
  const view = new DataView(data.buffer);
@@ -59,12 +59,12 @@ export function nextU64(): bigint {
59
59
  export class SecureRandomNumberGenerator implements RandomNumberGenerator {
60
60
  /**
61
61
  * Returns the next random 32-bit unsigned integer.
62
+ *
63
+ * Mirrors Rust's `next_u32` impl which returns `next_u64() as u32` —
64
+ * the low 32 bits of a 64-bit draw.
62
65
  */
63
66
  nextU32(): number {
64
- const data = new Uint8Array(4);
65
- fillRandomData(data);
66
- const view = new DataView(data.buffer);
67
- return view.getUint32(0, true) >>> 0; // little-endian, unsigned
67
+ return Number(this.nextU64() & 0xffffffffn) >>> 0;
68
68
  }
69
69
 
70
70
  /**
@@ -95,3 +95,13 @@ export class SecureRandomNumberGenerator implements RandomNumberGenerator {
95
95
  fillRandomData(data);
96
96
  }
97
97
  }
98
+
99
+ /**
100
+ * Returns a thread-local cryptographically-strong RNG. Mirrors Rust's
101
+ * `thread_rng()`. In TypeScript there are no thread-locals, so this returns
102
+ * a fresh `SecureRandomNumberGenerator` — every instance backs onto the
103
+ * same Web Crypto source, so the effect is equivalent.
104
+ */
105
+ export function threadRng(): SecureRandomNumberGenerator {
106
+ return new SecureRandomNumberGenerator();
107
+ }