From fbd0dd7b0aaeaf78aa27eb4a7b5fd5dddb5586be Mon Sep 17 00:00:00 2001 From: dev Date: Tue, 6 Oct 2026 02:09:58 +0200 Subject: [PATCH] Stage 6a: a tool that times the age writer tool/age_writer_bench.dart times the header of each age file of a capsule, the file of an author key and a large PAYLOAD_AGE in streaming, with the CSPRNG of the platform. On the VM, 64 MiB take 1.38 s, 46 MiB/s; compiled to JavaScript, 16 MiB take 0.33 s, 49 MiB/s. The header of PAYLOAD_AGE costs 4 ms, INNER_ACCESS_AGE with 16 stanzas about 45 ms on the VM and 35 ms in Node.js, OUTER_TIME_AGE 40 ms and 0.55 s, and an author key file with scrypt of logN 16, 0.6 s and 0.85 s. Co-Authored-By: Claude Opus 5.5 --- tool/age_writer_bench.dart | 89 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tool/age_writer_bench.dart diff --git a/tool/age_writer_bench.dart b/tool/age_writer_bench.dart new file mode 100644 index 0000000..ef98f0b --- /dev/null +++ b/tool/age_writer_bench.dart @@ -0,0 +1,89 @@ +// Times the age writer of datekeys-dart, stage 6a, on the VM and compiled +// to JavaScript: the throughput of a large PAYLOAD_AGE encrypted in +// streaming, in pieces of 1 MiB, 64 MiB on the VM and 16 MiB in Node.js; and +// the header of each file of a capsule: PAYLOAD_AGE for one X25519 +// recipient, INNER_ACCESS_AGE for 16 and a control of 103 bytes, +// OUTER_TIME_AGE for the tlock recipient, and the file of an author key, a +// scrypt recipient with logN 16. Each is the median of a few runs. The +// random values come from the CSPRNG of the platform, as in production. +// +// dart run tool/age_writer_bench.dart +// +// dart compile js -O2 -o age_writer_bench.js tool/age_writer_bench.dart +// node -e "globalThis.self = globalThis; require('./age_writer_bench.js')" +// +// ignore_for_file: avoid_print +import 'dart:typed_data'; + +import 'package:datekeys/datekeys.dart' show quicknet; +import 'package:datekeys/src/age_writer.dart'; +import 'package:datekeys/src/random.dart'; +import 'package:datekeys/src/recipient.dart'; + +const isWeb = identical(0, 0.0); + +double median(List xs) => (xs..sort())[xs.length ~/ 2]; + +/// The median time of [f], in milliseconds, over [runs] runs. +double time(int runs, void Function() f) { + final times = []; + for (var i = 0; i < runs; i++) { + final sw = Stopwatch()..start(); + f(); + times.add(sw.elapsedMicroseconds / 1000); + } + return median(times); +} + +void main() { + final id = generateX25519Identity(); + final payload = X25519Recipient.of(id); + final slots = [ + for (var i = 0; i < 16; i++) X25519Recipient.of(generateX25519Identity()), + ]; + final tlock = TimeRecipient(quicknet(), 1000); + final scrypt = ScryptRecipient('a passphrase', workFactor: 16); + final control = Uint8List(103); + + print('age writer, ${isWeb ? 'Node.js' : 'VM'}'); + print( + 'PAYLOAD_AGE header, one X25519 stanza: ' + '${time(15, () => AgeEncryptor([payload]).close()).toStringAsFixed(2)} ms', + ); + print( + 'INNER_ACCESS_AGE, 16 X25519 stanzas, 103 bytes: ' + '${time(7, () => ageEncrypt(control, slots)).toStringAsFixed(1)} ms', + ); + print( + 'OUTER_TIME_AGE, the tlock stanza of round 1000, 2128 bytes: ' + '${time(5, () => ageEncrypt(Uint8List(1773), [tlock])).toStringAsFixed(0)} ms', + ); + print( + 'an author key file, scrypt with logN 16: ' + '${time(3, () => ageEncrypt(Uint8List(120), [scrypt])).toStringAsFixed(0)} ms', + ); + + // The throughput in streaming, in pieces of 1 MiB. + final mib = isWeb ? 16 : 64; + final piece = Uint8List(1 << 20); + for (var i = 0; i < piece.length; i++) { + piece[i] = (31 * i + 7) & 0xff; + } + final ms = time(3, () { + final e = AgeEncryptor([payload], random: secureRandom); + var n = e.header.length + e.nonce.length; + for (var i = 0; i < mib; i++) { + for (final c in e.add(piece)) { + n += c.length; + } + } + n += e.close().length; + if (n != ageFileLength([x25519StanzaLength], mib << 20)) { + throw StateError('$n bytes'); + } + }); + print( + '$mib MiB in streaming: ${(ms / 1000).toStringAsFixed(2)} s, ' + '${(mib / ms * 1000).toStringAsFixed(1)} MiB/s', + ); +}