packages feed

musig2-0.1.0: bench/Main.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Main where

import Control.DeepSeq
import Criterion.Main
import qualified Crypto.Curve.Secp256k1 as S
import Crypto.Curve.Secp256k1.MuSig2 (
  KeyAggContext,
  PubNonce (..),
  SecKey (..),
  SecNonce (..),
  SecNonceGenParams,
  SessionContext,
  Tweak (..),
 )
import qualified Crypto.Curve.Secp256k1.MuSig2 as M
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base16 as B16

-- NFData instances for benchmarking
instance NFData S.Projective
instance NFData S.Affine
instance NFData S.ECDSA
instance NFData S.Context

instance NFData SecKey where
  rnf (SecKey i) = rnf i

instance NFData SecNonce where
  rnf (SecNonce k1 k2) = rnf k1 `seq` rnf k2

instance NFData PubNonce where
  rnf (PubNonce r1 r2) = rnf r1 `seq` rnf r2

instance NFData Tweak where
  rnf (XOnlyTweak i) = rnf i
  rnf (PlainTweak i) = rnf i

instance NFData KeyAggContext where
  rnf ctx = rnf (M.aggregatedPubkey ctx)

instance NFData SessionContext where
  rnf _ = () -- SessionContext contains multiple fields, but we'll keep it simple

instance NFData SecNonceGenParams where
  rnf _ = () -- SecNonceGenParams contains multiple fields, but we'll keep it simple

-- | Main benchmark function.
main :: IO ()
main =
  defaultMain
    [ keyAgg
    , tweakOps
    , nonceGen
    , nonceAgg
    , sessionOps
    , signing
    , verification
    ]

-- | A big scalar.
largeScalar :: Integer
largeScalar = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed

-- | Key aggregation.
keyAgg :: Benchmark
keyAgg =
  bgroup
    "key_aggregation"
    [ bench "mkKeyAggContext (2 keys)" $ nf (M.mkKeyAggContext [p, q]) Nothing
    , bench "mkKeyAggContext (3 keys)" $ nf (M.mkKeyAggContext [p, q, r]) Nothing
    , bench "mkKeyAggContext (5 keys)" $ nf (M.mkKeyAggContext [p, q, r, s, t]) Nothing
    , bench "sortPublicKeys (5 keys)" $ nf M.sortPublicKeys [p, q, r, s, t]
    , bench "aggregatedPubkey" $ nf M.aggregatedPubkey keyCtx2
    ]

-- | Tweak operations.
tweakOps :: Benchmark
tweakOps =
  bgroup
    "tweaks"
    [ bench "applyTweak (PlainTweak)" $ nf (M.applyTweak keyCtx2) plainTweak
    , bench "applyTweak (XOnlyTweak)" $ nf (M.applyTweak keyCtx2) xonlyTweak
    , bench "mkKeyAggContext with tweak" $ nf (M.mkKeyAggContext [p, q]) (Just plainTweak)
    ]

-- | Nonce generation.
nonceGen :: Benchmark
nonceGen = env setupNonce $ \ ~(params, rand) ->
  bgroup
    "nonce_generation"
    [ bench "mkSecNonce" $ nfIO M.mkSecNonce
    , bench "secNonceGen (minimal params)" $ nfIO (M.secNonceGen params)
    , bench "secNonceGenWithRand" $ nf (M.secNonceGenWithRand rand) params
    , bench "publicNonce" $ nf M.publicNonce secNonce1
    ]
 where
  setupNonce = do
    let params = M.defaultSecNonceGenParams p
    let rand = BS.replicate 32 0x42
    pure (params, rand)

-- | Nonce aggregation.
nonceAgg :: Benchmark
nonceAgg =
  bgroup
    "nonce_aggregation"
    [ bench "aggNonces (2 nonces)" $ nf M.aggNonces [pubNonce1, pubNonce2]
    , bench "aggNonces (3 nonces)" $ nf M.aggNonces [pubNonce1, pubNonce2, pubNonce3]
    , bench "aggNonces (5 nonces)" $ nf M.aggNonces [pubNonce1, pubNonce2, pubNonce3, pubNonce4, pubNonce5]
    ]

-- | Session operations.
sessionOps :: Benchmark
sessionOps = env setupSession $ \ ~(aggNonce, pks, tweaks) ->
  bgroup
    "session_context"
    [ bench "mkSessionContext (no tweaks)" $ nf (M.mkSessionContext aggNonce pks []) sMsg
    , bench "mkSessionContext (with tweaks)" $ nf (M.mkSessionContext aggNonce pks tweaks) sMsg
    ]
 where
  setupSession = do
    let !aggNonce = case M.aggNonces [pubNonce1, pubNonce2] of
          Nothing -> error "failed to aggregate nonces"
          Just n -> n
        !pks = [p, q]
        !tweaks = [plainTweak, xonlyTweak]
    pure (aggNonce, pks, tweaks)

-- | Signing benchmarks.
signing :: Benchmark
signing = env setupSigning $ \ ~(ctx, secNonce, sk) ->
  bgroup
    "signing"
    [ bench "sign (2 signers)" $ nf (M.sign secNonce sk) ctx
    ]
 where
  setupSigning = do
    let !aggNonce = case M.aggNonces [pubNonce1, pubNonce2] of
          Nothing -> error "failed to aggregate nonces"
          Just n -> n
        !ctx = M.mkSessionContext aggNonce [p, q] [] sMsg
        !secNonce = SecNonce sk1 sk2
        !sk = SecKey ssk
    pure (ctx, secNonce, sk)

-- | Verification.
verification :: Benchmark
verification = env setupVerification $ \ ~(partial, nonces, pks, ctx) ->
  bgroup
    "verification"
    [ bench "partialSigVerify" $ nf (\p -> M.partialSigVerify p nonces pks [] sMsg 0) partial
    , bench "aggPartials" $ nf (M.aggPartials [partial]) ctx
    ]
 where
  setupVerification = do
    let !aggNonce = case M.aggNonces [pubNonce1, pubNonce2] of
          Nothing -> error "failed to aggregate nonces"
          Just n -> n
        !ctx = M.mkSessionContext aggNonce [p, q] [] sMsg
        !secNonce = SecNonce sk1 sk2
        !sk = SecKey ssk
        !partial = M.sign secNonce sk ctx
        !nonces = [pubNonce1, pubNonce2]
        !pks = [p, q]
    pure (partial, nonces, pks, ctx)

-- Test data points.
pBS :: BS.ByteString
pBS =
  B16.decodeLenient
    "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"

p :: S.Projective
p = case S.parse_point pBS of
  Nothing -> error "failed to parse point p"
  Just !pt -> pt

qBS :: BS.ByteString
qBS =
  B16.decodeLenient
    "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"

q :: S.Projective
q = case S.parse_point qBS of
  Nothing -> error "failed to parse point q"
  Just !pt -> pt

rBS :: BS.ByteString
rBS =
  B16.decodeLenient
    "03a2113cf152585d96791a42cdd78782757fbfb5c6b2c11b59857eb4f7fda0b0e8"

r :: S.Projective
r = case S.parse_point rBS of
  Nothing -> error "failed to parse point r"
  Just !pt -> pt

sBS :: BS.ByteString
sBS =
  B16.decodeLenient
    "0306413898a49c93cccf3db6e9078c1b6a8e62568e4a4770e0d7d96792d1c580ad"

s :: S.Projective
s = case S.parse_point sBS of
  Nothing -> error "failed to parse point s"
  Just !pt -> pt

tBS :: BS.ByteString
tBS = B16.decodeLenient "04b838ff44e5bc177bf21189d0766082fc9d843226887fc9760371100b7ee20a6ff0c9d75bfba7b31a6bca1974496eeb56de357071955d83c4b1badaa0b21832e9"

t :: S.Projective
t = case S.parse_point tBS of
  Nothing -> error "failed to parse point t"
  Just !pt -> pt

-- Test keys and nonces
ssk :: Integer
ssk = 0xB7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF

sk1 :: Integer
sk1 = 0x508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61

sk2 :: Integer
sk2 = 0xFAA32323B08D52AE5ED0E85F09CEB3EB73B97FCA9D074924598B38DBBF966A9B

sMsg :: BS.ByteString
sMsg =
  B16.decodeLenient
    "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89"

-- Contexts and nonces for testing
keyCtx2 :: KeyAggContext
keyCtx2 = M.mkKeyAggContext [p, q] Nothing

plainTweak :: Tweak
plainTweak = PlainTweak 0x1234567890ABCDEF

xonlyTweak :: Tweak
xonlyTweak = XOnlyTweak 0xFEDCBA0987654321

secNonce1 :: SecNonce
secNonce1 = SecNonce sk1 sk2

pubNonce1 :: PubNonce
pubNonce1 = M.publicNonce secNonce1

secNonce2 :: SecNonce
secNonce2 = SecNonce (sk1 + 1) (sk2 + 1)

pubNonce2 :: PubNonce
pubNonce2 = M.publicNonce secNonce2

secNonce3 :: SecNonce
secNonce3 = SecNonce (sk1 + 2) (sk2 + 2)

pubNonce3 :: PubNonce
pubNonce3 = M.publicNonce secNonce3

secNonce4 :: SecNonce
secNonce4 = SecNonce (sk1 + 3) (sk2 + 3)

pubNonce4 :: PubNonce
pubNonce4 = M.publicNonce secNonce4

secNonce5 :: SecNonce
secNonce5 = SecNonce (sk1 + 4) (sk2 + 4)

pubNonce5 :: PubNonce
pubNonce5 = M.publicNonce secNonce5