def _pubkey_recovery(ec: Curve, c: int, sig: ECDS) -> Sequence[Point]: """Private function provided for testing purposes only.""" # ECDSA public key recovery operation according to SEC 1 # http://www.secg.org/sec1-v2.pdf # See SEC 1 v.2 section 4.1.6 r, s = _to_sig(ec, sig) # precomputations r1 = mod_inv(r, ec.n) r1s = r1 * s r1e = -r1 * c keys: Sequence[Point] = list() for j in range(ec.h): # 1 x = r + j * ec.n # 1.1 try: #TODO: check test reporting 1, 2, 3, or 4 keys x %= ec._p R = x, ec.y_odd(x, 1) # 1.2, 1.3, and 1.4 # skip 1.5: in this function, c is an input Q = double_mult(ec, r1s, R, r1e, ec.G) # 1.6.1 if Q[1] != 0 and _verhlp(ec, c, Q, sig): # 1.6.2 keys.append(Q) R = ec.opposite(R) # 1.6.3 Q = double_mult(ec, r1s, R, r1e, ec.G) if Q[1] != 0 and _verhlp(ec, c, Q, sig): # 1.6.2 keys.append(Q) # 1.6.2 except Exception: # R is not a curve point pass return keys
def point_from_octets(ec: Curve, o: octets) -> Point: """Return a tuple (Px, Py) that belongs to the curve SEC 1 v.2, section 2.3.4 """ if isinstance(o, str): o = bytes.fromhex(o) bsize = len(o) # bytes if bsize == 1 and o[0] == 0x00: # infinity point return Point() if bsize == ec.psize + 1: # compressed point if o[0] not in (0x02, 0x03): m = f"{ec.psize+1} bytes, but not a compressed point" raise ValueError(m) Px = int.from_bytes(o[1:], 'big') try: Py = ec.y_odd(Px, o[0] % 2) # also check Px validity return Point(Px, Py) except: raise ValueError("point not on curve") else: # uncompressed point if bsize != 2 * ec.psize + 1: m = f"wrong byte-size ({bsize}) for a point: it " m += f"should have be {ec.psize+1} or {2*ec.psize+1}" raise ValueError(m) if o[0] != 0x04: raise ValueError("not an uncompressed point") Px = int.from_bytes(o[1:ec.psize + 1], 'big') P = Point(Px, int.from_bytes(o[ec.psize + 1:], 'big')) if ec.is_on_curve(P): return P else: raise ValueError("point not on curve")
def _batch_verify(ec: Curve, hf, ms: List[bytes], P: List[Point], sig: List[ECSS]) -> bool: t = 0 scalars: List(int) = list() points: List[Point] = list() for i in range(len(P)): _ensure_msg_size(hf, ms[i]) ec.require_on_curve(P[i]) r, s = _to_sig(ec, sig[i]) e = _e(ec, hf, r, P[i], ms[i]) y = ec.y(r) # raises an error if y does not exist # deterministically generated using a CSPRNG seeded by a cryptographic # hash (e.g., SHA256) of all inputs of the algorithm, or randomly # generated independently for each run of the batch verification # algorithm FIXME a = (1 if i == 0 else random.getrandbits(ec.nlen) % ec.n) scalars.append(a) points.append(_jac_from_aff((r, y))) scalars.append(a * e % ec.n) points.append(_jac_from_aff(P[i])) t += a * s % ec.n TJ = _mult_jac(ec, t, ec.GJ) RHSJ = _multi_mult(ec, scalars, points) # return T == RHS, checked in Jacobian coordinates RHSZ2 = RHSJ[2] * RHSJ[2] TZ2 = TJ[2] * TJ[2] if (TJ[0] * RHSZ2) % ec._p != (RHSJ[0] * TZ2) % ec._p: return False return (TJ[1] * RHSZ2 * RHSJ[2]) % ec._p == (RHSJ[1] * TZ2 * TJ[2]) % ec._p
def _verhlp(ec: Curve, c: int, P: Point, sig: ECDS) -> bool: # Private function for test/dev purposes # Fail if r is not [1, n-1] # Fail if s is not [1, n-1] r, s = _to_sig(ec, sig) # 1 # Let P = point(pk); fail if point(pk) fails. ec.require_on_curve(P) if P[1] == 0: raise ValueError("public key is infinite") w = mod_inv(s, ec.n) u = c * w v = r * w # 4 # Let R = u*G + v*P. RJ = _double_mult(ec, u, ec.GJ, v, (P[0], P[1], 1)) # 5 # Fail if infinite(R). assert RJ[2] != 0, "how did you do that?!?" # 5 Rx = (RJ[0] * mod_inv(RJ[2] * RJ[2], ec._p)) % ec._p x = Rx % ec.n # 6, 7 # Fail if r ≠ x(R) %n. return r == x # 8
def _batch_verify(ec: Curve, hf: Callable[[Any], Any], ms: Sequence[bytes], P: Sequence[Point], sig: Sequence[ECSS]) -> bool: # the bitcoin proposed standard is only valid for curves # whose prime p = 3 % 4 if not ec.pIsThreeModFour: errmsg = 'curve prime p must be equal to 3 (mod 4)' raise ValueError(errmsg) batch_size = len(P) if len(ms) != batch_size: errMsg = f"mismatch between number of pubkeys ({batch_size}) " errMsg += f"and number of messages ({len(ms)})" raise ValueError(errMsg) if len(sig) != batch_size: errMsg = f"mismatch between number of pubkeys ({batch_size}) " errMsg += f"and number of signatures ({len(sig)})" raise ValueError(errMsg) if batch_size == 1: return _verify(ec, hf, ms[0], P[0], sig[0]) t = 0 scalars: Sequence(int) = list() points: Sequence[Point] = list() for i in range(batch_size): r, s = _to_sig(ec, sig[i]) _ensure_msg_size(hf, ms[i]) ec.require_on_curve(P[i]) e = _e(ec, hf, r, P[i], ms[i]) # raises an error if y does not exist # no need to check for quadratic residue y = ec.y(r) # a in [1, n-1] # deterministically generated using a CSPRNG seeded by a # cryptographic hash (e.g., SHA256) of all inputs of the # algorithm, or randomly generated independently for each # run of the batch verification algorithm a = (1 if i == 0 else (1 + random.getrandbits(ec.nlen)) % ec.n) scalars.append(a) points.append(_jac_from_aff((r, y))) scalars.append(a * e % ec.n) points.append(_jac_from_aff(P[i])) t += a * s TJ = _mult_jac(ec, t, ec.GJ) RHSJ = _multi_mult(ec, scalars, points) # return T == RHS, checked in Jacobian coordinates RHSZ2 = RHSJ[2] * RHSJ[2] TZ2 = TJ[2] * TJ[2] if (TJ[0] * RHSZ2) % ec._p != (RHSJ[0] * TZ2) % ec._p: return False return (TJ[1] * RHSZ2 * RHSJ[2]) % ec._p == (RHSJ[1] * TZ2 * TJ[2]) % ec._p
def octets_from_point(ec: Curve, Q: Point, compressed: bool) -> bytes: """Return a compressed (0x02, 0x03) or uncompressed (0x04) point as octets SEC 1 v.2, section 2.3.3 """ # check that Q is a point and that is on curve ec.require_on_curve(Q) if Q[1] == 0: # infinity point in affine coordinates return b'\x00' bPx = Q[0].to_bytes(ec.psize, byteorder='big') if compressed: return (b'\x03' if (Q[1] & 1) else b'\x02') + bPx return b'\x04' + bPx + Q[1].to_bytes(ec.psize, byteorder='big')
def _verify(ec: Curve, hf: Callable[[Any], Any], mhd: bytes, P: Point, sig: ECSS) -> bool: # This raises Exceptions, while verify should always return True or False # the bitcoin proposed standard is only valid for curves # whose prime p = 3 % 4 if not ec.pIsThreeModFour: errmsg = 'curve prime p must be equal to 3 (mod 4)' raise ValueError(errmsg) # Let r = int(sig[ 0:32]). # Let s = int(sig[32:64]); fail if s is not [0, n-1]. r, s = _to_sig(ec, sig) # The message mhd: a 32-byte array _ensure_msg_size(hf, mhd) # Let P = point(pk); fail if point(pk) fails. ec.require_on_curve(P) if P[1] == 0: raise ValueError("public key is infinite") # Let e = int(hf(bytes(r) || bytes(P) || mhd)) mod n. e = _e(ec, hf, r, P, mhd) # Let R = sG - eP. # in Jacobian coordinates R = _double_mult(ec, s, ec.GJ, -e, (P[0], P[1], 1)) # Fail if infinite(R). if R[2] == 0: raise ValueError("sG - eP is infinite") # Fail if jacobi(R.y) ≠ 1. if legendre_symbol(R[1] * R[2] % ec._p, ec._p) != 1: raise ValueError("(sG - eP).y is not a quadratic residue") # Fail if R.x ≠ r. return R[0] == (R[2] * R[2] * r % ec._p)
def test_exceptions(self): # good Curve(11, 2, 7, (6, 9), 7, 2, False) # p not odd self.assertRaises(ValueError, Curve, 10, 2, 7, (6, 9), 7, 1, False) # p not prime self.assertRaises(ValueError, Curve, 15, 2, 7, (6, 9), 7, 1, False) # a > p self.assertRaises(ValueError, Curve, 11, 12, 7, (6, 9), 13, 1, False) # b > p self.assertRaises(ValueError, Curve, 11, 2, 12, (6, 9), 13, 1, False) # zero discriminant self.assertRaises(ValueError, Curve, 11, 7, 7, (6, 9), 7, 1, False) # G not Tuple (int, int) self.assertRaises(ValueError, Curve, 11, 2, 7, (6, 9, 1), 7, 1, False) # G not on curve self.assertRaises(ValueError, Curve, 11, 2, 7, (7, 9), 7, 1, False) # n not prime self.assertRaises(ValueError, Curve, 11, 2, 7, (6, 9), 8, 1, False) # n not Hesse self.assertRaises(ValueError, Curve, 11, 2, 7, (6, 9), 71, 1, True) # h not as expected self.assertRaises(ValueError, Curve, 11, 2, 7, (6, 9), 7, 1, True) # Curve(11, 2, 7, (6, 9), 7, 1, 0, True) # n not group order self.assertRaises(ValueError, Curve, 11, 2, 7, (6, 9), 13, 1, False) # n=p -> weak curve # missing # weak curve self.assertRaises(UserWarning, Curve, 11, 2, 7, (6, 9), 7, 2, True) # x-coordinate not in [0, p-1] ec = CURVES['secp256k1'] self.assertRaises(ValueError, ec.y, ec.p) # secp256k1.y(secp256k1.p) # INF point does not generate a prime order subgroup self.assertRaises(ValueError, Curve, 11, 2, 7, INF, 7, 2, False)
def test_jac(): ec = Curve(13, 0, 2, (1, 9), 19, 1, False) assert ec._jac_equality(ec.GJ, _jac_from_aff(ec.G)) # q in [2, n-1] q = 2 + secrets.randbelow(ec.n - 2) Q = _mult_aff(q, ec.G, ec) QJ = _mult_jac(q, ec.GJ, ec) assert ec._jac_equality(QJ, _jac_from_aff(Q)) assert not ec._jac_equality(QJ, ec.negate(QJ)) assert not ec._jac_equality(QJ, ec.GJ)
def test_jac_equality() -> None: ec = Curve(13, 0, 2, (1, 9), 19, 1, False) assert ec._jac_equality(ec.GJ, _jac_from_aff(ec.G)) # q in [2, n-1], as the difference with ec.GJ is checked below q = 2 + secrets.randbelow(ec.n - 2) Q = _mult_aff(q, ec.G, ec) QJ = _mult(q, ec.GJ, ec) assert ec._jac_equality(QJ, _jac_from_aff(Q)) assert not ec._jac_equality(QJ, ec.negate_jac(QJ)) assert not ec._jac_equality(QJ, ec.GJ)
def _pubkey_recovery(ec: Curve, hf, e: int, sig: ECSS) -> Point: # Private function provided for testing purposes only. r, s = _to_sig(ec, sig) K = r, ec.y_quadratic_residue(r, True) # FIXME y_quadratic_residue in Jacobian coordinates? if e == 0: raise ValueError("invalid (zero) challenge e") e1 = mod_inv(e, ec.n) P = double_mult(ec, e1 * s, ec.G, -e1, K) assert P[1] != 0, "how did you do that?!?" return P
def verify_commit(c: bytes, ec: Curve, hf, receipt: Receipt) -> bool: w, R = receipt # w in [1..n-1] dsa # w in [1..p-1] ssa # different verify functions? # verify R is a good point? ch = hf(c).digest() e = hf(octets_from_point(ec, R, True) + ch).digest() e = int_from_bits(ec, e) W = ec.add(R, mult(ec, e, ec.G)) # different verify functions? # return w == W[0] # ECSS return w == W[0] % ec.n # ECDS, FIXME: ECSSA
def _verhlp(ec: Curve, e: int, P: Point, sig: ECDS) -> bool: """Private function provided for testing purposes only.""" # Fail if r is not [1, n-1] # Fail if s is not [1, n-1] r, s = _to_sig(ec, sig) # 1 # Let P = point(pk); fail if point(pk) fails. ec.require_on_curve(P) if P[1] == 0: raise ValueError("public key is infinite") s1 = mod_inv(s, ec.n) u1 = e*s1 u2 = r*s1 # 4 # Let R = u*G + v*P. RJ = _double_mult(ec, u1, ec.GJ, u2, (P[0], P[1], 1)) # 5 # Fail if infinite(R). assert RJ[2] != 0, "how did you do that?!?" # 5 Rx = (RJ[0]*mod_inv(RJ[2]*RJ[2], ec._p)) % ec._p v = Rx % ec.n # 6, 7 # Fail if r ≠ x(R) %n. return r == v # 8
def second_generator(ec: Curve, hf) -> Point: """Nothing-Up-My-Sleeve (NUMS) second generator H wrt ec.G source: https://github.com/ElementsProject/secp256k1-zkp/blob/secp256k1-zkp/src/modules/rangeproof/main_impl.h idea: https://crypto.stackexchange.com/questions/25581/second-generator-for-secp256k1-curve Get the hash of G, then coerce it to a point (hx, hy). The resulting point could not be a curvepoint: in this case keep on incrementing hx until a valid curve point (hx, hy) is obtained. """ G_bytes = octets_from_point(ec, ec.G, False) hd = hf(G_bytes).digest() hx = int_from_bits(ec, hd) isCurvePoint = False while not isCurvePoint: try: hy = ec.y_odd(hx, False) isCurvePoint = True except: hx += 1 return Point(hx, hy)
def test_exceptions(): # good curve Curve(13, 0, 2, (1, 9), 19, 1, False) with pytest.raises(ValueError, match="p is not prime: "): Curve(15, 0, 2, (1, 9), 19, 1, False) with pytest.raises(ValueError, match="negative a: "): Curve(13, -1, 2, (1, 9), 19, 1, False) with pytest.raises(ValueError, match="p <= a: "): Curve(13, 13, 2, (1, 9), 19, 1, False) with pytest.raises(ValueError, match="negative b: "): Curve(13, 0, -2, (1, 9), 19, 1, False) with pytest.raises(ValueError, match="p <= b: "): Curve(13, 0, 13, (1, 9), 19, 1, False) with pytest.raises(ValueError, match="zero discriminant"): Curve(11, 7, 7, (1, 9), 19, 1, False) err_msg = "Generator must a be a sequence\\[int, int\\]" with pytest.raises(ValueError, match=err_msg): Curve(13, 0, 2, (1, 9, 1), 19, 1, False) with pytest.raises(ValueError, match="Generator is not on the curve"): Curve(13, 0, 2, (2, 9), 19, 1, False) with pytest.raises(ValueError, match="n is not prime: "): Curve(13, 0, 2, (1, 9), 20, 1, False) with pytest.raises(ValueError, match="n not in "): Curve(13, 0, 2, (1, 9), 71, 1, False) with pytest.raises(ValueError, match="INF point cannot be a generator"): Curve(13, 0, 2, INF, 19, 1, False) with pytest.raises(ValueError, match="n is not the group order: "): Curve(13, 0, 2, (1, 9), 17, 1, False) with pytest.raises(ValueError, match="invalid h: "): Curve(13, 0, 2, (1, 9), 19, 2, False) # n=p -> weak curve # missing with pytest.raises(UserWarning, match="weak curve"): Curve(11, 2, 7, (6, 9), 7, 2, True)
from btclib.curve import Curve # low cardinality curves p<100 ec11_7 = Curve(11, 2, 7, (6, 9), 7, 2, 0, False) ec11_17 = Curve(11, 2, 4, (0, 9), 17, 1, 0, False) ec13_11 = Curve(13, 7, 6, (1, 1), 11, 1, 0, False) ec13_19 = Curve(13, 0, 2, (1, 9), 19, 1, 0, False) ec17_13 = Curve(17, 6, 8, (0, 12), 13, 2, 0, False) ec17_23 = Curve(17, 3, 5, (1, 14), 23, 1, 0, False) ec19_13 = Curve(19, 0, 2, (4, 16), 13, 2, 0, False) ec19_23 = Curve(19, 2, 9, (0, 16), 23, 1, 0, False) ec23_19 = Curve(23, 9, 7, (5, 4), 19, 1, 0, False) ec23_31 = Curve(23, 5, 1, (0, 1), 31, 1, 0, False) ec29_37 = Curve(29, 4, 9, (0, 26), 37, 1, 0, False) ec31_23 = Curve(31, 4, 7, (0, 10), 23, 1, 0, False) ec31_43 = Curve(31, 0, 3, (1, 2), 43, 1, 0, False) ec37_31 = Curve(37, 2, 8, (1, 23), 31, 1, 0, False) ec37_43 = Curve(37, 2, 9, (0, 34), 43, 1, 0, False) ec41_37 = Curve(41, 2, 6, (1, 38), 37, 1, 0, False) ec41_53 = Curve(41, 4, 4, (0, 2), 53, 1, 0, False) ec43_37 = Curve(43, 1, 5, (2, 31), 37, 1, 0, False) ec43_47 = Curve(43, 1, 3, (2, 23), 47, 1, 0, False) ec47_41 = Curve(47, 3, 9, (0, 3), 41, 1, 0, False) ec47_61 = Curve(47, 3, 5, (1, 3), 61, 1, 0, False) ec53_47 = Curve(53, 9, 4, (0, 51), 47, 1, 0, False) ec53_61 = Curve(53, 1, 8, (1, 13), 61, 1, 0, False) ec59_53 = Curve(59, 9, 3, (0, 48), 53, 1, 0, False) ec59_73 = Curve(59, 3, 3, (0, 48), 73, 1, 0, False) ec61_59 = Curve(61, 2, 5, (0, 35), 59, 1, 0, False) ec61_73 = Curve(61, 1, 9, (0, 58), 73, 1, 0, False) ec67_61 = Curve(67, 3, 8, (2, 25), 61, 1, 0, False)
"Tests for `btclib.secpoint` module." import secrets from typing import Dict import pytest from btclib.curve import Curve, _mult_aff from btclib.curves import CURVES from btclib.secpoint import bytes_from_point, point_from_octets # test curves: very low cardinality low_card_curves: Dict[str, Curve] = {} # 13 % 4 = 1; 13 % 8 = 5 low_card_curves["ec13_11"] = Curve(13, 7, 6, (1, 1), 11, 1, False) low_card_curves["ec13_19"] = Curve(13, 0, 2, (1, 9), 19, 1, False) # 17 % 4 = 1; 17 % 8 = 1 low_card_curves["ec17_13"] = Curve(17, 6, 8, (0, 12), 13, 2, False) low_card_curves["ec17_23"] = Curve(17, 3, 5, (1, 14), 23, 1, False) # 19 % 4 = 3; 19 % 8 = 3 low_card_curves["ec19_13"] = Curve(19, 0, 2, (4, 16), 13, 2, False) low_card_curves["ec19_23"] = Curve(19, 2, 9, (0, 16), 23, 1, False) # 23 % 4 = 3; 23 % 8 = 7 low_card_curves["ec23_19"] = Curve(23, 9, 7, (5, 4), 19, 1, False) low_card_curves["ec23_31"] = Curve(23, 5, 1, (0, 1), 31, 1, False) all_curves: Dict[str, Curve] = {} all_curves.update(low_card_curves) all_curves.update(CURVES)
def test_exceptions(self): # good Curve(11, 2, 7, (6, 9), 7, 2, 0, False) # p not odd self.assertRaises(ValueError, Curve, 10, 2, 7, (6, 9), 7, 1, 0, False) # p not prime self.assertRaises(ValueError, Curve, 15, 2, 7, (6, 9), 7, 1, 0, False) # required security level not in the allowed range ec = secp112r1 p = ec._p a = ec._a b = ec._b G = ec.G n = ec.n t = ec.t h = ec.h self.assertRaises(UserWarning, Curve, p, a, b, G, n, h, 273) #Curve(p, a, b, G, n, h, 273) # not enough bits for required security level ec = secp160r1 p = ec._p a = ec._a b = ec._b G = ec.G n = ec.n t = ec.t h = ec.h self.assertRaises(UserWarning, Curve, p, a, b, G, n, h, 2*t) #Curve(p, a, b, G, n, h, 2*t) # a > p self.assertRaises(ValueError, Curve, 11, 12, 7, (6, 9), 13, 1, 0, False) # b > p self.assertRaises(ValueError, Curve, 11, 2, 12, (6, 9), 13, 1, 0, False) # zero discriminant self.assertRaises(ValueError, Curve, 11, 7, 7, (6, 9), 7, 1, 0, False) # G not Tuple (int, int) self.assertRaises(ValueError, Curve, 11, 2, 7, (6, 9, 1), 7, 1, 0, False) # G not on curve self.assertRaises(ValueError, Curve, 11, 2, 7, (7, 9), 7, 1, 0, False) # n not prime self.assertRaises(ValueError, Curve, 11, 2, 7, (6, 9), 8, 1, 0, False) # n not Hesse self.assertRaises(ValueError, Curve, 11, 2, 7, (6, 9), 71, 1, 0, True) # h not as expected self.assertRaises(ValueError, Curve, 11, 2, 7, (6, 9), 7, 1, 0, True) #Curve(11, 2, 7, (6, 9), 7, 1, 0, True) # n not group order self.assertRaises(ValueError, Curve, 11, 2, 7, (6, 9), 13, 1, 0, False) # n=p -> weak curve # missing # weak curve self.assertRaises(UserWarning, Curve, 11, 2, 7, (6, 9), 7, 2, 0, True) # x-coordinate not in [0, p-1] self.assertRaises(ValueError, secp256k1.y, secp256k1._p)
# scroll down at the end of the file for 'relevant' code from btclib.curve import Curve # SEC 2 v.1 curves, removed from SEC 2 v.2 as insecure ones # http://www.secg.org/SEC2-Ver-1.0.pdf __p = (2**128 - 3) // 76439 __a = 0xDB7C2ABF62E35E668076BEAD2088 __b = 0x659EF8BA043916EEDE8911702B22 __Gx = 0x09487239995A5EE76B55F9C2F098 __Gy = 0xA89CE5AF8724C0A23E0E0FF77500 __n = 0xDB7C2ABF62E35E7628DFAC6561C5 __h = 1 secp112r1 = Curve(__p, __a, __b, (__Gx, __Gy), __n, __h, 56, True) __p = (2**128 - 3) // 76439 __a = 0x6127C24C05F38A0AAAF65C0EF02C __b = 0x51DEF1815DB5ED74FCC34C85D709 __Gx = 0x4BA30AB5E892B4E1649DD0928643 __Gy = 0xADCD46F5882E3747DEF36E956E97 __n = 0x36DF0AAFD8B8D7597CA10520D04B __h = 4 secp112r2 = Curve(__p, __a, __b, (__Gx, __Gy), __n, __h, 56, False) __p = 2**128 - 2**97 - 1 __a = 0xFFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFC __b = 0xE87579C11079F43DD824993C2CEE5ED3 __Gx = 0x161FF7528B899B2D0C28607CA52C5B86 __Gy = 0xCF5AC8395BAFEB13C02DA292DDED7A83