Skip to content

Commit 424ec64

Browse files
authored
add benches (#7)
1 parent 8259e86 commit 424ec64

File tree

11 files changed

+842
-3
lines changed

11 files changed

+842
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/benches/node_modules
12
/node_modules
23
/target
34
/tests/browser

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
/tests/fixtures
44
/util/gen-fixtures/secp256k1
55

6+
benches/package-lock.json
67
package-lock.json

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ clean:
3434
.PHONY: format
3535
format:
3636
cargo-fmt
37-
npx prettier -w .
37+
npx prettier -w .
38+
npx sort-package-json package.json benches/package.json
3839

3940
.PHONY: lint
4041
lint:

benches/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
## Run benches
2+
3+
```
4+
npm install
5+
npm start
6+
```

benches/cryptocoinjs_secp256k1.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import secp256k1_js from "secp256k1/elliptic.js";
2+
import secp256k1_native from "secp256k1/bindings.js";
3+
4+
export const js = createApi(secp256k1_js);
5+
export const native = createApi(secp256k1_native);
6+
7+
function createApi(secp256k1) {
8+
return {
9+
isPoint: (p) => secp256k1.publicKeyVerify(p),
10+
// isPointCompressed
11+
isPrivate: (d) => secp256k1.privateKeyVerify(d),
12+
pointAdd: (pA, pB) => secp256k1.publicKeyCombine([pA, pB]),
13+
pointAddScalar: (p, tweak) => secp256k1.publicKeyTweakAdd(p, tweak),
14+
// pointCompress
15+
pointFromScalar: (d) => secp256k1.publicKeyCreate(d),
16+
pointMultiply: (p, tweak) => secp256k1.publicKeyTweakMul(p, tweak),
17+
privateAdd: (d, tweak) =>
18+
secp256k1.privateKeyTweakAdd(new Uint8Array(d), tweak),
19+
// privateSub
20+
sign: (h, d) => secp256k1.ecdsaSign(h, d),
21+
// signWithEntropy
22+
verify: (h, Q, signature) => secp256k1.ecdsaVerify(signature, h, Q),
23+
};
24+
}

benches/fixtures.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import * as tiny_secp256k1 from "../lib/index.js";
2+
import _fecdsa from "../tests/fixtures/ecdsa.json";
3+
import _fpoints from "../tests/fixtures/points.json";
4+
import _fprivates from "../tests/fixtures/privates.json";
5+
6+
export const fecdsa = _fecdsa.valid.map((f) => ({
7+
d: Buffer.from(f.d, "hex"),
8+
Q: Buffer.from(tiny_secp256k1.pointFromScalar(Buffer.from(f.d, "hex"))),
9+
m: Buffer.from(f.m, "hex"),
10+
signature: Buffer.from(f.signature, "hex"),
11+
}));
12+
13+
export const fpoints = {
14+
isPoint: _fpoints.valid.isPoint.map((f) => ({
15+
P: Buffer.from(f.P, "hex"),
16+
})),
17+
pointAdd: _fpoints.valid.pointAdd
18+
.filter((f) => f.expected !== null)
19+
.map((f) => ({
20+
P: Buffer.from(f.P, "hex"),
21+
Q: Buffer.from(f.Q, "hex"),
22+
})),
23+
pointAddScalar: _fpoints.valid.pointAddScalar
24+
.filter((f) => f.expected !== null)
25+
.map((f) => ({
26+
P: Buffer.from(f.P, "hex"),
27+
d: Buffer.from(f.d, "hex"),
28+
})),
29+
pointCompress: _fpoints.valid.pointCompress.map((f) => ({
30+
P: Buffer.from(f.P, "hex"),
31+
})),
32+
pointFromScalar: _fpoints.valid.pointFromScalar.map((f) => ({
33+
d: Buffer.from(f.d, "hex"),
34+
})),
35+
pointMultiply: _fpoints.valid.pointMultiply
36+
.filter((f) => f.expected !== null)
37+
.map((f) => ({
38+
P: Buffer.from(f.P, "hex"),
39+
d: Buffer.from(f.d, "hex"),
40+
})),
41+
};
42+
43+
export const fprivates = {
44+
isPrivate: _fprivates.valid.isPrivate.map((f) => ({
45+
d: Buffer.from(f.d, "hex"),
46+
})),
47+
privateAdd: _fprivates.valid.privateAdd
48+
.filter((f) => f.expected !== null)
49+
.map((f) => ({
50+
d: Buffer.from(f.d, "hex"),
51+
tweak: Buffer.from(f.tweak, "hex"),
52+
})),
53+
privateSub: _fprivates.valid.privateSub
54+
.filter((f) => f.expected !== null)
55+
.map((f) => ({
56+
d: Buffer.from(f.d, "hex"),
57+
tweak: Buffer.from(f.tweak, "hex"),
58+
})),
59+
};

benches/index.js

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import tiny_secp256k1_prev_js from "tiny-secp256k1/js.js";
2+
import tiny_secp256k1_prev_native from "tiny-secp256k1/native.js";
3+
import * as tiny_secp256k1 from "../lib/index.js";
4+
import * as cryptocoinjs_secp256k1 from "./cryptocoinjs_secp256k1.js";
5+
import { fecdsa, fpoints, fprivates } from "./fixtures.js";
6+
7+
const modules = [
8+
{
9+
name: "tiny-secp256k1 (rust addon)",
10+
secp256k1: tiny_secp256k1.__addon,
11+
},
12+
{
13+
name: "tiny-secp256k1 (wasm)",
14+
secp256k1: tiny_secp256k1.__wasm,
15+
},
16+
{
17+
name: "tiny-secp256k1@1.1.6 (C++ addon, NAN/V8)",
18+
secp256k1: tiny_secp256k1_prev_native,
19+
},
20+
{
21+
name: "tiny-secp256k1@1.1.6 (elliptic)",
22+
secp256k1: tiny_secp256k1_prev_js,
23+
},
24+
{
25+
name: "secp256k1@4.0.2 (C++ addon, N-API)",
26+
secp256k1: cryptocoinjs_secp256k1.native,
27+
},
28+
{
29+
name: "secp256k1@4.0.2 (elliptic)",
30+
secp256k1: cryptocoinjs_secp256k1.js,
31+
},
32+
];
33+
34+
const benchmarks = [
35+
{
36+
name: "isPoint",
37+
bench: createBenchmarkFn(fpoints.isPoint, (secp256k1, f) =>
38+
secp256k1.isPoint(f.P)
39+
),
40+
},
41+
{
42+
name: "isPrivate",
43+
bench: createBenchmarkFn(fprivates.isPrivate, (secp256k1, f) =>
44+
secp256k1.isPrivate(f.d)
45+
),
46+
},
47+
{
48+
name: "pointAdd",
49+
bench: createBenchmarkFn(fpoints.pointAdd, (secp256k1, f) =>
50+
secp256k1.pointAdd(f.P, f.Q)
51+
),
52+
},
53+
{
54+
name: "pointAddScalar",
55+
bench: createBenchmarkFn(fpoints.pointAddScalar, (secp256k1, f) =>
56+
secp256k1.pointAddScalar(f.P, f.d)
57+
),
58+
},
59+
{
60+
name: "pointCompress",
61+
bench: createBenchmarkFn(fpoints.pointCompress, (secp256k1, f) =>
62+
secp256k1.pointCompress(f.P)
63+
),
64+
},
65+
{
66+
name: "pointFromScalar",
67+
bench: createBenchmarkFn(fpoints.pointFromScalar, (secp256k1, f) =>
68+
secp256k1.pointFromScalar(f.d)
69+
),
70+
},
71+
{
72+
name: "pointMultiply",
73+
bench: createBenchmarkFn(fpoints.pointMultiply, (secp256k1, f) =>
74+
secp256k1.pointMultiply(f.P, f.d)
75+
),
76+
},
77+
{
78+
name: "privateAdd",
79+
bench: createBenchmarkFn(fprivates.privateAdd, (secp256k1, f) =>
80+
secp256k1.privateAdd(f.d, f.tweak)
81+
),
82+
},
83+
{
84+
name: "privateSub",
85+
bench: createBenchmarkFn(fprivates.privateSub, (secp256k1, f) =>
86+
secp256k1.privateSub(f.d, f.tweak)
87+
),
88+
},
89+
{
90+
name: "sign",
91+
bench: createBenchmarkFn(fecdsa, (secp256k1, f) =>
92+
secp256k1.sign(f.m, f.d)
93+
),
94+
},
95+
{
96+
name: "verify",
97+
bench: createBenchmarkFn(fecdsa, (secp256k1, f) =>
98+
secp256k1.verify(f.m, f.Q, f.signature)
99+
),
100+
},
101+
];
102+
103+
// Covert milliseconds as Number to nanoseconds as BigInt
104+
const millis2nanos = (ms) => BigInt(ms) * 10n ** 6n;
105+
106+
// Warmup bench function during
107+
function warmingUp(bench, minIter, maxTime) {
108+
const start = process.hrtime.bigint();
109+
for (let i = 0; ; ) {
110+
bench();
111+
if (process.hrtime.bigint() - start > maxTime && ++i >= minIter) {
112+
break;
113+
}
114+
}
115+
}
116+
117+
// Create benchmark function from fixtures
118+
function createBenchmarkFn(fixtures, fn) {
119+
return function (secp256k1) {
120+
for (const f of fixtures) {
121+
fn(secp256k1, f);
122+
}
123+
return fixtures.length;
124+
};
125+
}
126+
127+
// Run benchmarks
128+
const lineEqual = new Array(100).fill("=").join("");
129+
const lineDash = new Array(100).fill("-").join("");
130+
let isFirstResult = true;
131+
for (const benchmark of benchmarks) {
132+
const {
133+
name,
134+
bench,
135+
warmingUpMinIter,
136+
warmingUpMaxTime,
137+
benchmarkMinIter,
138+
benchmarkMaxTime,
139+
} = {
140+
warmingUpMinIter: 1,
141+
benchmarkMinIter: 2,
142+
warmingUpMaxTime: millis2nanos(2000),
143+
benchmarkMaxTime: millis2nanos(5000),
144+
...benchmark,
145+
};
146+
147+
if (isFirstResult) {
148+
console.log(lineEqual);
149+
isFirstResult = false;
150+
}
151+
console.log(`Benchmarking function: ${name}`);
152+
console.log(lineDash);
153+
const results = [];
154+
for (const module of modules) {
155+
if (module.secp256k1[name] === undefined) {
156+
continue;
157+
}
158+
159+
warmingUp(
160+
() => bench(module.secp256k1),
161+
warmingUpMinIter,
162+
warmingUpMaxTime
163+
);
164+
165+
const results_ns = [];
166+
const start = process.hrtime.bigint();
167+
let start_fn = start;
168+
for (let i = 0; ; ) {
169+
const ops = bench(module.secp256k1);
170+
const current = process.hrtime.bigint();
171+
results_ns.push(Number(current - start_fn) / ops);
172+
if (current - start > benchmarkMaxTime && ++i >= benchmarkMinIter) {
173+
break;
174+
}
175+
start_fn = current;
176+
}
177+
178+
const ops_avg_ns =
179+
results_ns.reduce((total, time) => total + time, 0) / results_ns.length;
180+
const ops_err_ns =
181+
results_ns.length > 1
182+
? results_ns.reduce(
183+
(total, time) => total + Math.abs(ops_avg_ns - time),
184+
0
185+
) /
186+
(results_ns.length - 1)
187+
: 0;
188+
const ops_err = (ops_err_ns / ops_avg_ns) * 100;
189+
190+
console.log(
191+
`${module.name}: ${(ops_avg_ns / 1000).toFixed(2)} us/op (${(
192+
10 ** 9 /
193+
ops_avg_ns
194+
).toFixed(2)} op/s), ±${ops_err.toFixed(2)} %`
195+
);
196+
197+
results.push({ name: module.name, ops_avg_ns });
198+
}
199+
if (results.length > 1) {
200+
const fastest = results.reduce((a, b) =>
201+
a.ops_avg_ns < b.ops_avg_ns ? a : b
202+
);
203+
console.log(lineDash);
204+
console.log(`Fastest: ${fastest.name}`);
205+
}
206+
console.log(lineEqual);
207+
}

0 commit comments

Comments
 (0)