// The sealing of the locator and the envelope against Go, also compiled to // JavaScript: test/vectors/locator_seal.json, from // tool/locator_seal_go_vectors.go, holds what Seal and NewEnvelope of Go // write while crypto/rand reads the keystream of a seed. With the same // seed, sealLocator and newEnvelope draw the same values and write the same // bytes; the sealed locators open with the release of their round, and the // envelopes with their locator. Seal refuses what Go refuses, with its // text, before drawing anything. import 'dart:convert'; import 'dart:typed_data'; import 'package:datekeys/src/bytes.dart'; import 'package:datekeys/src/errors.dart'; import 'package:datekeys/src/locator.dart'; import 'package:datekeys/src/locator_seal.dart'; import 'package:datekeys/src/profile.dart'; import 'package:datekeys/src/release.dart' show Release; import 'package:datekeys/src/sha256.dart'; import 'package:test/test.dart'; import 'age_support.dart' show pattern; import 'random_support.dart'; import 'vectors/locator_seal.g.dart'; final Json vectors = jsonDecode(locatorSealJson) as Json; Locator locatorOf(Json j) => Locator( addresses: [ for (final a in listOf(j['addresses'])) LocatorAddress(a['uri']! as String, a['offset']! as int), ], envelopeKey: fromHex(j['envelope_key']! as String), envelopeHeader: fromHex(j['envelope_header']! as String), restDigest: fromHex(j['rest_digest']! as String), restSize: j['rest_size']! as int, capsuleDigest: fromHex(j['capsule_digest']! as String), ); Json jsonOf(Locator l) => { 'addresses': [ for (final a in l.addresses) {'uri': a.uri, 'offset': a.offset}, ], 'envelope_key': toHex(l.envelopeKey), 'envelope_header': toHex(l.envelopeHeader), 'rest_digest': toHex(l.restDigest), 'rest_size': l.restSize, 'capsule_digest': toHex(l.capsuleDigest), }; void expectFile(Uint8List b, Json want) { expect(b.length, want['length']); expect(toHex(sha256(b)), want['sha256']); if (want['hex'] != null) expect(toHex(b), want['hex']); } Release releaseOf(int round) => Release( round, fromHex((vectors['releases']! as Json)['$round']! as String), ); void main() { final q = quicknet(); for (final c in listOf(vectors['seal'])) { test('Seal writes the bytes of Go: ${c['name']}', () { final round = c['round']! as int; final loc = locatorOf(c['locator']! as Json); expect(loc.marshal().length, c['plaintext_length']); final r = RecordingSource(seeded(c['seed']! as String)); final sealed = sealLocator(q, round, loc, random: r); expectFile(sealed, c['sealed']! as Json); expect(r.draws, hasLength(c['draws'])); expect(r.json.first, c['first_draw']); if (c['opens'] == true) { final back = openLocator(q, round, releaseOf(round), sealed); expect(jsonOf(back), c['locator']); // Another round does not open it. final other = round == 1000 ? 1001 : 1000; expect( () => openLocator(q, other, releaseOf(other), sealed), throwsA(isA()), ); } }); } test('Seal refuses what Go refuses, before drawing anything', () { for (final c in listOf(vectors['seal_errors'])) { final r = RecordingSource(seeded('unused')); String? got; try { sealLocator( q, c['round']! as int, locatorOf(c['locator']! as Json), random: r, ); } on LocatorException catch (e) { got = e.message; } on DateKeysException catch (e) { got = e.message; } expect(got, c['error'], reason: c['name'] as String?); expect(r.draws, hasLength(c['draws'])); } }); for (final c in listOf(vectors['envelope'])) { if (isWeb && c['node'] != true) continue; test('NewEnvelope writes the envelope of Go: ${c['dkc_length']} bytes', () { final dkc = pattern(c['dkc_length']! as int); final r = RecordingSource(seeded(c['seed']! as String)); final e = newEnvelope(dkc, random: r); expect(jsonOf(e.locator), c['locator']); expectFile(e.rest, c['rest']! as Json); expect(r.json.first, (c['draws']! as List).first); expect(e.locator.openEnvelope(e.rest), dkc); }); } test('a writer and a reader, as Go: envelope, addresses, seal, open', () { final c = vectors['flow']! as Json; final dkc = pattern(c['dkc_length']! as int); final r = RecordingSource(seeded(c['seed']! as String)); final e = newEnvelope(dkc, random: r); final hidden = hideRest(fromHex(c['host']! as String), e.rest); final loc = e.locator.withAddresses([ LocatorAddress('https://example.com/capsule'), LocatorAddress('https://example.org/host.gif', hidden.offset), ]); final round = c['round']! as int; final sealed = sealLocator(q, round, loc, random: r); expect(r.draws, hasLength(c['draws'])); expect(jsonOf(loc), c['locator']); expectFile(e.rest, c['rest']! as Json); expectFile(sealed, c['sealed']! as Json); final got = openLocator(q, round, releaseOf(round), sealed); final a = got.addresses[1]; expect(got.openEnvelope(got.restIn(hidden.file, a.offset)), dkc); }); test('sealing leaves the locator as it was', () { final loc = locatorOf((listOf(vectors['seal']).first)['locator']! as Json); final before = loc.marshal(); sealLocator(q, 1000, loc, random: seeded('as it was')); expect(loc.marshal(), before); }); }