You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dateKeys-dart/test/random_test.dart

146 lines
5.3 KiB

// The random sources of the writers against test/vectors/age_writer.json
// (tool/age_writer_go_vectors.go): SeededRandomSource gives the keystream
// that Go reads as crypto/rand.Reader, across fills of any size;
// randomIndex gives what crypto/rand.Int gives over it, with the same draws;
// and permute gives the order of the permute of capsule.Encrypt. Also the
// bounds of randomIndex, the uniformity of permute and the CSPRNG of the
// platform, on the VM: compiled to JavaScript, Random.secure is not
// available to the tests.
import 'dart:convert';
import 'dart:typed_data';
import 'package:datekeys/datekeys.dart';
import 'package:datekeys/src/random.dart';
import 'package:test/test.dart';
import 'random_support.dart';
import 'vectors/age_writer.g.dart';
final Json vectors = jsonDecode(ageWriterJson) as Json;
// A source that replays the given bytes and fails past them.
final class Replay implements RandomSource {
Replay(List<int> bytes) : _bytes = Uint8List.fromList(bytes);
final Uint8List _bytes;
int _at = 0;
@override
void fill(Uint8List out) {
if (_at + out.length > _bytes.length) throw StateError('exhausted');
out.setRange(0, out.length, _bytes, _at);
_at += out.length;
}
}
void main() {
test('SeededRandomSource is the keystream that Go reads', () {
for (final c in listOf(vectors['seeded'])) {
final source = seeded(c['seed']! as String);
final out = BytesBuilder();
for (final n in (c['fills']! as List).cast<int>()) {
out.add(randomBytes(source, n));
}
expect(toHex(out.takeBytes()), c['hex'], reason: c['seed'] as String?);
// One fill of everything gives the same bytes.
final all = randomBytes(
seeded(c['seed']! as String),
fromHex(c['hex']! as String).length,
);
expect(toHex(all), c['hex']);
}
// fill overwrites what the buffer held.
final b = Uint8List(8)..fillRange(0, 8, 0xff);
seeded('').fill(b);
expect(b, randomBytes(seeded(''), 8));
expect(() => randomBytes(seeded(''), -1), throwsRangeError);
});
test('randomIndex is crypto/rand.Int, with the same draws', () {
for (final c in listOf(vectors['rand_int'])) {
final n = c['n']! as int;
final source = RecordingSource(seeded(c['seed']! as String));
final got = [for (var i = 0; i < 24; i++) randomIndex(source, n)];
expect(got, c['results'], reason: '$n');
expect(source.draws, hasLength(c['reads']), reason: '$n');
// The stream goes on where Go's goes on.
expect(toHex(randomBytes(source, 4)), c['next'], reason: '$n');
}
});
test('randomIndex draws again exactly above the largest result', () {
// n = 3: one byte, two bits kept; 3 is drawn again.
expect(randomIndex(Replay([0xff, 0xfe]), 3), 2);
expect(randomIndex(Replay([0x03, 0x01]), 3), 1);
// n = 256: one byte, all kept, none drawn again.
expect(randomIndex(Replay([0xff]), 256), 255);
// n = 257: two bytes, the first with one bit; 257 to 511 drawn again.
expect(randomIndex(Replay([0x01, 0x01, 0x01, 0x00]), 257), 256);
expect(randomIndex(Replay([0xff, 0xff, 0x00, 0x05]), 257), 5);
// n = 2^32: four bytes, any. (No shift of 32 bits: the web would
// truncate it.)
expect(
randomIndex(Replay([0xff, 0xff, 0xff, 0xff]), 0x100000000),
0xffffffff,
);
// n = 2^31 + 1: four bytes, 32 bits kept.
expect(
randomIndex(Replay([0x80, 0, 0, 1, 0x80, 0, 0, 0]), 0x80000001),
0x80000000,
);
// n = 1: nothing drawn.
expect(randomIndex(Replay(const []), 1), 0);
for (final n in [0, -1, 0x100000001]) {
expect(() => randomIndex(Replay(const []), n), throwsRangeError);
}
});
test('permute is the permute of capsule.Encrypt', () {
for (final c in listOf(vectors['permute'])) {
final n = c['n']! as int;
final items = List.generate(n, (i) => i);
final source = RecordingSource(seeded(c['seed']! as String));
permute(items, source);
expect(items, c['order'], reason: c['seed'] as String?);
expect(source.draws, hasLength(c['reads']));
}
});
// As TestStanzaOrderIsUniform of the Go reference and the test of
// datekeys-ts: the positions of the first and of the last of 16 items
// over many permutations, each a chi-square with 15 degrees of freedom
// under 60 (p ≈ 10⁻⁷ by chance).
test('permute puts each item in every position uniformly', () {
final source = seeded('uniform');
const n = 16;
final rounds = isWeb ? 4000 : 32000;
final first = List.filled(n, 0);
final last = List.filled(n, 0);
for (var r = 0; r < rounds; r++) {
final items = List.generate(n, (i) => i);
permute(items, source);
first[items.indexOf(0)]++;
last[items.indexOf(n - 1)]++;
}
for (final counts in [first, last]) {
final e = rounds / n;
var chi = 0.0;
for (final o in counts) {
chi += (o - e) * (o - e) / e;
}
expect(chi, lessThan(60), reason: '$counts');
}
});
test('the CSPRNG of the platform', testOn: 'vm', () {
final a = randomBytes(secureRandom, 32);
final b = randomBytes(secureRandom, 32);
expect(a, isNot(b));
expect(a.toSet().length, greaterThan(16));
for (var i = 0; i < 100; i++) {
final v = randomIndex(secureRandom, 16);
expect(v, inInclusiveRange(0, 15));
}
});
}

Powered by TurnKey Linux.