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.
162 lines
5.3 KiB
162 lines
5.3 KiB
// Helpers of the tests of the locator (stage 7a): the vectors of
|
|
// tool/locator_go_vectors.go, their edits and their compact addresses, and
|
|
// what a reader reads of a locator, as the generator writes it. They read no
|
|
// file, so that the tests that run on Node.js can use them.
|
|
library;
|
|
|
|
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:datekeys/src/bytes.dart';
|
|
import 'package:datekeys/src/locator.dart';
|
|
import 'package:datekeys/src/profile.dart';
|
|
import 'package:datekeys/src/release.dart';
|
|
import 'package:datekeys/src/sha256.dart';
|
|
|
|
typedef Json = Map<String, Object?>;
|
|
|
|
/// The list [key] of [v], each item a list.
|
|
List<List<Object?>> rows(Json v, String key) => [
|
|
for (final r in v[key]! as List<Object?>) r! as List<Object?>,
|
|
];
|
|
|
|
/// The text [i] of the table of [v]: `''` for no error.
|
|
String textOf(Json v, Object? i) =>
|
|
(v['texts']! as List<Object?>)[i! as int]! as String;
|
|
|
|
/// An address as the generator writes it: the string, or [before, byte,
|
|
/// count, after] for one with a run of count copies of a byte.
|
|
String uriOf(Object? x) {
|
|
if (x is String) return x;
|
|
final l = x! as List<Object?>;
|
|
return '${l[0]}${(l[1]! as String) * (l[2]! as int)}${l[3]}';
|
|
}
|
|
|
|
/// The edits [edits] of [base] applied in one pass: each [at, delete,
|
|
/// insert] replaces delete bytes at the offset at of the base with the bytes
|
|
/// of the hexadecimal insert, and [at, delete, byte, count] with count
|
|
/// copies of the byte. The offsets are those of the base, in order.
|
|
Uint8List applyLocatorEdits(List<int> base, Object? edits) {
|
|
final out = BytesBuilder(copy: false);
|
|
var pos = 0;
|
|
for (final e in (edits! as List<Object?>).cast<List<Object?>>()) {
|
|
final at = e[0]! as int;
|
|
final delete = e[1]! as int;
|
|
if (at < pos || at + delete > base.length) {
|
|
throw StateError('edit at $at out of order or beyond the base');
|
|
}
|
|
out.add(base.sublist(pos, at));
|
|
final insert = fromHex(e[2]! as String);
|
|
final repeat = e.length > 3 ? e[3]! as int : 1;
|
|
for (var i = 0; i < repeat; i++) {
|
|
out.add(insert);
|
|
}
|
|
pos = at + delete;
|
|
}
|
|
out.add(base.sublist(pos));
|
|
return out.takeBytes();
|
|
}
|
|
|
|
/// The lower-case hexadecimal SHA-256 of [b].
|
|
String sha256Hex(List<int> b) => toHex(sha256(b));
|
|
|
|
/// What a reader reads of [l], as the generator writes it: [[uri, offset,
|
|
/// host, usable]...], the key, the SHA-256 of the header, the digest of the
|
|
/// rest, its size and capsule_digest, with each address in full.
|
|
List<Object?> summaryOf(Locator l) => [
|
|
[
|
|
for (final a in l.addresses)
|
|
[a.uri, a.offset, a.host, l.usable.contains(a)],
|
|
],
|
|
toHex(l.envelopeKey),
|
|
sha256Hex(l.envelopeHeader),
|
|
toHex(l.restDigest),
|
|
l.restSize,
|
|
toHex(l.capsuleDigest),
|
|
];
|
|
|
|
/// The summary [s] of the generator, with each address in full.
|
|
List<Object?> expandSummary(Object? s) {
|
|
final l = s! as List<Object?>;
|
|
return [
|
|
[
|
|
for (final a in (l[0]! as List<Object?>).cast<List<Object?>>())
|
|
[uriOf(a[0]), a[1], uriOf(a[2]), a[3]],
|
|
],
|
|
...l.sublist(1),
|
|
];
|
|
}
|
|
|
|
/// The release of [round] of the vectors [v], public data of drand.
|
|
Release releaseOf(Json v, int round) {
|
|
final r = (v['releases']! as Json)['$round']! as String;
|
|
return Release(round, fromHex(r));
|
|
}
|
|
|
|
/// The pinned Quicknet profile, edited as the generator names the edit.
|
|
Profile profileOf(String edit) {
|
|
final p = quicknet();
|
|
switch (edit) {
|
|
case '':
|
|
return p;
|
|
case 'key not on the curve':
|
|
final k = Uint8List.fromList(p.publicKey);
|
|
k[k.length - 1] ^= 1;
|
|
return p.copyWith(publicKey: k);
|
|
case 'key at infinity':
|
|
return p.copyWith(publicKey: [0xc0, ...List.filled(95, 0)]);
|
|
case 'key of another network':
|
|
// The generator of G2, compressed.
|
|
return p.copyWith(
|
|
publicKey: fromHex(
|
|
'93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049'
|
|
'334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051'
|
|
'c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8',
|
|
),
|
|
);
|
|
case 'chain hash of another network':
|
|
final h = Uint8List.fromList(p.chainHash);
|
|
h[0] ^= 1;
|
|
return p.copyWith(chainHash: h);
|
|
}
|
|
throw ArgumentError(edit);
|
|
}
|
|
|
|
/// The release that an open case names: a round, or an edit of the release
|
|
/// of the case.
|
|
Release openRelease(Json v, int round, Object? release) {
|
|
if (release is int) return releaseOf(v, release);
|
|
final r = releaseOf(v, round);
|
|
switch (release) {
|
|
case 'flipped':
|
|
final s = Uint8List.fromList(r.signature);
|
|
s[s.length - 1] ^= 1;
|
|
return Release(round, s);
|
|
case 'short':
|
|
return Release(round, r.signature.sublist(0, 47));
|
|
case 'other round':
|
|
return Release(round, releaseOf(v, 1001).signature);
|
|
}
|
|
throw ArgumentError('$release');
|
|
}
|
|
|
|
/// The text of the error that [body] throws, `''` when it returns: a
|
|
/// [LocatorException] or a DateKeysException by its message.
|
|
String errorText(void Function() body) {
|
|
try {
|
|
body();
|
|
return '';
|
|
} on LocatorException catch (e) {
|
|
return e.message;
|
|
} on ArgumentError catch (e) {
|
|
// An error of the caller without a normative code, as checkWrite
|
|
// reports an extension out of its place.
|
|
return '${e.message}';
|
|
} on Exception catch (e) {
|
|
return '$e';
|
|
}
|
|
}
|
|
|
|
/// The JSON of a constant of test/vectors.
|
|
Json decodeJson(String s) => jsonDecode(s) as Json;
|