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_forge_hash_sig() -> None: """forging valid hash signatures""" # pylint: disable=protected-access ec = CURVES["secp256k1"] # see https://twitter.com/pwuille/status/1063582706288586752 # Satoshi's key key = "03 11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" Q = point_from_octets(key, ec) # pick u1 and u2 at will u1 = 1 u2 = 2 R = double_mult(u2, Q, u1, ec.G, ec) r = R[0] % ec.n u2inv = mod_inv(u2, ec.n) s = r * u2inv % ec.n s = ec.n - s if s > ec.n / 2 else s e = s * u1 % ec.n dsa._assert_as_valid_(e, (Q[0], Q[1], 1), r, s, lower_s=True, ec=ec) # pick u1 and u2 at will u1 = 1234567890 u2 = 987654321 R = double_mult(u2, Q, u1, ec.G, ec) r = R[0] % ec.n u2inv = mod_inv(u2, ec.n) s = r * u2inv % ec.n s = ec.n - s if s > ec.n / 2 else s e = s * u1 % ec.n dsa._assert_as_valid_(e, (Q[0], Q[1], 1), r, s, lower_s=True, ec=ec)
def crack_prv_key_( msg_hash1: Octets, sig1: Union[Sig, Octets], msg_hash2: Octets, sig2: Union[Sig, Octets], hf: HashF = sha256, ) -> Tuple[int, int]: if isinstance(sig1, Sig): sig1.assert_valid() else: sig1 = Sig.parse(sig1) if isinstance(sig2, Sig): sig2.assert_valid() else: sig2 = Sig.parse(sig2) ec = sig2.ec if sig1.ec != ec: raise BTClibValueError("not the same curve in signatures") if sig1.r != sig2.r: raise BTClibValueError("not the same r in signatures") if sig1.s == sig2.s: raise BTClibValueError("identical signatures") c_1 = challenge_(msg_hash1, ec, hf) c_2 = challenge_(msg_hash2, ec, hf) nonce = (c_1 - c_2) * mod_inv(sig1.s - sig2.s, ec.n) % ec.n q = (sig2.s * nonce - c_2) * mod_inv(sig1.r, ec.n) % ec.n return q, nonce
def aff_from_jac(self, Q: JacPoint) -> Point: # point is assumed to be on curve if Q[2] == 0: # Infinity point in Jacobian coordinates return INF Z2 = Q[2] * Q[2] x = Q[0] * mod_inv(Z2, self.p) y = Q[1] * mod_inv(Z2 * Q[2], self.p) return x % self.p, y % self.p
def test_mod_inv_prime() -> None: for p in primes: with pytest.raises(BTClibValueError, match="No inverse for 0 mod"): mod_inv(0, p) for a in range(1, min(p, 500)): # exhausted only for small p inv = mod_inv(a, p) assert a * inv % p == 1 inv = mod_inv(a + p, p) assert a * inv % p == 1
def test_mod_inv() -> None: max_m = 100 for m in range(2, max_m): nums = list(range(m)) for a in nums: mult = [a * i % m for i in nums] if 1 in mult: inv = mod_inv(a, m) assert a * inv % m == 1 inv = mod_inv(a + m, m) assert a * inv % m == 1 else: err_msg = "No inverse for " with pytest.raises(BTClibValueError, match=err_msg): mod_inv(a, m)
def y_aff_from_jac(self, Q: JacPoint) -> int: # point is assumed to be on curve if Q[2] == 0: # Infinity point in Jacobian coordinates raise BTClibValueError("INF has no y-coordinate") Z2 = Q[2] * Q[2] return (Q[1] * mod_inv(Z2 * Q[2], self.p)) % self.p
def double_aff(self, Q: Point) -> Point: # point is assumed to be on curve if Q[1] == 0: # Infinity point in affine coordinates return INF lam = (3 * Q[0] * Q[0] + self._a) * mod_inv(2 * Q[1], self.p) x = lam * lam - Q[0] - Q[0] y = lam * (Q[0] - x) - Q[1] return x % self.p, y % self.p
def _assert_as_valid_(c: int, QJ: JacPoint, r: int, s: int, lower_s: bool, ec: Curve) -> None: # Private function for test/dev purposes if lower_s and s > ec.n / 2: raise BTClibValueError("not a low s") w = mod_inv(s, ec.n) u = c * w % ec.n v = r * w % ec.n # 4 # Let K = u*G + v*Q. KJ = _double_mult(v, QJ, u, ec.GJ, ec) # 5 # Fail if infinite(K). # edge case that cannot be reproduced in the test suite if KJ[2] == 0: # 5 err_msg = "invalid (INF) key" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover # affine x_K-coordinate of K x_K = (KJ[0] * mod_inv(KJ[2] * KJ[2], ec.p)) % ec.p # Fail if r ≠ x_K %n. if r != x_K % ec.n: # 6, 7, 8 raise BTClibRuntimeError("signature verification failed")
def _recover_pub_key_(c: int, r: int, s: int, ec: Curve) -> int: # Private function provided for testing purposes only. if c == 0: raise BTClibRuntimeError("invalid zero challenge") KJ = r, ec.y_even(r), 1 e1 = mod_inv(c, ec.n) QJ = _double_mult(ec.n - e1, KJ, e1 * s, ec.GJ, ec) # edge case that cannot be reproduced in the test suite if QJ[2] == 0: err_msg = "invalid (INF) key" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover return ec.x_aff_from_jac(QJ)
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 add_aff(self, Q: Point, R: Point) -> Point: # points are assumed to be on curve # FIXME: it would be better if INF handling was not a special case if R[1] == 0: # Infinity point in affine coordinates return Q if Q[1] == 0: # Infinity point in affine coordinates return R # FIXME: it would be better if doubling was checked before INF handling if R[0] == Q[0]: if R[1] == Q[1]: # point doubling return self.double_aff(R) # opposite points return INF lam = (R[1] - Q[1]) * mod_inv(R[0] - Q[0], self.p) x = lam * lam - Q[0] - R[0] y = lam * (Q[0] - x) - Q[1] return x % self.p, y % self.p
def _recover_pub_keys_(c: int, r: int, s: int, lower_s: bool, ec: Curve) -> List[JacPoint]: # Private function provided for testing purposes only. # precomputations r_1 = mod_inv(r, ec.n) r1s = r_1 * s % ec.n r1e = -r_1 * c % ec.n keys: List[JacPoint] = [] # r = K[0] % ec.n # if ec.n < K[0] < ec.p (likely when cofactor ec.cofactor > 1) # then both x_K=r and x_K=r+ec.n must be tested for j in range(ec.cofactor + 1): # 1 # affine x_K-coordinate of K (field element) x_K = (r + j * ec.n) % ec.p # 1.1 # two possible y_K-coordinates, i.e. two possible keys for each cycle try: # even root first for bitcoin message signing compatibility yodd = ec.y_even(x_K) KJ = x_K, yodd, 1 # 1.2, 1.3, and 1.4 # 1.5 has been performed in the recover_pub_keys calling function QJ = _double_mult(r1s, KJ, r1e, ec.GJ, ec) # 1.6.1 try: _assert_as_valid_(c, QJ, r, s, lower_s, ec) # 1.6.2 except (BTClibValueError, BTClibRuntimeError): pass else: keys.append(QJ) # 1.6.2 KJ = x_K, ec.p - yodd, 1 # 1.6.3 QJ = _double_mult(r1s, KJ, r1e, ec.GJ, ec) try: _assert_as_valid_(c, QJ, r, s, lower_s, ec) # 1.6.2 except (BTClibValueError, BTClibRuntimeError): pass else: keys.append(QJ) # 1.6.2 except (BTClibValueError, BTClibRuntimeError): # K is not a curve point pass return keys
def _recover_pub_key_(key_id: int, c: int, r: int, s: int, lower_s: bool, ec: Curve) -> JacPoint: # Private function provided for testing purposes only. # precomputations r_1 = mod_inv(r, ec.n) r1s = r_1 * s % ec.n r1e = -r_1 * c % ec.n # r = K[0] % ec.n # if ec.n < K[0] < ec.p (likely when cofactor ec.cofactor > 1) # then both x_K=r and x_K=r+ec.n must be tested j = key_id & 0b110 # allow for key_id in [0, 7] x_K = (r + j * ec.n) % ec.p # 1.1 # even root first for Bitcoin Core compatibility i = key_id & 0b01 y_even = ec.y_even(x_K) y_K = ec.p - y_even if i else y_even KJ = x_K, y_K, 1 # 1.2, 1.3, and 1.4 # 1.5 has been performed in the recover_pub_keys calling function QJ = _double_mult(r1s, KJ, r1e, ec.GJ, ec) # 1.6.1 _assert_as_valid_(c, QJ, r, s, lower_s, ec) # 1.6.2 return QJ
def crack_prv_key_( msg_hash1: Octets, sig1: Union[Sig, Octets], msg_hash2: Octets, sig2: Union[Sig, Octets], Q: BIP340PubKey, hf: HashF = sha256, ) -> Tuple[int, int]: if isinstance(sig1, Sig): sig1.assert_valid() else: sig1 = Sig.parse(sig1) if isinstance(sig2, Sig): sig2.assert_valid() else: sig2 = Sig.parse(sig2) ec = sig2.ec if sig1.ec != ec: raise BTClibValueError("not the same curve in signatures") if sig1.r != sig2.r: raise BTClibValueError("not the same r in signatures") if sig1.s == sig2.s: raise BTClibValueError("identical signatures") x_Q = point_from_bip340pub_key(Q, ec)[0] c_1 = challenge_(msg_hash1, x_Q, sig1.r, ec, hf) c_2 = challenge_(msg_hash2, x_Q, sig2.r, ec, hf) q = (sig1.s - sig2.s) * mod_inv(c_2 - c_1, ec.n) % ec.n nonce = (sig1.s + c_1 * q) % ec.n q, _ = gen_keys(q) nonce, _ = gen_keys(nonce) return q, nonce
def test_threshold() -> None: "testing 2-of-3 threshold signature (Pedersen secret sharing)" ec = CURVES["secp256k1"] # parameters m = 2 H = second_generator(ec) # FIRST PHASE: key pair generation ################################### # 1.1 signer one acting as the dealer commits1: List[Point] = [] q1, _ = ssa.gen_keys() q1_prime, _ = ssa.gen_keys() commits1.append(double_mult(q1_prime, H, q1, ec.G)) # sharing polynomials f1 = [q1] f1_prime = [q1_prime] for i in range(1, m): f1.append(ssa.gen_keys()[0]) f1_prime.append(ssa.gen_keys()[0]) commits1.append(double_mult(f1_prime[i], H, f1[i], ec.G)) # shares of the secret alpha12 = 0 # share of q1 belonging to signer two alpha12_prime = 0 alpha13 = 0 # share of q1 belonging to signer three alpha13_prime = 0 for i in range(m): alpha12 += (f1[i] * pow(2, i)) % ec.n alpha12_prime += (f1_prime[i] * pow(2, i)) % ec.n alpha13 += (f1[i] * pow(3, i)) % ec.n alpha13_prime += (f1_prime[i] * pow(3, i)) % ec.n # signer two verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(2, i), commits1[i])) t = double_mult(alpha12_prime, H, alpha12, ec.G) assert t == RHS, "signer one is cheating" # signer three verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(3, i), commits1[i])) t = double_mult(alpha13_prime, H, alpha13, ec.G) assert t == RHS, "signer one is cheating" # 1.2 signer two acting as the dealer commits2: List[Point] = [] q2, _ = ssa.gen_keys() q2_prime, _ = ssa.gen_keys() commits2.append(double_mult(q2_prime, H, q2, ec.G)) # sharing polynomials f2 = [q2] f2_prime = [q2_prime] for i in range(1, m): f2.append(ssa.gen_keys()[0]) f2_prime.append(ssa.gen_keys()[0]) commits2.append(double_mult(f2_prime[i], H, f2[i], ec.G)) # shares of the secret alpha21 = 0 # share of q2 belonging to signer one alpha21_prime = 0 alpha23 = 0 # share of q2 belonging to signer three alpha23_prime = 0 for i in range(m): alpha21 += (f2[i] * pow(1, i)) % ec.n alpha21_prime += (f2_prime[i] * pow(1, i)) % ec.n alpha23 += (f2[i] * pow(3, i)) % ec.n alpha23_prime += (f2_prime[i] * pow(3, i)) % ec.n # signer one verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(1, i), commits2[i])) t = double_mult(alpha21_prime, H, alpha21, ec.G) assert t == RHS, "signer two is cheating" # signer three verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(3, i), commits2[i])) t = double_mult(alpha23_prime, H, alpha23, ec.G) assert t == RHS, "signer two is cheating" # 1.3 signer three acting as the dealer commits3: List[Point] = [] q3, _ = ssa.gen_keys() q3_prime, _ = ssa.gen_keys() commits3.append(double_mult(q3_prime, H, q3, ec.G)) # sharing polynomials f3 = [q3] f3_prime = [q3_prime] for i in range(1, m): f3.append(ssa.gen_keys()[0]) f3_prime.append(ssa.gen_keys()[0]) commits3.append(double_mult(f3_prime[i], H, f3[i], ec.G)) # shares of the secret alpha31 = 0 # share of q3 belonging to signer one alpha31_prime = 0 alpha32 = 0 # share of q3 belonging to signer two alpha32_prime = 0 for i in range(m): alpha31 += (f3[i] * pow(1, i)) % ec.n alpha31_prime += (f3_prime[i] * pow(1, i)) % ec.n alpha32 += (f3[i] * pow(2, i)) % ec.n alpha32_prime += (f3_prime[i] * pow(2, i)) % ec.n # signer one verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(1, i), commits3[i])) t = double_mult(alpha31_prime, H, alpha31, ec.G) assert t == RHS, "signer three is cheating" # signer two verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(2, i), commits3[i])) t = double_mult(alpha32_prime, H, alpha32, ec.G) assert t == RHS, "signer three is cheating" # shares of the secret key q = q1 + q2 + q3 alpha1 = (alpha21 + alpha31) % ec.n alpha2 = (alpha12 + alpha32) % ec.n alpha3 = (alpha13 + alpha23) % ec.n for i in range(m): alpha1 += (f1[i] * pow(1, i)) % ec.n alpha2 += (f2[i] * pow(2, i)) % ec.n alpha3 += (f3[i] * pow(3, i)) % ec.n # 1.4 it's time to recover the public key # each participant i = 1, 2, 3 shares Qi as follows # Q = Q1 + Q2 + Q3 = (q1 + q2 + q3) G A1: List[Point] = [] A2: List[Point] = [] A3: List[Point] = [] for i in range(m): A1.append(mult(f1[i])) A2.append(mult(f2[i])) A3.append(mult(f3[i])) # signer one checks others' values RHS2 = INF RHS3 = INF for i in range(m): RHS2 = ec.add(RHS2, mult(pow(1, i), A2[i])) RHS3 = ec.add(RHS3, mult(pow(1, i), A3[i])) assert mult(alpha21) == RHS2, "signer two is cheating" assert mult(alpha31) == RHS3, "signer three is cheating" # signer two checks others' values RHS1 = INF RHS3 = INF for i in range(m): RHS1 = ec.add(RHS1, mult(pow(2, i), A1[i])) RHS3 = ec.add(RHS3, mult(pow(2, i), A3[i])) assert mult(alpha12) == RHS1, "signer one is cheating" assert mult(alpha32) == RHS3, "signer three is cheating" # signer three checks others' values RHS1 = INF RHS2 = INF for i in range(m): RHS1 = ec.add(RHS1, mult(pow(3, i), A1[i])) RHS2 = ec.add(RHS2, mult(pow(3, i), A2[i])) assert mult(alpha13) == RHS1, "signer one is cheating" assert mult(alpha23) == RHS2, "signer two is cheating" # commitment at the global sharing polynomial A: List[Point] = [] for i in range(m): A.append(ec.add(A1[i], ec.add(A2[i], A3[i]))) # aggregated public key Q = A[0] if Q[1] % 2: # print('Q has been negated') A[1] = ec.negate(A[1]) # pragma: no cover alpha1 = ec.n - alpha1 # pragma: no cover alpha2 = ec.n - alpha2 # pragma: no cover alpha3 = ec.n - alpha3 # pragma: no cover Q = ec.negate(Q) # pragma: no cover # SECOND PHASE: generation of the nonces' pair ###################### # Assume signer one and three want to sign msg = "message to sign".encode() msg_hash = reduce_to_hlen(msg, hf) # 2.1 signer one acting as the dealer commits1 = [] k1 = ssa.det_nonce_(msg_hash, q1, None, ec, hf) k1_prime = ssa.det_nonce_(msg_hash, q1_prime, None, ec, hf) commits1.append(double_mult(k1_prime, H, k1, ec.G)) # sharing polynomials f1 = [k1] f1_prime = [k1_prime] for i in range(1, m): f1.append(ssa.gen_keys()[0]) f1_prime.append(ssa.gen_keys()[0]) commits1.append(double_mult(f1_prime[i], H, f1[i], ec.G)) # shares of the secret beta13 = 0 # share of k1 belonging to signer three beta13_prime = 0 for i in range(m): beta13 += (f1[i] * pow(3, i)) % ec.n beta13_prime += (f1_prime[i] * pow(3, i)) % ec.n # signer three verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(3, i), commits1[i])) t = double_mult(beta13_prime, H, beta13, ec.G) assert t == RHS, "signer one is cheating" # 2.2 signer three acting as the dealer commits3 = [] k3 = ssa.det_nonce_(msg_hash, q3, None, ec, hf) k3_prime = ssa.det_nonce_(msg_hash, q3_prime, None, ec, hf) commits3.append(double_mult(k3_prime, H, k3, ec.G)) # sharing polynomials f3 = [k3] f3_prime = [k3_prime] for i in range(1, m): f3.append(ssa.gen_keys()[0]) f3_prime.append(ssa.gen_keys()[0]) commits3.append(double_mult(f3_prime[i], H, f3[i], ec.G)) # shares of the secret beta31 = 0 # share of k3 belonging to signer one beta31_prime = 0 for i in range(m): beta31 += (f3[i] * pow(1, i)) % ec.n beta31_prime += (f3_prime[i] * pow(1, i)) % ec.n # signer one verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(1, i), commits3[i])) t = double_mult(beta31_prime, H, beta31, ec.G) assert t == RHS, "signer three is cheating" # 2.3 shares of the secret nonce beta1 = beta31 % ec.n beta3 = beta13 % ec.n for i in range(m): beta1 += (f1[i] * pow(1, i)) % ec.n beta3 += (f3[i] * pow(3, i)) % ec.n # 2.4 it's time to recover the public nonce # each participant i = 1, 3 shares Qi as follows B1: List[Point] = [] B3: List[Point] = [] for i in range(m): B1.append(mult(f1[i])) B3.append(mult(f3[i])) # signer one checks values from signer three RHS3 = INF for i in range(m): RHS3 = ec.add(RHS3, mult(pow(1, i), B3[i])) assert mult(beta31) == RHS3, "signer three is cheating" # signer three checks values from signer one RHS1 = INF for i in range(m): RHS1 = ec.add(RHS1, mult(pow(3, i), B1[i])) assert mult(beta13) == RHS1, "signer one is cheating" # commitment at the global sharing polynomial B: List[Point] = [] for i in range(m): B.append(ec.add(B1[i], B3[i])) # aggregated public nonce K = B[0] if K[1] % 2: # print('K has been negated') B[1] = ec.negate(B[1]) # pragma: no cover beta1 = ec.n - beta1 # pragma: no cover beta3 = ec.n - beta3 # pragma: no cover K = ec.negate(K) # pragma: no cover # PHASE THREE: signature generation ### # partial signatures e = ssa.challenge_(msg_hash, Q[0], K[0], ec, hf) gamma1 = (beta1 + e * alpha1) % ec.n gamma3 = (beta3 + e * alpha3) % ec.n # each participant verifies the other partial signatures # signer one RHS3 = ec.add(K, mult(e, Q)) for i in range(1, m): temp = double_mult(pow(3, i), B[i], e * pow(3, i), A[i]) RHS3 = ec.add(RHS3, temp) assert mult(gamma3) == RHS3, "signer three is cheating" # signer three RHS1 = ec.add(K, mult(e, Q)) for i in range(1, m): temp = double_mult(pow(1, i), B[i], e * pow(1, i), A[i]) RHS1 = ec.add(RHS1, temp) assert mult(gamma1) == RHS1, "signer one is cheating" # PHASE FOUR: aggregating the signature ### omega1 = 3 * mod_inv(3 - 1, ec.n) % ec.n omega3 = 1 * mod_inv(1 - 3, ec.n) % ec.n sigma = (gamma1 * omega1 + gamma3 * omega3) % ec.n sig = ssa.Sig(K[0], sigma, ec) assert ssa.verify_(msg_hash, Q[0], sig) # ADDITIONAL PHASE: reconstruction of the private key ### secret = (omega1 * alpha1 + omega3 * alpha3) % ec.n assert (q1 + q2 + q3) % ec.n in (secret, ec.n - secret)