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 196a2bdeae3f4fbb39bf155ca9d88342afffe5d7
parent 5d1ba01983c7e005d4cb88227f6eee7932267242
Author: Jared Tobin <jared@jtobin.io>
Date:   Fri, 19 Dec 2025 10:29:42 -0330

bench: benchmarks working

Diffstat:
Mbench/Main.hs | 272+++++++++++++++++++++++++++++++++++--------------------------------------------
Mbench/Weight.hs | 17+++++++----------
Mppad-secp256k1.cabal | 6++++++
3 files changed, 133 insertions(+), 162 deletions(-)

diff --git a/bench/Main.hs b/bench/Main.hs @@ -15,7 +15,7 @@ import qualified Numeric.Montgomery.Secp256k1.Curve as C instance NFData S.Projective instance NFData S.Affine --- instance NFData S.ECDSA +instance NFData S.ECDSA instance NFData S.Context decodeLenient :: BS.ByteString -> BS.ByteString @@ -25,50 +25,40 @@ decodeLenient bs = case B16.decode bs of main :: IO () main = defaultMain [ - -- parse_point - add + parse_point + , add , mul - --, precompute + , precompute , mul_wnaf , derive_pub - --, schnorr - --, ecdsa + , schnorr + , ecdsa , ecdh ] -parse_int256 :: BS.ByteString -> Integer +parse_int256 :: BS.ByteString -> W.Wider parse_int256 bs = case S.parse_int256 bs of Nothing -> error "bang" Just v -> v --- remQ :: Benchmark --- remQ = env setup $ \x -> --- bgroup "remQ (remainder modulo _CURVE_Q)" [ --- bench "remQ 2 " $ nf S.remQ 2 --- , bench "remQ (2 ^ 255 - 19)" $ nf S.remQ x --- ] --- where --- setup = pure . parse_int256 $ decodeLenient --- "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" --- --- parse_point :: Benchmark --- parse_point = bgroup "parse_point" [ --- bench "compressed" $ nf S.parse_point p_bs --- , bench "uncompressed" $ nf S.parse_point t_bs --- , bench "bip0340" $ nf S.parse_point (BS.drop 1 p_bs) --- ] --- --- parse_integer :: Benchmark --- parse_integer = env setup $ \ ~(small, big) -> --- bgroup "parse_int256" [ --- bench "parse_int256 (small)" $ nf parse_int256 small --- , bench "parse_int256 (big)" $ nf parse_int256 big --- ] --- where --- setup = do --- let small = BS.replicate 32 0x00 --- big = BS.replicate 32 0xFF --- pure (small, big) +parse_point :: Benchmark +parse_point = bgroup "parse_point" [ + bench "compressed" $ nf S.parse_point p_bs + , bench "uncompressed" $ nf S.parse_point t_bs + , bench "bip0340" $ nf S.parse_point (BS.drop 1 p_bs) + ] + +parse_integer :: Benchmark +parse_integer = env setup $ \ ~(small, big) -> + bgroup "parse_int256" [ + bench "parse_int256 (small)" $ nf parse_int256 small + , bench "parse_int256 (big)" $ nf parse_int256 big + ] + where + setup = do + let small = BS.replicate 32 0x00 + big = BS.replicate 32 0xFF + pure (small, big) mul_fixed :: Benchmark mul_fixed = bgroup "mul_fixed" [ @@ -86,21 +76,17 @@ add = bgroup "add" [ ] mul :: Benchmark -mul = bench "mul" $ nf (S.mul p) (W.to 12831231) - - --- mul :: Benchmark --- mul = env setup $ \x -> --- bgroup "mul" [ --- bench "2 G" $ nf (S.mul S._CURVE_G) (W.to 2) --- , bench "(2 ^ 255 - 19) G" $ nf (S.mul S._CURVE_G) x --- ] --- where --- setup = pure . W.to . parse_int256 $ decodeLenient --- "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" +mul = env setup $ \x -> + bgroup "mul" [ + bench "2 G" $ nf (S.mul S._CURVE_G) 2 + , bench "(2 ^ 255 - 19) G" $ nf (S.mul S._CURVE_G) x + ] + where + setup = pure . parse_int256 $ decodeLenient + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" --- precompute :: Benchmark --- precompute = bench "precompute" $ nfIO (pure S.precompute) +precompute :: Benchmark +precompute = bench "precompute" $ nfIO (pure S.precompute) mul_wnaf :: Benchmark mul_wnaf = env setup $ \ ~(tex, x) -> @@ -118,65 +104,65 @@ mul_wnaf = env setup $ \ ~(tex, x) -> derive_pub :: Benchmark derive_pub = env setup $ \ ~(tex, x) -> bgroup "derive_pub" [ - bench "sk = 2" $ nf S.derive_pub (W.to 2) + bench "sk = 2" $ nf S.derive_pub 2 , bench "sk = 2 ^ 255 - 19" $ nf S.derive_pub x , bench "wnaf, sk = 2" $ nf (S.derive_pub' tex) 2 - , bench "wnaf, sk = 2 ^ 255 - 19" $ nf (S.derive_pub' tex) (W.from x) + , bench "wnaf, sk = 2 ^ 255 - 19" $ nf (S.derive_pub' tex) x ] where setup = do let !tex = S.precompute !int = parse_int256 $ decodeLenient "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" - pure (tex, W.to int) - --- schnorr :: Benchmark --- schnorr = env setup $ \ ~(tex, big) -> --- bgroup "schnorr" [ --- bench "sign_schnorr (small)" $ nf (S.sign_schnorr 2 s_msg) s_aux --- , bench "sign_schnorr (large)" $ nf (S.sign_schnorr big s_msg) s_aux --- , bench "sign_schnorr' (small)" $ nf (S.sign_schnorr' tex 2 s_msg) s_aux --- , bench "sign_schnorr' (large)" $ nf (S.sign_schnorr' tex big s_msg) s_aux --- , bench "verify_schnorr" $ nf (S.verify_schnorr s_msg s_pk) s_sig --- , bench "verify_schnorr'" $ nf (S.verify_schnorr' tex s_msg s_pk) s_sig --- ] --- where --- setup = do --- let !tex = S.precompute --- !int = parse_int256 $ decodeLenient --- "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" --- pure (tex, int) --- --- ecdsa :: Benchmark --- ecdsa = env setup $ \ ~(tex, big, pub, msg, sig) -> --- bgroup "ecdsa" [ --- bench "sign_ecdsa (small)" $ nf (S.sign_ecdsa 2) s_msg --- , bench "sign_ecdsa (large)" $ nf (S.sign_ecdsa big) s_msg --- , bench "sign_ecdsa' (small)" $ nf (S.sign_ecdsa' tex 2) s_msg --- , bench "sign_ecdsa' (large)" $ nf (S.sign_ecdsa' tex big) s_msg --- , bench "verify_ecdsa" $ nf (S.verify_ecdsa msg pub) sig --- , bench "verify_ecdsa'" $ nf (S.verify_ecdsa' tex msg pub) sig --- ] --- where --- setup = do --- let !tex = S.precompute --- big = parse_int256 $ decodeLenient --- "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" --- Just pub = S.derive_pub big --- msg = "i approve of this message" --- Just sig = S.sign_ecdsa big s_msg --- pure (tex, big, pub, msg, sig) + pure (tex, int) + +schnorr :: Benchmark +schnorr = env setup $ \ ~(tex, big) -> + bgroup "schnorr" [ + bench "sign_schnorr (small)" $ nf (S.sign_schnorr 2 s_msg) s_aux + , bench "sign_schnorr (large)" $ nf (S.sign_schnorr big s_msg) s_aux + , bench "sign_schnorr' (small)" $ nf (S.sign_schnorr' tex 2 s_msg) s_aux + , bench "sign_schnorr' (large)" $ nf (S.sign_schnorr' tex big s_msg) s_aux + , bench "verify_schnorr" $ nf (S.verify_schnorr s_msg s_pk) s_sig + , bench "verify_schnorr'" $ nf (S.verify_schnorr' tex s_msg s_pk) s_sig + ] + where + setup = do + let !tex = S.precompute + !int = parse_int256 $ decodeLenient + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" + pure (tex, int) + +ecdsa :: Benchmark +ecdsa = env setup $ \ ~(tex, big, pub, msg, sig) -> + bgroup "ecdsa" [ + bench "sign_ecdsa (small)" $ nf (S.sign_ecdsa 2) s_msg + , bench "sign_ecdsa (large)" $ nf (S.sign_ecdsa big) s_msg + , bench "sign_ecdsa' (small)" $ nf (S.sign_ecdsa' tex 2) s_msg + , bench "sign_ecdsa' (large)" $ nf (S.sign_ecdsa' tex big) s_msg + , bench "verify_ecdsa" $ nf (S.verify_ecdsa msg pub) sig + , bench "verify_ecdsa'" $ nf (S.verify_ecdsa' tex msg pub) sig + ] + where + setup = do + let !tex = S.precompute + big = parse_int256 $ decodeLenient + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" + Just pub = S.derive_pub big + msg = "i approve of this message" + Just sig = S.sign_ecdsa big s_msg + pure (tex, big, pub, msg, sig) ecdh :: Benchmark ecdh = env setup $ \ ~(big, pub) -> bgroup "ecdh" [ - bench "ecdh (small)" $ nf (S.ecdh pub) (W.to 2) + bench "ecdh (small)" $ nf (S.ecdh pub) 2 , bench "ecdh (large)" $ nf (S.ecdh pub) big ] where setup = do let !big = - W.to 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed !(Just !pub) = S.parse_point . decodeLenient $ "bd02b9dfc8ef760708950bd972f2dc244893b61b6b46c3b19be1b2da7b034ac5" pure (big, pub) @@ -200,32 +186,17 @@ r = S.Projective 77311080844824646227678701997218206005272179480834599837053144390237051080427 1 --- p_bs :: BS.ByteString --- p_bs = decodeLenient --- "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" --- --- p :: S.Projective --- p = case S.parse_point p_bs of --- Nothing -> error "bang" --- Just !pt -> pt --- --- q_bs :: BS.ByteString --- q_bs = decodeLenient --- "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9" --- --- q :: S.Projective --- q = case S.parse_point q_bs of --- Nothing -> error "bang" --- Just !pt -> pt --- --- r_bs :: BS.ByteString --- r_bs = decodeLenient --- "03a2113cf152585d96791a42cdd78782757fbfb5c6b2c11b59857eb4f7fda0b0e8" --- --- r :: S.Projective --- r = case S.parse_point r_bs of --- Nothing -> error "bang" --- Just !pt -> pt +p_bs :: BS.ByteString +p_bs = decodeLenient + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + +q_bs :: BS.ByteString +q_bs = decodeLenient + "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9" + +r_bs :: BS.ByteString +r_bs = decodeLenient + "03a2113cf152585d96791a42cdd78782757fbfb5c6b2c11b59857eb4f7fda0b0e8" s_bs :: BS.ByteString s_bs = decodeLenient @@ -236,38 +207,35 @@ s = case S.parse_point s_bs of Nothing -> error "bang" Just !pt -> pt --- t_bs :: BS.ByteString --- t_bs = decodeLenient "04b838ff44e5bc177bf21189d0766082fc9d843226887fc9760371100b7ee20a6ff0c9d75bfba7b31a6bca1974496eeb56de357071955d83c4b1badaa0b21832e9" --- --- t :: S.Projective --- t = case S.parse_point t_bs of --- Nothing -> error "bang" --- Just !pt -> pt - --- s_sk :: Integer --- s_sk = parse_int256 . decodeLenient $ --- "B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF" --- --- s_sig :: BS.ByteString --- s_sig = decodeLenient "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A" --- --- s_pk_raw :: BS.ByteString --- s_pk_raw = decodeLenient --- "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" --- --- s_pk :: S.Projective --- s_pk = case S.parse_point s_pk_raw of --- Nothing -> error "bang" --- Just !pt -> pt --- --- s_msg :: BS.ByteString --- s_msg = decodeLenient --- "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89" --- --- s_aux :: BS.ByteString --- s_aux = decodeLenient --- "0000000000000000000000000000000000000000000000000000000000000001" --- --- -- e_msg = decodeLenient "313233343030" --- -- e_sig = decodeLenient "3045022100813ef79ccefa9a56f7ba805f0e478584fe5f0dd5f567bc09b5123ccbc983236502206ff18a52dcc0336f7af62400a6dd9b810732baf1ff758000d6f613a556eb31ba" --- +t_bs :: BS.ByteString +t_bs = decodeLenient "04b838ff44e5bc177bf21189d0766082fc9d843226887fc9760371100b7ee20a6ff0c9d75bfba7b31a6bca1974496eeb56de357071955d83c4b1badaa0b21832e9" + +t :: S.Projective +t = case S.parse_point t_bs of + Nothing -> error "bang" + Just !pt -> pt + +s_sk :: W.Wider +s_sk = parse_int256 . decodeLenient $ + "B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF" + +s_sig :: BS.ByteString +s_sig = decodeLenient "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A" + +s_pk_raw :: BS.ByteString +s_pk_raw = decodeLenient + "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" + +s_pk :: S.Projective +s_pk = case S.parse_point s_pk_raw of + Nothing -> error "bang" + Just !pt -> pt + +s_msg :: BS.ByteString +s_msg = decodeLenient + "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89" + +s_aux :: BS.ByteString +s_aux = decodeLenient + "0000000000000000000000000000000000000000000000000000000000000001" + diff --git a/bench/Weight.hs b/bench/Weight.hs @@ -2,10 +2,13 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} +-- XX need to make sure arguments aren't allocating in the benchmarks + module Main where import qualified Data.ByteString as BS import qualified Data.ByteString.Base16 as B16 +import Data.Word.Wider (Wider(..)) import Control.DeepSeq import qualified Crypto.Curve.Secp256k1 as S import qualified Weigh as W @@ -20,12 +23,12 @@ decodeLenient bs = case B16.decode bs of Nothing -> error "bang" Just b -> b -parse_int :: BS.ByteString -> Integer +parse_int :: BS.ByteString -> Wider parse_int bs = case S.parse_int256 bs of Nothing -> error "bang" Just v -> v -big :: Integer +big :: Wider big = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed tex :: S.Context @@ -34,7 +37,6 @@ tex = S.precompute -- note that 'weigh' doesn't work properly in a repl main :: IO () main = W.mainWith $ do - remQ parse_int256 add mul @@ -45,11 +47,6 @@ main = W.mainWith $ do ecdsa ecdh -remQ :: W.Weigh () -remQ = W.wgroup "remQ" $ do - W.func "remQ 2" S.remQ 2 - W.func "remQ (2 ^ 255 - 19)" S.remQ big - parse_int256 :: W.Weigh () parse_int256 = W.wgroup "parse_int256" $ do W.func' "parse_int (small)" parse_int (BS.replicate 32 0x00) @@ -75,7 +72,7 @@ mul_unsafe = W.wgroup "mul_unsafe" $ do mul_wnaf :: W.Weigh () mul_wnaf = W.wgroup "mul_wnaf" $ do - W.value "precompute" S.precompute + W.value "precompute" S.precompute -- XX ? W.func "2 G" (S.mul_wnaf tex) 2 W.func "(2 ^ 255 - 19) G" (S.mul_wnaf tex) big @@ -117,7 +114,7 @@ ecdh = W.wgroup "ecdh" $ do Just pub = S.parse_point . decodeLenient $ "bd02b9dfc8ef760708950bd972f2dc244893b61b6b46c3b19be1b2da7b034ac5" -s_sk :: Integer +s_sk :: Wider s_sk = parse_int . decodeLenient $ "B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF" diff --git a/ppad-secp256k1.cabal b/ppad-secp256k1.cabal @@ -15,6 +15,11 @@ description: Pure BIP0340-style Schnorr signatures, deterministic RFC6979 ECDSA, and ECDH shared secret computation on the elliptic curve secp256k1. +flag llvm + description: Use GHC's LLVM backend. + default: False + manual: True + source-repository head type: git location: git.ppad.tech/secp256k1.git @@ -93,6 +98,7 @@ benchmark secp256k1-weigh , bytestring , deepseq , ppad-base16 + , ppad-fixed , ppad-secp256k1 , weigh