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.
241 lines
7.5 KiB
241 lines
7.5 KiB
// Times the opening of datekeys-dart, stages 4c and 5c, on the VM and
|
|
// compiled to JavaScript: the opening in memory of a fixture of each kind
|
|
// (time_only, time_and_key with a .dkk, and format 3 with its files), each
|
|
// with the verification of its release; the opening of the two fixtures of
|
|
// format 3 whose security area holds a signature of alg 2 and a seal of
|
|
// seal_type 2, with the time of the evaluation of that area alone; and the
|
|
// throughput of a capsule of 64 MiB opened from a source in streaming, in
|
|
// format 1 and in format 3, which large_capsule.dart makes from the
|
|
// fixtures. It reads no file: the fixtures are those of
|
|
// test/vectors/open_vectors.g.dart and securitycms_vectors.g.dart.
|
|
//
|
|
// dart run tool/open_bench.dart
|
|
//
|
|
// dart compile js -O2 -o open_bench.js tool/open_bench.dart
|
|
// node -e "globalThis.self = globalThis; require('./open_bench.js')"
|
|
//
|
|
// ignore_for_file: avoid_print
|
|
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:datekeys/datekeys.dart';
|
|
|
|
import '../test/large_capsule.dart';
|
|
import '../test/vectors/open_vectors.g.dart';
|
|
import '../test/vectors/securitycms_vectors.g.dart';
|
|
|
|
typedef Json = Map<String, Object?>;
|
|
|
|
final Json _fixtures = jsonDecode(openFixturesJson) as Json;
|
|
final Json _records = jsonDecode(openRecordsJson) as Json;
|
|
|
|
Uint8List fixture(String file) => fromHex(_fixtures[file]! as String);
|
|
|
|
Json record(String file) => _records[file]! as Json;
|
|
|
|
Release releaseOf(Json r) {
|
|
final rel = r['release']! as Json;
|
|
return Release(rel['round']! as int, fromHex(rel['signature']! as String));
|
|
}
|
|
|
|
/// A sink of bytes that counts them and keeps nothing.
|
|
final class Discard implements ByteSink {
|
|
int n = 0;
|
|
|
|
@override
|
|
void add(Uint8List bytes) => n += bytes.length;
|
|
|
|
@override
|
|
void close() {}
|
|
|
|
@override
|
|
void abort(Object reason) {}
|
|
}
|
|
|
|
/// A sink of files that counts their bytes and keeps nothing.
|
|
final class DiscardFiles implements FileSink {
|
|
int n = 0;
|
|
|
|
@override
|
|
void begin(Head head) {}
|
|
|
|
@override
|
|
ByteSink create(int i) => _Count(this);
|
|
|
|
@override
|
|
void commit() {}
|
|
|
|
@override
|
|
void abort(Object reason) {}
|
|
}
|
|
|
|
final class _Count implements ByteSink {
|
|
_Count(this.s);
|
|
final DiscardFiles s;
|
|
|
|
@override
|
|
void add(Uint8List bytes) => s.n += bytes.length;
|
|
|
|
@override
|
|
void close() {}
|
|
|
|
@override
|
|
void abort(Object reason) {}
|
|
}
|
|
|
|
OpenOptions optionsOf(
|
|
Json r, {
|
|
ByteSink? output,
|
|
FileSink? sink,
|
|
Uint8List? dkk,
|
|
}) => OpenOptions(
|
|
source: suppliedRelease(releaseOf(r)),
|
|
now: () => parseRfc3339(r['unlock_at']! as String),
|
|
accessKeyFile: dkk,
|
|
output: output,
|
|
sink: sink,
|
|
);
|
|
|
|
double median(List<double> xs) {
|
|
final s = [...xs]..sort();
|
|
return s[s.length ~/ 2];
|
|
}
|
|
|
|
Future<void> main() async {
|
|
final js = identical(1, 1.0);
|
|
final runs = js ? 5 : 15;
|
|
print(
|
|
'datekeys-dart, the opening, ${js ? 'compiled to JavaScript' : 'on the VM'}',
|
|
);
|
|
|
|
// The verification of a release is kept for the next opening of the same
|
|
// round: a release of another round between two openings makes each pay
|
|
// for its own pairing, as an application that opens one capsule does.
|
|
final other = record('time_only_extensions.dkc');
|
|
for (final (label, file, withDkk) in [
|
|
('time_only, format 1', 'time_only_extensions.dkc', false),
|
|
('time_and_key with a .dkk, format 1', 'time_and_key_portable.dkc', true),
|
|
(
|
|
'time_and_key with a .dkk, format 2',
|
|
'format2_time_and_key_recipients.dkc',
|
|
true,
|
|
),
|
|
('format 3, one file', 'format3_single.dkc', false),
|
|
(
|
|
'format 3, time_and_key with a .dkk',
|
|
'format3_time_and_key_portable.dkc',
|
|
true,
|
|
),
|
|
]) {
|
|
final r = record(file);
|
|
final dkc = fixture(file);
|
|
final dkk = withDkk ? fixture(r['access_key_file']! as String) : null;
|
|
final format3 = r['format'] == 3;
|
|
final times = <double>[];
|
|
for (var i = 0; i < runs; i++) {
|
|
final evict = releaseOf(r).round == releaseOf(other).round
|
|
? record('time_and_key_portable.dkc')
|
|
: other;
|
|
verifyRelease(quicknet(), releaseOf(evict).round, releaseOf(evict));
|
|
final sw = Stopwatch()..start();
|
|
final o = await openCapsule(
|
|
dkc,
|
|
format3
|
|
? optionsOf(r, sink: DiscardFiles(), dkk: dkk)
|
|
: optionsOf(r, output: Discard(), dkk: dkk),
|
|
);
|
|
times.add(sw.elapsedMicroseconds / 1000);
|
|
if (!o.ok) throw StateError('$label: ${o.error}');
|
|
}
|
|
print(' $label: ${median(times).toStringAsFixed(1)} ms');
|
|
}
|
|
|
|
// The fixtures with certificates: the opening, and inside it the
|
|
// evaluation of the security area by the default evaluator, the reader of
|
|
// CMS included.
|
|
final cms =
|
|
((jsonDecode(securityCmsFixturesJson) as Json)['fixtures']! as List)
|
|
.cast<Json>();
|
|
for (final (label, r) in [
|
|
('format 3, a signature of alg 2 by two signers, each sealed', cms[0]),
|
|
('format 3, a signature of alg 1 and a seal of seal_type 2', cms[1]),
|
|
]) {
|
|
final dkc = fromHex(r['dkc']! as String);
|
|
final times = <double>[];
|
|
final evaluations = <double>[];
|
|
for (var i = 0; i < runs; i++) {
|
|
verifyRelease(quicknet(), releaseOf(other).round, releaseOf(other));
|
|
final evaluation = Stopwatch();
|
|
final sw = Stopwatch()..start();
|
|
final o = await openCapsule(
|
|
dkc,
|
|
OpenOptions(
|
|
source: suppliedRelease(releaseOf(r)),
|
|
now: () => parseRfc3339(r['unlock_at']! as String),
|
|
sink: DiscardFiles(),
|
|
evaluator: (input) {
|
|
evaluation.start();
|
|
final v = evaluateSecurityInput(input);
|
|
evaluation.stop();
|
|
return v;
|
|
},
|
|
),
|
|
);
|
|
times.add(sw.elapsedMicroseconds / 1000);
|
|
evaluations.add(evaluation.elapsedMicroseconds / 1000);
|
|
final want = (r['verdicts']! as Json)['signature'];
|
|
if (!o.ok || o.verdicts?.signature?.code != want) {
|
|
throw StateError('$label: ${o.error} ${o.verdicts}');
|
|
}
|
|
}
|
|
print(
|
|
' $label: ${median(times).toStringAsFixed(1)} ms, of which the '
|
|
'security area ${median(evaluations).toStringAsFixed(1)} ms',
|
|
);
|
|
}
|
|
|
|
// Throughput: 64 MiB on the VM, 16 MiB on Node.js, from a source.
|
|
final mib = js ? 16 : 64;
|
|
final content = pattern(mib << 20);
|
|
final r1 = record('time_only_extensions.dkc');
|
|
final big1 = withContent1(
|
|
fixture('time_only_extensions.dkc'),
|
|
fromHex(r1['payload_identity']! as String),
|
|
content,
|
|
);
|
|
final r3 = record('format3_single.dkc');
|
|
final big3 = withFiles3(
|
|
fixture('format3_single.dkc'),
|
|
fromHex(r3['payload_identity']! as String),
|
|
releaseOf(r3),
|
|
[('grande.bin', content)],
|
|
);
|
|
for (final (label, dkc, r, format3) in [
|
|
('format 1', big1, r1, false),
|
|
('format 3, one file', big3, r3, true),
|
|
]) {
|
|
final times = <double>[];
|
|
for (var i = 0; i < 3; i++) {
|
|
final out = Discard();
|
|
final files = DiscardFiles();
|
|
final sw = Stopwatch()..start();
|
|
final o = await openCapsuleSource(
|
|
BytesSource(dkc),
|
|
// Never `c ? null : sink`: dart2js of Dart 3.13 loses the writes to
|
|
// an object that reaches a field that way, and reads its counter as
|
|
// 0 afterwards (see the README).
|
|
format3 ? optionsOf(r, sink: files) : optionsOf(r, output: out),
|
|
);
|
|
times.add(sw.elapsedMicroseconds / 1e6);
|
|
if (!o.ok || (format3 ? files.n : out.n) != content.length) {
|
|
throw StateError('$label: ${o.error}');
|
|
}
|
|
}
|
|
final s = median(times);
|
|
print(
|
|
' $mib MiB, $label, from a source: ${s.toStringAsFixed(2)} s, '
|
|
'${(mib / s).toStringAsFixed(1)} MiB/s',
|
|
);
|
|
}
|
|
}
|