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 da803f28247261cea2f07462864a81920163eb25
parent a9d57ae1f50e8a2cb0e87dcf04d26089a79ef434
Author: Jared Tobin <jared@jtobin.io>
Date:   Thu, 21 May 2026 15:32:25 -0230

lib: add no-hash ecdsa verification

Diffstat:
Mlib/Crypto/Curve/Secp256k1.hs | 61++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mtest/Noble.hs | 3+++
2 files changed, 59 insertions(+), 5 deletions(-)

diff --git a/lib/Crypto/Curve/Secp256k1.hs b/lib/Crypto/Curve/Secp256k1.hs @@ -97,6 +97,8 @@ module Crypto.Curve.Secp256k1 ( , _precompute , _sign_ecdsa_no_hash , _sign_ecdsa_no_hash' + , _verify_ecdsa_no_hash + , _verify_ecdsa_no_hash' , roll32 , unsafe_roll32 , unroll32 @@ -1370,7 +1372,8 @@ verify_ecdsa_unrestricted -> 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. @@ -1389,17 +1392,21 @@ verify_ecdsa_unrestricted' -> 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 @@ -1407,7 +1414,9 @@ _verify_ecdsa_unrestricted _mul m p (ECDSA r0 s0) = M.isJust $ do 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) @@ -1415,3 +1424,45 @@ _verify_ecdsa_unrestricted _mul m p (ECDSA r0 s0) = M.isJust $ do 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) + | CT.decide (W.gt 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) + | CT.decide (W.gt s _CURVE_QH) = False + | otherwise = + _verify_ecdsa_unrestricted (mul_wnaf tex) NoHash m p sig + diff --git a/test/Noble.hs b/test/Noble.hs @@ -52,8 +52,11 @@ execute_valid tex (label, ValidTest {..}) = 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 {..}) =