Skip to content

Commit

Permalink
Ensure seed uniqueness in RandomGenerators across Isolates
Browse files Browse the repository at this point in the history
  • Loading branch information
dipu-bd committed Sep 14, 2024
1 parent 6650ea1 commit d904c66
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 49 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 1.20.4

- Ensure seed uniqueness in `RandomGenerators` across Isolates

# 1.20.3

- Exports hashlib_codecs from current package. To get it: `import 'package:hashlib/codecs.dart';`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2024, Sudipto Chandra
// All rights reserved. Check LICENSE file for details.

import 'dart:math';
import 'dart:math' show Random;
import 'dart:typed_data';

import 'package:hashlib/src/algorithms/keccak/keccak.dart';
Expand All @@ -11,8 +11,9 @@ import 'package:hashlib/src/algorithms/sm3.dart';
import 'package:hashlib/src/algorithms/xxh64/xxh64.dart';
import 'package:hashlib/src/core/hash_base.dart';

import 'random_vm.dart' if (dart.library.js) 'random_js.dart';

const int _mask32 = 0xFFFFFFFF;
const int _maxSafeNumber = 0x1FFFFFFFFFFFFF;

enum RandomGenerator {
secure,
Expand All @@ -28,38 +29,28 @@ extension RandomGeneratorIterable on RandomGenerator {
Iterable<int> build([int? seed]) {
switch (this) {
case RandomGenerator.keccak:
return RandomGenerators.$keccakGenerateor(seed);
return Generators.$keccakGenerateor(seed);
case RandomGenerator.sha256:
return RandomGenerators.$hashGenerateor(SHA256Hash(), seed);
return Generators.$hashGenerateor(SHA256Hash(), seed);
case RandomGenerator.md5:
return RandomGenerators.$hashGenerateor(MD4Hash(), seed);
return Generators.$hashGenerateor(MD4Hash(), seed);
case RandomGenerator.xxh64:
return RandomGenerators.$hashGenerateor(XXHash64Sink(111), seed);
return Generators.$hashGenerateor(XXHash64Sink(111), seed);
case RandomGenerator.sm3:
return RandomGenerators.$hashGenerateor(SM3Hash(), seed);
return Generators.$hashGenerateor(SM3Hash(), seed);
case RandomGenerator.secure:
return RandomGenerators.$secureGenerator();
return Generators.$secureGenerator();
case RandomGenerator.system:
default:
return RandomGenerators.$systemGenerator(seed);
return Generators.$systemGenerator(seed);
}
}
}

abstract class RandomGenerators {
static int _seedCounter = 0x9BDC06A7;

/// Generate a 64-bit random seed based on current time
static int $generateSeed() {
var now = DateTime.now();
var code = now.microsecondsSinceEpoch;
code -= _seedCounter++;
if (code.bitLength & 1 == 1) {
code *= ~code;
}
code ^= ~_seedCounter++ << 5;
return code & _maxSafeNumber;
}
abstract class Generators {
/// Get a random seed
@pragma('vm:prefer-inline')
static int $nextSeed() => $generateSeed();

/// Expand the seed to fill the list
static void $seedList(TypedData data, int seed) {
Expand Down Expand Up @@ -100,12 +91,7 @@ abstract class RandomGenerators {

/// Returns a iterable of 32-bit integers backed by system's [Random].
static Iterable<int> $secureGenerator() sync* {
Random random;
try {
random = Random.secure();
} catch (err) {
random = Random($generateSeed());
}
var random = secureRandom();
while (true) {
yield random.nextInt(_mask32);
}
Expand All @@ -114,7 +100,7 @@ abstract class RandomGenerators {
/// Returns a iterable of 32-bit integers backed by system's [Random].
static Iterable<int> $systemGenerator([int? seed]) sync* {
seed ??= $generateSeed();
Random random = Random(seed);
var random = Random(seed);
while (true) {
yield random.nextInt(_mask32);
}
Expand Down
23 changes: 23 additions & 0 deletions lib/src/algorithms/random/random_js.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) 2024, Sudipto Chandra
// All rights reserved. Check LICENSE file for details.

import 'dart:js' show context;
import 'dart:math' show Random;

const int _mask32 = 0xFFFFFFFF;

int _seedCounter = context.hashCode;

@pragma('vm:prefer-inline')
Random secureRandom() => Random($generateSeed());

int $generateSeed() {
int code = DateTime.now().microsecondsSinceEpoch;
code -= _seedCounter++;
if (code.bitLength & 1 == 1) {
code *= ~code;
}
code ^= ~_seedCounter << 5;
_seedCounter += code & 7;
return code & _mask32;
}
15 changes: 15 additions & 0 deletions lib/src/algorithms/random/random_vm.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) 2024, Sudipto Chandra
// All rights reserved. Check LICENSE file for details.

import 'dart:math' show Random;

const int _mask32 = 0xFFFFFFFF;

final secure = Random.secure();

@pragma('vm:prefer-inline')
Random secureRandom() => secure;

@pragma('vm:prefer-inline')
int $generateSeed() =>
(DateTime.now().microsecondsSinceEpoch & _mask32) ^ secure.nextInt(_mask32);
5 changes: 2 additions & 3 deletions lib/src/core/hashlib_random.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@

import 'dart:typed_data';

import 'package:hashlib/src/algorithms/random_generators.dart';
import 'package:hashlib/src/algorithms/random/random.dart';

export 'package:hashlib/src/algorithms/random_generators.dart'
show RandomGenerator;
export 'package:hashlib/src/algorithms/random/random.dart' show RandomGenerator;

const int _mask32 = 0xFFFFFFFF;

Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: hashlib
description: Secure hash functions, checksum generators, and key derivation algorithms optimized for Dart.
homepage: https://github.com/bitanon/hashlib
version: 1.20.3
version: 1.20.4

environment:
sdk: '>=2.14.0 <4.0.0'
Expand Down
49 changes: 34 additions & 15 deletions test/random_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

// ignore_for_file: no_leading_underscores_for_local_identifiers

import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';

import 'package:hashlib/hashlib.dart';
import 'package:hashlib/src/algorithms/random_generators.dart';
import 'package:hashlib/src/algorithms/random/random.dart';
import 'package:test/test.dart';

const int _maxInt = 0xFFFFFFFF;
Expand Down Expand Up @@ -78,15 +80,32 @@ void main() {
}, tags: ['vm-only']);
});

test('seed generator uniqueness', () {
int n = 10000;
var m = <int>{};
for (int i = 0; i < n; ++i) {
m.add(RandomGenerators.$generateSeed());
}
expect(m.length, n);
test('seed generator uniqueness with futures', () async {
final seeds = await Future.wait(List.generate(
1000,
(_) => Future.microtask(() {
return Generators.$nextSeed();
}),
));
expect(seeds.toSet().length, 1000);
});

test('seed generator uniqueness with isolates', () async {
var version = Platform.version;
var major = int.parse(version.split('.')[0]);
var minor = int.parse(version.split('.')[1]);
if (major > 2 || (major == 2 && minor >= 19)) {
final seeds = await Future.wait(List.generate(
1000,
// ignore: sdk_version_since
(_) => Isolate.run(() {
return Generators.$nextSeed();
}),
));
expect(seeds.toSet().length, 1000);
}
}, tags: ['vm-only']);

test('random bytes length = 0', () {
expect(randomBytes(0), []);
});
Expand Down Expand Up @@ -369,15 +388,15 @@ void main() {
test('Test with a normal length list', () {
int seed = 123456789;
var data = Uint8List(64);
RandomGenerators.$seedList(data, seed);
Generators.$seedList(data, seed);
expect(data, isNot(equals(Uint8List(64))));
});

test('Test with small list', () {
int seed = 123456789;
for (int i = 1; i < 8; ++i) {
var data = Uint8List(i);
RandomGenerators.$seedList(data, seed);
Generators.$seedList(data, seed);
expect(data, isNot(equals(Uint8List(i))));
}
});
Expand All @@ -386,7 +405,7 @@ void main() {
int seed = 123456789;
for (int i = 1; i < 4; ++i) {
var data = Uint8List(64 + i);
RandomGenerators.$seedList(data, seed);
Generators.$seedList(data, seed);
expect(data.skip(64), isNot(equals(Uint8List(i))));
}
});
Expand All @@ -395,8 +414,8 @@ void main() {
int seed = 123456789;
var data1 = Uint8List(255);
var data2 = Uint8List(255);
RandomGenerators.$seedList(data1, seed);
RandomGenerators.$seedList(data2, seed);
Generators.$seedList(data1, seed);
Generators.$seedList(data2, seed);
expect(data1, equals(data2));
});

Expand All @@ -405,8 +424,8 @@ void main() {
int seed2 = 987654321;
var data1 = Uint8List(255);
var data2 = Uint8List(255);
RandomGenerators.$seedList(data1, seed1);
RandomGenerators.$seedList(data2, seed2);
Generators.$seedList(data1, seed1);
Generators.$seedList(data2, seed2);
expect(data1, isNot(equals(data2)));
});
});
Expand Down

0 comments on commit d904c66

Please sign in to comment.