1"""ECDSA over secp256k1 (SEC 1 v2.0 §4.1) with RFC 6979 deterministic nonces, pure Python, exact arithmetic."""2from __future__ import annotations3import hashlib, hmac4from . import secp256k1 as k15from .ec import Curve67E: Curve = k1.CURVE8n = k1.n91011def bits2int(b: bytes, qlen: int = 256) -> int:12 z = int.from_bytes(b, "big")13 blen = len(b) * 814 return z >> (blen - qlen) if blen > qlen else z151617def rfc6979_nonce(d: int, h1: bytes) -> int:18 """Deterministic k per RFC 6979 §3.2 with HMAC-SHA256 (qlen = 256)."""19 x = d.to_bytes(32, "big")20 hz = (bits2int(h1) % n).to_bytes(32, "big")21 V = b"\x01" * 3222 Kk = b"\x00" * 3223 Kk = hmac.new(Kk, V + b"\x00" + x + hz, hashlib.sha256).digest()24 V = hmac.new(Kk, V, hashlib.sha256).digest()25 Kk = hmac.new(Kk, V + b"\x01" + x + hz, hashlib.sha256).digest()26 V = hmac.new(Kk, V, hashlib.sha256).digest()27 while True:28 V = hmac.new(Kk, V, hashlib.sha256).digest()29 k = bits2int(V)30 if 1 <= k < n:31 return k32 Kk = hmac.new(Kk, V + b"\x00", hashlib.sha256).digest()33 V = hmac.new(Kk, V, hashlib.sha256).digest()343536def sign(d: int, msg_hash: bytes, k: int | None = None, low_s: bool = True) -> tuple[int, int]:37 z = bits2int(msg_hash) % n38 if k is None:39 k = rfc6979_nonce(d, msg_hash)40 R = E.mul(k, E.G)41 r = R[0] % n42 s = pow(k, -1, n) * (z + r * d) % n43 if r == 0 or s == 0:44 raise ValueError("degenerate signature; choose another nonce")45 if low_s and s > n // 2:46 s = n - s47 return r, s484950def verify(Q, msg_hash: bytes, sig: tuple[int, int]) -> bool:51 r, s = sig52 if not (1 <= r < n and 1 <= s < n) or Q is None or not E.is_on_curve(Q):53 return False54 z = bits2int(msg_hash) % n55 w = pow(s, -1, n)56 u1, u2 = z * w % n, r * w % n57 X = E.add(E.mul(u1, E.G), E.mul(u2, Q))58 return X is not None and X[0] % n == r596061def verify_shamir(Q, msg_hash: bytes, sig: tuple[int, int]) -> bool:62 """Same predicate using Shamir's trick (simultaneous double scalar multiplication)."""63 r, s = sig64 if not (1 <= r < n and 1 <= s < n) or Q is None:65 return False66 z = bits2int(msg_hash) % n67 w = pow(s, -1, n)68 u1, u2 = z * w % n, r * w % n69 X = shamir(u1, E.G, u2, Q)70 return X is not None and X[0] % n == r717273def shamir(u1: int, P, u2: int, Q):74 GQ = E.add(P, Q)75 R = None76 for i in range(max(u1.bit_length(), u2.bit_length()) - 1, -1, -1):77 R = E.double(R)78 b1, b2 = (u1 >> i) & 1, (u2 >> i) & 179 if b1 and b2:80 R = E.add(R, GQ)81 elif b1:82 R = E.add(R, P)83 elif b2:84 R = E.add(R, Q)85 return R868788def pubkey(d: int):89 return E.mul(d, E.G)909192def recover_private_key_from_nonce_reuse(sig1, sig2, z1: int, z2: int) -> int:93 """Textbook nonce-reuse recovery on synthetic signatures (r1 == r2): d = (s1 z2 − s2 z1)/(r (s2 − s1))."""94 r, s1 = sig195 r2, s2 = sig296 assert r == r297 k = (z1 - z2) * pow(s1 - s2, -1, n) % n98 return (s1 * k - z1) * pow(r, -1, n) % n99