diff --git a/tool/bls12381_bench.dart b/tool/bls12381_bench.dart new file mode 100644 index 0000000..985ba3b --- /dev/null +++ b/tool/bls12381_bench.dart @@ -0,0 +1,122 @@ +// Times the BLS12-381 and tlock code of stage 3 (docs/PLAN_dart.md, +// «Rendimiento»): decoding a point of G1 and of G2, one pairing, the +// verification of a Quicknet round signature (hash to G1 and the pairing +// check), the IBE decryption of a tlock stanza, the IBE encryption, and +// steps 10 and 11 of the opening together. On the VM: +// +// dart run tool/bls12381_bench.dart +// +// and compiled to JavaScript, on Node.js, where dart2js reaches the random +// generator through self: +// +// dart compile js -O2 -o bench.js tool/bls12381_bench.dart +// node -e "globalThis.self = globalThis; require('./bench.js')" +// +// Each figure is the median of several runs after one to warm up. The +// program reads no file, so that it runs on the web too. +// ignore_for_file: avoid_print +library; + +import 'package:datekeys/datekeys.dart' show fromHex; +import 'package:datekeys/src/bls12381_curve.dart'; +import 'package:datekeys/src/bls12381_pairing.dart'; +import 'package:datekeys/src/ibe.dart'; +import 'package:datekeys/src/release.dart'; +import 'package:datekeys/src/tlock.dart'; + +// The pinned Quicknet profile, as testdata/vectors/profile_quicknet.json. +final class _Quicknet implements PinnedProfile { + @override + String get id => 'datekeys:quicknet:v1'; + + @override + String get scheme => quicknetScheme; + + @override + final publicKey = fromHex( + '83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b' + '6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809' + 'bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a', + ); + + @override + final chainHash = fromHex( + '52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971', + ); + + @override + int get maxRound => 83903165811; +} + +// The releases of rounds 1000 and 1001 (testdata/fixtures/time_only.json and +// empty_payload.json). The runs alternate between them: the last release +// verified is kept (release.dart), and verifying it again costs no pairing. +final _signatures = { + 1000: fromHex( + 'b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a' + '8dd2bacbe47e4b6b63ed5e39', + ), + 1001: fromHex( + 'b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b2' + '5883abf2853d10337fb8fa41', + ), +}; + +double _median(List xs) => (xs..sort())[xs.length ~/ 2]; + +void _time(String name, int runs, void Function(int round) body) { + body(1001); + final ms = []; + for (var i = 0; i < runs; i++) { + final round = i.isEven ? 1000 : 1001; + final sw = Stopwatch()..start(); + body(round); + ms.add(sw.elapsedMicroseconds / 1000); + } + print('${name.padRight(48)} ${_median(ms).toStringAsFixed(1)} ms'); +} + +void main(List args) { + final runs = args.isEmpty ? 7 : int.parse(args.first); + final p = _Quicknet(); + Release release(int round) => Release(round, _signatures[round]!); + final sig = G1Point.decode(_signatures[1000]!)!; + final key = G2Point.decode(p.publicKey)!; + final message = fromHex('00112233445566778899aabbccddeeff'); + final ct = { + for (final round in [1000, 1001]) + round: encryptOnG2(p.publicKey, roundIdentity(round), message), + }; + final stanza = { + for (final round in [1000, 1001]) round: wrapTlockStanza(p, round, message), + }; + _time('decode a point of G1 (the signature)', runs, (round) { + G1Point.decode(_signatures[round]!); + }); + _time('decode a point of G2 (U)', runs, (round) { + G2Point.decode(ct[round]!.u); + }); + _time('one pairing', runs, (_) => pairing(sig, key)); + // The pinned key is decoded once and kept (pinnedKey of release.dart): the + // first verification pays for it. + _time('verify a Quicknet round signature, key kept', runs, (round) { + verifyRelease(p, round, release(round)); + }); + _time('the same, decoding the pinned key', runs, (round) { + G2Point.decode(p.publicKey); + verifyRelease(p, round, release(round)); + }); + _time('IBE decryption of a tlock stanza', runs, (round) { + decryptOnG2(_signatures[round]!, ct[round]!); + }); + _time('IBE encryption', runs, (round) { + encryptOnG2(p.publicKey, roundIdentity(round), message); + }); + // Steps 10 and 11 of the opening: the release verified, then the stanza + // unwrapped, which verifies the same release again, as Go does. + _time('steps 10 and 11: verify, then unwrap the stanza', runs, (round) { + verifyRelease(p, round, release(round)); + final (args, body) = stanza[round]!; + unwrapTlockStanza(p, round, release(round), args, body); + }); +}