"""Minimal secp256k1 (Jacobian) + glagla transcript hashing, pure Python.""" import hashlib P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 B = 7 GX = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 GY = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8 # ---- Jacobian point arithmetic (X, Y, Z); infinity = (1,1,0) ---- INF = (1, 1, 0) def is_inf(Pt): return Pt[2] == 0 def jdouble(Pt): X1, Y1, Z1 = Pt if Z1 == 0 or Y1 == 0: return INF # a = 0 curve S = (4 * X1 * Y1 * Y1) % P M = (3 * X1 * X1) % P X3 = (M * M - 2 * S) % P Y3 = (M * (S - X3) - 8 * Y1 * Y1 * Y1 * Y1) % P Z3 = (2 * Y1 * Z1) % P return (X3, Y3, Z3) def jadd(Pa, Pb): if Pa[2] == 0: return Pb if Pb[2] == 0: return Pa X1, Y1, Z1 = Pa X2, Y2, Z2 = Pb Z1Z1 = (Z1 * Z1) % P Z2Z2 = (Z2 * Z2) % P U1 = (X1 * Z2Z2) % P U2 = (X2 * Z1Z1) % P S1 = (Y1 * Z2 * Z2Z2) % P S2 = (Y2 * Z1 * Z1Z1) % P if U1 == U2: if S1 != S2: return INF return jdouble(Pa) H = (U2 - U1) % P R = (S2 - S1) % P HH = (H * H) % P HHH = (H * HH) % P V = (U1 * HH) % P X3 = (R * R - HHH - 2 * V) % P Y3 = (R * (V - X3) - S1 * HHH) % P Z3 = (H * Z1 * Z2) % P return (X3, Y3, Z3) def jmul(k, Pt): k %= N R = INF A = Pt while k: if k & 1: R = jadd(R, A) A = jdouble(A) k >>= 1 return R def to_affine(Pt): if Pt[2] == 0: return None # infinity X, Y, Z = Pt Zi = pow(Z, P - 2, P) Zi2 = (Zi * Zi) % P x = (X * Zi2) % P y = (Y * Zi2 * Zi) % P return (x, y) def from_affine(xy): if xy is None: return INF return (xy[0] % P, xy[1] % P, 1) G = (GX, GY, 1) def encode_point(Pt): """SEC1 compressed, 33 bytes.""" xy = to_affine(Pt) if xy is None: raise ValueError("cannot encode infinity") x, y = xy prefix = 2 + (y & 1) return bytes([prefix]) + x.to_bytes(32, "big") def decode_point(data): if isinstance(data, str): data = bytes.fromhex(data) prefix = data[0] if prefix in (2, 3): x = int.from_bytes(data[1:33], "big") y2 = (pow(x, 3, P) + B) % P y = pow(y2, (P + 1) // 4, P) if (y & 1) != (prefix & 1): y = (P - y) % P return (x, y, 1) elif prefix == 4: x = int.from_bytes(data[1:33], "big") y = int.from_bytes(data[33:65], "big") return (x, y, 1) raise ValueError("bad point prefix") # ---- glagla transcript hashing ---- def _reduce(digest): return int.from_bytes(digest, "big") % N def binding(signer_id, msg): h = hashlib.sha256(b"glagla/bind") h.update(bytes([signer_id])) h.update(len(msg).to_bytes(8, "big")) h.update(msg) return _reduce(h.digest()) def challenge(R_pt, Y_pt, msg): h = hashlib.sha256(b"glagla/chall") h.update(encode_point(R_pt)) h.update(encode_point(Y_pt)) h.update(len(msg).to_bytes(8, "big")) h.update(msg) return _reduce(h.digest())