diff --git a/test/functional/test_framework/ellswift.py b/test/functional/test_framework/ellswift.py new file mode 100644 index 0000000000000..bcb0b4baf46c2 --- /dev/null +++ b/test/functional/test_framework/ellswift.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test-only Elligator Swift implementation + +WARNING: This code is slow and uses bad randomness. +Do not use for anything but tests.""" + +import os +import hashlib +import unittest + +from .key import ECKey, ECPubKey, FE, GE + +C1 = FE(-3).sqrt() +C2 = -(C1 - FE(1))/2 +B = FE(7) + +def forward_map(u, t): + """Forward mapping function + + Parameters: + FE, FE : any field element + Returns: + FE : X coordinate of a point on the secp256k1 curve + """ + if u == 0: + u = FE(1) + if t == 0: + t = FE(1) + if u**3 + t**2 + B == 0: + t = 2 * t + X = (u**3 - t**2 + B) / (2 * t) + Y = (X + t) / (C1 * u) + x3 = u + 4 * Y**2 + if GE.is_valid_x(x3): + return x3 + x2 = (-X / Y - u) / 2 + if GE.is_valid_x(x2): + return x2 + x1 = (X / Y - u) / 2 + return x1 + +def reverse_map(x, u, i): + """Reverse mapping function + + Parameters: + FE, FE : x is X coordinate of a point, u is a random fe + i : integer in range [0,7] + Returns: + t (of type FE) : such that forward_map(u, t) = x or None + """ + g = u**3 + B + if i&2 == 0: + o = (-x - u)**3 + B + if o.is_square(): + return None + if i&1: + x = -x - u + w = g / (u * x - (x + u)**2) + else: + w = x - u + if w == FE(0): + return None + r = -w * (FE(4) * g + FE(3) * w * u**2) + r = r.sqrt() + if r is None: + return None + if i&1: + if r == FE(0): + return None + r = -r + x = -(r / w + u) / 2 + w = w.sqrt() + if w is None: + return None + if i&4: + w = -w + u = u * C2 + x + t = w * u + return t + +def encode(P, hasher): + cnt = 0 + while True: + if cnt % 64 == 0: + hash = hasher.copy() + hash.update(cnt.to_bytes(4, 'little')) + cnt += 1 + branch_hash = hash.digest() + + j = (branch_hash[(64-cnt) % 64 >> 1] >> (((64-cnt) % 64 & 1) << 2)) & 7 + hash = hasher.copy() + hash.update(cnt.to_bytes(4, 'little')) + cnt += 1 + u = FE(int.from_bytes(hash.digest(), 'big')) + if u == FE(0): + continue + t = reverse_map(P.x, u, j) + if t is None: + continue + if t.is_even() != P.y.is_even(): + t = -t + return u.to_bytes() + t.to_bytes() + +def ellswift_create(privkey, rnd32=bytearray(32)): + """ + generates elligator swift encoding of pubkey + with privkey also used as entropy + Parameters: + privkey : ECKey object + randombytes : 32 bytes entropy + Returns: 64 bytes encoding + """ + m = hashlib.sha256() + m.update(b"secp256k1_ellswift_create") + m.update(bytearray(7)) + m.update(privkey.get_bytes()) + m.update(rnd32) + m.update(bytearray(19)) + pubkey = privkey.get_pubkey() + return encode(pubkey.get_group_element(), m) + +def ellswift_decode(enc): + """ + decodes elligator swift encoding to obtain pubkey + Parameters: + enc : 64 bytes encoding + Returns: ECPubKey object + """ + u, t = FE.from_bytes(enc[:32]), FE.from_bytes(enc[32:]) + x = forward_map(u, t) + curve_point = GE.lift_x(x) + if not t.is_even(): + curve_point = -curve_point + pubkey = ECPubKey() + pubkey.set(curve_point.to_bytes_compressed()) + return pubkey + +def ellswift_ecdh_xonly(ellswift_theirs, secretkey): + their_pubkey = ellswift_decode(ellswift_theirs) + our_privkey = int.from_bytes(secretkey.get_bytes(), "big") + return (our_privkey * their_pubkey.get_group_element()).x.to_bytes() + +class TestFrameworkEllSwift(unittest.TestCase): + def test_create_decode(self): + for _ in range(32): + privkey = ECKey() + privkey.generate() + pubkey1 = privkey.get_pubkey() + rnd32 = os.urandom(32) + encoding = ellswift_create(privkey, rnd32) + pubkey2 = ellswift_decode(encoding) + assert pubkey1.get_bytes() == pubkey2.get_bytes() + + def test_ellswift_ecdh_xonly(self): + for _ in range(32): + randombytes1 = os.urandom(32) + randombytes2 = os.urandom(32) + privkey1 = ECKey() + privkey1.generate() + privkey2 = ECKey() + privkey2.generate() + encoding1 = ellswift_create(privkey1, randombytes1) + encoding2 = ellswift_create(privkey2, randombytes2) + shared_secret1 = ellswift_ecdh_xonly(encoding1, privkey2) + shared_secret2 = ellswift_ecdh_xonly(encoding2, privkey1) + assert shared_secret1 == shared_secret2 diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py index 68afc1383d972..201a1e74cb922 100644 --- a/test/functional/test_framework/key.py +++ b/test/functional/test_framework/key.py @@ -25,233 +25,312 @@ def TaggedHash(tag, data): ss += data return hashlib.sha256(ss).digest() -def jacobi_symbol(n, k): - """Compute the Jacobi symbol of n modulo k - - See https://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. +class FE: + """Objects of this class represent elements of the field GF(2**256 - 2**32 - 977). + + They are represented internally in numerator / denominator form, in order to delay inversions. """ - 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 - -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: + + SIZE = 2**256 - 2**32 - 977 + + def __init__(self, a=0, b=1): + """Initialize an FE as a/b; both a and b can be ints or field elements.""" + if isinstance(b, FE): + if isinstance(a, FE): + self.num = (a.num * b.den) % FE.SIZE + self.den = (a.den * b.num) % FE.SIZE + else: + self.num = (a * b.den) % FE.SIZE + self.den = a.num + else: + b = b % FE.SIZE + assert b != 0 + if isinstance(a, FE): + self.num = a.num + self.den = (a.den * b) % FE.SIZE + else: + self.num = a % FE.SIZE + self.den = b + + def __add__(self, a): + """Compute the sum of two field elements (second may be int).""" + if isinstance(a, FE): + return FE(self.num * a.den + self.den * a.num, self.den * a.den) + else: + return FE(self.num + self.den * a, self.den) + + def __radd__(self, a): + """Compute the sum of an integer and a field element.""" + return FE(self.num + self.den * a, self.den) + + def __sub__(self, a): + """Compute the difference of two field elements (second may be int).""" + if isinstance(a, FE): + return FE(self.num * a.den - self.den * a.num, self.den * a.den) + else: + return FE(self.num + self.den * a, self.den) + + def __rsub__(self, a): + """Compute the difference between an integer and a field element.""" + return FE(self.den * a - self.num, self.den) + + def __mul__(self, a): + """Compute the product of two field elements (second may be int).""" + if isinstance(a, FE): + return FE(self.num * a.num, self.den * a.den) + else: + return FE(self.num * a, self.den) + + def __rmul__(self, a): + """Compute the product of an integer with a field element.""" + return FE(self.num * a, self.den) + + def __truediv__(self, a): + """Compute the ratio of two field elements (second may be int).""" + return FE(self, a) + + def __rtruediv__(self, a): + """Compute the ratio of an integer and a field element.""" + return FE(a, self) + + def __pow__(self, a): + """Raise a field element to a (positive) integer power.""" + return FE(pow(self.num, a, FE.SIZE), pow(self.den, a, FE.SIZE)) + + def __neg__(self): + """Negate a field element.""" + return FE(-self.num, self.den) + + def __int__(self): + """Convert a field element to an integer. The result is cached.""" + if self.den != 1: + self.num = (self.num * modinv(self.den, FE.SIZE)) % FE.SIZE + self.den = 1 + return self.num + + def sqrt(self): + """Compute the square root of a field element. + + Due to the fact that our modulus is of the form (p % 4) == 3, the Tonelli-Shanks + algorithm (https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm) is simply + raising the argument to the power (p + 3) / 4.""" + v = int(self) + s = pow(v, (FE.SIZE + 1) // 4, FE.SIZE) + if s**2 % FE.SIZE == v: + return FE(s) + return None + + def is_square(self): + """Determine if this field element has a square root.""" + # Compute the Jacobi symbol of (self / p). Since our modulus is prime, this + # is the same as the Legendre symbol, which determines quadratic residuosity. + # See https://en.wikipedia.org/wiki/Jacobi_symbol for the algorithm. + n, k, t = (self.num * self.den) % FE.SIZE, FE.SIZE, 0 + if n == 0: + return True + 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 + assert k == 1 + return not t + + def is_even(self): + """Determine whether this field element, represented as integer in 0..p-1, is even.""" + return int(self) & 1 == 0 + + def __eq__(self, a): + """Check whether two field elements are equal (second may be an int).""" + if isinstance(a, FE): + return (self.num * a.den - self.den * a.num) % FE.SIZE == 0 + else: + return (self.num - self.den * a) % FE.SIZE == 0 + + def to_bytes(self): + """Convert a field element to 32-byte big endian encoding.""" + return int(self).to_bytes(32, 'big') + + @staticmethod + def from_bytes(b): + """Convert a 32-byte big endian encoding of a field element to an FE.""" + v = int.from_bytes(b, 'big') + if v >= FE.SIZE: 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 for which the Y coordinate is even.""" - 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 FE(v) + + def __str__(self): + return "0x%064x" % int(self) + +class GE: + """Objects of this class represent points (group elements) on the secp256k1 curve. + + The point at infinity is represented as None.""" + + ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 + ORDER_HALF = ORDER // 2 + + def __init__(self, x, y): + """Initialize a group element with specified x and y coordinates (must be on curve).""" + fx = FE(x) + fy = FE(y) + assert fy**2 == fx**3 + 7 + self.x = fx + self.y = fy + + def double(self): + """Compute the double of a point.""" + l = 3 * self.x**2 / (2 * self.y) + x3 = l**2 - 2 * self.x + y3 = l * (self.x - x3) - self.y + return GE(x3, y3) + + def __add__(self, a): + """Add two points, or a point and infinity, together.""" + if a is None: + # Adding point at infinity + return self + if self.x != a.x: + # Adding distinct x coordinates + l = (a.y - self.y) / (a.x - self.x) + x3 = l**2 - self.x - a.x + y3 = l * (self.x - x3) - self.y + return GE(x3, y3) + elif self.y == a.y: + # Adding point to itself + return self.double() + else: + # Adding point to its negation return None - return (x, self.p - y if y & 1 else 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) + + def __radd__(self, a): + """Add infinity to a point.""" + assert a is None + return self + + def __sub__(self, a): + """Subtract two points, or subtract infinity from a point.""" + if a is None: + return self + return self + (-a) + + def __rsub__(self, a): + """Subtract a point from infinity.""" + assert a is None + return -a + + def __mul__(self, a): + """Multiply a point with an integer (scalar multiplication).""" + r = None + for i in range(a.bit_length() - 1, -1, -1): + if r is not None: + r = r.double() + if (a >> i) & 1: + r += self + return r + + def __rmul__(self, a): + """Multiply an integer with a point (scalar multiplication).""" + return self * a + + @staticmethod + def mmul(*ps): + """Compute a (multi) point multiplication. + + mmul((p1, a1), (p2, a2), (p3, a3)) is identical to p1*a1 + p2*a2 + p3*a3, + but more efficient.""" + r = None for i in range(255, -1, -1): - r = self.double(r) + if r is not None: + r = r.double() for (p, n) in ps: if ((n >> i) & 1): - r = self.add(r, p) + 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 + def __neg__(self): + """Compute the negation of a point.""" + return GE(self.x, -self.y) + + def to_bytes_compressed(self): + """Convert a point to 33-byte compressed encoding.""" + return bytes([3 - self.y.is_even()]) + self.x.to_bytes() + + def to_bytes_uncompressed(self): + """Convert a point to 65-byte uncompressed encoding.""" + return b'\x04' + self.x.to_bytes() + self.y.to_bytes() + + def to_bytes_xonly(self): + """Convert (the X coordinate of) a point to 32-byte xonly encoding.""" + return self.x.to_bytes() + + @staticmethod + def lift_x(x): + """Take an FE, and return the point with that as X coordinate, and even Y.""" + y = (FE(x)**3 + 7).sqrt() + if y is None: + return None + if not y.is_even(): + y = -y + return GE(x, y) + + @staticmethod + def is_valid_x(x): + """Determine whether the provided field element is a valid X coordinate.""" + return (FE(x)**3 + 7).is_square() + + @staticmethod + def from_bytes(b): + """Convert a compressed or uncompressed encoding to a point.""" + if len(b) == 33: + if b[0] != 2 and b[0] != 3: + return None + x = FE.from_bytes(b[1:]) + if x is None: + return None + r = GE.lift_x(x) + if r is None: + return None + if b[0] == 3: + r = -r + return r + if len(b) == 65: + if b[0] != 4: + return None + x = FE.from_bytes(b[1:33]) + y = FE.from_bytes(b[33:]) + if y**2 != x**3 + 7: + return None + return GE(x, y) + + @staticmethod + def from_bytes_xonly(b): + """Convert a point given in xonly encoding to a point.""" + assert len(b) == 32 + x = FE.from_bytes(b) + if x is None: + return None + return GE.lift_x(x) + + def __str__(self): + return "(%s,%s)" % (self.x, self.y) + +SECP256K1_G = GE(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8) class ECPubKey(): """A secp256k1 public key""" def __init__(self): """Construct an uninitialized public key""" - self.valid = False + self.p = None def set(self, data): """Construct a public key from a serialization in compressed or uncompressed 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) - # Make the Y coordinate odd if required (lift_x always produces - # a point with an even Y coordinate). - if data[0] & 1: - p = SECP256K1.negate(p) - self.p = p - self.valid = True - self.compressed = True - else: - self.valid = False - else: - self.valid = False + self.p = GE.from_bytes(data) + self.compressed = len(data) == 33 @property def is_compressed(self): @@ -259,24 +338,25 @@ def is_compressed(self): @property def is_valid(self): - return self.valid + return self.p is not None def get_bytes(self): - assert(self.valid) - p = SECP256K1.affine(self.p) - if p is None: - return None + assert(self.is_valid) if self.compressed: - return bytes([0x02 + (p[1] & 1)]) + p[0].to_bytes(32, 'big') + return self.p.to_bytes_compressed() else: - return bytes([0x04]) + p[0].to_bytes(32, 'big') + p[1].to_bytes(32, 'big') + return self.p.to_bytes_uncompressed() + + def get_group_element(self): + assert self.is_valid + return self.p 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) + assert(self.is_valid) # Extract r and s from the DER formatted signature. Return false for # any DER encoding errors. @@ -312,24 +392,24 @@ def verify_ecdsa(self, sig, msg, low_s=True): 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: + if r < 1 or s < 1 or r >= GE.ORDER or s >= GE.ORDER: return False - if low_s and s >= SECP256K1_ORDER_HALF: + if low_s and s >= GE.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] % SECP256K1_ORDER) != r: + w = modinv(s, GE.ORDER) + u1 = z*w % GE.ORDER + u2 = r*w % GE.ORDER + R = GE.mmul((SECP256K1_G, u1), (self.p, u2)) + if R is None or (int(R.x) % GE.ORDER) != r: return False return True def generate_privkey(): """Generate a valid random 32-byte private key.""" - return random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big') + return random.randrange(1, GE.ORDER).to_bytes(32, 'big') def rfc6979_nonce(key): """Compute signing nonce using RFC6979.""" @@ -351,7 +431,7 @@ def set(self, secret, compressed): """Construct a private key object with given 32-byte secret and compressed flag.""" assert(len(secret) == 32) secret = int.from_bytes(secret, 'big') - self.valid = (secret > 0 and secret < SECP256K1_ORDER) + self.valid = (secret > 0 and secret < GE.ORDER) if self.valid: self.secret = secret self.compressed = compressed @@ -377,9 +457,7 @@ 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.p = self.secret * SECP256K1_G ret.compressed = self.compressed return ret @@ -394,12 +472,12 @@ def sign_ecdsa(self, msg, low_s=True, rfc6979=False): 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 + k = random.randrange(1, GE.ORDER) + R = k * SECP256K1_G + r = int(R.x) % GE.ORDER + s = (modinv(k, GE.ORDER) * (z + self.secret * r)) % GE.ORDER + if low_s and s > GE.ORDER_HALF: + s = GE.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). @@ -415,10 +493,10 @@ def compute_xonly_pubkey(key): assert len(key) == 32 x = int.from_bytes(key, 'big') - if x == 0 or x >= SECP256K1_ORDER: + if x == 0 or x >= GE.ORDER: return (None, None) - P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, x)])) - return (P[0].to_bytes(32, 'big'), not SECP256K1.has_even_y(P)) + P = x * SECP256K1_G + return (P.to_bytes_xonly(), not P.y.is_even()) def tweak_add_privkey(key, tweak): """Tweak a private key (after negating it if needed).""" @@ -427,14 +505,14 @@ def tweak_add_privkey(key, tweak): assert len(tweak) == 32 x = int.from_bytes(key, 'big') - if x == 0 or x >= SECP256K1_ORDER: + if x == 0 or x >= GE.ORDER: return None - if not SECP256K1.has_even_y(SECP256K1.mul([(SECP256K1_G, x)])): - x = SECP256K1_ORDER - x + if not (x * SECP256K1_G).y.is_even(): + x = GE.ORDER - x t = int.from_bytes(tweak, 'big') - if t >= SECP256K1_ORDER: + if t >= GE.ORDER: return None - x = (x + t) % SECP256K1_ORDER + x = (x + t) % GE.ORDER if x == 0: return None return x.to_bytes(32, 'big') @@ -445,19 +523,16 @@ def tweak_add_pubkey(key, tweak): assert len(key) == 32 assert len(tweak) == 32 - x_coord = int.from_bytes(key, 'big') - if x_coord >= SECP256K1_FIELD_SIZE: - return None - P = SECP256K1.lift_x(x_coord) + P = GE.from_bytes_xonly(key) if P is None: return None t = int.from_bytes(tweak, 'big') - if t >= SECP256K1_ORDER: + if t >= GE.ORDER: return None - Q = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, t), (P, 1)])) + Q = t * SECP256K1_G + P if Q is None: return None - return (Q[0].to_bytes(32, 'big'), not SECP256K1.has_even_y(Q)) + return (Q.to_bytes_xonly(), not Q.y.is_even()) def verify_schnorr(key, sig, msg): """Verify a Schnorr signature (see BIP 340). @@ -470,23 +545,20 @@ def verify_schnorr(key, sig, msg): assert len(msg) == 32 assert len(sig) == 64 - x_coord = int.from_bytes(key, 'big') - if x_coord == 0 or x_coord >= SECP256K1_FIELD_SIZE: - return False - P = SECP256K1.lift_x(x_coord) + P = GE.from_bytes_xonly(key) if P is None: return False r = int.from_bytes(sig[0:32], 'big') - if r >= SECP256K1_FIELD_SIZE: + if r >= FE.SIZE: return False s = int.from_bytes(sig[32:64], 'big') - if s >= SECP256K1_ORDER: + if s >= GE.ORDER: return False - e = int.from_bytes(TaggedHash("BIP0340/challenge", sig[0:32] + key + msg), 'big') % SECP256K1_ORDER - R = SECP256K1.mul([(SECP256K1_G, s), (P, SECP256K1_ORDER - e)]) - if not SECP256K1.has_even_y(R): + e = int.from_bytes(TaggedHash("BIP0340/challenge", sig[0:32] + key + msg), 'big') % GE.ORDER + R = GE.mmul((SECP256K1_G, s), (P, GE.ORDER - e)) + if R is None or not R.y.is_even(): return False - if ((r * R[2] * R[2]) % SECP256K1_FIELD_SIZE) != R[0]: + if r != R.x: return False return True @@ -501,23 +573,23 @@ def sign_schnorr(key, msg, aux=None, flip_p=False, flip_r=False): assert len(aux) == 32 sec = int.from_bytes(key, 'big') - if sec == 0 or sec >= SECP256K1_ORDER: + if sec == 0 or sec >= GE.ORDER: return None - P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, sec)])) - if SECP256K1.has_even_y(P) == flip_p: - sec = SECP256K1_ORDER - sec + P = sec * SECP256K1_G + if P.y.is_even() == flip_p: + sec = GE.ORDER - sec t = (sec ^ int.from_bytes(TaggedHash("BIP0340/aux", aux), 'big')).to_bytes(32, 'big') - kp = int.from_bytes(TaggedHash("BIP0340/nonce", t + P[0].to_bytes(32, 'big') + msg), 'big') % SECP256K1_ORDER + kp = int.from_bytes(TaggedHash("BIP0340/nonce", t + P.to_bytes_xonly() + msg), 'big') % GE.ORDER assert kp != 0 - R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, kp)])) - k = kp if SECP256K1.has_even_y(R) != flip_r else SECP256K1_ORDER - kp - e = int.from_bytes(TaggedHash("BIP0340/challenge", R[0].to_bytes(32, 'big') + P[0].to_bytes(32, 'big') + msg), 'big') % SECP256K1_ORDER - return R[0].to_bytes(32, 'big') + ((k + e * sec) % SECP256K1_ORDER).to_bytes(32, 'big') + R = kp * SECP256K1_G + k = kp if R.y.is_even() != flip_r else GE.ORDER - kp + e = int.from_bytes(TaggedHash("BIP0340/challenge", R.to_bytes_xonly() + P.to_bytes_xonly() + msg), 'big') % GE.ORDER + return R.to_bytes_xonly() + ((k + e * sec) % GE.ORDER).to_bytes(32, 'big') class TestFrameworkKey(unittest.TestCase): def test_schnorr(self): """Test the Python Schnorr implementation.""" - byte_arrays = [generate_privkey() for _ in range(3)] + [v.to_bytes(32, 'big') for v in [0, SECP256K1_ORDER - 1, SECP256K1_ORDER, 2**256 - 1]] + byte_arrays = [generate_privkey() for _ in range(3)] + [v.to_bytes(32, 'big') for v in [0, GE.ORDER - 1, GE.ORDER, 2**256 - 1]] keys = {} for privkey in byte_arrays: # build array of key/pubkey pairs pubkey, _ = compute_xonly_pubkey(privkey) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index bfc835f27213e..41bdab6ceda6d 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -16,6 +16,7 @@ import re import time import unittest +import sys from . import coverage from .authproxy import AuthServiceProxy, JSONRPCException @@ -562,7 +563,12 @@ def modinv(a, n): """Compute the modular inverse of a modulo n using the extended Euclidean Algorithm. See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers. """ - # TODO: Change to pow(a, -1, n) available in Python 3.8 + a = a % n + if a == 0: + return 0 + if sys.hexversion >= 0x3080000: + # More efficient version available in Python 3.8. + return pow(a, -1, n) t1, t2 = 0, 1 r1, r2 = n, a while r2 != 0: diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index d78c1c634fe3a..3f48626ad323b 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -70,8 +70,9 @@ TEST_FRAMEWORK_MODULES = [ "address", "blocktools", - "muhash", + "ellswift", "key", + "muhash", "script", "segwit_addr", "util",