@bcts/rand 1.0.0-alpha.9 → 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.
- package/LICENSE +3 -2
- package/dist/index.cjs +243 -112
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -91
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +87 -91
- package/dist/index.d.mts.map +1 -1
- package/dist/index.iife.js +286 -156
- package/dist/index.iife.js.map +1 -1
- package/dist/index.mjs +225 -110
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -15
- package/src/index.ts +28 -5
- package/src/magnitude.ts +6 -0
- package/src/random-number-generator.ts +309 -159
- package/src/secure-random.ts +28 -18
- package/src/seeded-random.ts +9 -12
- package/src/widening.ts +6 -0
|
@@ -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
|
-
*
|
|
17
|
-
*
|
|
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
|
|
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
|
|
75
|
-
if (upperBound ===
|
|
76
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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() &
|
|
93
|
-
m = wideMulU64(random,
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
136
|
-
* This matches Rust's behavior when called with i32 types.
|
|
206
|
+
* Alias of `rngNextInRangeU64`. Kept for API backwards compatibility.
|
|
137
207
|
*
|
|
138
|
-
* @
|
|
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
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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 (
|
|
251
|
+
return (lo + random) | 0;
|
|
158
252
|
}
|
|
159
253
|
|
|
160
|
-
/**
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
216
|
-
* Convenience function that handles signed 32-bit integers.
|
|
331
|
+
* Alias of `rngNextInClosedRangeU64`. Kept for API backwards compatibility.
|
|
217
332
|
*
|
|
218
|
-
* @
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
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.
|
|
382
|
+
return Number(fromU64ThrowsIfAbove(rng.nextU64(), 0x7fffffffn));
|
|
242
383
|
}
|
|
243
|
-
|
|
244
384
|
const random = rngNextWithUpperBoundU32(rng, delta + 1);
|
|
245
|
-
return (
|
|
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
|
|
405
|
+
* Returns a random fixed-size byte array.
|
|
250
406
|
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
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
|
|
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();
|
package/src/secure-random.ts
CHANGED
|
@@ -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
|
-
*
|
|
7
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|