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 _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 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 sign(msg: bytes, k: List[int], sign_key_idx: List[int], sign_keys: List[int], pubk_rings: Dict[int, List[Point]]) -> Tuple[bytes, Dict[int, List[int]]]: """ Borromean ring signature - signing algorithm https://github.com/ElementsProject/borromean-signatures-writeup https://github.com/Blockstream/borromean_paper/blob/master/borromean_draft_0.01_9ade1e49.pdf inputs: - msg: msg to be signed (bytes) - sign_key_idx: list of indexes representing each signing key per ring - sign_keys: list containing the whole set of signing keys (one per ring) - pubk_rings: dictionary of lists where internal lists represent single rings of pubkeys """ s: Dict[int, List[int]] = defaultdict(list) e: Dict[int, List[int]] = defaultdict(list) m = _get_msg_format(msg, pubk_rings) e0bytes = m ring_size = len(pubk_rings) # step 1 for i in range(ring_size): keys_size = len(pubk_rings[i]) s[i] = [0]*keys_size e[i] = [0]*keys_size j_star = sign_key_idx[i] start_idx = (j_star + 1) % keys_size R = octets_from_point(ec, mult(ec, k[i], ec.G), True) if start_idx != 0: for j in range(start_idx, keys_size): s[i][j] = random.getrandbits(256) e[i][j] = int_from_bits(ec, _hash(m, R, i, j)) assert 0 < e[i][j] < ec.n, "sign fail: how did you do that?!?" T = double_mult(ec, s[i][j], ec.G, -e[i][j], pubk_rings[i][j]) R = octets_from_point(ec, T, True) e0bytes += R e0 = hf(e0bytes).digest() # step 2 for i in range(ring_size): e[i][0] = int_from_bits(ec, _hash(m, e0, i, 0)) assert 0 < e[i][0] < ec.n, "sign fail: how did you do that?!?" j_star = sign_key_idx[i] for j in range(1, j_star+1): s[i][j-1] = random.getrandbits(256) T = double_mult(ec, s[i][j-1], ec.G, -e[i][j-1], pubk_rings[i][j-1]) R = octets_from_point(ec, T, True) e[i][j] = int_from_bits(ec, _hash(m, R, i, j)) assert 0 < e[i][j] < ec.n, "sign fail: how did you do that?!?" s[i][j_star] = k[i] + sign_keys[i]*e[i][j_star] return e0, s
def test_assorted_mult() -> None: ec = ec23_31 H = second_generator(ec) for k1 in range(-ec.n + 1, ec.n): K1 = mult(k1, ec.G, ec) for k2 in range(ec.n): K2 = mult(k2, H, ec) shamir = double_mult(k1, ec.G, k2, ec.G, ec) assert shamir == mult(k1 + k2, ec.G, ec) shamir = double_mult(k1, INF, k2, H, ec) assert ec.is_on_curve(shamir) assert shamir == K2 shamir = double_mult(k1, ec.G, k2, INF, ec) assert ec.is_on_curve(shamir) assert shamir == K1 shamir = double_mult(k1, ec.G, k2, H, ec) assert ec.is_on_curve(shamir) K1K2 = ec.add(K1, K2) assert K1K2 == shamir k3 = 1 + secrets.randbelow(ec.n - 1) K3 = mult(k3, ec.G, ec) K1K2K3 = ec.add(K1K2, K3) assert ec.is_on_curve(K1K2K3) boscoster = multi_mult([k1, k2, k3], [ec.G, H, ec.G], ec) assert ec.is_on_curve(boscoster) assert K1K2K3 == boscoster, k3 k4 = 1 + secrets.randbelow(ec.n - 1) K4 = mult(k4, H, ec) K1K2K3K4 = ec.add(K1K2K3, K4) assert ec.is_on_curve(K1K2K3K4) points = [ec.G, H, ec.G, H] boscoster = multi_mult([k1, k2, k3, k4], points, ec) assert ec.is_on_curve(boscoster) assert K1K2K3K4 == boscoster, k4 assert K1K2K3 == multi_mult([k1, k2, k3, 0], points, ec) assert K1K2 == multi_mult([k1, k2, 0, 0], points, ec) assert K1 == multi_mult([k1, 0, 0, 0], points, ec) assert INF == 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.G, H, ec.G], ec)
def _verify(msg: bytes, e0: bytes, s: Dict[int, List[int]], pubk_rings: Dict[int, List[Point]]) -> bool: ring_size = len(pubk_rings) m = _get_msg_format(msg, pubk_rings) e: Dict[int, List[int]] = defaultdict(list) e0bytes = m for i in range(ring_size): keys_size = len(pubk_rings[i]) e[i] = [0]*keys_size e[i][0] = int_from_bits(ec, _hash(m, e0, i, 0)) assert e[i][0] != 0, "invalid sig: how did you do that?!?" R = b'\0x00' for j in range(keys_size): T = double_mult(ec, s[i][j], ec.G, -e[i][j], pubk_rings[i][j]) R = octets_from_point(ec, T, True) if j != len(pubk_rings[i])-1: e[i][j+1] = int_from_bits(ec, _hash(m, R, i, j+1)) assert e[i][j+1] != 0, "invalid sig: how did you do that?!?" else: e0bytes += R e0_prime = hf(e0bytes).digest() return e0_prime == e0
def test_shamir(self): ec = ec23_31 for k1 in range(ec.n): for k2 in range(ec.n): shamir = double_mult(ec, k1, ec.G, k2, ec.G) std = ec.add(mult(ec, k1, ec.G), mult(ec, k2, ec.G)) self.assertEqual(shamir, std) shamir = double_mult(ec, k1, Inf, k2, ec.G) std = ec.add(mult(ec, k1, Inf), mult(ec, k2, ec.G)) self.assertEqual(shamir, std) shamir = double_mult(ec, k1, ec.G, k2, Inf) std = ec.add(mult(ec, k1, ec.G), mult(ec, k2, Inf)) self.assertEqual(shamir, std)
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 test_second_generator(self): """ important remark on secp256-zkp prefix for compressed encoding of the second generator: https://github.com/garyyu/rust-secp256k1-zkp/wiki/Pedersen-Commitment """ ec = secp256k1 hf = sha256 H = pedersen.second_generator(ec, hf) self.assertEqual(H, point_from_octets(ec, '0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0')) # 0*G + 1*H T = double_mult(ec, 1, H, 0) self.assertEqual(T, point_from_octets(ec, '0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0')) # 0*G + 2*H T = double_mult(ec, 2, H, 0) self.assertEqual(T, point_from_octets(ec, '03fad265e0a0178418d006e247204bcf42edb6b92188074c9134704c8686eed37a')) T = mult(ec, 2, H) self.assertEqual(T, point_from_octets(ec, '03fad265e0a0178418d006e247204bcf42edb6b92188074c9134704c8686eed37a')) # 0*G + 3*H T = double_mult(ec, 3, H, 0) self.assertEqual(T, point_from_octets(ec, '025ef47fcde840a435e831bbb711d466fc1ee160da3e15437c6c469a3a40daacaa')) T = mult(ec, 3, H) self.assertEqual(T, point_from_octets(ec, '025ef47fcde840a435e831bbb711d466fc1ee160da3e15437c6c469a3a40daacaa')) # 1*G+0*H T = double_mult(ec, 0, H, 1) self.assertEqual(T, point_from_octets(ec, '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')) T = mult(ec, 1) self.assertEqual(T, point_from_octets(ec, '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')) # 2*G+0*H T = double_mult(ec, 0, H, 2) self.assertEqual(T, point_from_octets(ec, '02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5')) T = mult(ec, 2) self.assertEqual(T, point_from_octets(ec, '02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5')) # 3*G+0*H T = double_mult(ec, 0, H, 3) self.assertEqual(T, point_from_octets(ec, '02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9')) T = mult(ec, 3) self.assertEqual(T, point_from_octets(ec, '02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9')) # 0*G+5*H T = double_mult(ec, 5, H, 0) self.assertEqual(T, point_from_octets(ec, '039e431be0851721f9ce35cc0f718fce7d6d970e3ddd796643d71294d7a09b554e')) T = mult(ec, 5, H) self.assertEqual(T, point_from_octets(ec, '039e431be0851721f9ce35cc0f718fce7d6d970e3ddd796643d71294d7a09b554e')) # 0*G-5*H T = double_mult(ec, -5, H, 0) self.assertEqual(T, point_from_octets(ec, '029e431be0851721f9ce35cc0f718fce7d6d970e3ddd796643d71294d7a09b554e')) T = mult(ec, -5, H) self.assertEqual(T, point_from_octets(ec, '029e431be0851721f9ce35cc0f718fce7d6d970e3ddd796643d71294d7a09b554e')) # 1*G-5*H U = double_mult(ec, -5, H, 1) self.assertEqual(U, point_from_octets(ec, '02b218ddacb34d827c71760e601b41d309bc888cf7e3ab7cc09ec082b645f77e5a')) U = ec.add(ec.G, T) # reusing previous T value self.assertEqual(U, point_from_octets(ec, '02b218ddacb34d827c71760e601b41d309bc888cf7e3ab7cc09ec082b645f77e5a')) H = pedersen.second_generator(secp256r1, hf) H = pedersen.second_generator(secp384r1, sha384)
def commit(r: int, v: int, ec: Curve, hf) -> Point: """Return rG + vH, with H being second (NUMS) generator of the curve""" H = second_generator(ec, hf) Q = double_mult(ec, r, ec.G, v, H) assert Q[1] != 0, "how did you do that?!?" return Q
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)
def test_musig() -> None: """testing 3-of-3 MuSig. https://github.com/ElementsProject/secp256k1-zkp/blob/secp256k1-zkp/src/modules/musig/musig.md https://blockstream.com/2019/02/18/musig-a-new-multisignature-standard/ https://eprint.iacr.org/2018/068 https://blockstream.com/2018/01/23/musig-key-aggregation-schnorr-signatures.html https://medium.com/@snigirev.stepan/how-schnorr-signatures-may-improve-bitcoin-91655bcb4744 """ ec = CURVES["secp256k1"] m = hf(b"message to sign").digest() # the signers private and public keys, # including both the curve Point and the BIP340-Schnorr public key q1, x_Q1_int = ssa.gen_keys() x_Q1 = x_Q1_int.to_bytes(ec.psize, "big") q2, x_Q2_int = ssa.gen_keys() x_Q2 = x_Q2_int.to_bytes(ec.psize, "big") q3, x_Q3_int = ssa.gen_keys() x_Q3 = x_Q3_int.to_bytes(ec.psize, "big") # (non interactive) key setup # this is MuSig core: the rest is just Schnorr signature additivity # 1. lexicographic sorting of public keys keys: List[bytes] = [] keys.append(x_Q1) keys.append(x_Q2) keys.append(x_Q3) keys.sort() # 2. coefficients prefix = b"".join(keys) a1 = int_from_bits(hf(prefix + x_Q1).digest(), ec.nlen) % ec.n a2 = int_from_bits(hf(prefix + x_Q2).digest(), ec.nlen) % ec.n a3 = int_from_bits(hf(prefix + x_Q3).digest(), ec.nlen) % ec.n # 3. aggregated public key Q1 = mult(q1) Q2 = mult(q2) Q3 = mult(q3) Q = ec.add(double_mult(a1, Q1, a2, Q2), mult(a3, Q3)) if Q[1] % 2: # print("Q has been negated") a1 = ec.n - a1 # pragma: no cover a2 = ec.n - a2 # pragma: no cover a3 = ec.n - a3 # pragma: no cover # ready to sign: nonces and nonce commitments k1, _ = ssa.gen_keys() K1 = mult(k1) k2, _ = ssa.gen_keys() K2 = mult(k2) k3, _ = ssa.gen_keys() K3 = mult(k3) # exchange {K_i} (interactive) # computes s_i (non interactive) # WARNING: signers must exchange the nonces commitments {K_i} # before sharing {s_i} # same for all signers K = ec.add(ec.add(K1, K2), K3) if K[1] % 2: k1 = ec.n - k1 # pragma: no cover k2 = ec.n - k2 # pragma: no cover k3 = ec.n - k3 # pragma: no cover r = K[0] e = ssa._challenge(m, Q[0], r, ec, hf) s_1 = (k1 + e * a1 * q1) % ec.n s_2 = (k2 + e * a2 * q2) % ec.n s3 = (k3 + e * a3 * q3) % ec.n # exchange s_i (interactive) # finalize signature (non interactive) s = (s_1 + s_2 + s3) % ec.n sig = r, s # check signature is valid ssa._assert_as_valid(m, Q[0], sig, ec, hf)
k1 = int_from_bits(k1_bytes, ec.nlen) % ec.n assert 0 < k1 < ec.n, "Invalid ephemeral key" print(f"eph k: {hex(k1).upper()}") K1 = mult(k1, ec.G) c1 = _challenge(msg1, Q[0], K1[0], ec, sha256) print(f" c1: {hex(c1).upper()}") print("2. Sign message") r1 = K1[0] s1 = (k1 + c1 * q) % ec.n print(f" r1: {hex(r1).upper()}") print(f" s1: {hex(s1).upper()}") print("3. Verify signature") K = double_mult(-c1, Q, s1, ec.G) print(K[0] == r1) print("\n0. Another message to sign") orig_msg2 = "and Paolo is right to be afraid" msg2 = sha256(orig_msg2.encode()).digest() print(msg2.hex().upper()) print("\n*** Ephemeral key and challenge") # ephemeral key k must be kept secret and never reused !!!!! k2 = k1 print(f"eph k: {hex(k2).upper()}") K2 = mult(k2, ec.G) c2 = _challenge(msg2, Q[0], K2[0], ec, sha256) print(f" c2: {hex(c2).upper()}")
import time from btclib.curve import mult, double_mult from btclib.curves import secp256k1 random.seed(42) ec = secp256k1 # setup k1 = [] k2 = [] Q = [] for _ in range(50): k1.append(random.getrandbits(ec.nlen) % ec.n) k2.append(random.getrandbits(ec.nlen) % ec.n) q = random.getrandbits(ec.nlen) % ec.n Q.append(mult(ec, q, ec.G)) start = time.time() for i in range(len(Q)): ec.add(mult(ec, k1[i], ec.G), mult(ec, k2[i], Q[i])) elapsed1 = time.time() - start start = time.time() for i in range(len(Q)): double_mult(ec, k1[i], ec.G, k2[i], Q[i]) elapsed2 = time.time() - start print(elapsed2 / elapsed1)
def test_musig(self): """ testing 3-of-3 MuSig https://github.com/ElementsProject/secp256k1-zkp/blob/secp256k1-zkp/src/modules/musig/musig.md https://blockstream.com/2019/02/18/musig-a-new-multisignature-standard/ https://eprint.iacr.org/2018/068 https://blockstream.com/2018/01/23/musig-key-aggregation-schnorr-signatures.html https://medium.com/@snigirev.stepan/how-schnorr-signatures-may-improve-bitcoin-91655bcb4744 """ ec = secp256k1 M = hf('message to sign'.encode()).digest() # key setup is not interactive # first signer q1 = int_from_octets( '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d92ad1d') Q1 = mult(ec, q1, ec.G) k1 = rfc6979(ec, hf, M, q1) K1 = mult(ec, k1, ec.G) # second signer q2 = int_from_octets( '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d') Q2 = mult(ec, q2, ec.G) k2 = rfc6979(ec, hf, M, q2) K2 = mult(ec, k2, ec.G) # third signer q3 = int_from_octets( '0c28fca386c7aff7600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d') Q3 = mult(ec, q3, ec.G) k3 = rfc6979(ec, hf, M, q3) K3 = mult(ec, k3, ec.G) # this is MuSig core: the rest is just Schnorr signature additivity L: List[Point] = list() # multiset of public keys L.append(octets_from_point(ec, Q1, False)) L.append(octets_from_point(ec, Q2, False)) L.append(octets_from_point(ec, Q3, False)) L.sort() # using lexicographic ordering L_brackets = b'' for i in range(len(L)): L_brackets += L[i] h1 = hf(L_brackets + octets_from_point(ec, Q1, False)).digest() a1 = int_from_bits(ec, h1) h2 = hf(L_brackets + octets_from_point(ec, Q2, False)).digest() a2 = int_from_bits(ec, h2) h3 = hf(L_brackets + octets_from_point(ec, Q3, False)).digest() a3 = int_from_bits(ec, h3) # aggregated public key Q = ec.add(double_mult(ec, a1, Q1, a2, Q2), mult(ec, a3, Q3)) Q_bytes = octets_from_point(ec, Q, True) ######################## # interactive signature: exchange K, compute s # WARNING: the signers should exchange commitments to the public # nonces before sending the nonces themselves # first signer # K, r_bytes, and e as calculated by any signer # are the same as the ones by the other signers K = ec.add(ec.add(K1, K2), K3) r_bytes = K[0].to_bytes(32, byteorder="big") e = int_from_bits(ec, hf(r_bytes + Q_bytes + M).digest()) if legendre_symbol(K[1], ec._p) != 1: # no need to actually change K[1], as it is not used anymore # let's fix k1 instead, as it is used later # note that all other signers will change their k too k1 = ec.n - k1 s1 = (k1 + e * a1 * q1) % ec.n # second signer # K, r_bytes, and e as calculated by any signer # are the same as the ones by the other signers if legendre_symbol(K[1], ec._p) != 1: # no need to actually change K[1], as it is not used anymore # let's fix k2 instead, as it is used later # note that all other signers will change their k too k2 = ec.n - k2 s2 = (k2 + e * a2 * q2) % ec.n # third signer # K, r_bytes, and e as calculated by any signer # are the same as the ones by the other signers if legendre_symbol(K[1], ec._p) != 1: # no need to actually change K[1], as it is not used anymore # let's fix k3 instead, as it is used later # note that all other signers will change their k too k3 = ec.n - k3 s3 = (k3 + e * a3 * q3) % ec.n ############################################ # interactive signature: exchange signatures # combine all (K[0], s) signatures into a single signature # anyone can do the following sig = K[0], (s1 + s2 + s3) % ec.n self.assertTrue(ssa.verify(ec, hf, M, Q, sig))
def test_threshold(self): """testing 2-of-3 threshold signature (Pedersen secret sharing)""" ec = secp256k1 # parameters t = 2 H = second_generator(ec, hf) msg = hf('message to sign'.encode()).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(ec, q1, ec.G, q1_prime, H)) # 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(ec, f1[i], ec.G, f1_prime[i], H)) # 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(ec, pow(2, i), commits1[i])) assert double_mult(ec, alpha12, ec.G, alpha12_prime, H) == 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(ec, pow(3, i), commits1[i])) assert double_mult(ec, alpha13, ec.G, alpha13_prime, H) == 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(ec, q2, ec.G, q2_prime, H)) # 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(ec, f2[i], ec.G, f2_prime[i], H)) # 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(ec, pow(1, i), commits2[i])) assert double_mult(ec, alpha21, ec.G, alpha21_prime, H) == 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(ec, pow(3, i), commits2[i])) assert double_mult(ec, alpha23, ec.G, alpha23_prime, H) == 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(ec, q3, ec.G, q3_prime, H)) # 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(ec, f3[i], ec.G, f3_prime[i], H)) # 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(ec, pow(1, i), commits3[i])) assert double_mult(ec, alpha31, ec.G, alpha31_prime, H) == 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(ec, pow(2, i), commits3[i])) assert double_mult(ec, alpha32, ec.G, alpha32_prime, H) == 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(ec, f1[i], ec.G)) A2.append(mult(ec, f2[i], ec.G)) A3.append(mult(ec, f3[i], ec.G)) # he checks the others' values # player one RHS2 = 1, 0 RHS3 = 1, 0 for i in range(t): RHS2 = ec.add(RHS2, mult(ec, pow(1, i), A2[i])) RHS3 = ec.add(RHS3, mult(ec, pow(1, i), A3[i])) assert mult(ec, alpha21, ec.G) == RHS2, 'player two is cheating' assert mult(ec, alpha31, ec.G) == RHS3, 'player three is cheating' # player two RHS1 = 1, 0 RHS3 = 1, 0 for i in range(t): RHS1 = ec.add(RHS1, mult(ec, pow(2, i), A1[i])) RHS3 = ec.add(RHS3, mult(ec, pow(2, i), A3[i])) assert mult(ec, alpha12, ec.G) == RHS1, 'player one is cheating' assert mult(ec, alpha32, ec.G) == RHS3, 'player three is cheating' # player three RHS1 = 1, 0 RHS2 = 1, 0 for i in range(t): RHS1 = ec.add(RHS1, mult(ec, pow(3, i), A1[i])) RHS2 = ec.add(RHS2, mult(ec, pow(3, i), A2[i])) assert mult(ec, alpha13, ec.G) == RHS1, 'player one is cheating' assert mult(ec, alpha23, ec.G) == 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(ec, k1, ec.G, k1_prime, H)) # 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(ec, f1[i], ec.G, f1_prime[i], H)) # 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(ec, pow(3, i), commits1[i])) assert double_mult(ec, beta13, ec.G, beta13_prime, H) == 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(ec, k3, ec.G, k3_prime, H)) # 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(ec, f3[i], ec.G, f3_prime[i], H)) # 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(ec, pow(1, i), commits3[i])) assert double_mult(ec, beta31, ec.G, beta31_prime, H) == 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(ec, f1[i], ec.G)) B3.append(mult(ec, f3[i], ec.G)) # he checks the others' values # player one RHS3 = 1, 0 for i in range(t): RHS3 = ec.add(RHS3, mult(ec, pow(1, i), B3[i])) assert mult(ec, beta31, ec.G) == RHS3, 'player three is cheating' # player three RHS1 = 1, 0 for i in range(t): RHS1 = ec.add(RHS1, mult(ec, pow(3, i), B1[i])) assert mult(ec, beta13, ec.G) == 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(ec, Q, True) ebytes += msg e = int_from_bits(ec, hf(ebytes).digest()) 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(ec, e, Q)) for i in range(1, t): temp = double_mult(ec, 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(ec, e, Q)) for i in range(1, t): temp = double_mult(ec, pow(3, i), ec.opposite(B[i]), e * pow(3, i), A[i]) RHS3 = ec.add(RHS3, temp) assert mult(ec, gamma3, ec.G) == RHS3, 'player three is cheating' # player three if legendre_symbol(K[1], ec._p) == 1: RHS1 = ec.add(K, mult(ec, e, Q)) for i in range(1, t): temp = double_mult(ec, 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(ec, e, Q)) for i in range(1, t): temp = double_mult(ec, pow(1, i), ec.opposite(B[i]), e * pow(1, i), A[i]) RHS1 = ec.add(RHS1, temp) assert mult(ec, gamma1, ec.G) == 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(ec, hf, 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_double_mult() -> None: H = second_generator(secp256k1) G = secp256k1.G # 0*G + 1*H T = double_mult(1, H, 0, G) assert T == H T = multi_mult([1, 0], [H, G]) assert T == H # 0*G + 2*H exp = mult(2, H) T = double_mult(2, H, 0, G) assert T == exp T = multi_mult([2, 0], [H, G]) assert T == exp # 0*G + 3*H exp = mult(3, H) T = double_mult(3, H, 0, G) assert T == exp T = multi_mult([3, 0], [H, G]) assert T == exp # 1*G + 0*H T = double_mult(0, H, 1, G) assert T == G T = multi_mult([0, 1], [H, G]) assert T == G # 2*G + 0*H exp = mult(2, G) T = double_mult(0, H, 2, G) assert T == exp T = multi_mult([0, 2], [H, G]) assert T == exp # 3*G + 0*H exp = mult(3, G) T = double_mult(0, H, 3, G) assert T == exp T = multi_mult([0, 3], [H, G]) assert T == exp # 0*G + 5*H exp = mult(5, H) T = double_mult(5, H, 0, G) assert T == exp T = multi_mult([5, 0], [H, G]) assert T == exp # 0*G - 5*H exp = mult(-5, H) T = double_mult(-5, H, 0, G) assert T == exp T = multi_mult([-5, 0], [H, G]) assert T == exp # 1*G - 5*H exp = secp256k1.add(G, T) T = double_mult(-5, H, 1, G) assert T == exp
def test_musig(self): """ testing 3-of-3 MuSig https://eprint.iacr.org/2018/068 https://blockstream.com/2018/01/23/musig-key-aggregation-schnorr-signatures.html https://medium.com/@snigirev.stepan/how-schnorr-signatures-may-improve-bitcoin-91655bcb4744 """ ec = secp256k1 L: List[Point] = list() # multiset of public keys M = hf('message to sign'.encode()).digest() # first signer q1 = int_from_octets( '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d92ad1d') Q1 = mult(ec, q1, ec.G) L.append(octets_from_point(ec, Q1, False)) # ephemeral private nonce k1 = 0x012a2a833eac4e67e06611aba01345b85cdd4f5ad44f72e369ef0dd640424dbb K1 = mult(ec, k1, ec.G) K1_x = K1[0] if legendre_symbol(K1[1], ec._p) != 1: k1 = ec.n - k1 K1 = K1_x, ec.y_quadratic_residue(K1_x, True) #K1 = mult(ec, k1, ec.G) # second signer q2 = int_from_octets( '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d') Q2 = mult(ec, q2, ec.G) L.append(octets_from_point(ec, Q2, False)) k2 = 0x01a2a0d3eac4e67e06611aba01345b85cdd4f5ad44f72e369ef0dd640424dbdb K2 = mult(ec, k2, ec.G) K2_x = K2[0] if legendre_symbol(K2[1], ec._p) != 1: k2 = ec.n - k2 K2 = K2_x, ec.y_quadratic_residue(K2_x, True) #K2 = mult(ec, k2, ec.G) # third signer q3 = random.getrandbits(ec.nlen) % ec.n Q3 = mult(ec, q3, ec.G) while Q3 == None: # plausible only for small (test) cardinality groups q3 = random.getrandbits(ec.nlen) % ec.n Q3 = mult(ec, q3, ec.G) L.append(octets_from_point(ec, Q3, False)) k3 = random.getrandbits(ec.nlen) % ec.n K3 = mult(ec, k3, ec.G) while K3 == None: # plausible only for small (test) cardinality groups k3 = random.getrandbits(ec.nlen) % ec.n K3 = mult(ec, k3, ec.G) K3_x = K3[0] if legendre_symbol(K3[1], ec._p) != 1: k3 = ec.n - k3 K3 = K3_x, ec.y_quadratic_residue(K3_x, True) #K3 = mult(ec, k3, ec.G) L.sort() # using lexicographic ordering L_brackets = b'' for i in range(len(L)): L_brackets += L[i] h1 = hf(L_brackets + octets_from_point(ec, Q1, False)).digest() a1 = int_from_bits(ec, h1) h2 = hf(L_brackets + octets_from_point(ec, Q2, False)).digest() a2 = int_from_bits(ec, h2) h3 = hf(L_brackets + octets_from_point(ec, Q3, False)).digest() a3 = int_from_bits(ec, h3) # aggregated public key Q_All = double_mult(ec, a1, Q1, a2, Q2) Q_All = ec.add(Q_All, mult(ec, a3, Q3)) Q_All_bytes = octets_from_point(ec, Q_All, True) ######################## # exchange K_x, compute s # WARNING: the signers should exchange commitments to the public # nonces before sending the nonces themselves # first signer use K2_x and K3_x y = ec.y_quadratic_residue(K2_x, True) K2_recovered = (K2_x, y) y = ec.y_quadratic_residue(K3_x, True) K3_recovered = (K3_x, y) K1_All = ec.add(ec.add(K1, K2_recovered), K3_recovered) if legendre_symbol(K1_All[1], ec._p) != 1: # no need to actually change K1_All[1], as it is not used anymore # let's fix k1 instead, as it is used later k1 = ec.n - k1 K1_All0_bytes = K1_All[0].to_bytes(32, byteorder="big") h1 = hf(K1_All0_bytes + Q_All_bytes + M).digest() c1 = int_from_bits(ec, h1) assert 0 < c1 and c1 < ec.n, "sign fail" s1 = (k1 + c1 * a1 * q1) % ec.n # second signer use K1_x and K3_x y = ec.y_quadratic_residue(K1_x, True) K1_recovered = (K1_x, y) y = ec.y_quadratic_residue(K3_x, True) K3_recovered = (K3_x, y) K2_All = ec.add(ec.add(K2, K1_recovered), K3_recovered) if legendre_symbol(K2_All[1], ec._p) != 1: # no need to actually change K2_All[1], as it is not used anymore # let's fix k2 instead, as it is used later k2 = ec.n - k2 K2_All0_bytes = K2_All[0].to_bytes(32, byteorder="big") h2 = hf(K2_All0_bytes + Q_All_bytes + M).digest() c2 = int_from_bits(ec, h2) assert 0 < c2 and c2 < ec.n, "sign fail" s2 = (k2 + c2 * a2 * q2) % ec.n # third signer use K1_x and K2_x y = ec.y_quadratic_residue(K1_x, True) K1_recovered = (K1_x, y) y = ec.y_quadratic_residue(K2_x, True) K2_recovered = (K2_x, y) K3_All = ec.add(ec.add(K1_recovered, K2_recovered), K3) if legendre_symbol(K3_All[1], ec._p) != 1: # no need to actually change K3_All[1], as it is not used anymore # let's fix k3 instead, as it is used later k3 = ec.n - k3 K3_All0_bytes = K3_All[0].to_bytes(32, byteorder="big") h3 = hf(K3_All0_bytes + Q_All_bytes + M).digest() c3 = int_from_bits(ec, h3) assert 0 < c3 and c3 < ec.n, "sign fail" s3 = (k3 + c3 * a3 * q3) % ec.n ############################################ # combine signatures into a single signature # anyone can do the following assert K1_All[0] == K2_All[0], "sign fail" assert K2_All[0] == K3_All[0], "sign fail" s_All = (s1 + s2 + s3) % ec.n sig = (K1_All[0], s_All) self.assertTrue(ssa.verify(ec, hf, M, Q_All, sig))