1"""BIP340 Schnorr signatures over secp256k1 — reference implementation following the BIP text, exact arithmetic."""2from __future__ import annotations3import hashlib, random4from . import secp256k1 as k15from .ec import Curve, sqrt_mod67E: Curve = k1.CURVE8n, p = k1.n, k1.p91011def tagged_hash(tag: str, msg: bytes) -> bytes:12 th = hashlib.sha256(tag.encode()).digest()13 return hashlib.sha256(th + th + msg).digest()141516def bytes_from_int(x: int) -> bytes:17 return x.to_bytes(32, "big")181920def int_from_bytes(b: bytes) -> int:21 return int.from_bytes(b, "big")222324def has_even_y(P) -> bool:25 return P is not None and P[1] % 2 == 0262728def lift_x(x: int):29 if x >= p:30 return None31 c = (pow(x, 3, p) + 7) % p32 y = pow(c, (p + 1) // 4, p)33 if y * y % p != c:34 return None35 return (x, y if y % 2 == 0 else p - y)363738def pubkey_gen(seckey: bytes) -> bytes:39 d0 = int_from_bytes(seckey)40 if not (1 <= d0 <= n - 1):41 raise ValueError("secret key out of range")42 P = E.mul(d0, E.G)43 return bytes_from_int(P[0])444546def sign(msg: bytes, seckey: bytes, aux_rand: bytes) -> bytes:47 d0 = int_from_bytes(seckey)48 if not (1 <= d0 <= n - 1):49 raise ValueError("secret key out of range")50 P = E.mul(d0, E.G)51 d = d0 if has_even_y(P) else n - d052 t = bytes_from_int(d ^ int_from_bytes(tagged_hash("BIP0340/aux", aux_rand)))53 k0 = int_from_bytes(tagged_hash("BIP0340/nonce", t + bytes_from_int(P[0]) + msg)) % n54 if k0 == 0:55 raise RuntimeError("nonce is zero")56 R = E.mul(k0, E.G)57 k = n - k0 if not has_even_y(R) else k058 e = int_from_bytes(tagged_hash("BIP0340/challenge", bytes_from_int(R[0]) + bytes_from_int(P[0]) + msg)) % n59 sig = bytes_from_int(R[0]) + bytes_from_int((k + e * d) % n)60 if not verify(msg, bytes_from_int(P[0]), sig):61 raise RuntimeError("signature failed self-check")62 return sig636465def verify(msg: bytes, pubkey: bytes, sig: bytes) -> bool:66 if len(pubkey) != 32 or len(sig) != 64:67 return False68 P = lift_x(int_from_bytes(pubkey))69 r = int_from_bytes(sig[0:32])70 s = int_from_bytes(sig[32:64])71 if P is None or r >= p or s >= n:72 return False73 e = int_from_bytes(tagged_hash("BIP0340/challenge", sig[0:32] + pubkey + msg)) % n74 R = E.add(E.mul(s, E.G), E.mul(n - e, P))75 return R is not None and has_even_y(R) and R[0] == r767778def batch_verify(items: list[tuple[bytes, bytes, bytes]], rng: random.Random | None = None) -> bool:79 """Batch verification: Σ a_i s_i G = Σ a_i R_i + Σ a_i e_i P_i with random 128-bit weights (a_1 = 1)."""80 rng = rng or random.SystemRandom()81 S = 082 rhs = None83 for i, (msg, pubkey, sig) in enumerate(items):84 P = lift_x(int_from_bytes(pubkey))85 r, s = int_from_bytes(sig[:32]), int_from_bytes(sig[32:])86 if P is None or r >= p or s >= n:87 return False88 R = lift_x(r)89 if R is None:90 return False91 e = int_from_bytes(tagged_hash("BIP0340/challenge", sig[:32] + pubkey + msg)) % n92 ai = 1 if i == 0 else rng.getrandbits(128)93 S = (S + ai * s) % n94 rhs = E.add(rhs, E.add(E.mul(ai, R), E.mul(ai * e % n, P)))95 return E.mul(S, E.G) == rhs96