commit 4e4f12380524e1721315cfff125b5b3f06e4f6c3
parent 2c182b701fcbbbbea04ce3b9fd14e0bbafb4083c
Author: Jared Tobin <jared@jtobin.io>
Date: Fri, 14 Mar 2025 14:37:53 +0400
meta: readme perf update
Diffstat:
M | README.md | | | 129 | +++++++++++++++++++++++++++++++++++++++++++++---------------------------------- |
1 file changed, 74 insertions(+), 55 deletions(-)
diff --git a/README.md b/README.md
@@ -4,9 +4,10 @@

[](https://docs.ppad.tech/secp256k1)
-A pure Haskell implementation of [BIP0340][bp340] Schnorr signatures
-and deterministic [RFC6979][r6979] ECDSA (with [BIP0146][bp146]-style
-"low-S" signatures) on the elliptic curve secp256k1.
+A pure Haskell implementation of [BIP0340][bp340] Schnorr signatures,
+deterministic [RFC6979][r6979] ECDSA (with [BIP0146][bp146]-style
+"low-S" signatures), ECDH, and various primitives on the elliptic curve
+secp256k1.
(See also [ppad-csecp256k1][csecp] for FFI bindings to
bitcoin-core/secp256k1.)
@@ -59,33 +60,33 @@ Haddocks (API documentation, etc.) are hosted at
The aim is best-in-class performance for pure, highly-auditable Haskell
code.
-Current benchmark figures on my mid-2020 MacBook Air look like (use
-`cabal bench` to run the benchmark suite):
+Current benchmark figures on a relatively-beefy NixOS VPS look like
+(use `cabal bench` to run the benchmark suite):
```
benchmarking schnorr/sign_schnorr'
- time 2.557 ms (2.534 ms .. 2.596 ms)
- 0.998 R² (0.997 R² .. 0.999 R²)
- mean 2.579 ms (2.556 ms .. 2.605 ms)
- std dev 83.75 μs (69.50 μs .. 100.5 μs)
+ time 2.584 ms (2.491 ms .. 2.646 ms)
+ 0.994 R² (0.991 R² .. 0.997 R²)
+ mean 2.459 ms (2.426 ms .. 2.492 ms)
+ std dev 114.5 μs (95.32 μs .. 142.8 μs)
benchmarking schnorr/verify_schnorr'
- time 1.429 ms (1.381 ms .. 1.482 ms)
- 0.993 R² (0.987 R² .. 0.998 R²)
- mean 1.372 ms (1.355 ms .. 1.396 ms)
- std dev 67.72 μs (50.44 μs .. 110.5 μs)
+ time 1.283 ms (1.263 ms .. 1.301 ms)
+ 0.999 R² (0.998 R² .. 0.999 R²)
+ mean 1.273 ms (1.260 ms .. 1.284 ms)
+ std dev 41.56 μs (31.12 μs .. 54.35 μs)
benchmarking ecdsa/sign_ecdsa'
- time 233.7 μs (230.3 μs .. 237.2 μs)
- 0.997 R² (0.996 R² .. 0.998 R²)
- mean 238.6 μs (234.8 μs .. 243.3 μs)
- std dev 14.85 μs (12.27 μs .. 19.17 μs)
+ time 222.6 μs (219.9 μs .. 224.9 μs)
+ 0.999 R² (0.999 R² .. 1.000 R²)
+ mean 219.1 μs (217.8 μs .. 220.5 μs)
+ std dev 4.523 μs (3.525 μs .. 6.158 μs)
benchmarking ecdsa/verify_ecdsa'
- time 1.460 ms (1.418 ms .. 1.497 ms)
- 0.994 R² (0.989 R² .. 0.997 R²)
- mean 1.419 ms (1.398 ms .. 1.446 ms)
- std dev 80.76 μs (66.73 μs .. 104.9 μs)
+ time 1.267 ms (1.260 ms .. 1.276 ms)
+ 1.000 R² (1.000 R² .. 1.000 R²)
+ mean 1.278 ms (1.273 ms .. 1.286 ms)
+ std dev 21.32 μs (15.43 μs .. 30.82 μs)
```
In terms of allocations, we get:
@@ -102,6 +103,12 @@ ecdsa
Case Allocated GCs
sign_ecdsa' 324,672 0
verify_ecdsa' 3,796,328 0
+
+ecdh
+
+ Case Allocated GCs
+ ecdh (small) 2,141,736 0
+ ecdh (large) 2,145,464 0
```
## Security
@@ -120,41 +127,53 @@ so as to execute *algorithmically* in time constant with respect to
secret data, and evidence from benchmarks supports this:
```
- benchmarking derive_public/sk = 2
- time 1.703 ms (1.680 ms .. 1.728 ms)
+ benchmarking derive_pub/sk = 2
+ time 1.513 ms (1.468 ms .. 1.565 ms)
+ 0.994 R² (0.991 R² .. 0.997 R²)
+ mean 1.579 ms (1.557 ms .. 1.600 ms)
+ std dev 74.25 μs (60.33 μs .. 93.80 μs)
+
+ benchmarking derive_pub/sk = 2 ^ 255 - 19
+ time 1.571 ms (1.530 ms .. 1.599 ms)
+ 0.997 R² (0.995 R² .. 0.998 R²)
+ mean 1.574 ms (1.553 ms .. 1.589 ms)
+ std dev 57.72 μs (45.29 μs .. 71.48 μs)
+
+ benchmarking schnorr/sign_schnorr' (small)
+ time 2.436 ms (2.357 ms .. 2.516 ms)
+ 0.995 R² (0.994 R² .. 0.998 R²)
+ mean 2.563 ms (2.532 ms .. 2.588 ms)
+ std dev 95.87 μs (71.98 μs .. 127.2 μs)
+
+ benchmarking schnorr/sign_schnorr' (large)
+ time 2.470 ms (2.372 ms .. 2.543 ms)
+ 0.993 R² (0.989 R² .. 0.997 R²)
+ mean 2.407 ms (2.374 ms .. 2.443 ms)
+ std dev 123.7 μs (110.7 μs .. 144.9 μs)
+
+ benchmarking ecdsa/sign_ecdsa' (small)
+ time 206.9 μs (202.7 μs .. 211.8 μs)
+ 0.997 R² (0.996 R² .. 0.999 R²)
+ mean 213.8 μs (211.5 μs .. 215.9 μs)
+ std dev 7.476 μs (5.572 μs .. 9.698 μs)
+
+ benchmarking ecdsa/sign_ecdsa' (large)
+ time 216.7 μs (211.6 μs .. 221.7 μs)
0.997 R² (0.995 R² .. 0.999 R²)
- mean 1.704 ms (1.684 ms .. 1.730 ms)
- std dev 81.99 μs (67.86 μs .. 98.34 μs)
-
- benchmarking derive_public/sk = 2 ^ 255 - 19
- time 1.686 ms (1.654 ms .. 1.730 ms)
- 0.998 R² (0.997 R² .. 0.999 R²)
- mean 1.658 ms (1.645 ms .. 1.673 ms)
- std dev 44.75 μs (34.84 μs .. 59.81 μs)
-
- benchmarking schnorr/sign_schnorr (small secret)
- time 5.388 ms (5.345 ms .. 5.438 ms)
- 1.000 R² (0.999 R² .. 1.000 R²)
- mean 5.429 ms (5.410 ms .. 5.449 ms)
- std dev 58.14 μs (48.93 μs .. 68.69 μs)
-
- benchmarking schnorr/sign_schnorr (large secret)
- time 5.364 ms (5.324 ms .. 5.410 ms)
- 0.999 R² (0.999 R² .. 1.000 R²)
- mean 5.443 ms (5.414 ms .. 5.486 ms)
- std dev 102.1 μs (74.16 μs .. 146.4 μs)
-
- benchmarking ecdsa/sign_ecdsa (small secret)
- time 1.740 ms (1.726 ms .. 1.753 ms)
- 1.000 R² (0.999 R² .. 1.000 R²)
- mean 1.752 ms (1.744 ms .. 1.761 ms)
- std dev 32.33 μs (26.12 μs .. 43.34 μs)
-
- benchmarking ecdsa/sign_ecdsa (large secret)
- time 1.748 ms (1.731 ms .. 1.766 ms)
- 0.999 R² (0.998 R² .. 0.999 R²)
- mean 1.756 ms (1.743 ms .. 1.771 ms)
- std dev 48.99 μs (40.83 μs .. 62.77 μs)
+ mean 221.8 μs (219.5 μs .. 224.1 μs)
+ std dev 7.673 μs (6.124 μs .. 10.69 μs)
+
+ benchmarking ecdh/ecdh (small)
+ time 1.623 ms (1.605 ms .. 1.639 ms)
+ 0.999 R² (0.998 R² .. 1.000 R²)
+ mean 1.617 ms (1.603 ms .. 1.624 ms)
+ std dev 32.52 μs (20.66 μs .. 55.97 μs)
+
+ benchmarking ecdh/ecdh (large)
+ time 1.623 ms (1.580 ms .. 1.661 ms)
+ 0.996 R² (0.992 R² .. 0.998 R²)
+ mean 1.625 ms (1.606 ms .. 1.641 ms)
+ std dev 58.38 μs (44.31 μs .. 78.23 μs)
```
Due to the use of arbitrary-precision integers, integer division modulo