secp256k1

Pure Haskell Schnorr, ECDSA on the elliptic curve secp256k1 (docs.ppad.tech/secp256k1).
git clone git://git.ppad.tech/secp256k1.git
Log | Files | Refs | README | LICENSE

commit 0d550cbad23f33d1e085fd94f2fda5ab137330e8
parent 9940200d92ff96d532ac4460b9f05e6f18f65c9e
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun, 21 Dec 2025 17:57:22 -0330

meta: readme update

Diffstat:
MREADME.md | 134+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
1 file changed, 77 insertions(+), 57 deletions(-)

diff --git a/README.md b/README.md @@ -57,37 +57,35 @@ Haddocks (API documentation, etc.) are hosted at ## Performance -The aim is best-in-class performance for pure, highly-auditable Haskell -code. +The aim is best-in-class performance for pure Haskell code. Current benchmark figures on an M4 MacBook Air look like (use `cabal bench` to run the benchmark suite): ``` benchmarking schnorr/sign_schnorr' (large) - time 48.00 μs (47.93 μs .. 48.09 μs) - 1.000 R² (1.000 R² .. 1.000 R²) - mean 48.01 μs (47.96 μs .. 48.10 μs) - std dev 219.6 ns (121.9 ns .. 407.9 ns) + time 77.54 μs (77.14 μs .. 78.16 μs) + 1.000 R² (0.999 R² .. 1.000 R²) + mean 77.27 μs (77.11 μs .. 77.63 μs) + std dev 802.7 ns (166.4 ns .. 1.331 μs) benchmarking schnorr/verify_schnorr' - time 131.0 μs (130.7 μs .. 131.4 μs) + time 161.1 μs (160.9 μs .. 161.3 μs) 1.000 R² (1.000 R² .. 1.000 R²) - mean 132.0 μs (131.4 μs .. 133.0 μs) - std dev 2.521 μs (1.745 μs .. 3.350 μs) - variance introduced by outliers: 13% (moderately inflated) + mean 160.9 μs (160.6 μs .. 161.0 μs) + std dev 618.3 ns (374.1 ns .. 1.070 μs) benchmarking ecdsa/sign_ecdsa' (large) - time 58.25 μs (58.14 μs .. 58.44 μs) + time 72.37 μs (72.32 μs .. 72.43 μs) 1.000 R² (1.000 R² .. 1.000 R²) - mean 58.27 μs (58.19 μs .. 58.44 μs) - std dev 383.9 ns (192.0 ns .. 687.1 ns) + mean 72.50 μs (72.43 μs .. 72.60 μs) + std dev 268.5 ns (167.6 ns .. 413.0 ns) benchmarking ecdsa/verify_ecdsa' - time 135.3 μs (135.2 μs .. 135.5 μs) + time 151.8 μs (151.6 μs .. 152.0 μs) 1.000 R² (1.000 R² .. 1.000 R²) - mean 135.5 μs (135.4 μs .. 135.7 μs) - std dev 384.2 ns (271.7 ns .. 558.1 ns) + mean 152.0 μs (151.8 μs .. 152.3 μs) + std dev 781.7 ns (421.4 ns .. 1.175 μs) ``` ## Security @@ -105,74 +103,96 @@ faulty nonce generation or malicious inputs for signature or public key parameters. Timing-sensitive operations, e.g. elliptic curve scalar multiplication, -have been explicitly written so as to execute algorithmically in time -constant with respect to secret data. Moreover, fixed-size (256-bit) -wide words with constant-time operations provided by [ppad-fixed][fixed] -are used exclusively internally, avoiding timing variations incurred by -use of GHC's variable-size Integer type. +have been explicitly written so as to execute in time constant with +respect to secret data. Moreover, fixed-size (256-bit) wide words with +constant-time operations provided by [ppad-fixed][fixed] are used +exclusively internally, avoiding timing variations incurred by use of +GHC's variable-size Integer type. -Benchmarks validate the constant-time implementation, with any timing -differential attributable to benchmarking noise: +Criterion benchmarks attest that any timing variation between cases with +differing inputs is attributable to noise: ``` benchmarking derive_pub/wnaf, sk = 2 - time 12.74 μs (12.58 μs .. 13.00 μs) - 0.999 R² (0.996 R² .. 1.000 R²) - mean 12.65 μs (12.60 μs .. 12.84 μs) - std dev 290.1 ns (107.0 ns .. 573.0 ns) - variance introduced by outliers: 23% (moderately inflated) + time 27.08 μs (27.07 μs .. 27.10 μs) + 1.000 R² (1.000 R² .. 1.000 R²) + mean 27.10 μs (27.09 μs .. 27.12 μs) + std dev 60.01 ns (41.51 ns .. 98.05 ns) benchmarking derive_pub/wnaf, sk = 2 ^ 255 - 19 - time 12.64 μs (12.62 μs .. 12.66 μs) + time 27.09 μs (27.07 μs .. 27.11 μs) 1.000 R² (1.000 R² .. 1.000 R²) - mean 12.64 μs (12.62 μs .. 12.66 μs) - std dev 54.35 ns (43.90 ns .. 68.18 ns) -``` + mean 27.09 μs (27.08 μs .. 27.12 μs) + std dev 61.41 ns (34.20 ns .. 116.9 ns) -``` benchmarking schnorr/sign_schnorr' (small) - time 47.33 μs (47.04 μs .. 47.57 μs) + time 77.40 μs (77.11 μs .. 77.87 μs) 1.000 R² (1.000 R² .. 1.000 R²) - mean 47.17 μs (47.03 μs .. 47.32 μs) - std dev 491.1 ns (410.2 ns .. 615.3 ns) + mean 77.32 μs (77.25 μs .. 77.53 μs) + std dev 366.0 ns (197.5 ns .. 704.2 ns) benchmarking schnorr/sign_schnorr' (large) - time 47.37 μs (47.20 μs .. 47.54 μs) + time 77.39 μs (77.33 μs .. 77.45 μs) 1.000 R² (1.000 R² .. 1.000 R²) - mean 47.21 μs (47.14 μs .. 47.31 μs) - std dev 286.5 ns (224.0 ns .. 349.4 ns) -``` + mean 77.36 μs (77.31 μs .. 77.42 μs) + std dev 178.8 ns (147.8 ns .. 222.9 ns) -``` benchmarking ecdsa/sign_ecdsa' (small) - time 58.12 μs (57.89 μs .. 58.29 μs) + time 72.32 μs (72.24 μs .. 72.42 μs) 1.000 R² (1.000 R² .. 1.000 R²) - mean 57.77 μs (57.61 μs .. 57.91 μs) - std dev 505.6 ns (420.2 ns .. 639.8 ns) + mean 72.42 μs (72.38 μs .. 72.50 μs) + std dev 199.4 ns (151.0 ns .. 298.5 ns) benchmarking ecdsa/sign_ecdsa' (large) - time 58.03 μs (57.86 μs .. 58.19 μs) + time 72.42 μs (72.31 μs .. 72.56 μs) 1.000 R² (1.000 R² .. 1.000 R²) - mean 57.87 μs (57.74 μs .. 57.99 μs) - std dev 406.9 ns (341.1 ns .. 512.9 ns) -``` + mean 72.41 μs (72.37 μs .. 72.49 μs) + std dev 199.9 ns (141.9 ns .. 344.1 ns) -``` benchmarking ecdh/ecdh (small) - time 158.6 μs (158.4 μs .. 159.0 μs) + time 257.2 μs (256.9 μs .. 257.5 μs) 1.000 R² (1.000 R² .. 1.000 R²) - mean 158.8 μs (158.4 μs .. 159.4 μs) - std dev 1.586 μs (1.139 μs .. 2.612 μs) + mean 257.0 μs (256.8 μs .. 257.2 μs) + std dev 667.5 ns (480.7 ns .. 973.1 ns) benchmarking ecdh/ecdh (large) - time 159.2 μs (158.9 μs .. 159.4 μs) + time 256.9 μs (256.7 μs .. 257.0 μs) 1.000 R² (1.000 R² .. 1.000 R²) - mean 158.8 μs (158.6 μs .. 159.0 μs) - std dev 584.2 ns (496.4 ns .. 689.3 ns) + mean 256.9 μs (256.7 μs .. 257.0 μs) + std dev 369.9 ns (278.8 ns .. 570.4 ns) +``` + +Note also that care has been taken to ensure that allocation is held +constant across input sizes for all sensitive operations: + +``` + derive_pub + + Case Allocated GCs + wnaf, sk = 2 304 0 + wnaf, sk = 2 ^ 255 - 19 304 0 + + schnorr + + Case Allocated GCs + sign_schnorr' (small) 27,104 0 + sign_schnorr' (large) 27,104 0 + + ecdsa + + Case Allocated GCs + sign_ecdsa' (small) 61,624 0 + sign_ecdsa' (large) 61,624 0 + + ecdh + + Case Allocated GCs + ecdh (small) 1,880 0 + ecdh (large) 1,880 0 ``` -Though we rigorously target constant-time execution, take reasonable -security precautions as appropriate. You shouldn't deploy the +Though constant-resource execution is enforced rigorously, take +reasonable security precautions as appropriate. You shouldn't deploy the implementations within in any situation where they could easily be used as an oracle to construct a [timing attack][timea], and you shouldn't give sophisticated malicious actors [access to your computer][flurl].