packages feed

ppad-secp256k1 0.1.0 → 0.2.0

raw patch · 8 files changed

+409/−80 lines, 8 filesdep +primitivePVP ok

version bump matches the API change (PVP)

Dependencies added: primitive

API changes (from Hackage documentation)

+ Crypto.Curve.Secp256k1: Context :: {-# UNPACK #-} !Int -> !Array Projective -> Context
+ Crypto.Curve.Secp256k1: [ctxArray] :: Context -> !Array Projective
+ Crypto.Curve.Secp256k1: [ctxW] :: Context -> {-# UNPACK #-} !Int
+ Crypto.Curve.Secp256k1: data Context
+ Crypto.Curve.Secp256k1: instance GHC.Classes.Eq Crypto.Curve.Secp256k1.Context
+ Crypto.Curve.Secp256k1: instance GHC.Generics.Generic Crypto.Curve.Secp256k1.Context
+ Crypto.Curve.Secp256k1: instance GHC.Show.Show Crypto.Curve.Secp256k1.Context
+ Crypto.Curve.Secp256k1: parse_sig :: ByteString -> Maybe ECDSA
+ Crypto.Curve.Secp256k1: precompute :: Context
+ Crypto.Curve.Secp256k1: sign_ecdsa' :: Context -> Integer -> ByteString -> ECDSA
+ Crypto.Curve.Secp256k1: sign_ecdsa_unrestricted' :: Context -> Integer -> ByteString -> ECDSA
+ Crypto.Curve.Secp256k1: sign_schnorr' :: Context -> Integer -> ByteString -> ByteString -> ByteString
+ Crypto.Curve.Secp256k1: verify_ecdsa' :: Context -> ByteString -> Pub -> ECDSA -> Bool
+ Crypto.Curve.Secp256k1: verify_ecdsa_unrestricted' :: Context -> ByteString -> Pub -> ECDSA -> Bool
+ Crypto.Curve.Secp256k1: verify_schnorr' :: Context -> ByteString -> Pub -> ByteString -> Bool

Files

CHANGELOG view
@@ -1,5 +1,23 @@ # Changelog +- 0.2.0 (2024-11-08)+  * Adds wNAF ("w-ary non-adjacent form") scalar multiplication, as+    well as fast 'sign' and 'verify' variants for Schnorr and ECDSA+    (disambiguated by a trailing apostrophe) that make use of it.++    Each wNAF-powered function requires a 'Context' consisting of+    precomputed secp256k1 generator multiples; one can be gotten via+    'precompute'.++    Note that the non-wNAF-powered sign and verify functions have+    incurred a slight performance decrease (on the order of tens to+    hundreds of microseconds) as a result.++  * Adds 'parse_sig' for parsing compact signatures.++  * Adds a dependency on the 'primitive' library (already transitively required+    via 'ppad-hmac-drbg').+ - 0.1.0 (2024-10-19)   * Initial release, supporting public key derivation and Schnorr &     ECDSA signatures on secp256k1.
bench/Main.hs view
@@ -13,6 +13,7 @@ instance NFData S.Projective instance NFData S.Affine instance NFData S.ECDSA+instance NFData S.Context instance NFData S.Word256  main :: IO ()@@ -20,6 +21,8 @@     parse_point   , add   , mul+  , precompute+  , mul_wnaf   , derive_pub   , schnorr   , ecdsa@@ -73,6 +76,22 @@     setup = pure . S.parse_int256 $ B16.decodeLenient       "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" +precompute :: Benchmark+precompute = bench "precompute" $ nfIO (pure S.precompute)++mul_wnaf :: Benchmark+mul_wnaf = env setup $ \ ~(tex, x) ->+    bgroup "mul_wnaf" [+      bench "2 G" $ nf (S.mul_wnaf tex) 2+    , bench "(2 ^ 255 - 19) G" $ nf (S.mul_wnaf tex) x+    ]+  where+    setup = do+      let !tex = S.precompute+          !int = S.parse_int256 $ B16.decodeLenient+            "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed"+      pure (tex, int)+ derive_pub :: Benchmark derive_pub = env setup $ \x ->     bgroup "derive_pub" [@@ -84,31 +103,41 @@       "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed"  schnorr :: Benchmark-schnorr = env setup $ \big ->+schnorr = env setup $ \ ~(tex, big) ->     bgroup "schnorr" [-      bench "sign_schnorr (small secret)" $ nf (S.sign_schnorr 2 s_msg) s_aux-    , bench "sign_schnorr (large secret)" $ nf (S.sign_schnorr big s_msg) s_aux+      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 = pure . S.parse_int256 $ B16.decodeLenient-      "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed"+    setup = do+      let !tex = S.precompute+          !int = S.parse_int256 $ B16.decodeLenient+            "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed"+      pure (tex, int)  ecdsa :: Benchmark-ecdsa = env setup $ \ ~(big, pub, msg, sig) ->+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 big = S.parse_int256 $ B16.decodeLenient+      let !tex = S.precompute+          big = S.parse_int256 $ B16.decodeLenient             "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed"           pub = S.derive_pub big           msg = "i approve of this message"           sig = S.sign_ecdsa big s_msg-      pure (big, pub, msg, sig)+      pure (tex, big, pub, msg, sig)  p_bs :: BS.ByteString p_bs = B16.decodeLenient
lib/Crypto/Curve/Secp256k1.hs view
@@ -4,6 +4,7 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE UnboxedSums #-} {-# LANGUAGE ViewPatterns #-} @@ -27,18 +28,25 @@   -- * Parsing   , parse_int256   , parse_point+  , parse_sig    -- * BIP0340 Schnorr signatures   , sign_schnorr+  , sign_schnorr'   , verify_schnorr+  , verify_schnorr'    -- * RFC6979 ECDSA   , ECDSA(..)   , SigType(..)   , sign_ecdsa+  , sign_ecdsa'   , sign_ecdsa_unrestricted+  , sign_ecdsa_unrestricted'   , verify_ecdsa+  , verify_ecdsa'   , verify_ecdsa_unrestricted+  , verify_ecdsa_unrestricted'    -- Elliptic curve group operations   , neg@@ -47,6 +55,10 @@   , mul   , mul_unsafe +  , Context(..)+  , precompute+  , mul_wnaf+   -- Coordinate systems and transformations   , Affine(..)   , Projective(..)@@ -57,6 +69,7 @@   -- for testing/benchmarking   , Word256(..)   , _sign_ecdsa_no_hash+  , _sign_ecdsa_no_hash'   , _CURVE_P   , _CURVE_Q   , _CURVE_G@@ -72,6 +85,7 @@ import qualified Data.Bits as B import qualified Data.ByteString as BS import qualified Data.ByteString.Unsafe as BU+import qualified Data.Primitive.Array as A import Data.STRef import Data.Word (Word8, Word64) import GHC.Generics@@ -342,26 +356,26 @@ -- For a, return x such that a = x x mod _CURVE_P. modsqrtP :: Integer -> Maybe Integer modsqrtP n = runST $ do-    r   <- newSTRef 1-    num <- newSTRef n-    e   <- newSTRef ((_CURVE_P + 1) `I.integerQuot` 4)+  r   <- newSTRef 1+  num <- newSTRef n+  e   <- newSTRef ((_CURVE_P + 1) `I.integerQuot` 4) -    let loop = do-          ev <- readSTRef e-          when (ev > 0) $ do-            when (I.integerTestBit ev 0) $ do-              numv <- readSTRef num-              modifySTRef' r (\rv -> (rv * numv) `I.integerRem` _CURVE_P)-            modifySTRef' num (\numv -> (numv * numv) `I.integerRem` _CURVE_P)-            modifySTRef' e (`I.integerShiftR` 1)-            loop+  let loop = do+        ev <- readSTRef e+        when (ev > 0) $ do+          when (I.integerTestBit ev 0) $ do+            numv <- readSTRef num+            modifySTRef' r (\rv -> (rv * numv) `I.integerRem` _CURVE_P)+          modifySTRef' num (\numv -> (numv * numv) `I.integerRem` _CURVE_P)+          modifySTRef' e (`I.integerShiftR` 1)+          loop -    loop-    rv  <- readSTRef r-    pure $-      if   remP (rv * rv) == n-      then Just $! rv-      else Nothing+  loop+  rv  <- readSTRef r+  pure $+    if   remP (rv * rv) == n+    then Just $! rv+    else Nothing  -- ec point operations -------------------------------------------------------- @@ -560,7 +574,7 @@           in  if   I.integerTestBit m 0               then loop (succ j) (add acc d) f nd nm               else loop (succ j) acc (add f d) nd nm-{-# NOINLINE mul #-}+{-# INLINE mul #-}  -- Timing-unsafe scalar multiplication of secp256k1 points. --@@ -580,6 +594,90 @@               nr = if I.integerTestBit m 0 then add r d else r           in  loop nr nd nm +-- | Precomputed multiples of the secp256k1 base or generator point.+data Context = Context {+    ctxW     :: {-# UNPACK #-} !Int+  , ctxArray :: !(A.Array Projective)+  } deriving (Eq, Generic)++instance Show Context where+  show Context {} = "<secp256k1 context>"++-- | Create a secp256k1 context by precomputing multiples of the curve's+--   generator point.+--+--   This should be used once to create a 'Context' to be reused+--   repeatedly afterwards.+--+--   >>> let !tex = precompute+--   >>> sign_ecdsa' tex sec msg+--   >>> sign_schnorr' tex sec msg aux+precompute :: Context+precompute = _precompute 8++-- dumb strict pair+data Pair a b = Pair !a !b++-- translation of noble-secp256k1's 'precompute'+_precompute :: Int -> Context+_precompute ctxW = Context {..} where+  ctxArray = A.arrayFromListN size (loop_w mempty _CURVE_G 0)+  capJ = (2 :: Int) ^ (ctxW - 1)+  ws = 256 `quot` ctxW + 1+  size = ws * capJ++  loop_w !acc !p !w+    | w == ws = reverse acc+    | otherwise =+        let b = p+            !(Pair nacc nb) = loop_j p (b : acc) b 1+            np = double nb+        in  loop_w nacc np (succ w)++  loop_j !p !acc !b !j+    | j == capJ = Pair acc b+    | otherwise =+        let nb = add b p+        in  loop_j p (nb : acc) nb (succ j)++-- Timing-safe wNAF (w-ary non-adjacent form) scalar multiplication of+-- secp256k1 points.+mul_wnaf :: Context -> Integer -> Projective+mul_wnaf Context {..} _SECRET =+    loop 0 _ZERO _CURVE_G _SECRET+  where+    wins = 256 `quot` ctxW + 1+    wsize = 2 ^ (ctxW - 1)+    mask = 2 ^ ctxW - 1+    mnum = 2 ^ ctxW++    loop !w !acc !f !n+      | w == wins = acc+      | otherwise =+          let !off0 = w * fi wsize++              !b0 = n `I.integerAnd` mask+              !n0 = n `I.integerShiftR` fi ctxW++              !(Pair b1 n1) | b0 > wsize = Pair (b0 - mnum) (n0 + 1)+                            | otherwise  = Pair b0 n0++              !c0 = B.testBit w 0+              !c1 = b1 < 0++              !off1 = off0 + fi (abs b1) - 1++          in  if   b1 == 0+              then let !pr = A.indexArray ctxArray off0+                       !pt | c0 = neg pr+                           | otherwise = pr+                   in  loop (w + 1) acc (add f pt) n1+              else let !pr = A.indexArray ctxArray off1+                       !pt | c1 = neg pr+                           | otherwise = pr+                   in  loop (w + 1) (add acc pt) f n1+{-# INLINE mul_wnaf #-}+ -- | Derive a public key (i.e., a secp256k1 point) from the provided --   secret. --@@ -659,6 +757,17 @@           then Just $! p           else Nothing +-- | Parse an ECDSA signature encoded in 64-byte "compact" form.+--+--   >>> parse_sig <64-byte compact signature>+--   "<ecdsa signature>"+parse_sig :: BS.ByteString -> Maybe ECDSA+parse_sig bs+  | BS.length bs /= 64 = Nothing+  | otherwise = pure $+      let (roll -> r, roll -> s) = BS.splitAt 32 bs+      in  ECDSA r s+ -- schnorr -------------------------------------------------------------------- -- see https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki @@ -682,10 +791,37 @@   -> BS.ByteString  -- ^ message   -> BS.ByteString  -- ^ 32 bytes of auxilliary random data   -> BS.ByteString  -- ^ 64-byte Schnorr signature-sign_schnorr _SECRET m a+sign_schnorr = _sign_schnorr (mul _CURVE_G)++-- | The same as 'sign_schnorr', except uses a 'Context' to optimise+--   internal calculations.+--+--   You can expect about a 2x performance increase when using this+--   function, compared to 'sign_schnorr'.+--+--   >>> import qualified System.Entropy as E+--   >>> aux <- E.getEntropy 32+--   >>> let !tex = precompute+--   >>> sign_schnorr' tex sec msg aux+--   "<64-byte schnorr signature>"+sign_schnorr'+  :: Context        -- ^ secp256k1 context+  -> Integer        -- ^ secret key+  -> BS.ByteString  -- ^ message+  -> BS.ByteString  -- ^ 32 bytes of auxilliary random data+  -> BS.ByteString  -- ^ 64-byte Schnorr signature+sign_schnorr' tex = _sign_schnorr (mul_wnaf tex)++_sign_schnorr+  :: (Integer -> Projective)  -- partially-applied multiplication function+  -> Integer                  -- secret key+  -> BS.ByteString            -- message+  -> BS.ByteString            -- 32 bytes of auxilliary random data+  -> BS.ByteString+_sign_schnorr _mul _SECRET m a   | not (ge _SECRET) = error "ppad-secp256k1 (sign_schnorr): invalid secret key"   | otherwise  =-      let p_proj = mul _CURVE_G _SECRET+      let p_proj = _mul _SECRET           Affine x_p y_p = affine p_proj           d | I.integerTestBit y_p 0 = _CURVE_Q - _SECRET             | otherwise = _SECRET@@ -702,7 +838,7 @@       in  if   k' == 0 -- negligible probability           then error "ppad-secp256k1 (sign_schnorr): invalid k"           else-            let Affine x_r y_r = affine (mul _CURVE_G k')+            let Affine x_r y_r = affine (_mul k')                 k | I.integerTestBit y_r 0 = _CURVE_Q - k'                   | otherwise = k' @@ -717,6 +853,7 @@             in  if   verify_schnorr m p_proj sig                 then sig                 else error "ppad-secp256k1 (sign_schnorr): invalid signature"+{-# INLINE _sign_schnorr #-}  -- | Verify a 64-byte Schnorr signature for the provided message with --   the supplied public key.@@ -730,7 +867,34 @@   -> Pub            -- ^ public key   -> BS.ByteString  -- ^ 64-byte Schnorr signature   -> Bool-verify_schnorr m (affine -> Affine x_p _) sig+verify_schnorr = _verify_schnorr (mul_unsafe _CURVE_G)++-- | The same as 'verify_schnorr', except uses a 'Context' to optimise+--   internal calculations.+--+--   You can expect about a 1.5x performance increase when using this+--   function, compared to 'verify_schnorr'.+--+--   >>> let !tex = precompute+--   >>> verify_schnorr' tex msg pub <valid signature>+--   True+--   >>> verify_schnorr' tex msg pub <invalid signature>+--   False+verify_schnorr'+  :: Context        -- ^ secp256k1 context+  -> BS.ByteString  -- ^ message+  -> Pub            -- ^ public key+  -> BS.ByteString  -- ^ 64-byte Schnorr signature+  -> Bool+verify_schnorr' tex = _verify_schnorr (mul_wnaf tex)++_verify_schnorr+  :: (Integer -> Projective) -- partially-applied multiplication function+  -> BS.ByteString+  -> Pub+  -> BS.ByteString+  -> Bool+_verify_schnorr _mul m (affine -> Affine x_p _) sig   | BS.length sig /= 64 = False   | otherwise = case lift x_p of       Nothing -> False@@ -740,12 +904,13 @@             then False             else let e = modQ . roll32 $ hash_tagged "BIP0340/challenge"                            (unroll32 r <> unroll32 x_P <> m)-                     dif = add (mul_unsafe _CURVE_G s)+                     dif = add (_mul s)                                (neg (mul_unsafe (projective capP) e))                  in  if   dif == _ZERO                      then False                      else let Affine x_R y_R = affine dif                           in  not (I.integerTestBit y_R 0 || x_R /= r)+{-# INLINE _verify_schnorr #-}  -- ecdsa ---------------------------------------------------------------------- -- see https://www.rfc-editor.org/rfc/rfc6979, https://secg.org/sec1-v2.pdf@@ -810,8 +975,24 @@   :: Integer         -- ^ secret key   -> BS.ByteString   -- ^ message   -> ECDSA-sign_ecdsa = _sign_ecdsa LowS Hash+sign_ecdsa = _sign_ecdsa (mul _CURVE_G) LowS Hash +-- | The same as 'sign_ecdsa', except uses a 'Context' to optimise internal+--   calculations.+--+--   You can expect about a 10x performance increase when using this+--   function, compared to 'sign_ecdsa'.+--+--   >>> let !tex = precompute+--   >>> sign_ecdsa' tex sec msg+--   "<ecdsa signature>"+sign_ecdsa'+  :: Context         -- ^ secp256k1 context+  -> Integer         -- ^ secret key+  -> BS.ByteString   -- ^ message+  -> ECDSA+sign_ecdsa' tex = _sign_ecdsa (mul_wnaf tex) LowS Hash+ -- | Produce an ECDSA signature for the provided message, using the --   provided private key. --@@ -826,8 +1007,24 @@   :: Integer        -- ^ secret key   -> BS.ByteString  -- ^ message   -> ECDSA-sign_ecdsa_unrestricted = _sign_ecdsa Unrestricted Hash+sign_ecdsa_unrestricted = _sign_ecdsa (mul _CURVE_G) Unrestricted Hash +-- | The same as 'sign_ecdsa_unrestricted', except uses a 'Context' to+--   optimise internal calculations.+--+--   You can expect about a 10x performance increase when using this+--   function, compared to 'sign_ecdsa_unrestricted'.+--+--   >>> let !tex = precompute+--   >>> sign_ecdsa_unrestricted' tex sec msg+--   "<ecdsa signature>"+sign_ecdsa_unrestricted'+  :: Context        -- ^ secp256k1 context+  -> Integer        -- ^ secret key+  -> BS.ByteString  -- ^ message+  -> ECDSA+sign_ecdsa_unrestricted' tex = _sign_ecdsa (mul_wnaf tex) Unrestricted Hash+ -- Produce a "low-s" ECDSA signature for the provided message, using -- the provided private key. Assumes that the message has already been -- pre-hashed.@@ -838,14 +1035,27 @@   :: Integer        -- ^ secret key   -> BS.ByteString  -- ^ message digest   -> ECDSA-_sign_ecdsa_no_hash = _sign_ecdsa LowS NoHash+_sign_ecdsa_no_hash = _sign_ecdsa (mul _CURVE_G) LowS NoHash -_sign_ecdsa :: SigType -> HashFlag -> Integer -> BS.ByteString -> ECDSA-_sign_ecdsa ty hf _SECRET m+_sign_ecdsa_no_hash'+  :: Context+  -> Integer+  -> BS.ByteString+  -> ECDSA+_sign_ecdsa_no_hash' tex = _sign_ecdsa (mul_wnaf tex) LowS NoHash++_sign_ecdsa+  :: (Integer -> Projective) -- partially-applied multiplication function+  -> SigType+  -> HashFlag+  -> Integer+  -> BS.ByteString+  -> ECDSA+_sign_ecdsa _mul ty hf _SECRET m   | not (ge _SECRET) = error "ppad-secp256k1 (sign_ecdsa): invalid secret key"   | otherwise  = runST $ do       -- RFC6979 sec 3.3a-      let entropy = int2octets _SECRET -- XX timing concern+      let entropy = int2octets _SECRET           nonce   = bits2octets h       drbg <- DRBG.new SHA256.hmac entropy nonce mempty       -- RFC6979 sec 2.4@@ -859,7 +1069,7 @@        sign_loop g = do         k <- gen_k g-        let kg = mul _CURVE_G k+        let kg = _mul k             Affine (modQ -> r) _ = affine kg             s = case modinv k (fi _CURVE_Q) of               Nothing   -> error "ppad-secp256k1 (sign_ecdsa): bad k value"@@ -870,6 +1080,7 @@              in  case ty of                    Unrestricted -> pure sig                    LowS -> pure (low sig)+{-# INLINE _sign_ecdsa #-}  -- RFC6979 sec 3.3b gen_k :: DRBG.DRBG s -> ST s Integer@@ -909,6 +1120,27 @@   | s > B.unsafeShiftR _CURVE_Q 1 = False   | otherwise = verify_ecdsa_unrestricted m p sig +-- | The same as 'verify_ecdsa', except uses a 'Context' to optimise+--   internal calculations.+--+--   You can expect about a 2x performance increase when using this+--   function, compared to 'verify_ecdsa'.+--+--   let !tex = precompute+--   >>> verify_ecdsa' tex msg pub valid_sig+--   True+--   >>> verify_ecdsa' tex msg pub invalid_sig+--   False+verify_ecdsa'+  :: Context       -- ^ secp256k1 context+  -> BS.ByteString -- ^ message+  -> Pub           -- ^ public key+  -> ECDSA         -- ^ signature+  -> Bool+verify_ecdsa' tex m p sig@(ECDSA _ s)+  | s > B.unsafeShiftR _CURVE_Q 1 = False+  | otherwise = verify_ecdsa_unrestricted' tex m p sig+ -- | Verify an unrestricted ECDSA signature for the provided message and --   public key. --@@ -921,7 +1153,34 @@   -> Pub           -- ^ public key   -> ECDSA         -- ^ signature   -> Bool-verify_ecdsa_unrestricted (SHA256.hash -> h) p (ECDSA r s)+verify_ecdsa_unrestricted = _verify_ecdsa_unrestricted (mul_unsafe _CURVE_G)++-- | The same as 'verify_ecdsa_unrestricted', except uses a 'Context' to+--   optimise internal calculations.+--+--   You can expect about a 2x performance increase when using this+--   function, compared to 'verify_ecdsa_unrestricted'.+--+--   let !tex = precompute+--   >>> verify_ecdsa_unrestricted' tex msg pub valid_sig+--   True+--   >>> verify_ecdsa_unrestricted' tex msg pub invalid_sig+--   False+verify_ecdsa_unrestricted'+  :: Context       -- ^ secp256k1 context+  -> BS.ByteString -- ^ message+  -> Pub           -- ^ public key+  -> ECDSA         -- ^ signature+  -> Bool+verify_ecdsa_unrestricted' tex = _verify_ecdsa_unrestricted (mul_wnaf tex)++_verify_ecdsa_unrestricted+  :: (Integer -> Projective) -- partially-applied multiplication function+  -> BS.ByteString+  -> Pub+  -> ECDSA+  -> Bool+_verify_ecdsa_unrestricted _mul (SHA256.hash -> h) p (ECDSA r s)   -- SEC1-v2 4.1.4   | not (ge r) || not (ge s) = False   | otherwise =@@ -933,9 +1192,10 @@             Just si -> si           u1   = remQ (e * s_inv)           u2   = remQ (r * s_inv)-          capR = add (mul_unsafe _CURVE_G u1) (mul_unsafe p u2)+          capR = add (_mul u1) (mul_unsafe p u2)       in  if   capR == _ZERO           then False           else let Affine (modQ -> v) _ = affine capR                in  v == r+{-# INLINE _verify_ecdsa_unrestricted #-} 
ppad-secp256k1.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               ppad-secp256k1-version:            0.1.0+version:            0.2.0 synopsis:           Schnorr signatures & ECDSA on the elliptic curve secp256k1 license:            MIT license-file:       LICENSE@@ -30,6 +30,7 @@     , bytestring >= 0.9 && < 0.13     , ppad-hmac-drbg >= 0.1 && < 0.2     , ppad-sha256 >= 0.2 && < 0.3+    , primitive >= 0.8 && < 0.10  test-suite secp256k1-tests   type:                exitcode-stdio-1.0
test/BIP340.hs view
@@ -37,26 +37,38 @@   , c_comment :: !BS.ByteString   } deriving Show -execute :: Case -> TestTree-execute Case {..} = testCase ("bip0340 " <> show c_index) $+execute :: Context -> Case -> TestTree+execute tex Case {..} = testCase ("bip0340 " <> show c_index) $   case parse_point (B16.decodeLenient c_pk) of     Nothing -> assertBool mempty (not c_res)     Just pk -> do       if   c_sk == mempty       then do -- no signature; test verification-        let ver = verify_schnorr c_msg pk c_sig+        let ver  = verify_schnorr c_msg pk c_sig+            ver' = verify_schnorr' tex c_msg pk c_sig         if   c_res-        then assertBool mempty ver-        else assertBool mempty (not ver)+        then do+          assertBool mempty ver+          assertBool mempty ver'+        else do+          assertBool mempty (not ver)+          assertBool mempty (not ver')       -- XX test pubkey derivation from sk       else do -- signature present; test sig too         let sk = roll c_sk-            sig = sign_schnorr sk c_msg c_aux-            ver = verify_schnorr c_msg pk sig+            sig  = sign_schnorr sk c_msg c_aux+            sig' = sign_schnorr' tex sk c_msg c_aux+            ver  = verify_schnorr c_msg pk sig+            ver' = verify_schnorr' tex c_msg pk sig         assertEqual mempty c_sig sig+        assertEqual mempty c_sig sig'         if   c_res-        then assertBool mempty ver-        else assertBool mempty (not ver)+        then do+          assertBool mempty ver+          assertBool mempty ver'+        else do+          assertBool mempty (not ver)+          assertBool mempty (not ver')  header :: AT.Parser () header = do
test/Main.hs view
@@ -27,7 +27,8 @@     "etc/ecdsa_secp256k1_sha256_bitcoin_test.json"   noble_ecdsa <- TIO.readFile "etc/noble_ecdsa.json"   bip340 <- BS.readFile "etc/bip-0340-test-vectors.csv"-  let quar = do+  let !tex = precompute+      quar = do         wp0 <- A.decodeStrictText wp_ecdsa_sha256 :: Maybe W.Wycheproof         wp1 <- A.decodeStrictText wp_ecdsa_sha256_bitcoin :: Maybe W.Wycheproof         nob <- A.decodeStrictText noble_ecdsa :: Maybe N.Ecdsa@@ -39,16 +40,17 @@     Nothing -> error "couldn't parse wycheproof vectors"     Just (w0, w1, no, ip) -> defaultMain $ testGroup "ppad-secp256k1" [         units-      , wycheproof_ecdsa_verify_tests "(ecdsa, sha256)" Unrestricted w0-      , wycheproof_ecdsa_verify_tests "(ecdsa, sha256, low-s)" LowS w1-      , N.execute_ecdsa no-      , testGroup "bip0340 vectors (schnorr)" (fmap BIP340.execute ip)+      , wycheproof_ecdsa_verify_tests tex "(ecdsa, sha256)" Unrestricted w0+      , wycheproof_ecdsa_verify_tests tex "(ecdsa, sha256, low-s)" LowS w1+      , N.execute_ecdsa tex no+      , testGroup "bip0340 vectors (schnorr)" (fmap (BIP340.execute tex) ip)       ] -wycheproof_ecdsa_verify_tests :: String -> SigType -> W.Wycheproof -> TestTree-wycheproof_ecdsa_verify_tests msg ty W.Wycheproof {..} =+wycheproof_ecdsa_verify_tests+  :: Context -> String -> SigType -> W.Wycheproof -> TestTree+wycheproof_ecdsa_verify_tests tex msg ty W.Wycheproof {..} =   testGroup ("wycheproof vectors " <> msg) $-    fmap (W.execute_group ty) wp_testGroups+    fmap (W.execute_group tex ty) wp_testGroups  units :: TestTree units = testGroup "unit tests" [
test/Noble.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ViewPatterns #-}@@ -24,46 +25,51 @@   , ec_invalid :: !InvalidTest   } deriving Show -execute_ecdsa :: Ecdsa -> TestTree-execute_ecdsa Ecdsa {..} = testGroup "noble_ecdsa" [-      testGroup "valid" (fmap execute_valid ec_valid)-    , testGroup "invalid (sign)" (fmap execute_invalid_sign iv_sign)-    , testGroup "invalid (verify)" (fmap execute_invalid_verify iv_verify)+execute_ecdsa :: Context -> Ecdsa -> TestTree+execute_ecdsa tex Ecdsa {..} = testGroup "noble_ecdsa" [+      testGroup "valid" (fmap (execute_valid tex) ec_valid)+    , testGroup "invalid (sign)" (fmap (execute_invalid_sign tex) iv_sign)+    , testGroup "invalid (verify)" (fmap (execute_invalid_verify tex) iv_verify)     ]   where     InvalidTest {..} = ec_invalid -execute_valid :: (Int, ValidTest) -> TestTree-execute_valid (label, ValidTest {..}) =+execute_valid :: Context -> (Int, ValidTest) -> TestTree+execute_valid tex (label, ValidTest {..}) =   testCase ("noble-secp256k1, valid (" <> show label <> ")") $ do     let msg = vt_m         x   = vt_d         pec = parse_compact vt_signature         sig = _sign_ecdsa_no_hash x msg+        sig' = _sign_ecdsa_no_hash' tex x msg+    assertEqual mempty sig sig'     assertEqual mempty pec sig -execute_invalid_sign :: (Int, InvalidSignTest) -> TestTree-execute_invalid_sign (label, InvalidSignTest {..}) =+execute_invalid_sign :: Context -> (Int, InvalidSignTest) -> TestTree+execute_invalid_sign tex (label, InvalidSignTest {..}) =     testCase ("noble-secp256k1, invalid sign (" <> show label <> ")") $ do       let x   = ivs_d           m   = ivs_m       err <- catch (pure (_sign_ecdsa_no_hash x m) >> pure False) handler-      if   err+      err' <- catch (pure (_sign_ecdsa_no_hash' tex x m) >> pure False) handler+      if   err || err'       then assertFailure "expected error not caught"       else pure ()   where     handler :: ErrorCall -> IO Bool     handler _ = pure True -execute_invalid_verify :: (Int, InvalidVerifyTest) -> TestTree-execute_invalid_verify (label, InvalidVerifyTest {..}) =+execute_invalid_verify :: Context -> (Int, InvalidVerifyTest) -> TestTree+execute_invalid_verify tex (label, InvalidVerifyTest {..}) =   testCase ("noble-secp256k1, invalid verify (" <> show label <> ")") $     case parse_point (B16.decodeLenient ivv_Q) of       Nothing -> assertBool "no parse" True       Just pub -> do         let sig = parse_compact ivv_signature             ver = verify_ecdsa ivv_m pub sig+            ver' = verify_ecdsa' tex ivv_m pub sig         assertBool mempty (not ver)+        assertBool mempty (not ver')  fi :: (Integral a, Num b) => a -> b fi = fromIntegral@@ -100,9 +106,9 @@     <*> fmap toBS (m .: "signature")  parse_compact :: BS.ByteString -> ECDSA-parse_compact bs =-  let (roll -> r, roll -> s) = BS.splitAt 32 bs-  in  ECDSA r s+parse_compact bs = case parse_sig bs of+  Nothing -> error "bang"+  Just s -> s  data InvalidTest = InvalidTest {     iv_sign   :: ![(Int, InvalidSignTest)]
test/Wycheproof.hs view
@@ -30,23 +30,24 @@ roll = BS.foldl' unstep 0 where   unstep a (fi -> b) = (a `I.integerShiftL` 8) `I.integerOr` b -execute_group :: SigType -> EcdsaTestGroup -> TestTree-execute_group ty EcdsaTestGroup {..} =-    testGroup msg (fmap (execute ty pk_uncompressed) etg_tests)+execute_group :: Context -> SigType -> EcdsaTestGroup -> TestTree+execute_group tex ty EcdsaTestGroup {..} =+    testGroup msg (fmap (execute tex ty pk_uncompressed) etg_tests)   where     msg = "wycheproof (" <> T.unpack etg_type <> ", " <> T.unpack etg_sha <> ")"     PublicKey {..} = etg_publicKey -execute :: SigType -> Projective -> EcdsaVerifyTest -> TestTree-execute ty pub EcdsaVerifyTest {..} = testCase report $ do+execute :: Context -> SigType -> Projective -> EcdsaVerifyTest -> TestTree+execute tex ty pub EcdsaVerifyTest {..} = testCase report $ do     let msg = B16.decodeLenient (TE.encodeUtf8 t_msg)         sig = toEcdsa t_sig     case sig of       Left _  -> assertBool mempty (t_result == "invalid")       Right s -> do         let ver = case ty of-              LowS -> verify_ecdsa msg pub s+              LowS -> verify_ecdsa msg pub s && verify_ecdsa' tex msg pub s               Unrestricted -> verify_ecdsa_unrestricted msg pub s+                           && verify_ecdsa_unrestricted' tex msg pub s         if   t_result == "invalid"         then assertBool mempty (not ver)         else assertBool mempty ver