def test_forge_hash_sig(self): """forging valid hash signatures""" ec = secp256k1 # see https://twitter.com/pwuille/status/1063582706288586752 # Satoshi's key P = point_from_octets( secp256k1, "0311db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" ) u1 = 1 u2 = 2 # pick them at will R = double_mult(ec, u1, ec.G, u2, P) r = R[0] % ec.n u2inv = mod_inv(u2, ec.n) s = r * u2inv % ec.n sig = r, s e = s * u1 % ec.n dsa._verhlp(ec, e, P, sig) u1 = 1234567890 u2 = 987654321 # pick them at will R = double_mult(ec, u1, ec.G, u2, P) r = R[0] % ec.n u2inv = mod_inv(u2, ec.n) s = r * u2inv % ec.n sig = r, s e = s * u1 % ec.n dsa._verhlp(ec, e, P, sig)
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 _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 test_low_cardinality(self): """test low-cardinality curves for all msg/key pairs.""" # ec.n has to be prime to sign prime = [11, 13, 17, 19] for ec in low_card_curves: # only low card or it would take forever if ec._p in prime: # only few curves or it would take too long for q in range(1, ec.n): # all possible private keys PJ = _mult_jac(q, ec.GJ, ec) # public key for e in range(ec.n): # all possible int from hash for k in range(1, ec.n): # all possible ephemeral keys RJ = _mult_jac(k, ec.GJ, ec) Rx = (RJ[0] * mod_inv(RJ[2] * RJ[2], ec._p)) % ec._p r = Rx % ec.n s = mod_inv(k, ec.n) * (e + q * r) % ec.n # bitcoin canonical 'low-s' encoding for ECDSA if s > ec.n / 2: s = ec.n - s if r == 0 or s == 0: self.assertRaises(ValueError, dsa._sign, e, q, k, ec) continue sig = dsa._sign(e, q, k, ec) self.assertEqual((r, s), sig) # valid signature must pass verification self.assertIsNone(dsa._verhlp(e, PJ, r, s, ec)) JacobianKeys = dsa._recover_pubkeys(e, r, s, ec) Qs = [ ec._aff_from_jac(key) for key in JacobianKeys ] self.assertIn(ec._aff_from_jac(PJ), Qs)
def test_forge_hash_sig() -> None: """forging valid hash signatures""" 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 e = s * u1 % ec.n dsa.__assert_as_valid(e, (Q[0], Q[1], 1), r, s, 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 e = s * u1 % ec.n dsa.__assert_as_valid(e, (Q[0], Q[1], 1), r, s, ec)
def test_mod_inv_prime(self): for p in primes: # zero has no inverse self.assertRaises(ValueError, mod_inv, 0, p) for a in range(1, min(p, 500)): # exhausted only for small p inv = mod_inv(a, p) self.assertEqual(a * inv % p, 1) inv = mod_inv(a + p, p) self.assertEqual(a * inv % p, 1)
def _affine_from_jac(self, Q: _JacPoint) -> Point: # point is assumed to be on curve if Q[2] == 0: # Infinity point in Jacobian coordinates return 1, 0 else: Z2 = Q[2] * Q[2] x = (Q[0] * mod_inv(Z2, self._p)) % self._p y = (Q[1] * mod_inv(Z2 * Q[2], self._p)) % self._p return x, y
def test_mod_inv_prime() -> None: for p in primes: with pytest.raises(ValueError, 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(self): 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) self.assertEqual(a * inv % m, 1) inv = mod_inv(a + m, m) self.assertEqual(a * inv % m, 1) else: self.assertRaises(ValueError, mod_inv, a, m)
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(ValueError, match=err_msg): mod_inv(a, m)
def _ecdsa_pubkey_recovery(ec: EC, e: int, sig: ECDS) -> List[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_dsasig(ec, sig) # precomputations r1 = mod_inv(r, ec.n) r1s = r1 * s r1e = -r1 * e keys = [] for j in range(ec.h): # 1 x = r + j * ec.n # 1.1 try: R = (x % ec._p, ec.yOdd(x, 1)) # 1.2, 1.3, and 1.4 # 1.5 already taken care outside this for loop Q = DblScalarMult(ec, r1s, R, r1e, ec.G) # 1.6.1 if Q[1] != 0 and _ecdsa_verhlp(ec, e, Q, sig): # 1.6.2 keys.append(Q) R = ec.opposite(R) # 1.6.3 Q = DblScalarMult(ec, r1s, R, r1e, ec.G) if Q[1] != 0 and _ecdsa_verhlp(ec, e, Q, sig): # 1.6.2 keys.append(Q) # 1.6.2 except Exception: # R is not a curve point pass return keys
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 pointDouble(self, P: Optional[Point]) -> Optional[Point]: if P is None or P[1] == 0: return None f = ((3 * P[0] * P[0] + self.__a) * mod_inv(2 * P[1], self.__prime)) % self.__prime x = (f * f - 2 * P[0]) % self.__prime y = (f * (P[0] - x) - P[1]) % self.__prime return x, y
def pointAdd(self, Q: Optional[Point], R: Optional[Point]) -> Optional[Point]: if R is None: return Q if Q is None: return R if R[0] == Q[0]: if R[1] != Q[1] or Q[1] == 0: # opposite points return None else: # point doubling lam = ((3 * Q[0] * Q[0] + self.__a) * mod_inv(2 * Q[1], self.__p)) % self.__p else: lam = ((R[1] - Q[1]) * mod_inv(R[0] - Q[0], self.__p)) % self.__p x = (lam * lam - Q[0] - R[0]) % self.__p y = (lam * (Q[0] - x) - Q[1]) % self.__p return x, y
def _add_aff(self, Q: Point, R: Point) -> Point: # points are assumed to be on curve if R[1] == 0: # Infinity point in affine coordinates return Q if Q[1] == 0: # Infinity point in affine coordinates return R if R[0] == Q[0]: if R[1] == Q[1]: # point doubling lam = (3 * Q[0] * Q[0] + self._a) * mod_inv(2 * Q[1], self._p) lam %= self._p else: # opposite points return Point() else: lam = ((R[1] - Q[1]) * mod_inv(R[0] - Q[0], self._p)) % self._p x = (lam * lam - Q[0] - R[0]) % self._p y = (lam * (Q[0] - x) - Q[1]) % self._p return Point(x, y)
def _addAffine(self, Q: Point, R: Point) -> Point: # points are assumed to be on curve if R[1] == 0: # Infinity point in affine coordinates return Q if Q[1] == 0: # Infinity point in affine coordinates return R if R[0] == Q[0]: if R[1] == Q[1]: # point doubling lam = ((3 * Q[0] * Q[0] + self._a) * mod_inv(2 * Q[1], self._p)) % self._p else: # must be opposite (points already checked to be on curve) # elif R[1] == self._p - Q[1]: # opposite points return 1, 0 # else: # raise ValueError("points are not on the same curve") else: lam = ((R[1] - Q[1]) * mod_inv(R[0] - Q[0], self._p)) % self._p x = (lam * lam - Q[0] - R[0]) % self._p y = (lam * (Q[0] - x) - Q[1]) % self._p return x, y
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 pointAdd(self, P: Optional[Point], Q: Optional[Point]) -> Optional[Point]: if Q is None: return P if P is None: return Q if Q[0] == P[0]: if Q[1] == P[1]: return self.pointDouble(P) else: return None lam = ( (Q[1] - P[1]) * mod_inv(Q[0] - P[0], self.__prime)) % self.__prime x = (lam * lam - P[0] - Q[0]) % self.__prime y = (lam * (P[0] - x) - P[1]) % self.__prime return x, y
def _ecssa_pubkey_recovery(ec: EC, hf, e: int, sig: ECSS) -> Point: """Private function provided for testing purposes only.""" r, s = to_ssasig(ec, sig) # could be obtained from to_ssasig... K = r, ec.yQuadraticResidue(r, True) if e == 0: raise ValueError("invalid (zero) challenge e") e1 = mod_inv(e, ec.n) P = DblScalarMult(ec, e1*s, ec.G, -e1, K) assert P[1] != 0, "how did you do that?!?" return P
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 _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 _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
def test_low_cardinality(self): """test all msg/key pairs of low cardinality elliptic curves""" # ec.n has to be prime to sign prime = [11, 13, 17, 19] for ec in low_card_curves: # only low card or it would take forever if ec._p in prime: # only few curves or it would take too long for d in range(ec.n): # all possible private keys if d == 0: # invalid prvkey = 0 self.assertRaises(ValueError, dsa._sign, ec, 1, d, 1) continue P = mult(ec, d, ec.G) # public key for e in range(ec.n): # all possible int from hash for k in range(ec.n): # all possible ephemeral keys if k == 0: self.assertRaises(ValueError, dsa._sign, ec, e, d, k) continue R = mult(ec, k, ec.G) r = R[0] % ec.n if r == 0: self.assertRaises(ValueError, dsa._sign, ec, e, d, k) continue s = mod_inv(k, ec.n) * (e + d * r) % ec.n if s == 0: self.assertRaises(ValueError, dsa._sign, ec, e, d, k) continue # bitcoin canonical 'low-s' encoding for ECDSA if s > ec.n / 2: s = ec.n - s # valid signature sig = dsa._sign(ec, e, d, k) self.assertEqual((r, s), sig) # valid signature must validate self.assertTrue(dsa._verhlp(ec, e, P, sig)) keys = dsa._pubkey_recovery(ec, e, sig) self.assertIn(P, keys) for Q in keys: self.assertTrue(dsa._verhlp(ec, e, Q, sig))
def test_low_cardinality() -> None: """test low-cardinality curves for all msg/key pairs.""" # 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"], ] low_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 low_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, low_s, ec) continue sig = dsa.__sign(e, q, k, low_s, ec) assert (r, s) == sig # valid signature must pass verification dsa.__assert_as_valid(e, QJ, r, s, ec) jacobian_keys = dsa.__recover_pubkeys(e, r, s, ec) # FIXME speed this up Qs = [ec._aff_from_jac(key) for key in jacobian_keys] assert ec._aff_from_jac(QJ) in Qs assert len(jacobian_keys) in (2, 4)
def _ecdsa_verhlp(ec: EC, 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_dsasig(ec, sig) # 1 # Let P = point(pk); fail if point(pk) fails. ec.requireOnCurve(P) if P[1] == 0: raise ValueError("public key is infinite") s1 = mod_inv(s, ec.n) u = e * s1 v = r * s1 # 4 # Let R = u*G + v*P. R = DblScalarMult(ec, u, ec.G, v, P) # 5 # Fail if infinite(R). assert R[1] != 0, "how did you do that?!?" # 5 v = R[0] % ec.n # 6, 7 # Fail if r ≠ x(R) %n. return r == v # 8
def _ecdsa_sign(ec: EC, 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. 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. R = pointMult(ec, k, ec.G) # 1 r = R[0] % 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: # required as the inverse of s is needed raise ValueError("s = 0, failed to sign") return r, s
# ephemeral key k must be kept secret and never reused !!!!! # good choice: k = sha256(msg|q) # different for each msg, private because of q temp = msg1 + hex(q) k_bytes = sha256(temp.encode()).digest() k1 = int.from_bytes(k_bytes, 'big') % ec.n assert k1 != 0 print("eph k1:", hex(k1)) K1 = ec.pointMultiply(k1, ec.G) r = K1[0] % ec.n # if r == 0 (extremely unlikely for large ec.n) go back to a different ephemeral key assert r != 0 s1 = ((h1 + r * q) * mod_inv(k1, ec.n)) % ec.n # if s1 == 0 (extremely unlikely for large ec.n) go back to a different ephemeral key assert s1 != 0 print(" r:", hex(r)) print(" s1:", hex(s1)) print("*** Signature Verification") w = mod_inv(s1, ec.n) u = (h1 * w) % ec.n v = (r * w) % ec.n assert u != 0 assert v != 0 U = ec.pointMultiply(u, ec.G) V = ec.pointMultiply(v, Q) x, y = ec.pointAdd(U, V)
# ephemeral key k must be kept secret and never reused !!!!! # good choice: k = hf(q||c) # different for each msg, private because of q temp = q.to_bytes(32, 'big') + c.to_bytes(32, 'big') k_bytes = hf(temp).digest() k = int.from_bytes(k_bytes, 'big') % ec.n assert 0 < k < ec.n, "Invalid ephemeral key" print("eph k:", hex(k)) K = mult(ec, k, ec.G) r = K[0] % ec.n # if r == 0 (extremely unlikely for large ec.n) go back to a different k assert r != 0 s = (c + r * q) * mod_inv(k, ec.n) % ec.n # if s == 0 (extremely unlikely for large ec.n) go back to a different k assert s != 0 print(" r:", hex(r)) print(" s:", hex(s)) print("*** Verify Signature") w = mod_inv(s, ec.n) u = (c * w) % ec.n v = (r * w) % ec.n assert u != 0 assert v != 0 U = mult(ec, u, ec.G) V = mult(ec, v, Q) x, y = ec.add(U, V)
def test_threshold(self): """testing 2-of-3 threshold signature (Pedersen secret sharing)""" ec = secp256k1 hf = sha256 # parameters t = 2 H = second_generator(ec, hf) msg = hf(b'message to sign').digest() ### FIRST PHASE: key pair generation ### # signer one acting as the dealer commits1: List[Point] = list() q1 = (1 + random.getrandbits(ec.nlen)) % ec.n q1_prime = (1 + random.getrandbits(ec.nlen)) % ec.n commits1.append(double_mult(q1_prime, H, q1)) # sharing polynomials f1: List[int] = list() f1.append(q1) f1_prime: List[int] = list() f1_prime.append(q1_prime) for i in range(1, t): temp = (1 + random.getrandbits(ec.nlen)) % ec.n f1.append(temp) temp = (1 + random.getrandbits(ec.nlen)) % ec.n f1_prime.append(temp) commits1.append(double_mult(f1_prime[i], H, f1[i])) # shares of the secret alpha12 = 0 # share of q1 belonging to P2 alpha12_prime = 0 alpha13 = 0 # share of q1 belonging to P3 alpha13_prime = 0 for i in range(t): 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 # player two verifies consistency of his share RHS = 1, 0 for i in range(t): RHS = ec.add(RHS, mult(pow(2, i), commits1[i])) assert double_mult(alpha12_prime, H, alpha12) == RHS, 'player one is cheating' # player three verifies consistency of his share RHS = 1, 0 for i in range(t): RHS = ec.add(RHS, mult(pow(3, i), commits1[i])) assert double_mult(alpha13_prime, H, alpha13) == RHS, 'player one is cheating' # signer two acting as the dealer commits2: List[Point] = list() q2 = (1 + random.getrandbits(ec.nlen)) % ec.n q2_prime = (1 + random.getrandbits(ec.nlen)) % ec.n commits2.append(double_mult(q2_prime, H, q2)) # sharing polynomials f2: List[int] = list() f2.append(q2) f2_prime: List[int] = list() f2_prime.append(q2_prime) for i in range(1, t): temp = (1 + random.getrandbits(ec.nlen)) % ec.n f2.append(temp) temp = (1 + random.getrandbits(ec.nlen)) % ec.n f2_prime.append(temp) commits2.append(double_mult(f2_prime[i], H, f2[i])) # shares of the secret alpha21 = 0 # share of q2 belonging to P1 alpha21_prime = 0 alpha23 = 0 # share of q2 belonging to P3 alpha23_prime = 0 for i in range(t): 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 # player one verifies consistency of his share RHS = 1, 0 for i in range(t): RHS = ec.add(RHS, mult(pow(1, i), commits2[i])) assert double_mult(alpha21_prime, H, alpha21) == RHS, 'player two is cheating' # player three verifies consistency of his share RHS = 1, 0 for i in range(t): RHS = ec.add(RHS, mult(pow(3, i), commits2[i])) assert double_mult(alpha23_prime, H, alpha23) == RHS, 'player two is cheating' # signer three acting as the dealer commits3: List[Point] = list() q3 = (1 + random.getrandbits(ec.nlen)) % ec.n q3_prime = (1 + random.getrandbits(ec.nlen)) % ec.n commits3.append(double_mult(q3_prime, H, q3)) # sharing polynomials f3: List[int] = list() f3.append(q3) f3_prime: List[int] = list() f3_prime.append(q3_prime) for i in range(1, t): temp = (1 + random.getrandbits(ec.nlen)) % ec.n f3.append(temp) temp = (1 + random.getrandbits(ec.nlen)) % ec.n f3_prime.append(temp) commits3.append(double_mult(f3_prime[i], H, f3[i])) # shares of the secret alpha31 = 0 # share of q3 belonging to P1 alpha31_prime = 0 alpha32 = 0 # share of q3 belonging to P2 alpha32_prime = 0 for i in range(t): 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 # player one verifies consistency of his share RHS = 1, 0 for i in range(t): RHS = ec.add(RHS, mult(pow(1, i), commits3[i])) assert double_mult(alpha31_prime, H, alpha31) == RHS, 'player three is cheating' # player two verifies consistency of his share RHS = 1, 0 for i in range(t): RHS = ec.add(RHS, mult(pow(2, i), commits3[i])) assert double_mult(alpha32_prime, H, alpha32) == RHS, 'player two 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(t): alpha1 += (f1[i] * pow(1, i)) % ec.n alpha2 += (f2[i] * pow(2, i)) % ec.n alpha3 += (f3[i] * pow(3, i)) % ec.n # it's time to recover the public key Q = Q1 + Q2 + Q3 = (q1 + q2 + q3)G A1: List[Point] = list() A2: List[Point] = list() A3: List[Point] = list() # each participant i = 1, 2, 3 shares Qi as follows # he broadcasts these values for i in range(t): A1.append(mult(f1[i])) A2.append(mult(f2[i])) A3.append(mult(f3[i])) # he checks the others' values # player one RHS2 = 1, 0 RHS3 = 1, 0 for i in range(t): RHS2 = ec.add(RHS2, mult(pow(1, i), A2[i])) RHS3 = ec.add(RHS3, mult(pow(1, i), A3[i])) assert mult(alpha21) == RHS2, 'player two is cheating' assert mult(alpha31) == RHS3, 'player three is cheating' # player two RHS1 = 1, 0 RHS3 = 1, 0 for i in range(t): RHS1 = ec.add(RHS1, mult(pow(2, i), A1[i])) RHS3 = ec.add(RHS3, mult(pow(2, i), A3[i])) assert mult(alpha12) == RHS1, 'player one is cheating' assert mult(alpha32) == RHS3, 'player three is cheating' # player three RHS1 = 1, 0 RHS2 = 1, 0 for i in range(t): RHS1 = ec.add(RHS1, mult(pow(3, i), A1[i])) RHS2 = ec.add(RHS2, mult(pow(3, i), A2[i])) assert mult(alpha13) == RHS1, 'player one is cheating' assert mult(alpha23) == RHS2, 'player two is cheating' A: List[Point] = list() # commitment at the global sharing polynomial for i in range(t): A.append(ec.add(A1[i], ec.add(A2[i], A3[i]))) Q = A[0] # aggregated public key ### SECOND PHASE: generation of the nonces' pair ### # This phase follows exactly the key generation procedure # suppose that player one and three want to sign # signer one acting as the dealer commits1: List[Point] = list() k1 = (1 + random.getrandbits(ec.nlen)) % ec.n k1_prime = (1 + random.getrandbits(ec.nlen)) % ec.n commits1.append(double_mult(k1_prime, H, k1)) # sharing polynomials f1: List[int] = list() f1.append(k1) f1_prime: List[int] = list() f1_prime.append(k1_prime) for i in range(1, t): temp = (1 + random.getrandbits(ec.nlen)) % ec.n f1.append(temp) temp = (1 + random.getrandbits(ec.nlen)) % ec.n f1_prime.append(temp) commits1.append(double_mult(f1_prime[i], H, f1[i])) # shares of the secret beta13 = 0 # share of k1 belonging to P3 beta13_prime = 0 for i in range(t): beta13 += (f1[i] * pow(3, i)) % ec.n beta13_prime += (f1_prime[i] * pow(3, i)) % ec.n # player three verifies consistency of his share RHS = 1, 0 for i in range(t): RHS = ec.add(RHS, mult(pow(3, i), commits1[i])) assert double_mult(beta13_prime, H, beta13) == RHS, 'player one is cheating' # signer three acting as the dealer commits3: List[Point] = list() k3 = (1 + random.getrandbits(ec.nlen)) % ec.n k3_prime = (1 + random.getrandbits(ec.nlen)) % ec.n commits3.append(double_mult(k3_prime, H, k3)) # sharing polynomials f3: List[int] = list() f3.append(k3) f3_prime: List[int] = list() f3_prime.append(k3_prime) for i in range(1, t): temp = (1 + random.getrandbits(ec.nlen)) % ec.n f3.append(temp) temp = (1 + random.getrandbits(ec.nlen)) % ec.n f3_prime.append(temp) commits3.append(double_mult(f3_prime[i], H, f3[i])) # shares of the secret beta31 = 0 # share of k3 belonging to P1 beta31_prime = 0 for i in range(t): beta31 += (f3[i] * pow(1, i)) % ec.n beta31_prime += (f3_prime[i] * pow(1, i)) % ec.n # player one verifies consistency of his share RHS = 1, 0 for i in range(t): RHS = ec.add(RHS, mult(pow(1, i), commits3[i])) assert double_mult(beta31_prime, H, beta31) == RHS, 'player three is cheating' # shares of the secret nonce beta1 = beta31 % ec.n beta3 = beta13 % ec.n for i in range(t): beta1 += (f1[i] * pow(1, i)) % ec.n beta3 += (f3[i] * pow(3, i)) % ec.n # it's time to recover the public nonce B1: List[Point] = list() B3: List[Point] = list() # each participant i = 1, 3 shares Qi as follows # he broadcasts these values for i in range(t): B1.append(mult(f1[i])) B3.append(mult(f3[i])) # he checks the others' values # player one RHS3 = 1, 0 for i in range(t): RHS3 = ec.add(RHS3, mult(pow(1, i), B3[i])) assert mult(beta31) == RHS3, 'player three is cheating' # player three RHS1 = 1, 0 for i in range(t): RHS1 = ec.add(RHS1, mult(pow(3, i), B1[i])) assert mult(beta13) == RHS1, 'player one is cheating' B: List[Point] = list() # commitment at the global sharing polynomial for i in range(t): B.append(ec.add(B1[i], B3[i])) K = B[0] # aggregated public nonce if legendre_symbol(K[1], ec._p) != 1: beta1 = ec.n - beta1 beta3 = ec.n - beta3 ### PHASE THREE: signature generation ### # partial signatures ebytes = K[0].to_bytes(32, byteorder='big') ebytes += octets_from_point(Q, True, ec) ebytes += msg e = int_from_bits(hf(ebytes).digest(), ec) gamma1 = (beta1 + e * alpha1) % ec.n gamma3 = (beta3 + e * alpha3) % ec.n # each participant verifies the other partial signatures # player one if legendre_symbol(K[1], ec._p) == 1: RHS3 = ec.add(K, mult(e, Q)) for i in range(1, t): temp = double_mult(pow(3, i), B[i], e * pow(3, i), A[i]) RHS3 = ec.add(RHS3, temp) else: assert legendre_symbol(K[1], ec._p) != 1 RHS3 = ec.add(ec.opposite(K), mult(e, Q)) for i in range(1, t): temp = double_mult(pow(3, i), ec.opposite(B[i]), e * pow(3, i), A[i]) RHS3 = ec.add(RHS3, temp) assert mult(gamma3) == RHS3, 'player three is cheating' # player three if legendre_symbol(K[1], ec._p) == 1: RHS1 = ec.add(K, mult(e, Q)) for i in range(1, t): temp = double_mult(pow(1, i), B[i], e * pow(1, i), A[i]) RHS1 = ec.add(RHS1, temp) else: assert legendre_symbol(K[1], ec._p) != 1 RHS1 = ec.add(ec.opposite(K), mult(e, Q)) for i in range(1, t): temp = double_mult(pow(1, i), ec.opposite(B[i]), e * pow(1, i), A[i]) RHS1 = ec.add(RHS1, temp) assert mult(gamma1) == RHS1, 'player two 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 = K[0], sigma self.assertTrue(ssa._verify(msg, Q, sig)) ### ADDITIONAL PHASE: reconstruction of the private key ### secret = (omega1 * alpha1 + omega3 * alpha3) % ec.n self.assertEqual((q1 + q2 + q3) % ec.n, secret)
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" # 2.1 signer one acting as the dealer commits1 = [] k1 = ssa.det_nonce(msg, q1, ec, hf) k1_prime = ssa.det_nonce(msg, q1_prime, 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, q3, ec, hf) k3_prime = ssa.det_nonce(msg, q3_prime, 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, 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 = K[0], sigma assert ssa.verify(msg, 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)