"""ECDSA over secp256k1 (SEC 1 v2.0 §4.1) with RFC 6979 deterministic nonces, pure Python, exact arithmetic.""" from __future__ import annotations import hashlib, hmac from . import secp256k1 as k1 from .ec import Curve E: Curve = k1.CURVE n = k1.n def bits2int(b: bytes, qlen: int = 256) -> int: z = int.from_bytes(b, "big") blen = len(b) * 8 return z >> (blen - qlen) if blen > qlen else z def rfc6979_nonce(d: int, h1: bytes) -> int: """Deterministic k per RFC 6979 §3.2 with HMAC-SHA256 (qlen = 256).""" x = d.to_bytes(32, "big") hz = (bits2int(h1) % n).to_bytes(32, "big") V = b"\x01" * 32 Kk = b"\x00" * 32 Kk = hmac.new(Kk, V + b"\x00" + x + hz, hashlib.sha256).digest() V = hmac.new(Kk, V, hashlib.sha256).digest() Kk = hmac.new(Kk, V + b"\x01" + x + hz, hashlib.sha256).digest() V = hmac.new(Kk, V, hashlib.sha256).digest() while True: V = hmac.new(Kk, V, hashlib.sha256).digest() k = bits2int(V) if 1 <= k < n: return k Kk = hmac.new(Kk, V + b"\x00", hashlib.sha256).digest() V = hmac.new(Kk, V, hashlib.sha256).digest() def sign(d: int, msg_hash: bytes, k: int | None = None, low_s: bool = True) -> tuple[int, int]: z = bits2int(msg_hash) % n if k is None: k = rfc6979_nonce(d, msg_hash) R = E.mul(k, E.G) r = R[0] % n s = pow(k, -1, n) * (z + r * d) % n if r == 0 or s == 0: raise ValueError("degenerate signature; choose another nonce") if low_s and s > n // 2: s = n - s return r, s def verify(Q, msg_hash: bytes, sig: tuple[int, int]) -> bool: r, s = sig if not (1 <= r < n and 1 <= s < n) or Q is None or not E.is_on_curve(Q): return False z = bits2int(msg_hash) % n w = pow(s, -1, n) u1, u2 = z * w % n, r * w % n X = E.add(E.mul(u1, E.G), E.mul(u2, Q)) return X is not None and X[0] % n == r def verify_shamir(Q, msg_hash: bytes, sig: tuple[int, int]) -> bool: """Same predicate using Shamir's trick (simultaneous double scalar multiplication).""" r, s = sig if not (1 <= r < n and 1 <= s < n) or Q is None: return False z = bits2int(msg_hash) % n w = pow(s, -1, n) u1, u2 = z * w % n, r * w % n X = shamir(u1, E.G, u2, Q) return X is not None and X[0] % n == r def shamir(u1: int, P, u2: int, Q): GQ = E.add(P, Q) R = None for i in range(max(u1.bit_length(), u2.bit_length()) - 1, -1, -1): R = E.double(R) b1, b2 = (u1 >> i) & 1, (u2 >> i) & 1 if b1 and b2: R = E.add(R, GQ) elif b1: R = E.add(R, P) elif b2: R = E.add(R, Q) return R def pubkey(d: int): return E.mul(d, E.G) def recover_private_key_from_nonce_reuse(sig1, sig2, z1: int, z2: int) -> int: """Textbook nonce-reuse recovery on synthetic signatures (r1 == r2): d = (s1 z2 − s2 z1)/(r (s2 − s1)).""" r, s1 = sig1 r2, s2 = sig2 assert r == r2 k = (z1 - z2) * pow(s1 - s2, -1, n) % n return (s1 * k - z1) * pow(r, -1, n) % n