def test_mult(): for ec in low_card_curves.values(): for q in range(ec.n): Q = _mult_aff(q, ec.G, ec) QJ = _mult_jac(q, ec.GJ, ec) assert Q == ec._aff_from_jac(QJ) assert INF == _mult_aff(q, INF, ec) assert INFJ == _mult_jac(q, INFJ, ec)
def test_mult(self): for ec in low_card_curves.values(): for q in range(ec.n): Q = _mult_aff(q, ec.G, ec) QJ = _mult_jac(q, ec.GJ, ec) Q2 = ec._aff_from_jac(QJ) self.assertEqual(Q, Q2) # with last curve self.assertEqual(INF, _mult_aff(3, INF, ec)) self.assertEqual(INFJ, _mult_jac(3, INFJ, ec))
def test_mult(self): for ec in low_card_curves: for q in range(ec.n): Q = _mult_aff(ec, q, ec.G) Qjac = _mult_jac(ec, q, ec.GJ) Q2 = ec._aff_from_jac(Qjac) self.assertEqual(Q, Q2) # with last curve self.assertEqual(Inf, _mult_aff(ec, 3, Inf)) self.assertEqual(InfJ, _mult_jac(ec, 3, InfJ))
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 _sign(ec: Curve, e: int, d: int, k: int) -> Tuple[int, int]: """Private function provided for testing purposes only.""" # e is assumed to be valid # Steps numbering follows SEC 1 v.2 section 4.1.3 # The secret key d: an integer in the range 1..n-1. # SEC 1 v.2 section 3.2.1 if not 0 < d < ec.n: raise ValueError(f"private key {hex(d)} not in (0, n)") # Fail if k' = 0. if not 0 < k < ec.n: raise ValueError(f"ephemeral key {hex(k)} not in (0, n)") # Let R = k'G. RJ = _mult_jac(ec, k, ec.GJ) # 1 Rx = (RJ[0]*mod_inv(RJ[2]*RJ[2], ec._p)) % ec._p r = Rx % ec.n # 2, 3 if r == 0: # r≠0 required as it multiplies the public key raise ValueError("r = 0, failed to sign") s = mod_inv(k, ec.n) * (e + r*d) % ec.n # 6 if s == 0: # s≠0 required as verify will need the inverse of s raise ValueError("s = 0, failed to sign") # bitcoin canonical 'low-s' encoding for ECDSA signatures # it removes signature malleability as cause of transaction malleability # see https://github.com/bitcoin/bitcoin/pull/6769 if s > ec.n / 2: s = ec.n - s return r, s
def test_assorted_mult(): ec = ec23_31 H = second_generator(ec) HJ = _jac_from_aff(H) for k1 in range(ec.n): K1J = _mult_jac(k1, ec.GJ, ec) for k2 in range(ec.n): K2J = _mult_jac(k2, HJ, ec) shamir = _double_mult(k1, ec.GJ, k2, ec.GJ, ec) assert ec._jac_equality(shamir, _mult_jac(k1 + k2, ec.GJ, ec)) shamir = _double_mult(k1, INFJ, k2, HJ, ec) assert ec._jac_equality(shamir, K2J) shamir = _double_mult(k1, ec.GJ, k2, INFJ, ec) assert ec._jac_equality(shamir, K1J) shamir = _double_mult(k1, ec.GJ, k2, HJ, ec) K1JK2J = ec._add_jac(K1J, K2J) assert ec._jac_equality(K1JK2J, shamir) k3 = 1 + secrets.randbelow(ec.n - 1) K3J = _mult_jac(k3, ec.GJ, ec) K1JK2JK3J = ec._add_jac(K1JK2J, K3J) boscoster = _multi_mult([k1, k2, k3], [ec.GJ, HJ, ec.GJ], ec) assert ec._jac_equality(K1JK2JK3J, boscoster) k4 = 1 + secrets.randbelow(ec.n - 1) K4J = _mult_jac(k4, HJ, ec) K1JK2JK3JK4J = ec._add_jac(K1JK2JK3J, K4J) points = [ec.GJ, HJ, ec.GJ, HJ] boscoster = _multi_mult([k1, k2, k3, k4], points, ec) assert ec._jac_equality(K1JK2JK3JK4J, boscoster) assert ec._jac_equality(K1JK2JK3J, _multi_mult([k1, k2, k3, 0], points, ec)) assert ec._jac_equality(K1JK2J, _multi_mult([k1, k2, 0, 0], points, ec)) assert ec._jac_equality(K1J, _multi_mult([k1, 0, 0, 0], points, ec)) assert ec._jac_equality(INFJ, _multi_mult([0, 0, 0, 0], points, ec)) err_msg = "mismatch between number of scalars and points: " with pytest.raises(ValueError, match=err_msg): _multi_mult([k1, k2, k3, k4], [ec.GJ, HJ, ec.GJ], ec) with pytest.raises(ValueError, match="negative first coefficient: "): _double_mult(-5, HJ, 1, ec.GJ, ec) with pytest.raises(ValueError, match="negative second coefficient: "): _double_mult(1, HJ, -5, ec.GJ, ec)
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 test_mult_jac_curves(): for ec in all_curves.values(): assert _mult_jac(0, ec.GJ, ec) == INFJ assert _mult_jac(0, INFJ, ec) == INFJ assert _mult_jac(1, INFJ, ec) == INFJ assert _mult_jac(1, ec.GJ, ec) == ec.GJ PJ = ec._add_jac(ec.GJ, ec.GJ) assert PJ == _mult_jac(2, ec.GJ, ec) PJ = _mult_jac(ec.n - 1, ec.GJ, ec) assert ec._jac_equality(ec.negate(ec.GJ), PJ) assert _mult_jac(ec.n - 1, INFJ, ec) == INFJ assert ec._add_jac(PJ, ec.GJ) == INFJ assert _mult_jac(ec.n, ec.GJ, ec) == INFJ with pytest.raises(ValueError, match="negative m: -0x"): _mult_jac(-1, ec.GJ, ec)
def sign(ec: Curve, hf: Callable[[Any], Any], mhd: bytes, d: int, k: Optional[int] = None) -> ECSS: """ ECSSA signing operation according to bip-schnorr This signature scheme supports 32-byte messages. Differently from ECDSA, the 32-byte message can be a digest of other messages, but it does not need to. https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki """ # 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) # The message mhd: a 32-byte array _ensure_msg_size(hf, mhd) # The secret key d: an integer in the range 1..n-1. if not 0 < d < ec.n: raise ValueError(f"private key {hex(d)} not in [1, n-1]") P = mult(ec, d, ec.G) # Fail if k' = 0. if k is None: k = rfc6979(ec, hf, mhd, d) if not 0 < k < ec.n: raise ValueError(f"ephemeral key {hex(k)} not in [1, n-1]") # Let R = k'G. RJ = _mult_jac(ec, k, ec.GJ) # break the simmetry: any criteria might have been used, # jacobi is the proposed bitcoin standard # Let k = k' if jacobi(y(R)) = 1, otherwise let k = n - k'. if legendre_symbol(RJ[1] * RJ[2] % ec._p, ec._p) != 1: k = ec.n - k Z2 = RJ[2] * RJ[2] r = (RJ[0] * mod_inv(Z2, ec._p)) % ec._p # Let e = int(hf(bytes(x(R)) || bytes(dG) || mhd)) mod n. e = _e(ec, hf, r, P, mhd) s = (k + e * d) % ec.n # s=0 is ok: in verification there is no inverse of s # The signature is bytes(x(R) || bytes((k + ed) mod n)). return r, s
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 _sign(ec: Curve, c: int, q: int, k: int) -> ECDS: # Private function for test/dev purposes # it is assumed that q, k, and c are in [1, n-1] # Steps numbering follows SEC 1 v.2 section 4.1.3 RJ = _mult_jac(ec, k, ec.GJ) # 1 Rx = (RJ[0] * mod_inv(RJ[2] * RJ[2], ec._p)) % ec._p r = Rx % ec.n # 2, 3 if r == 0: # r≠0 required as it multiplies the public key raise ValueError("r = 0, failed to sign") s = mod_inv(k, ec.n) * (c + r * q) % ec.n # 6 if s == 0: # s≠0 required as verify will need the inverse of s raise ValueError("s = 0, failed to sign") # bitcoin canonical 'low-s' encoding for ECDSA signatures # it removes signature malleability as cause of transaction malleability # see https://github.com/bitcoin/bitcoin/pull/6769 if s > ec.n / 2: s = ec.n - s return r, s
# or distributed except according to the terms contained in the LICENSE file. import random import time from btclib.curve import _jac_from_aff, _mult_aff, _mult_jac from btclib.curves import secp256k1 random.seed(42) ec = secp256k1 # setup qs = [] for _ in range(50): qs.append(random.getrandbits(ec.nlen) % ec.n) start = time.time() for q in qs: _mult_aff(ec, q, ec.G) elapsed1 = time.time() - start start = time.time() for q in qs: # starts from affine coordinates, ends with affine coordinates GJ = _jac_from_aff(ec.G) ec._aff_from_jac(_mult_jac(ec, q, GJ)) elapsed2 = time.time() - start print(elapsed2 / elapsed1)