def test_mult_recursive_aff() -> None: for ec in all_curves.values(): assert mult_recursive_aff(0, ec.G, ec) == INF assert mult_recursive_aff(0, INF, ec) == INF assert mult_recursive_aff(1, INF, ec) == INF assert mult_aff(1, ec.G, ec) == ec.G Q = ec.add_aff(ec.G, ec.G) assert Q == mult_recursive_aff(2, ec.G, ec) Q = mult_recursive_aff(ec.n - 1, ec.G, ec) assert ec.negate(ec.G) == Q assert mult_recursive_aff(ec.n - 1, INF, ec) == INF assert ec.add_aff(Q, ec.G) == INF assert mult_recursive_aff(ec.n, ec.G, ec) == INF assert mult_recursive_aff(ec.n, INF, ec) == INF with pytest.raises(BTClibValueError, match="negative m: "): mult_recursive_aff(-1, ec.G, ec) for ec in low_card_curves.values(): for q in range(ec.n): Q = mult_recursive_aff(q, ec.G, ec) assert ec.is_on_curve(Q), f"{q}, {ec}" QJ = _mult(q, ec.GJ, ec) assert ec.is_on_curve(ec.aff_from_jac(QJ)), f"{q}, {ec}" assert Q == ec.aff_from_jac(QJ), f"{q}, {ec}" assert INF == mult_recursive_aff(q, INF, ec), f"{q}, {ec}" assert ec.jac_equality(INFJ, _mult(q, INFJ, ec)), f"{q}, {ec}"
def test_mult_recursive_jac() -> None: for ec in all_curves.values(): assert ec.jac_equality(mult_recursive_jac(0, ec.GJ, ec), INFJ) assert ec.jac_equality(mult_recursive_jac(0, INFJ, ec), INFJ) assert ec.jac_equality(mult_recursive_jac(1, INFJ, ec), INFJ) assert ec.jac_equality(mult_recursive_jac(1, ec.GJ, ec), ec.GJ) PJ = ec.add_jac(ec.GJ, ec.GJ) assert ec.jac_equality(PJ, mult_recursive_jac(2, ec.GJ, ec)) PJ = mult_recursive_jac(ec.n - 1, ec.GJ, ec) assert ec.jac_equality(ec.negate_jac(ec.GJ), PJ) assert ec.jac_equality(mult_recursive_jac(ec.n - 1, INFJ, ec), INFJ) assert ec.jac_equality(ec.add_jac(PJ, ec.GJ), INFJ) assert ec.jac_equality(mult_recursive_jac(ec.n, ec.GJ, ec), INFJ) assert ec.jac_equality(mult_recursive_jac(ec.n, INFJ, ec), INFJ) with pytest.raises(BTClibValueError, match="negative m: "): mult_recursive_jac(-1, ec.GJ, ec) ec = ec23_31 for k1 in range(ec.n): K1 = mult_recursive_jac(k1, ec.GJ, ec) assert ec.jac_equality(K1, _mult(k1, ec.GJ, ec))
def _sign_(c: int, q: int, nonce: int, lower_s: bool, ec: Curve) -> Sig: # Private function for testing purposes: it allows to explore all # possible value of the challenge c (for low-cardinality curves). # It assume that c is in [0, n-1], while q and nonce are in [1, n-1] # Steps numbering follows SEC 1 v.2 section 4.1.3 KJ = _mult(nonce, ec.GJ, ec) # 1 # affine x_K-coordinate of K (field element) x_K = (KJ[0] * mod_inv(KJ[2] * KJ[2], ec.p)) % ec.p # mod n makes it a scalar r = x_K % ec.n # 2, 3 if r == 0: # r≠0 required as it multiplies the public key raise BTClibRuntimeError("failed to sign: r = 0") s = mod_inv(nonce, ec.n) * (c + r * q) % ec.n # 6 if s == 0: # s≠0 required as verify will need the inverse of s raise BTClibRuntimeError("failed to sign: s = 0") # 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 lower_s and s > ec.n / 2: s = ec.n - s # s = - s % ec.n return Sig(r, s, ec)
def test_mult_base_3() -> None: for ec in low_card_curves.values(): assert ec.jac_equality(mult_base_3(0, ec.GJ, ec), INFJ) assert ec.jac_equality(mult_base_3(0, INFJ, ec), INFJ) assert ec.jac_equality(mult_base_3(1, INFJ, ec), INFJ) assert ec.jac_equality(mult_base_3(1, ec.GJ, ec), ec.GJ) PJ = mult_base_3(2, ec.GJ, ec) assert ec.jac_equality(PJ, ec.add_jac(ec.GJ, ec.GJ)) PJ = mult_base_3(ec.n - 1, ec.GJ, ec) assert ec.jac_equality(ec.negate_jac(ec.GJ), PJ) assert ec.jac_equality(mult_base_3(ec.n - 1, INFJ, ec), INFJ) assert ec.jac_equality(ec.add_jac(PJ, ec.GJ), INFJ) assert ec.jac_equality(mult_base_3(ec.n, ec.GJ, ec), INFJ) assert ec.jac_equality(mult_mont_ladder(ec.n, INFJ, ec), INFJ) with pytest.raises(BTClibValueError, match="negative m: "): mult_base_3(-1, ec.GJ, ec) ec = ec23_31 for k1 in range(ec.n): K1 = mult_base_3(k1, ec.GJ, ec) assert ec.jac_equality(K1, _mult(k1, ec.GJ, ec))
def test_mult_w_NAF() -> None: for w in range(1, 6): for ec in low_card_curves.values(): assert ec.jac_equality(mult_w_NAF(0, ec.GJ, ec, w), INFJ) assert ec.jac_equality(mult_w_NAF(0, INFJ, ec, w), INFJ) assert ec.jac_equality(mult_w_NAF(1, INFJ, ec, w), INFJ) assert ec.jac_equality(mult_w_NAF(1, ec.GJ, ec, w), ec.GJ) PJ = mult_w_NAF(2, ec.GJ, ec, w) assert ec.jac_equality(PJ, ec.add_jac(ec.GJ, ec.GJ)) PJ = mult_w_NAF(ec.n - 1, ec.GJ, ec, w) assert ec.jac_equality(ec.negate_jac(ec.GJ), PJ) assert ec.jac_equality(mult_w_NAF(ec.n - 1, INFJ, ec, w), INFJ) assert ec.jac_equality(ec.add_jac(PJ, ec.GJ), INFJ) assert ec.jac_equality(mult_w_NAF(ec.n, ec.GJ, ec, w), INFJ) with pytest.raises(BTClibValueError, match="negative m: "): mult_w_NAF(-1, ec.GJ, ec, w) with pytest.raises(BTClibValueError, match="non positive w: "): mult_w_NAF(1, ec.GJ, ec, -w) ec = ec23_31 for w in range(1, 10): for k1 in range(ec.n): K1 = mult_w_NAF(k1, ec.GJ, ec, w) assert ec.jac_equality(K1, _mult(k1, ec.GJ, ec))
def test_low_cardinality() -> None: """test low-cardinality curves for all msg/key pairs.""" # pylint: disable=protected-access # ec.n has to be prime to sign test_curves = [ low_card_curves["ec13_11"], # low_card_curves["ec13_19"], # low_card_curves["ec17_13"], low_card_curves["ec17_23"], low_card_curves["ec19_13"], # low_card_curves["ec19_23"], low_card_curves["ec23_19"], low_card_curves["ec23_31"], ] lower_s = True # only low cardinality test curves or it would take forever for ec in test_curves: for q in range(1, ec.n): # all possible private keys QJ = _mult(q, ec.GJ, ec) # public key for k in range(1, ec.n): # all possible ephemeral keys RJ = _mult(k, ec.GJ, ec) r = ec.x_aff_from_jac(RJ) % ec.n k_inv = mod_inv(k, ec.n) for e in range(ec.n): # all possible challenges s = k_inv * (e + q * r) % ec.n # bitcoin canonical 'low-s' encoding for ECDSA if lower_s and s > ec.n / 2: s = ec.n - s if r == 0 or s == 0: err_msg = "failed to sign: " with pytest.raises(BTClibRuntimeError, match=err_msg): dsa._sign_(e, q, k, lower_s, ec) else: sig = dsa._sign_(e, q, k, lower_s, ec) assert r == sig.r assert s == sig.s assert ec == sig.ec # valid signature must pass verification dsa._assert_as_valid_(e, QJ, r, s, lower_s, ec) jac_keys = dsa._recover_pub_keys_(e, r, s, lower_s, ec) # FIXME speed this up Qs = [ec.aff_from_jac(key) for key in jac_keys] assert ec.aff_from_jac(QJ) in Qs assert len(jac_keys) in (2, 4)
def test_jac_equality() -> None: ec = ec23_31 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 gen_keys(prv_key: Optional[PrvKey] = None, ec: Curve = secp256k1) -> Tuple[int, Point]: "Return a private/public (int, Point) key-pair." if prv_key is None: # q in the range [1, ec.n-1] q = 1 + secrets.randbelow(ec.n - 1) else: q = int_from_prv_key(prv_key, ec) QJ = _mult(q, ec.GJ, ec) Q = ec.aff_from_jac(QJ) return q, Q
def mult(m: Integer, Q: Optional[Point] = None, ec: Curve = secp256k1) -> Point: "Elliptic curve scalar multiplication." if Q is None: QJ = ec.GJ else: ec.require_on_curve(Q) QJ = jac_from_aff(Q) m = int_from_integer(m) % ec.n R = _mult(m, QJ, ec) return ec.aff_from_jac(R)
def gen_keys_(prv_key: Optional[PrvKey] = None, ec: Curve = secp256k1) -> Tuple[int, int, JacPoint]: "Return a BIP340 private/public (int, JacPoint) key-pair." if prv_key is None: q = 1 + secrets.randbelow(ec.n - 1) else: q = int_from_prv_key(prv_key, ec) QJ = _mult(q, ec.GJ, ec) x_Q, y_Q = ec.aff_from_jac(QJ) if y_Q % 2: q = ec.n - q QJ = ec.negate_jac(QJ) return q, x_Q, QJ
def test_assorted_jac_mult() -> None: ec = ec23_31 H = second_generator(ec) HJ = jac_from_aff(H) for k1 in range(ec.n): K1J = _mult(k1, ec.GJ, ec) for k2 in range(ec.n): K2J = _mult(k2, HJ, ec) shamir = _double_mult(k1, ec.GJ, k2, ec.GJ, ec) assert ec.is_on_curve(ec.aff_from_jac(shamir)) assert ec.jac_equality(shamir, _mult(k1 + k2, ec.GJ, ec)) shamir = _double_mult(k1, INFJ, k2, HJ, ec) assert ec.is_on_curve(ec.aff_from_jac(shamir)) assert ec.jac_equality(shamir, K2J) shamir = _double_mult(k1, ec.GJ, k2, INFJ, ec) assert ec.is_on_curve(ec.aff_from_jac(shamir)) assert ec.jac_equality(shamir, K1J) shamir = _double_mult(k1, ec.GJ, k2, HJ, ec) assert ec.is_on_curve(ec.aff_from_jac(shamir)) K1JK2J = ec.add_jac(K1J, K2J) assert ec.jac_equality(K1JK2J, shamir) k3 = 1 + secrets.randbelow(ec.n - 1) K3J = _mult(k3, ec.GJ, ec) K1JK2JK3J = ec.add_jac(K1JK2J, K3J) assert ec.is_on_curve(ec.aff_from_jac(K1JK2JK3J)) boscoster = _multi_mult([k1, k2, k3], [ec.GJ, HJ, ec.GJ], ec) assert ec.is_on_curve(ec.aff_from_jac(boscoster)) assert ec.aff_from_jac(K1JK2JK3J) == ec.aff_from_jac(boscoster), k3 assert ec.jac_equality(K1JK2JK3J, boscoster) k4 = 1 + secrets.randbelow(ec.n - 1) K4J = _mult(k4, HJ, ec) K1JK2JK3JK4J = ec.add_jac(K1JK2JK3J, K4J) assert ec.is_on_curve(ec.aff_from_jac(K1JK2JK3JK4J)) points = [ec.GJ, HJ, ec.GJ, HJ] boscoster = _multi_mult([k1, k2, k3, k4], points, ec) assert ec.is_on_curve(ec.aff_from_jac(boscoster)) assert ec.aff_from_jac(K1JK2JK3JK4J) == ec.aff_from_jac( boscoster), k4 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(BTClibValueError, match=err_msg): _multi_mult([k1, k2, k3, k4], [ec.GJ, HJ, ec.GJ], ec) err_msg = "negative coefficient: " with pytest.raises(BTClibValueError, match=err_msg): _multi_mult([k1, k2, -k3], [ec.GJ, HJ, ec.GJ], ec) with pytest.raises(BTClibValueError, match="negative first coefficient: "): _double_mult(-5, HJ, 1, ec.GJ, ec) with pytest.raises(BTClibValueError, match="negative second coefficient: "): _double_mult(1, HJ, -5, ec.GJ, ec)
def assert_batch_as_valid_( m_hashes: Sequence[Octets], Qs: Sequence[BIP340PubKey], sigs: Sequence[Sig], hf: HashF = sha256, ) -> None: batch_size = len(Qs) if batch_size == 0: raise BTClibValueError("no signatures provided") if len(m_hashes) != batch_size: err_msg = f"mismatch between number of pub_keys ({batch_size}) " err_msg += f"and number of messages ({len(m_hashes)})" raise BTClibValueError(err_msg) if len(sigs) != batch_size: err_msg = f"mismatch between number of pub_keys ({batch_size}) " err_msg += f"and number of signatures ({len(sigs)})" raise BTClibValueError(err_msg) if batch_size == 1: assert_as_valid_(m_hashes[0], Qs[0], sigs[0], hf) return None ec = sigs[0].ec if any(sig.ec != ec for sig in sigs): raise BTClibValueError("not the same curve for all signatures") t = 0 scalars: List[int] = [] points: List[JacPoint] = [] for i, (msg_hash, Q, sig) in enumerate(zip(m_hashes, Qs, sigs)): msg_hash = bytes_from_octets(msg_hash, hf().digest_size) KJ = sig.r, ec.y_even(sig.r), 1 x_Q, y_Q = point_from_bip340pub_key(Q, ec) QJ = x_Q, y_Q, 1 c = challenge_(msg_hash, x_Q, sig.r, ec, hf) # rand 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 rand = 1 if i == 0 else 1 + secrets.randbelow(ec.n - 1) scalars.append(rand) points.append(KJ) scalars.append(rand * c % ec.n) points.append(QJ) t += rand * sig.s TJ = _mult(t, ec.GJ, ec) RHSJ = _multi_mult(scalars, points, ec) # 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) or ( TJ[1] * RHSZ2 * RHSJ[2] % ec.p != RHSJ[1] * TZ2 * TJ[2] % ec.p): raise BTClibRuntimeError("signature verification failed") return None
def __init__( self, p: Integer, a: Integer, b: Integer, G: Point, n: Integer, cofactor: int, weakness_check: bool = True, name: Optional[str] = None, ) -> None: super().__init__(p, a, b, G) n = int_from_integer(n) # Security level is expressed in bits, where n-bit security # means that the attacker would have to perform 2^n operations # to break it. Security bits are half the key size for asymmetric # elliptic curve cryptography, i.e. half of the number of bits # required to express the group order n or, holding Hasse theorem, # to express the field prime p self.n = n self.nlen = n.bit_length() self.n_size = (self.nlen + 7) // 8 # 5. Check that n is prime. if n < 2 or n % 2 == 0 or pow(2, n - 1, n) != 1: err_msg = "n is not prime: " err_msg += f"{hex_string(n)}" if n > HEX_THRESHOLD else f"{n}" raise BTClibValueError(err_msg) delta = int(2 * sqrt(self.p)) # also check n with Hasse Theorem if cofactor < 2 and not self.p + 1 - delta <= n <= self.p + 1 + delta: err_msg = "n not in p+1-delta..p+1+delta: " err_msg += f"{hex_string(n)}" if n > HEX_THRESHOLD else f"{n}" raise BTClibValueError(err_msg) # 7. Check that G ≠ INF, nG = INF if self.G[1] == 0: err_msg = "INF point cannot be a generator" raise BTClibValueError(err_msg) jac_inf = _mult(n, self.GJ, self) if jac_inf[2] != 0: err_msg = "n is not the group order: " err_msg += f"{hex_string(n)}" if n > HEX_THRESHOLD else f"{n}" raise BTClibValueError(err_msg) # 6. Check cofactor exp_cofactor = int(1 / n + delta / n + self.p / n) if cofactor != exp_cofactor: err_msg = f"invalid cofactor: {cofactor}, expected {exp_cofactor}" raise BTClibValueError(err_msg) self.cofactor = cofactor # 8. Check that n ≠ p if n == p: raise BTClibValueError( f"n=p weak curve: {hex_string(n)}") # pragma: no cover if weakness_check: # 8. Check that p^i % n ≠ 1 for all 1≤i<100 for i in range(1, 100): if pow(self.p, i, n) == 1: raise UserWarning("weak curve") self.name = name