# Copyright (c) 2019 Pieter Wuille # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test-only secp256k1 elliptic curve implementation WARNING: This code is slow, uses bad randomness, does not properly protect keys, and is trivially vulnerable to side channel attacks. Do not use for anything but tests.""" import random import hashlib import hmac def TaggedHash(tag, data): ss = hashlib.sha256(tag.encode('utf-8')).digest() ss += ss ss += data return hashlib.sha256(ss).digest() def modinv(a, n): """Compute the modular inverse of a modulo n See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers. """ t1, t2 = 0, 1 r1, r2 = n, a while r2 != 0: q = r1 // r2 t1, t2 = t2, t1 - q * t2 r1, r2 = r2, r1 - q * r2 if r1 > 1: return None if t1 < 0: t1 += n return t1 def jacobi_symbol(n, k): """Compute the Jacobi symbol of n modulo k See http://en.wikipedia.org/wiki/Jacobi_symbol For our application k is always prime, so this is the same as the Legendre symbol.""" assert k > 0 and k & 1, "jacobi symbol is only defined for positive odd k" n %= k t = 0 while n != 0: while n & 1 == 0: n >>= 1 r = k & 7 t ^= (r == 3 or r == 5) n, k = k, n t ^= (n & k & 3 == 3) n = n % k if k == 1: return -1 if t else 1 return 0 def modsqrt(a, p): """Compute the square root of a modulo p when p % 4 = 3. The Tonelli-Shanks algorithm can be used. See https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm Limiting this function to only work for p % 4 = 3 means we don't need to iterate through the loop. The highest n such that p - 1 = 2^n Q with Q odd is n = 1. Therefore Q = (p-1)/2 and sqrt = a^((Q+1)/2) = a^((p+1)/4) secp256k1's is defined over field of size 2**256 - 2**32 - 977, which is 3 mod 4. """ if p % 4 != 3: raise NotImplementedError("modsqrt only implemented for p % 4 = 3") sqrt = pow(a, (p + 1)//4, p) if pow(sqrt, 2, p) == a % p: return sqrt return None def int_or_bytes(s): "Convert 32-bytes to int while accepting also int and returning it as is." if isinstance(s, bytes): assert(len(s) == 32) s = int.from_bytes(s, 'big') elif not isinstance(s, int): raise TypeError return s class EllipticCurve: def __init__(self, p, a, b): """Initialize elliptic curve y^2 = x^3 + a*x + b over GF(p).""" self.p = p self.a = a % p self.b = b % p def affine(self, p1): """Convert a Jacobian point tuple p1 to affine form, or None if at infinity. An affine point is represented as the Jacobian (x, y, 1)""" x1, y1, z1 = p1 if z1 == 0: return None inv = modinv(z1, self.p) inv_2 = (inv**2) % self.p inv_3 = (inv_2 * inv) % self.p return ((inv_2 * x1) % self.p, (inv_3 * y1) % self.p, 1) def has_even_y(self, p1): """Whether the point p1 has an even Y coordinate when expressed in affine coordinates.""" return not (p1[2] == 0 or self.affine(p1)[1] & 1) def negate(self, p1): """Negate a Jacobian point tuple p1.""" x1, y1, z1 = p1 return (x1, (self.p - y1) % self.p, z1) def on_curve(self, p1): """Determine whether a Jacobian tuple p is on the curve (and not infinity)""" x1, y1, z1 = p1 z2 = pow(z1, 2, self.p) z4 = pow(z2, 2, self.p) return z1 != 0 and (pow(x1, 3, self.p) + self.a * x1 * z4 + self.b * z2 * z4 - pow(y1, 2, self.p)) % self.p == 0 def is_x_coord(self, x): """Test whether x is a valid X coordinate on the curve.""" x_3 = pow(x, 3, self.p) return jacobi_symbol(x_3 + self.a * x + self.b, self.p) != -1 def lift_x(self, x): """Given an X coordinate on the curve, return a corresponding affine point.""" x_3 = pow(x, 3, self.p) v = x_3 + self.a * x + self.b y = modsqrt(v, self.p) if y is None: return None return (x, y, 1) def double(self, p1): """Double a Jacobian tuple p1 See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Doubling""" x1, y1, z1 = p1 if z1 == 0: return (0, 1, 0) y1_2 = (y1**2) % self.p y1_4 = (y1_2**2) % self.p x1_2 = (x1**2) % self.p s = (4*x1*y1_2) % self.p m = 3*x1_2 if self.a: m += self.a * pow(z1, 4, self.p) m = m % self.p x2 = (m**2 - 2*s) % self.p y2 = (m*(s - x2) - 8*y1_4) % self.p z2 = (2*y1*z1) % self.p return (x2, y2, z2) def add_mixed(self, p1, p2): """Add a Jacobian tuple p1 and an affine tuple p2 See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition (with affine point)""" x1, y1, z1 = p1 x2, y2, z2 = p2 assert(z2 == 1) # Adding to the point at infinity is a no-op if z1 == 0: return p2 z1_2 = (z1**2) % self.p z1_3 = (z1_2 * z1) % self.p u2 = (x2 * z1_2) % self.p s2 = (y2 * z1_3) % self.p if x1 == u2: if (y1 != s2): # p1 and p2 are inverses. Return the point at infinity. return (0, 1, 0) # p1 == p2. The formulas below fail when the two points are equal. return self.double(p1) h = u2 - x1 r = s2 - y1 h_2 = (h**2) % self.p h_3 = (h_2 * h) % self.p u1_h_2 = (x1 * h_2) % self.p x3 = (r**2 - h_3 - 2*u1_h_2) % self.p y3 = (r*(u1_h_2 - x3) - y1*h_3) % self.p z3 = (h*z1) % self.p return (x3, y3, z3) def add(self, p1, p2): """Add two Jacobian tuples p1 and p2 See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition""" x1, y1, z1 = p1 x2, y2, z2 = p2 # Adding the point at infinity is a no-op if z1 == 0: return p2 if z2 == 0: return p1 # Adding an Affine to a Jacobian is more efficient since we save field multiplications and squarings when z = 1 if z1 == 1: return self.add_mixed(p2, p1) if z2 == 1: return self.add_mixed(p1, p2) z1_2 = (z1**2) % self.p z1_3 = (z1_2 * z1) % self.p z2_2 = (z2**2) % self.p z2_3 = (z2_2 * z2) % self.p u1 = (x1 * z2_2) % self.p u2 = (x2 * z1_2) % self.p s1 = (y1 * z2_3) % self.p s2 = (y2 * z1_3) % self.p if u1 == u2: if (s1 != s2): # p1 and p2 are inverses. Return the point at infinity. return (0, 1, 0) # p1 == p2. The formulas below fail when the two points are equal. return self.double(p1) h = u2 - u1 r = s2 - s1 h_2 = (h**2) % self.p h_3 = (h_2 * h) % self.p u1_h_2 = (u1 * h_2) % self.p x3 = (r**2 - h_3 - 2*u1_h_2) % self.p y3 = (r*(u1_h_2 - x3) - s1*h_3) % self.p z3 = (h*z1*z2) % self.p return (x3, y3, z3) def mul(self, ps): """Compute a (multi) point multiplication ps is a list of (Jacobian tuple, scalar) pairs. """ r = (0, 1, 0) for i in range(255, -1, -1): r = self.double(r) for (p, n) in ps: if ((n >> i) & 1): r = self.add(r, p) return r SECP256K1_FIELD_SIZE = 2**256 - 2**32 - 977 SECP256K1 = EllipticCurve(SECP256K1_FIELD_SIZE, 0, 7) SECP256K1_G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, 1) SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2 NUMS_H = 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 class ECPubKey(): """A secp256k1 public key""" def __init__(self): """Construct an uninitialized public key""" self.valid = False def __repr__(self): return self.get_bytes().hex() def __eq__(self, other): assert isinstance(other, ECPubKey) return self.get_bytes() == other.get_bytes() def __hash__(self): return hash(self.get_bytes()) def set(self, data): """Construct a public key from a serialization in compressed or uncompressed DER format or BIP340 format""" if (len(data) == 65 and data[0] == 0x04): p = (int.from_bytes(data[1:33], 'big'), int.from_bytes(data[33:65], 'big'), 1) self.valid = SECP256K1.on_curve(p) if self.valid: self.p = p self.compressed = False elif (len(data) == 33 and (data[0] == 0x02 or data[0] == 0x03)): x = int.from_bytes(data[1:33], 'big') if SECP256K1.is_x_coord(x): p = SECP256K1.lift_x(x) # if the oddness of the y co-ord isn't correct, find the other # valid y if (p[1] & 1) != (data[0] & 1): p = SECP256K1.negate(p) self.p = p self.valid = True self.compressed = True else: self.valid = False elif (len(data) == 32): x = int.from_bytes(data[0:32], 'big') if SECP256K1.is_x_coord(x): p = SECP256K1.lift_x(x) # if the oddness of the y co-ord isn't correct, find the other # valid y if p[1]%2 != 0: p = SECP256K1.negate(p) self.p = p self.valid = True self.compressed = True else: self.valid = False else: self.valid = False return self @property def is_compressed(self): return self.compressed @property def is_valid(self): return self.valid def get_y(self): return SECP256K1.affine(self.p)[1] def get_x(self): return SECP256K1.affine(self.p)[0] def get_bytes(self, bip340=True): assert(self.valid) p = SECP256K1.affine(self.p) if p is None: return None if bip340: return bytes(p[0].to_bytes(32, 'big')) elif self.compressed: return bytes([0x02 + (p[1] & 1)]) + p[0].to_bytes(32, 'big') else: return bytes([0x04]) + p[0].to_bytes(32, 'big') + p[1].to_bytes(32, 'big') def verify_ecdsa(self, sig, msg, low_s=True): """Verify a strictly DER-encoded ECDSA signature against this pubkey. See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the ECDSA verifier algorithm""" assert(self.valid) # Extract r and s from the DER formatted signature. Return false for # any DER encoding errors. if (sig[1] + 2 != len(sig)): return False if (len(sig) < 4): return False if (sig[0] != 0x30): return False if (sig[2] != 0x02): return False rlen = sig[3] if (len(sig) < 6 + rlen): return False if rlen < 1 or rlen > 33: return False if sig[4] >= 0x80: return False if (rlen > 1 and (sig[4] == 0) and not (sig[5] & 0x80)): return False r = int.from_bytes(sig[4:4+rlen], 'big') if (sig[4+rlen] != 0x02): return False slen = sig[5+rlen] if slen < 1 or slen > 33: return False if (len(sig) != 6 + rlen + slen): return False if sig[6+rlen] >= 0x80: return False if (slen > 1 and (sig[6+rlen] == 0) and not (sig[7+rlen] & 0x80)): return False s = int.from_bytes(sig[6+rlen:6+rlen+slen], 'big') # Verify that r and s are within the group order if r < 1 or s < 1 or r >= SECP256K1_ORDER or s >= SECP256K1_ORDER: return False if low_s and s >= SECP256K1_ORDER_HALF: return False z = int.from_bytes(msg, 'big') # Run verifier algorithm on r, s w = modinv(s, SECP256K1_ORDER) u1 = z*w % SECP256K1_ORDER u2 = r*w % SECP256K1_ORDER R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, u1), (self.p, u2)])) if R is None or R[0] != r: return False return True def verify_schnorr(self, sig, msg): assert(len(msg) == 32) assert(len(sig) == 64) assert(self.valid) r = int.from_bytes(sig[0:32], 'big') if r >= SECP256K1_FIELD_SIZE: return False s = int.from_bytes(sig[32:64], 'big') if s >= SECP256K1_ORDER: return False e = int.from_bytes(TaggedHash("BIP0340/challenge", sig[0:32] + self.get_bytes() + msg), 'big') % SECP256K1_ORDER R = SECP256K1.mul([(SECP256K1_G, s), (self.p, SECP256K1_ORDER - e)]) if not SECP256K1.has_even_y(R): return False if ((r * R[2] * R[2]) % SECP256K1_FIELD_SIZE) != R[0]: return False return True def __add__(self, other): """Adds two ECPubKey points.""" assert isinstance(other, ECPubKey) assert self.valid assert other.valid ret = ECPubKey() ret.p = SECP256K1.add(other.p, self.p) ret.valid = True ret.compressed = self.compressed return ret def __radd__(self, other): """Allows this ECPubKey to be added to 0 for sum()""" if other == 0: return self else: return self + other def __mul__(self, other): """Multiplies ECPubKey point with a scalar(int/32bytes/ECKey).""" if isinstance(other, ECKey): assert self.valid assert other.secret is not None multiplier = other.secret else: # int_or_bytes checks that other is `int` or `bytes` multiplier = int_or_bytes(other) assert multiplier < SECP256K1_ORDER multiplier = multiplier % SECP256K1_ORDER ret = ECPubKey() ret.p = SECP256K1.mul([(self.p, multiplier)]) ret.valid = True ret.compressed = self.compressed return ret def __rmul__(self, other): """Multiplies a scalar(int/32bytes/ECKey) with an ECPubKey point""" return self * other def __sub__(self, other): """Subtract one point from another""" assert isinstance(other, ECPubKey) assert self.valid assert other.valid ret = ECPubKey() ret.p = SECP256K1.add(self.p, SECP256K1.negate(other.p)) ret.valid = True ret.compressed = self.compressed return ret def tweak_add(self, tweak): assert(self.valid) t = int_or_bytes(tweak) if t >= SECP256K1_ORDER: return None tweaked = SECP256K1.affine(SECP256K1.mul([(self.p, 1), (SECP256K1_G, t)])) if tweaked is None: return None ret = ECPubKey() ret.p = tweaked ret.valid = True ret.compressed = self.compressed return ret def mul(self, data): """Multiplies ECPubKey point with scalar data.""" assert self.valid other = ECKey() other.set(data, True) return self * other def negate(self): self.p = SECP256K1.affine(SECP256K1.negate(self.p)) def rfc6979_nonce(key): """Compute signing nonce using RFC6979.""" v = bytes([1] * 32) k = bytes([0] * 32) k = hmac.new(k, v + b"\x00" + key, 'sha256').digest() v = hmac.new(k, v, 'sha256').digest() k = hmac.new(k, v + b"\x01" + key, 'sha256').digest() v = hmac.new(k, v, 'sha256').digest() return hmac.new(k, v, 'sha256').digest() class ECKey(): """A secp256k1 private key""" def __init__(self): self.valid = False def __repr__(self): return str(self.secret) def __eq__(self, other): assert isinstance(other, ECKey) return self.secret == other.secret def __hash__(self): return hash(self.secret) def set(self, secret, compressed=True): """Construct a private key object from either 32-bytes or an int secret and a compressed flag.""" secret = int_or_bytes(secret) self.valid = (secret > 0 and secret < SECP256K1_ORDER) if self.valid: self.secret = secret self.compressed = compressed return self def generate(self, compressed=True): """Generate a random private key (compressed or uncompressed).""" self.set(random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big'), compressed) return self def get_bytes(self): """Retrieve the 32-byte representation of this key.""" assert(self.valid) return self.secret.to_bytes(32, 'big') def as_int(self): return self.secret def from_int(self, secret, compressed=True): self.valid = (secret > 0 and secret < SECP256K1_ORDER) if self.valid: self.secret = secret self.compressed = compressed def __add__(self, other): """Add key secrets. Returns compressed key.""" assert isinstance(other, ECKey) assert other.secret > 0 and other.secret < SECP256K1_ORDER assert self.valid is True ret_data = ((self.secret + other.secret) % SECP256K1_ORDER).to_bytes(32, 'big') ret = ECKey() ret.set(ret_data, True) return ret def __radd__(self, other): """Allows this ECKey to be added to 0 for sum()""" if other == 0: return self else: return self + other def __sub__(self, other): """Subtract key secrets. Returns compressed key.""" assert isinstance(other, ECKey) assert other.secret > 0 and other.secret < SECP256K1_ORDER assert self.valid is True ret_data = ((self.secret - other.secret) % SECP256K1_ORDER).to_bytes(32, 'big') ret = ECKey() ret.set(ret_data, True) return ret def __mul__(self, other): """Multiply a private key by another private key or multiply a public key by a private key. Returns compressed key.""" if isinstance(other, ECKey): assert other.secret > 0 and other.secret < SECP256K1_ORDER assert self.valid is True ret_data = ((self.secret * other.secret) % SECP256K1_ORDER).to_bytes(32, 'big') ret = ECKey() ret.set(ret_data, True) return ret elif isinstance(other, ECPubKey): return other * self else: # ECKey().set() checks that other is an `int` or `bytes` assert self.valid second = ECKey().set(other, self.compressed) return self * second def __rmul__(self, other): return self * other def add(self, data): """Add key to scalar data. Returns compressed key.""" other = ECKey() other.set(data, True) return self + other def mul(self, data): """Multiply key secret with scalar data. Returns compressed key.""" other = ECKey() other.set(data, True) return self * other def negate(self): """Negate a private key.""" assert self.valid self.secret = SECP256K1_ORDER - self.secret @property def is_valid(self): return self.valid @property def is_compressed(self): return self.compressed def get_pubkey(self): """Compute an ECPubKey object for this secret key.""" assert(self.valid) ret = ECPubKey() p = SECP256K1.mul([(SECP256K1_G, self.secret)]) ret.p = p ret.valid = True ret.compressed = self.compressed return ret def sign_ecdsa(self, msg, low_s=True, rfc6979=False): """Construct a DER-encoded ECDSA signature with this key. See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the ECDSA signer algorithm.""" assert(self.valid) z = int.from_bytes(msg, 'big') # Note: no RFC6979 by default, but a simple random nonce (some tests rely on distinct transactions for the same operation) if rfc6979: k = int.from_bytes(rfc6979_nonce(self.secret.to_bytes(32, 'big') + msg), 'big') else: k = random.randrange(1, SECP256K1_ORDER) R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k)])) r = R[0] % SECP256K1_ORDER s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER if low_s and s > SECP256K1_ORDER_HALF: s = SECP256K1_ORDER - s # Represent in DER format. The byte representations of r and s have # length rounded up (255 bits becomes 32 bytes and 256 bits becomes 33 # bytes). rb = r.to_bytes((r.bit_length() + 8) // 8, 'big') sb = s.to_bytes((s.bit_length() + 8) // 8, 'big') return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb def sign_schnorr(self, msg, aux=None): """Create a Schnorr signature (see BIP340).""" if aux is None: aux = bytes(32) assert self.valid assert len(msg) == 32 assert len(aux) == 32 t = (self.secret ^ int.from_bytes(TaggedHash("BIP0340/aux", aux), 'big')).to_bytes(32, 'big') kp = int.from_bytes(TaggedHash("BIP0340/nonce", t + self.get_pubkey().get_bytes() + msg), 'big') % SECP256K1_ORDER assert kp != 0 R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, kp)])) k = kp if SECP256K1.has_even_y(R) else SECP256K1_ORDER - kp e = int.from_bytes(TaggedHash("BIP0340/challenge", R[0].to_bytes(32, 'big') + self.get_pubkey().get_bytes() + msg), 'big') % SECP256K1_ORDER return R[0].to_bytes(32, 'big') + ((k + e * self.secret) % SECP256K1_ORDER).to_bytes(32, 'big') def tweak_add(self, tweak): """Return a tweaked version of this private key.""" assert(self.valid) t = int_or_bytes(tweak) if t >= SECP256K1_ORDER: return None tweaked = (self.secret + t) % SECP256K1_ORDER if tweaked == 0: return None ret = ECKey() ret.set(tweaked.to_bytes(32, 'big'), self.compressed) return ret def generate_key_pair(secret=None, compressed=True): """Convenience function to generate a private-public key pair.""" d = ECKey() if secret: d.set(secret, compressed) else: d.generate(compressed) P = d.get_pubkey() return d, P def generate_bip340_key_pair(): """Convenience function to generate a BIP0340 private-public key pair.""" d = ECKey() d.generate() P = d.get_pubkey() if P.get_y()%2 != 0: d.negate() P.negate() return d, P def generate_schnorr_nonce(): """Generate a random valid BIP340 nonce. See https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki. This implementation ensures the y-coordinate of the nonce point is even.""" kp = random.randrange(1, SECP256K1_ORDER) assert kp != 0 R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, kp)])) k = kp if R[1] % 2 == 0 else SECP256K1_ORDER - kp k_key = ECKey() k_key.set(k.to_bytes(32, 'big'), True) return k_key