// 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', ); }