commit 4863b35572d4694cc209ff36f8d6c27b4e6ab55b
parent 35e5fcdb70e9319a81ac9da9e7625db869702f08
Author: Jared Tobin <jared@jtobin.io>
Date: Sat, 9 Mar 2024 12:44:43 +0400
bench: flesh out benchmark suite
Current benchmarks:
benchmarking csecp256k1/ecdsa/sign
time 34.56 μs (34.20 μs .. 34.86 μs)
0.999 R² (0.999 R² .. 0.999 R²)
mean 34.37 μs (34.09 μs .. 34.67 μs)
std dev 1.014 μs (880.4 ns .. 1.230 μs)
variance introduced by outliers: 31% (moderately inflated)
benchmarking csecp256k1/ecdsa/verify
time 39.23 μs (38.71 μs .. 39.73 μs)
0.998 R² (0.998 R² .. 0.999 R²)
mean 39.28 μs (38.74 μs .. 40.04 μs)
std dev 2.042 μs (1.546 μs .. 2.583 μs)
variance introduced by outliers: 58% (severely inflated)
benchmarking csecp256k1/schnorr/sign
time 53.14 μs (51.68 μs .. 54.89 μs)
0.995 R² (0.992 R² .. 0.999 R²)
mean 52.08 μs (51.42 μs .. 52.96 μs)
std dev 2.686 μs (2.187 μs .. 3.910 μs)
variance introduced by outliers: 56% (severely inflated)
benchmarking csecp256k1/schnorr/verify
time 42.69 μs (42.01 μs .. 43.62 μs)
0.997 R² (0.996 R² .. 0.999 R²)
mean 42.83 μs (42.28 μs .. 43.47 μs)
std dev 2.025 μs (1.721 μs .. 2.439 μs)
variance introduced by outliers: 53% (severely inflated)
benchmarking csecp256k1/ecdh/ecdh
time 49.23 μs (48.04 μs .. 50.44 μs)
0.997 R² (0.996 R² .. 0.998 R²)
mean 48.31 μs (47.70 μs .. 49.01 μs)
std dev 2.425 μs (1.959 μs .. 3.020 μs)
variance introduced by outliers: 55% (severely inflated)
Worth noting that I've benchmarked a few variations here:
* replacing various Data.ByteString internals (packCString,
useAsCString, etc.) with unsafe variants,
* replacing the use of allocaBytes with
Data.ByteString.Internal.mallocByteString &
Foreign.ForeignPtr.withForeignPtr, as is done in aseipp's 'ed25519'
package, and
* using the 'ccall unsafe' FFI calling convention instead of 'capi', as
recommend by bgamari as a potential performance improvement in GHC's
'capi' docs.
None yield any measurable performance improvement.
Diffstat:
3 files changed, 86 insertions(+), 44 deletions(-)
diff --git a/bench/Main.hs b/bench/Main.hs
@@ -5,8 +5,10 @@ module Main where
import Control.DeepSeq
import Criterion.Main
import qualified Crypto.Secp256k1 as S
+import qualified Crypto.Secp256k1.Internal as SI
import qualified Data.ByteString as BS
+instance NFData S.Context
instance NFData S.KeyPair
instance NFData S.Pub
instance NFData S.Sig
@@ -14,21 +16,32 @@ instance NFData S.XOnlyPub
main :: IO ()
main = defaultMain [
- sign
+ suite
]
-sign :: Benchmark
-sign = bgroup "sign" [
- bench "sign" . nfIO $ sign_bench _HAS _SEC
- , bench "sign_schnorr" . nfIO $
- sign_schnorr_bench _HAS _SEC (BS.replicate 32 0)
- ]
+suite :: Benchmark
+suite = envWithCleanup setup destroy $ \ ~(tex, fen, pub, sig) ->
+ bgroup "csecp256k1" [
+ bgroup "ecdsa" [
+ bench "sign" . nfIO $ S.sign tex _SEC _HAS
+ , bench "verify" . nfIO $ S.verify tex pub _HAS sig
+ ]
+ , bgroup "schnorr" [
+ bench "sign" . nfIO $ S.sign_schnorr tex _HAS _SEC fen
+ , bench "verify" . nfIO $ S.verify_schnorr tex pub _HAS _SIG_SCHNORR
+ ]
+ , bgroup "ecdh" [
+ bench "ecdh" . nfIO $ S.ecdh tex pub _SEC
+ ]
+ ]
where
- sign_bench has sec = S.wcontext $ \tex ->
- S.sign tex sec has
+ setup = do
+ ptr <- SI.secp256k1_context_create SI._SECP256K1_CONTEXT_NONE
+ pub <- S.wcontext $ \tex -> S.parse_pub tex _PUB_COMPRESSED
+ sig <- S.wcontext $ \tex -> S.parse_der tex _DER
+ pure (S.Context ptr, BS.replicate 32 0, pub, sig)
- sign_schnorr_bench has sec enn = S.wcontext $ \tex ->
- S.sign_schnorr tex has sec enn
+ destroy (S.Context tex, _, _, _) = SI.secp256k1_context_destroy tex
-- inputs
@@ -46,3 +59,26 @@ _SEC = mconcat [
, "+\ETX\FS\230\147>\ETX\154"
]
+-- 33-byte (compressed) public key
+_PUB_COMPRESSED :: BS.ByteString
+_PUB_COMPRESSED = mconcat [
+ "\ETX\221\237B\ETX\218\201j~\133\242\195t\163|\227\233\201\161U"
+ , "\167+d\180U\ESC\v\254w\157\212G\ENQ"
+ ]
+
+-- DER-encoded signature
+_DER :: BS.ByteString
+_DER = mconcat [
+ "0E\STX!\NUL\245\STX\191\160z\244>~\242ea\139\r\146\154v\EM\238\SOH\214"
+ , "\NAK\SO7\235n\170\242\200\189\&7\251\"\STX o\EOT\NAK\171\SO\154\151z"
+ , "\253x\178\194n\243\155\&9R\tm1\159\212\177\SOH\199h\173l\DC3.0E"
+ ]
+
+-- 64-byte schnorr signature
+_SIG_SCHNORR :: BS.ByteString
+_SIG_SCHNORR = mconcat [
+ "\214\185AtJ\189\250Gp\NAK2\221\DC2[\182\209\192j{\140^\222R\NUL~"
+ , "\139d@<\138\163rh\247\152\r\228\175\236\219\156\151\214~\135\&7"
+ , "\225\&6\234\220;\164R\191\170\186\243\NAK\147\f\144\156ez"
+ ]
+
diff --git a/lib/Crypto/Secp256k1.hs b/lib/Crypto/Secp256k1.hs
@@ -17,7 +17,7 @@
-- supporting ECDSA/Schnorr signatures and ECDH secret computation.
module Crypto.Secp256k1 (
- Context
+ Context(..)
, wcontext
, wrcontext
@@ -74,7 +74,12 @@ import qualified Foreign.Storable as S (poke, peek)
--
-- You should create and use values of this type via 'wrcontext' or
-- 'wcontext'.
+--
+-- The data constructor is exported only to make the implementation
+-- easier to benchmark. You should /not/ pattern match on or
+-- manipulate context values.
newtype Context = Context (Ptr I.Context)
+ deriving stock Generic
instance Show Context where
show (Context tex) = "<bitcoin-core/secp256k1 context " <> show tex <> ">"
diff --git a/ppad-csecp256k1.cabal b/ppad-csecp256k1.cabal
@@ -33,6 +33,39 @@ library
, bytestring
, secp256k1-sys
+test-suite csecp256k1-tests
+ type: exitcode-stdio-1.0
+ default-language: Haskell2010
+ hs-source-dirs: test
+ main-is: Main.hs
+
+ ghc-options:
+ -rtsopts -Wall
+
+ build-depends:
+ base
+ , bytestring
+ , ppad-csecp256k1
+ , tasty
+ , tasty-hunit
+
+benchmark csecp256k1-bench
+ type: exitcode-stdio-1.0
+ default-language: Haskell2010
+ hs-source-dirs: bench
+ main-is: Main.hs
+
+ ghc-options:
+ -rtsopts -O2 -Wall -fno-warn-orphans
+
+ build-depends:
+ base
+ , bytestring
+ , criterion
+ , deepseq
+ , ppad-csecp256k1
+ , secp256k1-sys
+
library secp256k1-sys
default-language: Haskell2010
hs-source-dirs: secp256k1-sys/lib
@@ -83,35 +116,3 @@ test-suite secp256k1-sys-tests
, tasty
, tasty-hunit
-test-suite csecp256k1-tests
- type: exitcode-stdio-1.0
- default-language: Haskell2010
- hs-source-dirs: test
- main-is: Main.hs
-
- ghc-options:
- -rtsopts -Wall
-
- build-depends:
- base
- , bytestring
- , ppad-csecp256k1
- , tasty
- , tasty-hunit
-
-benchmark csecp256k1-bench
- type: exitcode-stdio-1.0
- default-language: Haskell2010
- hs-source-dirs: bench
- main-is: Main.hs
-
- ghc-options:
- -rtsopts -O2 -Wall -fno-warn-orphans
-
- build-depends:
- base
- , bytestring
- , criterion
- , deepseq
- , ppad-csecp256k1
-