diff --git a/test/benchmark/index.ts b/test/benchmark/index.ts new file mode 100644 index 0000000..5aa1b65 --- /dev/null +++ b/test/benchmark/index.ts @@ -0,0 +1,141 @@ +import crypto from "crypto"; +import * as blst from "@chainsafe/blst-ts"; +import * as herumi from "../../src"; +import {runBenchmark} from "./runner"; + +(async function () { + await herumi.initBLS(); + + const aggCount = 30; + + // verify + + runBenchmark<{pk: blst.PublicKey; msg: Uint8Array; sig: blst.Signature}, boolean>({ + id: "BLST verify", + + prepareTest: () => { + const msg = randomMsg(); + const sk = blst.SecretKey.fromKeygen(crypto.randomBytes(32)); + const pk = sk.toPublicKey(); + const sig = sk.sign(msg); + return { + input: {pk, msg, sig}, + resultCheck: (valid) => valid === true, + }; + }, + testRunner: ({pk, msg, sig}) => { + return blst.verify(msg, pk, sig); + }, + }); + + runBenchmark<{pk: herumi.PublicKey; msg: Uint8Array; sig: herumi.Signature}, boolean>({ + id: "HERUMI verify", + + prepareTest: () => { + const msg = randomMsg(); + const keypair = herumi.generateKeyPair(); + const pk = keypair.publicKey; + const sig = keypair.privateKey.signMessage(msg); + return { + input: {pk, msg, sig}, + resultCheck: (valid) => valid === true, + }; + }, + testRunner: ({pk, msg, sig}) => { + return pk.verifyMessage(sig, msg); + }, + }); + + // Fast aggregate + + runBenchmark<{pks: blst.PublicKey[]; msg: Uint8Array; sig: blst.Signature}, boolean>({ + id: "BLST fastAggregateVerify", + + prepareTest: () => { + const msg = randomMsg(); + + const dataArr = range(aggCount).map(() => { + const sk = blst.SecretKey.fromKeygen(crypto.randomBytes(32)); + const pk = sk.toPublicKey(); + const sig = sk.sign(msg); + return {pk, sig}; + }); + + const pks = dataArr.map((data) => data.pk); + const aggSig = blst.AggregateSignature.fromSignatures(dataArr.map((data) => data.sig)); + const sig = aggSig.toSignature(); + + return { + input: {pks, msg, sig}, + resultCheck: (valid) => valid === true, + }; + }, + testRunner: ({pks, msg, sig}) => { + return blst.fastAggregateVerify(msg, pks, sig); + }, + }); + + runBenchmark<{pks: herumi.PublicKey[]; msg: Uint8Array; sig: herumi.Signature}, boolean>({ + id: "HERUMI fastAggregateVerify", + + prepareTest: () => { + const msg = randomMsg(); + + const dataArr = range(aggCount).map(() => { + const keypair = herumi.generateKeyPair(); + const pk = keypair.publicKey; + const sig = keypair.privateKey.signMessage(msg); + return {pk, sig}; + }); + + const pks = dataArr.map((data) => data.pk); + const sig = herumi.Signature.aggregate(dataArr.map((data) => data.sig)); + + return { + input: {pks, msg, sig}, + resultCheck: (valid) => valid === true, + }; + }, + testRunner: ({pks, msg, sig}) => { + return sig.verifyAggregate(pks, msg); + }, + }); + + // Aggregate sigs + + runBenchmark({ + id: `BLST aggregatePubkeys (${aggCount})`, + + prepareTest: () => { + return { + input: range(aggCount).map(() => blst.SecretKey.fromKeygen(crypto.randomBytes(32)).toPublicKey()), + }; + }, + testRunner: (pks) => { + blst.AggregatePublicKey.fromPublicKeys(pks); + }, + }); + + runBenchmark({ + id: `HERUMI aggregatePubkeys (${aggCount})`, + + prepareTest: () => { + return { + input: range(aggCount).map(() => herumi.generateKeyPair().publicKey), + }; + }, + testRunner: (pks) => { + pks.reduce((agg, pk) => agg.add(pk)); + }, + }); +})(); + +function range(n: number): number[] { + const nums: number[] = []; + for (let i = 0; i < n; i++) nums.push(i); + return nums; +} + +function randomMsg(): Uint8Array { + return Uint8Array.from(crypto.randomBytes(32)); +} diff --git a/test/benchmark/runner.ts b/test/benchmark/runner.ts new file mode 100644 index 0000000..19f06fa --- /dev/null +++ b/test/benchmark/runner.ts @@ -0,0 +1,33 @@ +export function runBenchmark({ + prepareTest, + testRunner, + runs = 100, + id, +}: { + prepareTest: (i: number) => {input: T; resultCheck?: (result: R) => boolean}; + testRunner: (input: T) => R; + runs?: number; + id: string; +}) { + const diffsNanoSec: bigint[] = []; + + for (let i = 0; i < runs; i++) { + const {input, resultCheck} = prepareTest(i); + + const start = process.hrtime.bigint(); + const result = testRunner(input); + const end = process.hrtime.bigint(); + + if (resultCheck && !resultCheck(result)) throw Error("Result fails check test"); + diffsNanoSec.push(end - start); + } + + const average = averageBigint(diffsNanoSec); + const opsPerSec = 1e9 / Number(average); + console.log(`${id}: ${opsPerSec.toPrecision(5)} ops/sec (${runs} runs)`); // ±1.74% +} + +function averageBigint(arr: bigint[]): bigint { + const total = arr.reduce((total, value) => total + value); + return total / BigInt(arr.length); +}