packages feed

ppad-secp256k1 0.5.4 → 0.5.5

raw patch · 5 files changed

+96/−26 lines, 5 filesdep ~ppad-fixedPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: ppad-fixed

API changes (from Hackage documentation)

+ Crypto.Curve.Secp256k1: _verify_ecdsa_no_hash :: ByteString -> Pub -> ECDSA -> Bool
+ Crypto.Curve.Secp256k1: _verify_ecdsa_no_hash' :: Context -> ByteString -> Pub -> ECDSA -> Bool

Files

CHANGELOG view
@@ -1,5 +1,9 @@ # Changelog +- 0.5.5 (2026-05-22)+  * Adds no-hash ECDSA signature verification utilities that skip the+    internal SHA256 step.+ - 0.5.4 (2026-02-04)   * Nonce generation for ECDSA is now more secure due to recent     improvements in ppad-hmac-drbg. The DRBG state is now guaranteed
bench/Main.hs view
@@ -177,6 +177,7 @@  ecdh :: Benchmark ecdh = env setup $ \ ~(big, pub) ->+    -- the order of these seems to matter particularly for some reason     bgroup "ecdh" [       bench "ecdh (small)" $ nf (S.ecdh pub) 2     , bench "ecdh (large)" $ nf (S.ecdh pub) big
lib/Crypto/Curve/Secp256k1.hs view
@@ -97,6 +97,8 @@   , _precompute   , _sign_ecdsa_no_hash   , _sign_ecdsa_no_hash'+  , _verify_ecdsa_no_hash+  , _verify_ecdsa_no_hash'   , roll32   , unsafe_roll32   , unroll32@@ -108,6 +110,7 @@ import qualified Crypto.DRBG.HMAC.SHA256 as DRBG import qualified Crypto.Hash.SHA256 as SHA256 import qualified Data.Bits as B+import Data.Bits ((.<<.)) import qualified Data.ByteString as BS import qualified Data.ByteString.Internal as BI import qualified Data.ByteString.Unsafe as BU@@ -136,9 +139,6 @@ -- Unboxed Projective synonym. type Proj = (# Limb4, Limb4, Limb4 #) -pattern Zero :: Wider-pattern Zero = Wider Z- pattern Z :: Limb4 pattern Z = (# Limb 0##, Limb 0##, Limb 0##, Limb 0## #) @@ -581,6 +581,28 @@           in  loop (succ j) nacc nf nd nm {-# INLINE mul# #-} +mul_vartime# :: Proj -> Limb4 -> (# () | Proj #)+mul_vartime# (# px, py, pz #) s+    | zero# s =+        let !(P zx zy zz) = _CURVE_ZERO+        in  (# | (# zx, zy, zz #) #)+    | CT.decide (CT.not (ge# s)) = (# () | #)+    | otherwise =+        let !(P zx zy zz) = _CURVE_ZERO+        in  (# | loop (# zx, zy, zz #) (# px, py, pz #) s #)+  where+    zero# (# Limb a, Limb b, Limb c, Limb d #) = Exts.isTrue#+      ((a `Exts.or#` b `Exts.or#` c `Exts.or#` d) `Exts.eqWord#` 0##)++    loop !r !d !m+      | zero# m = r+      | otherwise =+          let !nd = double# d+              !(# nm, lsb_set #) = W.shr1_c# m+              !nr = if CT.decide lsb_set then add_proj# r d else r+          in  loop nr nd nm+{-# INLINE mul_vartime# #-}+ ge# :: Limb4 -> CT.Choice ge# n =   let !(Wider q) = _CURVE_Q@@ -597,10 +619,10 @@   where     !one                  = (# Limb 1##, Limb 0##, Limb 0##, Limb 0## #)     !wins                 = fi (256 `quot` ctxW + 1)-    !size@(GHC.Word.W# s) = 2 ^ (ctxW - 1)-    !(GHC.Word.W# mask)   = 2 ^ ctxW - 1+    !size@(GHC.Word.W# s) = 1 .<<. (ctxW - 1)+    !(GHC.Word.W# mask)   = 1 .<<. ctxW - 1     !(GHC.Word.W# texW)   = fi ctxW-    !(GHC.Word.W# mnum)   = 2 ^ ctxW+    !(GHC.Word.W# mnum)   = 1 .<<. ctxW      loop !j@(GHC.Word.W# w) !acc !f !n@(# Limb lo, _, _, _ #)       | j == wins = acc@@ -720,18 +742,9 @@ -- -- Don't use this function if the scalar could potentially be a secret. mul_vartime :: Projective -> Wider -> Maybe Projective-mul_vartime p = \case-    Zero -> pure _CURVE_ZERO-    n | not (ge n) -> Nothing-      | otherwise  -> pure $! loop _CURVE_ZERO p n-  where-    loop !r !d = \case-      Zero -> r-      m ->-        let !nd = double d-            !(# nm, lsb_set #) = W.shr1_c m-            !nr = if CT.decide lsb_set then add r d else r-        in  loop nr nd nm+mul_vartime (P x y z) (Wider s) = case mul_vartime# (# x, y, z #) s of+  (# () | #)               -> Nothing+  (# | (# px, py, pz #) #) -> Just $! P px py pz  -- | Precomputed multiples of the secp256k1 base or generator point. data Context = Context {@@ -765,7 +778,7 @@ -- the ByteArray is (size * 96) bytes. _precompute :: Int -> Context _precompute ctxW = Context {..} where-  capJ = (2 :: Int) ^ (ctxW - 1)+  capJ = (1 :: Int) .<<. (ctxW - 1)   ws = 256 `quot` ctxW + 1   size = ws * capJ @@ -1359,7 +1372,8 @@   -> Pub           -- ^ public key   -> ECDSA         -- ^ signature   -> Bool-verify_ecdsa_unrestricted = _verify_ecdsa_unrestricted (mul_vartime _CURVE_G)+verify_ecdsa_unrestricted =+  _verify_ecdsa_unrestricted (mul_vartime _CURVE_G) Hash  -- | The same as 'verify_ecdsa_unrestricted', except uses a 'Context' to --   optimise internal calculations.@@ -1378,17 +1392,21 @@   -> Pub           -- ^ public key   -> ECDSA         -- ^ signature   -> Bool-verify_ecdsa_unrestricted' tex = _verify_ecdsa_unrestricted (mul_wnaf tex)+verify_ecdsa_unrestricted' tex =+  _verify_ecdsa_unrestricted (mul_wnaf tex) Hash  _verify_ecdsa_unrestricted   :: (Wider -> Maybe Projective) -- partially-applied multiplication function+  -> HashFlag   -> BS.ByteString   -> Pub   -> ECDSA   -> Bool-_verify_ecdsa_unrestricted _mul m p (ECDSA r0 s0) = M.isJust $ do+_verify_ecdsa_unrestricted _mul hf m p (ECDSA r0 s0) = M.isJust $ do   -- SEC1-v2 4.1.4-  let h = SHA256.hash m+  let h = case hf of+        Hash   -> SHA256.hash m+        NoHash -> m   guard (ge r0 && ge s0)   let r  = S.to r0       s  = S.to s0@@ -1396,11 +1414,55 @@       si = S.inv s       u1 = S.retr (e * si)       u2 = S.retr (r * si)-  pt0 <- _mul u1+      pt0 = case _mul u1 of+        Nothing -> _CURVE_ZERO+        Just pt -> pt   pt1 <- mul_vartime p u2   let capR = add pt0 pt1   guard (capR /= _CURVE_ZERO)   let Affine (S.to . C.retr -> v) _ = affine capR   guard (S.eq_vartime v r) {-# INLINE _verify_ecdsa_unrestricted #-}++-- | Verify a "low-s" ECDSA signature for the provided message digest+--   and public key.+--+--   Mirrors 'verify_ecdsa', but skips the internal SHA256 step,+--   treating the input as the message digest itself.+--+--   >>> _verify_ecdsa_no_hash dig pub valid_sig+--   True+--   >>> _verify_ecdsa_no_hash dig pub invalid_sig+--   False+_verify_ecdsa_no_hash+  :: BS.ByteString -- ^ message digest+  -> Pub           -- ^ public key+  -> ECDSA         -- ^ signature+  -> Bool+_verify_ecdsa_no_hash m p sig@(ECDSA _ s)+  | W.gt_vartime s _CURVE_QH = False+  | otherwise =+      _verify_ecdsa_unrestricted (mul_vartime _CURVE_G) NoHash m p sig++-- | The same as '_verify_ecdsa_no_hash', except uses a 'Context' to+--   optimise internal calculations.+--+--   You can expect about a 2x performance increase when using this+--   function, compared to '_verify_ecdsa_no_hash'.+--+--   >>> let !tex = precompute+--   >>> _verify_ecdsa_no_hash' tex dig pub valid_sig+--   True+--   >>> _verify_ecdsa_no_hash' tex dig pub invalid_sig+--   False+_verify_ecdsa_no_hash'+  :: Context       -- ^ secp256k1 context+  -> BS.ByteString -- ^ message digest+  -> Pub           -- ^ public key+  -> ECDSA         -- ^ signature+  -> Bool+_verify_ecdsa_no_hash' tex m p sig@(ECDSA _ s)+  | W.gt_vartime s _CURVE_QH = False+  | otherwise =+      _verify_ecdsa_unrestricted (mul_wnaf tex) NoHash m p sig 
ppad-secp256k1.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               ppad-secp256k1-version:            0.5.4+version:            0.5.5 synopsis:           Schnorr signatures, ECDSA, and ECDH on the elliptic curve                     secp256k1 license:            MIT@@ -38,7 +38,7 @@     , bytestring >= 0.9 && < 0.13     , ppad-hmac-drbg >= 0.3.1 && < 0.4     , ppad-sha256 >= 0.3.2 && < 0.4-    , ppad-fixed >= 0.1.3 && < 0.2+    , ppad-fixed >= 0.1.4 && < 0.2     , primitive >= 0.8 && < 0.10  test-suite secp256k1-tests
test/Noble.hs view
@@ -52,8 +52,11 @@         pec = parse_compact vt_signature         Just sig = _sign_ecdsa_no_hash x msg         Just sig' = _sign_ecdsa_no_hash' tex x msg+        Just pub = derive_pub x     assertEqual mempty sig sig'     assertEqual mempty pec sig+    assertBool mempty (_verify_ecdsa_no_hash msg pub sig)+    assertBool mempty (_verify_ecdsa_no_hash' tex msg pub sig)  execute_invalid_sign :: Context -> (Int, InvalidSignTest) -> TestTree execute_invalid_sign tex (label, InvalidSignTest {..}) =