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/tlock_support.dart

184 lines
6.2 KiB

// Helpers of the tests of stage 3 (BLS12-381, the IBE, tlock and releases):
// a pinned profile, and the little of age and of DKC1 that the tests read
// to reach a tlock stanza and to check a file key. They read no file, so
// that the tests that run on Node.js can use them; age itself is stage 2.
library;
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:datekeys/datekeys.dart' show concatBytes, fromHex;
import 'package:datekeys/src/release.dart';
/// A pinned profile for the tests.
final class TestProfile implements PinnedProfile {
TestProfile({
required this.id,
required this.scheme,
required this.publicKey,
required this.chainHash,
required this.maxRound,
});
@override
final String id;
@override
final String scheme;
@override
final Uint8List publicKey;
@override
final Uint8List chainHash;
@override
final int maxRound;
/// This profile with another [scheme] or [publicKey].
TestProfile copyWith({String? scheme, List<int>? publicKey}) => TestProfile(
id: id,
scheme: scheme ?? this.scheme,
publicKey: publicKey == null
? this.publicKey
: Uint8List.fromList(publicKey),
chainHash: chainHash,
maxRound: maxRound,
);
}
/// The Quicknet Provider Profile V1 of testdata/vectors/
/// profile_quicknet.json, which tlock_support_test.dart checks against it.
const quicknetId = 'datekeys:quicknet:v1';
const quicknetChainHash =
'52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971';
const quicknetPublicKey =
'83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b'
'6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809'
'bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a';
const quicknetGenesisTime = 1692803367;
const quicknetPeriod = 3;
/// 9999-12-31T23:59:59Z, the last representable instant (spec §15).
const maxUnixTime = 253402300799;
/// The pinned Quicknet profile.
TestProfile quicknet() => TestProfile(
id: quicknetId,
scheme: quicknetScheme,
publicKey: fromHex(quicknetPublicKey),
chainHash: fromHex(quicknetChainHash),
maxRound: (maxUnixTime - quicknetGenesisTime) ~/ quicknetPeriod + 1,
);
/// The published release signatures of rounds 1000 and 1001 of Quicknet, as
/// in the fixtures (time_only.json and empty_payload.json).
const signature1000 =
'b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a'
'8dd2bacbe47e4b6b63ed5e39';
const signature1001 =
'b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b2'
'5883abf2853d10337fb8fa41';
/// The edits of the mutation corpus applied to [base] in one pass, as
/// applyEdits of datekeys-ts: each [at, delete, insert hex] refers to
/// offsets of the unmodified base, sorted and not overlapping.
Uint8List applyEdits(List<int> base, List<Object?> edits) {
final parts = <List<int>>[];
var pos = 0;
for (final e in edits.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');
}
parts
..add(base.sublist(pos, at))
..add(fromHex(e[2]! as String));
pos = at + delete;
}
parts.add(base.sublist(pos));
return concatBytes(parts);
}
/// The round of the DateKey that the dk1_ string in [dkc] names: the tests
/// need it where the opening would read it from the public header, which
/// stage 4 decodes.
int dateKeyRound(List<int> dkc) {
final text = latin1.decode(dkc, allowInvalid: true);
final m = RegExp(r'dk1_([A-Za-z0-9_-]+)').firstMatch(text)!;
final json = utf8.decode(base64Url.decode(base64Url.normalize(m[1]!)));
return (jsonDecode(json) as Map<String, Object?>)['round']! as int;
}
/// SEALED_CONTROL of a DKC1 file: its PRELUDE gives the lengths of the
/// public header and of SEALED_CONTROL (spec §22).
Uint8List sealedControl(List<int> dkc) {
final view = ByteData.sublistView(Uint8List.fromList(dkc));
final header = view.getUint32(8);
final sealed = view.getUint32(12);
return Uint8List.fromList(dkc.sublist(16 + header, 16 + header + sealed));
}
/// A recipient stanza as the tests read it.
typedef Stanza = ({String type, List<String> args, Uint8List body});
/// The recipient stanzas and the MAC of the age header at the start of
/// [file], read leniently: enough for the files of the tests, which age
/// wrote. The grammar and its checks are those of stage 2.
({List<Stanza> stanzas, Uint8List macInput, Uint8List mac}) readAgeHeader(
List<int> file,
) {
var start = 0;
final stanzas = <Stanza>[];
for (var i = 0; i < file.length; i++) {
if (file[i] != 0x0a) continue;
final line = latin1.decode(file.sublist(start, i));
if (line.startsWith('---')) {
// The MAC covers the header up to and including "---".
return (
stanzas: stanzas,
macInput: Uint8List.fromList(file.sublist(0, start + 3)),
mac: base64.decode(base64.normalize(line.substring(4))),
);
}
start = i + 1;
if (line.startsWith('-> ')) {
final words = line.substring(3).split(' ');
final body = StringBuffer();
// The body: lines of 64 columns, the last one shorter.
var j = i + 1;
for (;;) {
final end = file.indexOf(0x0a, j);
final bodyLine = latin1.decode(file.sublist(j, end));
body.write(bodyLine);
j = end + 1;
if (bodyLine.length < 64) break;
}
stanzas.add((
type: words.first,
args: words.sublist(1),
body: base64.decode(base64.normalize(body.toString())),
));
i = j - 1;
start = j;
}
}
throw StateError('no MAC line');
}
/// Whether [fileKey] authenticates the age header whose MAC input and MAC
/// [readAgeHeader] returns: HMAC-SHA-256 under HKDF-SHA-256(fileKey, "",
/// "header"), as age computes it.
bool ageHeaderMacValid(List<int> fileKey, Uint8List macInput, Uint8List mac) {
final prk = Hmac(sha256, Uint8List(32)).convert(fileKey).bytes;
final key = Hmac(sha256, prk).convert([...ascii.encode('header'), 1]).bytes;
final got = Hmac(sha256, key).convert(macInput).bytes;
var diff = 0;
for (var i = 0; i < 32; i++) {
diff |= got[i] ^ mac[i];
}
return mac.length == 32 && diff == 0;
}

Powered by TurnKey Linux.