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 52967380909d1980604ee10f0683f068f3a865e0
parent 12197864a9b2b09ac24b4b9e594d6d9ec8a295f4
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat,  1 Aug 2026 12:31:09 -0230

lib: match bip0340's schnorr signature range checks

Verification used fe r && ge s, which additionally rejects r = 0 and
s = 0. BIP0340 fails only on r >= p and s >= n.

Being stricter than the spec can only cause a false reject, and a
valid signature with either component zero would take a fixed-point
search to construct, so this changes no reachable behaviour. It is
still a deviation.

s = 0 needs handling at the call site: mul_wnaf rejects a zero scalar
where mul_vartime returns the identity, so verify_schnorr and
verify_schnorr' would otherwise disagree.

Diffstat:
Mlib/Crypto/Curve/Secp256k1.hs | 9+++++++--
1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/lib/Crypto/Curve/Secp256k1.hs b/lib/Crypto/Curve/Secp256k1.hs @@ -1111,11 +1111,16 @@ _verify_schnorr _mul m p sig | otherwise = M.isJust $ do let capP = even_y_vartime p (unsafe_roll32 -> r, unsafe_roll32 -> s) = BS.splitAt 32 sig - guard (fe r && ge s) + -- bip0340 fails only on r >= p and s >= n, so zero is permitted + guard (W.lt_vartime r _CURVE_P && W.lt_vartime s _CURVE_Q) let Affine (C.retr -> x_P) _ = affine capP e = modQ . unsafe_roll32 $ hash_challenge (unroll32 r <> unroll32 x_P <> m) - pt0 <- _mul s + -- mul_wnaf rejects a zero scalar, mul_vartime does not; agree on + -- the identity so both multiplication functions behave alike + pt0 <- if W.eq_vartime s 0 + then pure _CURVE_ZERO + else _mul s pt1 <- mul_vartime capP e let dif = add pt0 (neg pt1) guard (dif /= _CURVE_ZERO)