// 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; /// The list [key] of [v], each item a list. List> rows(Json v, String key) => [ for (final r in v[key]! as List) r! as List, ]; /// The text [i] of the table of [v]: `''` for no error. String textOf(Json v, Object? i) => (v['texts']! as List)[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; 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 base, Object? edits) { final out = BytesBuilder(copy: false); var pos = 0; for (final e in (edits! as List).cast>()) { 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 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 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 expandSummary(Object? s) { final l = s! as List; return [ [ for (final a in (l[0]! as List).cast>()) [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;