diff --git a/tool/cms_bench.dart b/tool/cms_bench.dart new file mode 100644 index 0000000..5a0eacf --- /dev/null +++ b/tool/cms_bench.dart @@ -0,0 +1,120 @@ +// Times the CMS reader of datekeys-dart, stage 5a, on the VM and compiled +// to JavaScript: the verification of one ECDSA signature on each curve and +// of one RSA signature of each size and scheme, the reading of a +// certificate, and a whole signature and a whole token read and checked. It +// reads no file: the cases are those of test/vectors/cms_vectors.g.dart. +// +// dart run tool/cms_bench.dart +// +// dart compile js -O2 -o cms_bench.js tool/cms_bench.dart +// node cms_bench.js +// +// ignore_for_file: avoid_print +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:datekeys/src/bytes.dart'; +import 'package:datekeys/src/cms.dart'; +import 'package:datekeys/src/ecdsa.dart'; +import 'package:datekeys/src/nist_curves.dart'; +import 'package:datekeys/src/rsa.dart'; + +import '../test/vectors/cms_vectors.g.dart'; + +typedef Json = Map; + +List _cases(String json, String section) => + ((jsonDecode(json) as Json)[section]! as List).cast(); + +Json _named(String json, String section, String name) => + _cases(json, section).firstWhere((c) => c['name'] == name); + +Uint8List _hex(Object? v) => fromHex(v! as String); + +/// The mean time of [f] in milliseconds, over at least [minRuns] runs and +/// about [budgetMs] milliseconds, after one run to warm up. +double time(void Function() f, {int minRuns = 3, int budgetMs = 2000}) { + f(); + final sw = Stopwatch()..start(); + var runs = 0; + while (runs < minRuns || sw.elapsedMilliseconds < budgetMs) { + f(); + runs++; + } + return sw.elapsedMicroseconds / 1000 / runs; +} + +String ms(double v) => '${v.toStringAsFixed(v < 10 ? 2 : 0)} ms'; + +void main() { + final curves = {'P-256': p256, 'P-384': p384, 'P-521': p521}; + final keys = _cases(cmsEcdsaJson, 'keys'); + for (final (curve, hash) in const [ + ('P-256', 'SHA-256'), + ('P-384', 'SHA-384'), + ('P-521', 'SHA-512'), + ]) { + final c = _cases(cmsEcdsaJson, 'verify').firstWhere( + (c) => (c['name']! as String).startsWith('$curve, valid over $hash, '), + ); + final k = keys[c['key']! as int]; + final point = decodeUncompressed(curves[curve]!, _hex(k['point']))!; + final h = _hex(c['hash']); + final sig = _hex(c['sig']); + if (!verifyEcdsaAsn1(point, h, sig)) throw StateError('not valid'); + print( + 'ECDSA $curve, $hash, one verification: ' + '${ms(time(() => verifyEcdsaAsn1(point, h, sig)))}', + ); + } + + final rsaKeys = _cases(cmsRsaJson, 'keys'); + for (final c in _cases(cmsRsaJson, 'valid')) { + final k = rsaKeys[c['key']! as int]; + if (k['e'] != 65537) continue; + final key = RsaPublicKey( + BigInt.parse(k['n']! as String, radix: 16), + k['e']! as int, + ); + final digest = _hex(c['digest']); + final sig = _hex(c['sig']); + bool verify() => c['scheme'] == 'pss' + ? verifyPss(key, Sha2.sha256, digest, sig) + : verifyPkcs1v15(key, Sha2.sha256, digest, sig); + if (!verify()) throw StateError('not valid'); + print('RSA ${c['name']}, one verification: ${ms(time(verify))}'); + } + + for (final name in const ['field by field', 'rsa']) { + final der = _hex(_named(cmsCertsJson, 'bases', name)['der']); + print( + 'parseCert, the certificate "$name" (${der.length} bytes): ' + '${ms(time(() => parseCert(der).holder))}', + ); + } + + final message = _hex((jsonDecode(cmsMutationsJson) as Json)['message']); + for (final name in const ['sealed', 'co-signature']) { + final der = _hex(_named(cmsMutationsJson, 'signature_bases', name)['der']); + void check() { + for (final s in parseSignature(der).signers) { + if (s.check(message) != CmsResult.valid) throw StateError('invalid'); + } + } + + print( + 'parseSignature and check, "$name" (${der.length} bytes): ' + '${ms(time(check))}', + ); + } + final subject = _hex((jsonDecode(cmsMutationsJson) as Json)['subject']); + final token = _hex(_named(cmsMutationsJson, 'token_bases', 'ecdsa')['der']); + void checkToken() { + if (!parseToken(token).check(subject)) throw StateError('invalid'); + } + + print( + 'parseToken and check, ECDSA P-256 (${token.length} bytes): ' + '${ms(time(checkToken))}', + ); +}