Skip to content

Commit b636e8b

Browse files
Secure random number generation. (#35)
- generator_vm.dart - Avoid mixing predictable datetime output with CSPRNG output. - generator_js.dart - Avoid mixing predictable datetime output with CSPRNG output. - Added `NodeRandom` as an implementation of `Random` for NodeJS. - `nextInt(max)` draws 32-bit values from Node crypto, supports max ∈ [1, 2^32]. - `secureRandom()` uses `Random.secure()` for web browsers, and `NodeRandom` for Node.js. - `$generateSeed()` delegates to `secureRandom()`. - generators.dart - Fixed spelling of `_hashGenerator`
1 parent 983cdbd commit b636e8b

File tree

3 files changed

+66
-22
lines changed

3 files changed

+66
-22
lines changed

lib/src/random/generator_js.dart

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,70 @@
11
// Copyright (c) 2024, Sudipto Chandra
22
// All rights reserved. Check LICENSE file for details.
33

4-
import 'dart:async';
54
import 'dart:math' show Random;
5+
import 'dart:js_interop';
66

77
const int _mask32 = 0xFFFFFFFF;
88

9-
int _seedCounter = Zone.current.hashCode;
9+
const bool isDart2JS = bool.fromEnvironment('dart.tool.dart2js');
1010

11-
/// Returns a secure random generator in JS runtime
12-
Random secureRandom() => Random($generateSeed());
13-
14-
/// Generates a random seed in JS runtime
15-
int $generateSeed() {
16-
int code = DateTime.now().millisecondsSinceEpoch;
17-
code -= _seedCounter++;
18-
if (code.bitLength & 1 == 1) {
19-
code *= ~code;
11+
@JS()
12+
@staticInterop
13+
class Process {}
14+
15+
@JS()
16+
@staticInterop
17+
class Versions {}
18+
19+
@JS('process')
20+
external Process? get _process;
21+
22+
extension on Process {
23+
external Versions? get versions;
24+
}
25+
26+
extension on Versions {
27+
external JSAny get node;
28+
}
29+
30+
bool get isNodeDart2JS => _process?.versions?.node != null && isDart2JS;
31+
32+
@JS()
33+
@staticInterop
34+
class Crypto {}
35+
36+
extension on Crypto {
37+
external int randomInt(final int max);
38+
}
39+
40+
@JS()
41+
external Crypto require(final String id);
42+
43+
/// For Node.js environment + dart2js compiler
44+
class NodeRandom implements Random {
45+
@override
46+
int nextInt(final int max) {
47+
if (max < 1 || max > _mask32 + 1) {
48+
throw RangeError.range(
49+
max, 1, _mask32 + 1, 'max', 'max must be <= (1 << 32)');
50+
}
51+
return require('crypto').randomInt(max);
52+
}
53+
54+
@override
55+
double nextDouble() {
56+
final int first26Bits = nextInt(1 << 26);
57+
final int next27Bits = nextInt(1 << 27);
58+
final int random53Bits = (first26Bits << 27) + next27Bits; // JS int limit
59+
return random53Bits / (1 << 53);
2060
}
21-
code ^= ~_seedCounter << 5;
22-
_seedCounter += code & 7;
23-
return code & _mask32;
61+
62+
@override
63+
bool nextBool() => nextInt(2) == 1;
2464
}
65+
66+
/// Returns a secure random generator in JS runtime
67+
Random secureRandom() => isNodeDart2JS ? NodeRandom() : Random.secure();
68+
69+
/// Generates a random seed
70+
int $generateSeed() => secureRandom().nextInt(_mask32);

lib/src/random/generator_vm.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,4 @@ Random secureRandom() => Random.secure();
1111

1212
/// Generates a random seed
1313
@pragma('vm:prefer-inline')
14-
int $generateSeed() =>
15-
(DateTime.now().microsecondsSinceEpoch & _mask32) ^
16-
Random.secure().nextInt(_mask32);
14+
int $generateSeed() => Random.secure().nextInt(_mask32);

lib/src/random/generators.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ enum RNG {
3434
case RNG.keccak:
3535
return _keccakGenerateor(seed);
3636
case RNG.sha256:
37-
return _hashGenerateor(SHA256Hash(), seed);
37+
return _hashGenerator(SHA256Hash(), seed);
3838
case RNG.md5:
39-
return _hashGenerateor(MD4Hash(), seed);
39+
return _hashGenerator(MD4Hash(), seed);
4040
case RNG.xxh64:
41-
return _hashGenerateor(XXHash64Sink(111), seed);
41+
return _hashGenerator(XXHash64Sink(111), seed);
4242
case RNG.sm3:
43-
return _hashGenerateor(SM3Hash(), seed);
43+
return _hashGenerator(SM3Hash(), seed);
4444
case RNG.system:
4545
return _systemGenerator(seed);
4646
case RNG.secure:
@@ -115,7 +115,7 @@ NextIntFunction _keccakGenerateor([int? seed]) {
115115
}
116116

117117
/// Returns a iterable of 32-bit integers generated from the [sink].
118-
NextIntFunction _hashGenerateor(
118+
NextIntFunction _hashGenerator(
119119
HashDigestSink sink, [
120120
int? seed,
121121
]) {

0 commit comments

Comments
 (0)