Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add benches #7

Merged
merged 2 commits into from
Mar 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/benches/node_modules
/node_modules
/target
/tests/browser
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
/tests/fixtures
/util/gen-fixtures/secp256k1

benches/package-lock.json
package-lock.json
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ clean:
.PHONY: format
format:
cargo-fmt
npx prettier -w .
npx prettier -w .
npx sort-package-json package.json benches/package.json

.PHONY: lint
lint:
Expand Down
6 changes: 6 additions & 0 deletions benches/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## Run benches

```
npm install
npm start
```
24 changes: 24 additions & 0 deletions benches/cryptocoinjs_secp256k1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import secp256k1_js from "secp256k1/elliptic.js";
import secp256k1_native from "secp256k1/bindings.js";

export const js = createApi(secp256k1_js);
export const native = createApi(secp256k1_native);

function createApi(secp256k1) {
return {
isPoint: (p) => secp256k1.publicKeyVerify(p),
// isPointCompressed
isPrivate: (d) => secp256k1.privateKeyVerify(d),
pointAdd: (pA, pB) => secp256k1.publicKeyCombine([pA, pB]),
pointAddScalar: (p, tweak) => secp256k1.publicKeyTweakAdd(p, tweak),
// pointCompress
pointFromScalar: (d) => secp256k1.publicKeyCreate(d),
pointMultiply: (p, tweak) => secp256k1.publicKeyTweakMul(p, tweak),
privateAdd: (d, tweak) =>
secp256k1.privateKeyTweakAdd(new Uint8Array(d), tweak),
// privateSub
sign: (h, d) => secp256k1.ecdsaSign(h, d),
// signWithEntropy
verify: (h, Q, signature) => secp256k1.ecdsaVerify(signature, h, Q),
};
}
59 changes: 59 additions & 0 deletions benches/fixtures.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as tiny_secp256k1 from "../lib/index.js";
import _fecdsa from "../tests/fixtures/ecdsa.json";
import _fpoints from "../tests/fixtures/points.json";
import _fprivates from "../tests/fixtures/privates.json";

export const fecdsa = _fecdsa.valid.map((f) => ({
d: Buffer.from(f.d, "hex"),
Q: Buffer.from(tiny_secp256k1.pointFromScalar(Buffer.from(f.d, "hex"))),
m: Buffer.from(f.m, "hex"),
signature: Buffer.from(f.signature, "hex"),
}));

export const fpoints = {
isPoint: _fpoints.valid.isPoint.map((f) => ({
P: Buffer.from(f.P, "hex"),
})),
pointAdd: _fpoints.valid.pointAdd
.filter((f) => f.expected !== null)
.map((f) => ({
P: Buffer.from(f.P, "hex"),
Q: Buffer.from(f.Q, "hex"),
})),
pointAddScalar: _fpoints.valid.pointAddScalar
.filter((f) => f.expected !== null)
.map((f) => ({
P: Buffer.from(f.P, "hex"),
d: Buffer.from(f.d, "hex"),
})),
pointCompress: _fpoints.valid.pointCompress.map((f) => ({
P: Buffer.from(f.P, "hex"),
})),
pointFromScalar: _fpoints.valid.pointFromScalar.map((f) => ({
d: Buffer.from(f.d, "hex"),
})),
pointMultiply: _fpoints.valid.pointMultiply
.filter((f) => f.expected !== null)
.map((f) => ({
P: Buffer.from(f.P, "hex"),
d: Buffer.from(f.d, "hex"),
})),
};

export const fprivates = {
isPrivate: _fprivates.valid.isPrivate.map((f) => ({
d: Buffer.from(f.d, "hex"),
})),
privateAdd: _fprivates.valid.privateAdd
.filter((f) => f.expected !== null)
.map((f) => ({
d: Buffer.from(f.d, "hex"),
tweak: Buffer.from(f.tweak, "hex"),
})),
privateSub: _fprivates.valid.privateSub
.filter((f) => f.expected !== null)
.map((f) => ({
d: Buffer.from(f.d, "hex"),
tweak: Buffer.from(f.tweak, "hex"),
})),
};
207 changes: 207 additions & 0 deletions benches/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import tiny_secp256k1_prev_js from "tiny-secp256k1/js.js";
import tiny_secp256k1_prev_native from "tiny-secp256k1/native.js";
import * as tiny_secp256k1 from "../lib/index.js";
import * as cryptocoinjs_secp256k1 from "./cryptocoinjs_secp256k1.js";
import { fecdsa, fpoints, fprivates } from "./fixtures.js";

const modules = [
{
name: "tiny-secp256k1 (rust addon)",
secp256k1: tiny_secp256k1.__addon,
},
{
name: "tiny-secp256k1 (wasm)",
secp256k1: tiny_secp256k1.__wasm,
},
{
name: "tiny-secp256k1@1.1.6 (C++ addon, NAN/V8)",
secp256k1: tiny_secp256k1_prev_native,
},
{
name: "tiny-secp256k1@1.1.6 (elliptic)",
secp256k1: tiny_secp256k1_prev_js,
},
{
name: "secp256k1@4.0.2 (C++ addon, N-API)",
secp256k1: cryptocoinjs_secp256k1.native,
},
{
name: "secp256k1@4.0.2 (elliptic)",
secp256k1: cryptocoinjs_secp256k1.js,
},
];

const benchmarks = [
{
name: "isPoint",
bench: createBenchmarkFn(fpoints.isPoint, (secp256k1, f) =>
secp256k1.isPoint(f.P)
),
},
{
name: "isPrivate",
bench: createBenchmarkFn(fprivates.isPrivate, (secp256k1, f) =>
secp256k1.isPrivate(f.d)
),
},
{
name: "pointAdd",
bench: createBenchmarkFn(fpoints.pointAdd, (secp256k1, f) =>
secp256k1.pointAdd(f.P, f.Q)
),
},
{
name: "pointAddScalar",
bench: createBenchmarkFn(fpoints.pointAddScalar, (secp256k1, f) =>
secp256k1.pointAddScalar(f.P, f.d)
),
},
{
name: "pointCompress",
bench: createBenchmarkFn(fpoints.pointCompress, (secp256k1, f) =>
secp256k1.pointCompress(f.P)
),
},
{
name: "pointFromScalar",
bench: createBenchmarkFn(fpoints.pointFromScalar, (secp256k1, f) =>
secp256k1.pointFromScalar(f.d)
),
},
{
name: "pointMultiply",
bench: createBenchmarkFn(fpoints.pointMultiply, (secp256k1, f) =>
secp256k1.pointMultiply(f.P, f.d)
),
},
{
name: "privateAdd",
bench: createBenchmarkFn(fprivates.privateAdd, (secp256k1, f) =>
secp256k1.privateAdd(f.d, f.tweak)
),
},
{
name: "privateSub",
bench: createBenchmarkFn(fprivates.privateSub, (secp256k1, f) =>
secp256k1.privateSub(f.d, f.tweak)
),
},
{
name: "sign",
bench: createBenchmarkFn(fecdsa, (secp256k1, f) =>
secp256k1.sign(f.m, f.d)
),
},
{
name: "verify",
bench: createBenchmarkFn(fecdsa, (secp256k1, f) =>
secp256k1.verify(f.m, f.Q, f.signature)
),
},
];

// Covert milliseconds as Number to nanoseconds as BigInt
const millis2nanos = (ms) => BigInt(ms) * 10n ** 6n;

// Warmup bench function during
function warmingUp(bench, minIter, maxTime) {
const start = process.hrtime.bigint();
for (let i = 0; ; ) {
bench();
if (process.hrtime.bigint() - start > maxTime && ++i >= minIter) {
break;
}
}
}

// Create benchmark function from fixtures
function createBenchmarkFn(fixtures, fn) {
return function (secp256k1) {
for (const f of fixtures) {
fn(secp256k1, f);
}
return fixtures.length;
};
}

// Run benchmarks
const lineEqual = new Array(100).fill("=").join("");
const lineDash = new Array(100).fill("-").join("");
let isFirstResult = true;
for (const benchmark of benchmarks) {
const {
name,
bench,
warmingUpMinIter,
warmingUpMaxTime,
benchmarkMinIter,
benchmarkMaxTime,
} = {
warmingUpMinIter: 1,
benchmarkMinIter: 2,
warmingUpMaxTime: millis2nanos(2000),
benchmarkMaxTime: millis2nanos(5000),
...benchmark,
};

if (isFirstResult) {
console.log(lineEqual);
isFirstResult = false;
}
console.log(`Benchmarking function: ${name}`);
console.log(lineDash);
const results = [];
for (const module of modules) {
if (module.secp256k1[name] === undefined) {
continue;
}

warmingUp(
() => bench(module.secp256k1),
warmingUpMinIter,
warmingUpMaxTime
);

const results_ns = [];
const start = process.hrtime.bigint();
let start_fn = start;
for (let i = 0; ; ) {
const ops = bench(module.secp256k1);
const current = process.hrtime.bigint();
results_ns.push(Number(current - start_fn) / ops);
if (current - start > benchmarkMaxTime && ++i >= benchmarkMinIter) {
break;
}
start_fn = current;
}

const ops_avg_ns =
results_ns.reduce((total, time) => total + time, 0) / results_ns.length;
const ops_err_ns =
results_ns.length > 1
? results_ns.reduce(
(total, time) => total + Math.abs(ops_avg_ns - time),
0
) /
(results_ns.length - 1)
: 0;
const ops_err = (ops_err_ns / ops_avg_ns) * 100;

console.log(
`${module.name}: ${(ops_avg_ns / 1000).toFixed(2)} us/op (${(
10 ** 9 /
ops_avg_ns
).toFixed(2)} op/s), ±${ops_err.toFixed(2)} %`
);

results.push({ name: module.name, ops_avg_ns });
}
if (results.length > 1) {
const fastest = results.reduce((a, b) =>
a.ops_avg_ns < b.ops_avg_ns ? a : b
);
console.log(lineDash);
console.log(`Fastest: ${fastest.name}`);
}
console.log(lineEqual);
}
Loading