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 +4/−0
- bench/Main.hs +1/−0
- lib/Crypto/Curve/Secp256k1.hs +86/−24
- ppad-secp256k1.cabal +2/−2
- test/Noble.hs +3/−0
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 {..}) =