def assert_as_valid(msg: Octets, e0: bytes, s: SValues, pubk_rings: PubkeyRing) -> bool: msg = bytes_from_octets(msg) m = _get_msg_format(msg, pubk_rings) ring_size = len(pubk_rings) e: SValues = 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(_hash(m, e0, i, 0), ec.nlen) % ec.n # edge case that cannot be reproduced in the test suite if e[i][0] == 0: err_msg = "implausibile signature failure" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover r = b"\0x00" for j in range(keys_size): t = double_mult(-e[i][j], pubk_rings[i][j], s[i][j], ec.G) r = bytes_from_point(t, ec) if j != len(pubk_rings[i]) - 1: h = _hash(m, r, i, j + 1) e[i][j + 1] = int_from_bits(h, ec.nlen) % ec.n # edge case that cannot be reproduced in the test suite if e[i][j + 1] == 0: err_msg = "implausibile signature failure" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover else: e0bytes += r e0_prime = hf(e0bytes).digest() return e0_prime == e0
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 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 sign(ec: Curve, hf, msg: bytes, d: int, k: Optional[int] = None) -> Tuple[int, int]: """ECDSA signing operation according to SEC 1 http://www.secg.org/sec1-v2.pdf Steps numbering follows SEC 1 v.2 section 4.1.3 """ # https://tools.ietf.org/html/rfc6979#section-3.2 # The message msg is first processed by hf, yielding the value mhd=hf(msg), # a sequence of bits of length hlen. Normally, hf is chosen such that # its output length hlen is roughly equal to nlen, since the overall # security of the signature scheme will depend on the smallest of hlen # and nlen; however, the (Curve)DSA standard support all combinations of # hlen and nlen. mhd = hf(msg).digest() # 4 # H(m) is transformed into an integer modulo ec.n using int_from_bits: e = int_from_bits(ec, mhd) # 5 if k is None: k = rfc6979(ec, hf, mhd, d) # 1 if not 0 < k < ec.n: raise ValueError(f"ephemeral key {hex(k)} not in (0, n)") # second part delegated to helper function used in testing return _sign(ec, e, d, k)
def second_generator(ec: Curve = secp256k1, hf: HashF = sha256) -> Point: """Second (with respect to G) elliptic curve generator. Second (with respect to G) Nothing-Up-My-Sleeve (NUMS) elliptic curve generator. The hash of G is coerced it to a point (x_H, y_H). If the resulting point is not on the curve, keep on incrementing x_H until a valid curve point (x_H, y_H) is obtained. idea: https://crypto.stackexchange.com/questions/25581/second-generator-for-secp256k1-curve source: https://github.com/ElementsProject/secp256k1-zkp/blob/secp256k1-zkp/src/modules/rangeproof/main_impl.h """ G_bytes = bytes_from_point(ec.G, ec, compressed=False) hash_ = hf() hash_.update(G_bytes) hash_digest = hash_.digest() x_H = int_from_bits(hash_digest, ec.nlen) % ec.n while True: try: y_H = ec.y_even(x_H) return x_H, y_H except BTClibValueError: x_H += 1 x_H %= ec.p
def _e(ec: Curve, hf, r: int, P: Point, mhd: bytes) -> int: # Let e = int(hf(bytes(x(R)) || bytes(dG) || mhd)) mod n. ebytes = octets_from_int(r, ec.psize) # FIXME: hsize, nsize ? ebytes += octets_from_point(ec, P, True) ebytes += mhd ebytes = hf(ebytes).digest() e = int_from_bits(ec, ebytes) return e
def _e(ec: Curve, hf: Callable[[Any], Any], r: int, P: Point, mhd: bytes) -> int: # Let e = int(hf(bytes(x(R)) || bytes(dG) || mhd)) mod n. h = hf() h.update(octets_from_int(r, ec.psize)) h.update(octets_from_point(ec, P, True)) h.update(mhd) e = int_from_bits(ec, h.digest()) return e
def challenge_(msg_hash: Octets, ec: Curve = secp256k1, hf: HashF = hashlib.sha256) -> int: # the message msg_hash: a hf_len array hf_len = hf().digest_size msg_hash = bytes_from_octets(msg_hash, hf_len) # leftmost ec.nlen bits %= ec.n return int_from_bits(msg_hash, ec.nlen) % ec.n
def _verify(ec: Curve, hf: Callable[[Any], Any], msg: bytes, P: Point, sig: ECDS) -> bool: # Private function for test/dev purposes # It raises Errors, while verify should always return True or False # The message digest m: a 32-byte array msghd = hf(msg).digest() # 2 c = int_from_bits(ec, msghd) # 3 # second part delegated to helper function return _verhlp(ec, c, P, sig)
def pubkey_recovery(ec: Curve, hf, msg: bytes, sig: ECDS) -> List[Point]: """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 """ # The message digest m: a 32-byte array mhd = hf(msg).digest() # 1.5 e = int_from_bits(ec, mhd) # 1.5 return _pubkey_recovery(ec, e, sig)
def pubkey_recovery(ec: Curve, hf: Callable[[Any], Any], msg: bytes, sig: ECDS) -> Sequence[Point]: """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 """ # The message digest m: a 32-byte array msghd = hf(msg).digest() # 1.5 c = int_from_bits(ec, msghd) # 1.5 return _pubkey_recovery(ec, c, sig)
def rfc6979(ec: Curve, hf: Callable[[Any], Any], mhd: bytes, x: int) -> int: """Return a deterministic ephemeral key following rfc6979""" if not 0 < x < ec.n: raise ValueError(f"private key {hex(x)} not in [1, n-1]") hsize = hf().digest_size if len(mhd) != hsize: errMsg = f"mismatch between hf digest size ({hsize}) and " errMsg += f"hashed message size ({len(mhd)})" raise ValueError(errMsg) h_int = int_from_bits(ec, mhd) # leftmost ec.nlen bits %= ec.n return _rfc6979(ec, hf, h_int, x)
def verify_commit(c: bytes, ec: Curve, hf, receipt: Receipt) -> bool: w, R = receipt # w in [1..n-1] dsa # w in [1..p-1] ssa # different verify functions? # verify R is a good point? ch = hf(c).digest() e = hf(octets_from_point(ec, R, True) + ch).digest() e = int_from_bits(ec, e) W = ec.add(R, mult(ec, e, ec.G)) # different verify functions? # return w == W[0] # ECSS return w == W[0] % ec.n # ECDS, FIXME: ECSSA
def _verify(ec: Curve, hf, msg: bytes, P: Point, sig: ECDS) -> bool: """Private function provided for testing purposes only. It raises Errors, while verify should always return True or False See SEC 1 v.2 section 4.1.4 http://www.secg.org/sec1-v2.pdf """ # The message digest m: a 32-byte array mhd = hf(msg).digest() # 2 e = int_from_bits(ec, mhd) # 3 # Let P = point(pk); fail if point(pk) fails. # P on point will be checked below by double_mult # second part delegated to helper function used in testing return _verhlp(ec, e, P, sig)
def challenge_(msg_hash: Octets, x_Q: int, x_K: int, ec: Curve, hf: HashF) -> int: # the message msg_hash: a hf_len array hf_len = hf().digest_size msg_hash = bytes_from_octets(msg_hash, hf_len) t = b"".join([ x_K.to_bytes(ec.p_size, byteorder="big", signed=False), x_Q.to_bytes(ec.p_size, byteorder="big", signed=False), msg_hash, ]) t = tagged_hash("BIP0340/challenge".encode(), t, hf) c = int_from_bits(t, ec.nlen) % ec.n if c == 0: raise BTClibRuntimeError("invalid zero challenge") # pragma: no cover return c
def _det_nonce_( msg_hash: bytes, q: int, Q: int, aux: bytes, ec: Curve, hf: HashF ) -> int: # assume the random oracle model for the hash function, # i.e. hash values can be considered uniformly random # Note that in general, taking a uniformly random integer # modulo the curve order n would produce a biased result. # However, if the order n is sufficiently close to 2^hf_len, # then the bias is not observable: # e.g. for secp256k1 and sha256 1-n/2^256 it is about 1.27*2^-128 # # the unbiased implementation is provided here, # which works also for very-low-cardinality test curves randomizer = tagged_hash("BIP0340/aux".encode(), aux, hf) xor = q ^ int.from_bytes(randomizer, "big", signed=False) max_len = max(ec.n_size, hf().digest_size) t = b"".join( [ xor.to_bytes(max_len, byteorder="big", signed=False), Q.to_bytes(ec.p_size, byteorder="big", signed=False), msg_hash, ] ) nonce_tag = "BIP0340/nonce".encode() while True: t = tagged_hash(nonce_tag, t, hf) # The following lines would introduce a bias # nonce = int.from_bytes(t, 'big') % ec.n # nonce = int_from_bits(t, ec.nlen) % ec.n # In general, taking a uniformly random integer (like those # obtained from a hash function in the random oracle model) # modulo the curve order n would produce a biased result. # However, if the order n is sufficiently close to 2^hf_len, # then the bias is not observable: e.g. # for secp256k1 and sha256 1-n/2^256 it is about 1.27*2^-128 nonce = int_from_bits(t, ec.nlen) # candidate nonce if 0 < nonce < ec.n: # acceptable value for nonce return nonce # successful candidate
def _tweak(commit_hash: Octets, R: Point, ec: Curve, hf: HashF) -> int: "Return the hash(R||commit_hash) tweak for the provided R." t = bytes_from_point(R, ec) + bytes_from_octets(commit_hash) while True: h = hf() h.update(t) t = h.digest() # The following lines would introduce a bias # nonce = int.from_bytes(t, 'big') % ec.n # nonce = int_from_bits(t, ec.nlen) % ec.n # In general, taking a uniformly random integer (like those # obtained from a hash function in the random oracle model) # modulo the curve order n would produce a biased result. # However, if the order n is sufficiently close to 2^hf_len, # then the bias is not observable: e.g. # for secp256k1 and sha256 1-n/2^256 it is about 1.27*2^-128 tweak = int_from_bits(t, ec.nlen) # candidate tweak if 0 < tweak < ec.n: # acceptable value for tweak return tweak # successful candidate
def second_generator(ec: Curve, hf) -> Point: """Nothing-Up-My-Sleeve (NUMS) second generator H wrt ec.G source: https://github.com/ElementsProject/secp256k1-zkp/blob/secp256k1-zkp/src/modules/rangeproof/main_impl.h idea: https://crypto.stackexchange.com/questions/25581/second-generator-for-secp256k1-curve Get the hash of G, then coerce it to a point (hx, hy). The resulting point could not be a curvepoint: in this case keep on incrementing hx until a valid curve point (hx, hy) is obtained. """ G_bytes = octets_from_point(ec, ec.G, False) hd = hf(G_bytes).digest() hx = int_from_bits(ec, hd) isCurvePoint = False while not isCurvePoint: try: hy = ec.y_odd(hx, False) isCurvePoint = True except: hx += 1 return Point(hx, hy)
def _rfc6979_(c: int, q: int, ec: Curve, hf: HashF) -> int: # https://tools.ietf.org/html/rfc6979 section 3.2 # convert the private key q to an octet sequence of size n_size q_bytes = q.to_bytes(ec.n_size, byteorder="big", signed=False) # truncate and/or expand c: encoding size is driven by n_size c_bytes = c.to_bytes(ec.n_size, byteorder="big", signed=False) bprvbm = q_bytes + c_bytes hf_size = hf().digest_size v = b"\x01" * hf_size # 3.2.b k = b"\x00" * hf_size # 3.2.c k = hmac.new(k, v + b"\x00" + bprvbm, hf).digest() # 3.2.d v = hmac.new(k, v, hf).digest() # 3.2.e k = hmac.new(k, v + b"\x01" + bprvbm, hf).digest() # 3.2.f v = hmac.new(k, v, hf).digest() # 3.2.g while True: # 3.2.h t = b"" # 3.2.h.1 while len(t) < ec.n_size: # 3.2.h.2 v = hmac.new(k, v, hf).digest() t += v # The following line would introduce a bias # det_nonce = int.from_bytes(t, 'big') % ec.n # det_nonce = int_from_bits(t, ec.nlen) % ec.n # In general, taking a uniformly random integer (like those # obtained from a hash function in the random oracle model) # modulo the curve order n would produce a biased result. # However, if the order n is sufficiently close to 2^hf_len, # then the bias is not observable: e.g. # for secp256k1 and sha256 1-n/2^256 it is about 1.27*2^-128 det_nonce = int_from_bits( t, ec.nlen) # candidate det_nonce # 3.2.h.3 if 0 < det_nonce < ec.n: # acceptable values for det_nonce return det_nonce # successful candidate k = hmac.new(k, v + b"\x00", hf).digest() v = hmac.new(k, v, hf).digest()
def sign(ec: Curve, hf: Callable[[Any], Any], msg: bytes, q: int, k: Optional[int] = None) -> ECDS: """ECDSA signing operation according to SEC 1 http://www.secg.org/sec1-v2.pdf """ # https://tools.ietf.org/html/rfc6979#section-3.2 # The message msg is first processed by hf, yielding the value # msghd = hf(msg), a sequence of bits of length hlen. # Normally, hf is chosen such that # its output length hlen is roughly equal to nlen, since the overall # security of the signature scheme will depend on the smallest of hlen # and nlen; however, the ECDSA standard support all combinations of # hlen and nlen. # Steps numbering follows SEC 1 v.2 section 4.1.3 msghd = hf(msg).digest() # 4 # H(msg) is transformed into an integer modulo ec.n using int_from_bits: c = int_from_bits(ec, msghd) # 5 # The secret key q: an integer in the range 1..n-1. # SEC 1 v.2 section 3.2.1 if not 0 < q < ec.n: raise ValueError(f"private key {hex(q)} not in [1, n-1]") if k is None: k = _rfc6979(ec, hf, c, q) # 1 if not 0 < k < ec.n: raise ValueError(f"ephemeral key {hex(k)} not in [1, n-1]") # second part delegated to helper function return _sign(ec, c, q, k)
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 """ M = sha256(b'message to sign').digest() ec = secp256k1 hf = sha256 # key setup is not interactive # first signer q1 = int_from_octets( '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d92ad1d') Q1 = mult(q1) k1 = rfc6979(M, q1) K1 = mult(k1) # second signer q2 = int_from_octets( '0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d') Q2 = mult(q2) k2 = rfc6979(M, q2) K2 = mult(k2) # third signer q3 = int_from_octets( '0c28fca386c7aff7600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d') Q3 = mult(q3) k3 = rfc6979(M, q3) K3 = mult(k3) # this is MuSig core: the rest is just Schnorr signature additivity L: List[Point] = list() # multiset of public keys L.append(octets_from_point(Q1, False, ec)) L.append(octets_from_point(Q2, False, ec)) L.append(octets_from_point(Q3, False, ec)) 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(Q1, False, ec)).digest() a1 = int_from_bits(h1, ec) h2 = hf(L_brackets + octets_from_point(Q2, False, ec)).digest() a2 = int_from_bits(h2, ec) h3 = hf(L_brackets + octets_from_point(Q3, False, ec)).digest() a3 = int_from_bits(h3, ec) # aggregated public key Q = ec.add(double_mult(a1, Q1, a2, Q2), mult(a3, Q3)) Q_bytes = octets_from_point(Q, True, ec) ######################## # 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(hf(r_bytes + Q_bytes + M).digest(), ec) 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(M, Q, sig))
# # This file is part of btclib. It is subject to the license terms in the # LICENSE file found in the top-level directory of this distribution. # # No part of btclib including this file, may be copied, modified, propagated, # or distributed except according to the terms contained in the LICENSE file. """ Deterministic Wallet (Type-1) """ import random from hashlib import sha256 as hf from btclib.curve import mult from btclib.curves import secp256k1 as ec from btclib.utils import int_from_bits # master prvkey mprvkey = random.getrandbits(ec.nlen) % ec.n print('\nmaster private key =', hex(mprvkey)) mprvkey_bytes = mprvkey.to_bytes(ec.nlen, 'big') nKeys = 3 for i in range(nKeys): ibytes = i.to_bytes(ec.nlen, 'big') hd = hf(ibytes + mprvkey_bytes).digest() q = int_from_bits(ec, hd) Q = mult(ec, q, ec.G) print('\nprvkey#', i, ':', hex(q)) print('Pubkey#', i, ':', hex(Q[0])) print(' ', hex(Q[1]))
mprvkey = random.getrandbits(ec.nlen) % ec.n print('\nmaster private key:', hex(mprvkey)) # Master Pubkey: mpubkey = mult(mprvkey, ec.G) print('Master Public Key:', hex(mpubkey[0])) print(' ', hex(mpubkey[1])) # public random number r = random.getrandbits(ec.nlen) print('\npublic ephemeral key:', format(r, '#064x')) q = [] hint = [] rbytes = r.to_bytes(ec.nsize, 'big') nKeys = 3 for i in range(nKeys): ibytes = i.to_bytes(ec.nsize, 'big') hd = hf(ibytes + rbytes).digest() hint.append(int_from_bits(hd)) q.append((mprvkey + hint[i]) % ec.n) Q = mult(q[i], ec.G) print('\nprvkey#', i, ':', hex(q[i])) print('Pubkey#', i, ':', hex(Q[0])) print(' ', hex(Q[1])) # Pubkeys could be calculated without using prvkeys for i in range(nKeys): Q = ec.add(mpubkey, mult(hint[i], ec.G)) assert Q == mult(q[i], ec.G)
# Copyright (C) 2017-2019 The btclib developers # # This file is part of btclib. It is subject to the license terms in the # LICENSE file found in the top-level directory of this distribution. # # No part of btclib including this file, may be copied, modified, propagated, # or distributed except according to the terms contained in the LICENSE file. """ Deterministic Wallet (Type-1)""" import random from hashlib import sha256 as hf from btclib.curvemult import mult from btclib.curves import secp256k1 as ec from btclib.utils import int_from_bits # master prvkey mprvkey = random.getrandbits(ec.nlen) % ec.n print('\nmaster private key =', hex(mprvkey)) mprvkey_bytes = mprvkey.to_bytes(ec.nlen, 'big') nKeys = 3 for i in range(nKeys): ibytes = i.to_bytes(ec.nlen, 'big') hd = hf(ibytes + mprvkey_bytes).digest() q = int_from_bits(hd) Q = mult(q, ec.G) print('\nprvkey#', i, ':', hex(q)) print('Pubkey#', i, ':', hex(Q[0])) print(' ', hex(Q[1]))
print(f"prvkey: {hex(q).upper()}") print(f"PubKey: {hex(Q[0]).upper()}") print("\n0. Message to be signed") orig_msg1 = "Paolo is afraid of ephemeral random numbers" msg1 = sha256(orig_msg1.encode()).digest() print(msg1.hex().upper()) print("\n*** Ephemeral key and challenge") # ephemeral key k must be kept secret and never reused !!!!! # good choice: k = hf(q||msg) # different for each msg, private because of q temp = q.to_bytes(32, "big") + msg1 k1_bytes = sha256(temp).digest() k1 = int.from_bytes(k1_bytes, "big") % ec.n 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)
def sign( msg: Octets, ks: Sequence[int], sign_key_idx: Sequence[int], sign_keys: Sequence[int], pubk_rings: PubkeyRing, ) -> Tuple[bytes, SValues]: """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: message 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 sequences representing single rings of pub_keys """ msg = bytes_from_octets(msg) m = _get_msg_format(msg, pubk_rings) e0bytes = m s: SValues = defaultdict(list) e: SValues = defaultdict(list) # step 1 for i, (pubk_ring, j_star, k) in enumerate( zip(pubk_rings.values(), sign_key_idx, ks) ): keys_size = len(pubk_ring) s[i] = [0] * keys_size e[i] = [0] * keys_size start_idx = (j_star + 1) % keys_size r = bytes_from_point(mult(k), ec) if start_idx != 0: for j in range(start_idx, keys_size): s[i][j] = secrets.randbits(256) e[i][j] = int_from_bits(_hash(m, r, i, j), ec.nlen) % ec.n # edge case that cannot be reproduced in the test suite if not 0 < e[i][j] < ec.n: err_msg = "implausibile signature failure" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover t = double_mult(-e[i][j], pubk_ring[j], s[i][j], ec.G) r = bytes_from_point(t, ec) e0bytes += r e0 = hf(e0bytes).digest() # step 2 for i, (j_star, k) in enumerate(zip(sign_key_idx, ks)): e[i][0] = int_from_bits(_hash(m, e0, i, 0), ec.nlen) % ec.n # edge case that cannot be reproduced in the test suite if not 0 < e[i][0] < ec.n: err_msg = "implausibile signature failure" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover for j in range(1, j_star + 1): s[i][j - 1] = secrets.randbits(256) t = double_mult(-e[i][j - 1], pubk_rings[i][j - 1], s[i][j - 1], ec.G) r = bytes_from_point(t, ec) e[i][j] = int_from_bits(_hash(m, r, i, j), ec.nlen) % ec.n # edge case that cannot be reproduced in the test suite if not 0 < e[i][j] < ec.n: err_msg = "implausibile signature failure" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover s[i][j_star] = k + sign_keys[i] * e[i][j_star] return e0, s
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)
def test_schnorr_bip_tv(self): """Bip-Schnorr Test Vectors https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki """ hf = sha256 # test vector 1 prv = int_from_bits(b'\x00' * 31 + b'\x01') pub = mult(prv) msg = b'\x00' * 32 expected_sig = ( 0x787A848E71043D280C50470E8E1532B2DD5D20EE912A45DBDD2BD1DFBF187EF6, 0x7031A98831859DC34DFFEEDDA86831842CCD0079E1F92AF177F7F22CC1DCED05) eph_prv = int.from_bytes(hf(prv.to_bytes(32, byteorder='big') + msg).digest(), byteorder='big') sig = ssa.sign(msg, prv, eph_prv) self.assertTrue(ssa._verify(msg, pub, sig)) self.assertEqual(sig, expected_sig) e = ssa._e(sig[0], pub, msg) self.assertEqual(ssa._pubkey_recovery(e, sig), pub) # test vector 2 prv = 0xB7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF pub = mult(prv) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") expected_sig = ( 0x2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D, 0x1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD) eph_prv = int.from_bytes(hf(prv.to_bytes(32, byteorder='big') + msg).digest(), byteorder='big') sig = ssa.sign(msg, prv, eph_prv) self.assertTrue(ssa._verify(msg, pub, sig)) self.assertEqual(sig, expected_sig) e = ssa._e(sig[0], pub, msg) self.assertEqual(ssa._pubkey_recovery(e, sig), pub) # test vector 3 prv = 0xC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C7 pub = mult(prv) msg = bytes.fromhex( "5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C") expected_sig = ( 0x00DA9B08172A9B6F0466A2DEFD817F2D7AB437E0D253CB5395A963866B3574BE, 0x00880371D01766935B92D2AB4CD5C8A2A5837EC57FED7660773A05F0DE142380) eph_prv = int.from_bytes(hf(prv.to_bytes(32, byteorder='big') + msg).digest(), byteorder='big') sig = ssa.sign(msg, prv, eph_prv) self.assertTrue(ssa._verify(msg, pub, sig)) self.assertEqual(sig, expected_sig) e = ssa._e(sig[0], pub, msg) self.assertEqual(ssa._pubkey_recovery(e, sig), pub) # test vector 4 pub = point_from_octets( "03DEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34" ) msg = bytes.fromhex( "4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703") sig = ( 0x00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C63, 0x02A8DC32E64E86A333F20EF56EAC9BA30B7246D6D25E22ADB8C6BE1AEB08D49D) self.assertTrue(ssa._verify(msg, pub, sig)) e = ssa._e(sig[0], pub, msg) self.assertEqual(ssa._pubkey_recovery(e, sig), pub) # test vector 5 # test would fail if jacobi symbol of x(R) instead of y(R) is used pub = point_from_octets( "031B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F" ) msg = bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000000") sig = ( 0x52818579ACA59767E3291D91B76B637BEF062083284992F2D95F564CA6CB4E35, 0x30B1DA849C8E8304ADC0CFE870660334B3CFC18E825EF1DB34CFAE3DFC5D8187) self.assertTrue(ssa._verify(msg, pub, sig)) e = ssa._e(sig[0], pub, msg) self.assertEqual(ssa._pubkey_recovery(e, sig), pub) # test vector 6 # test would fail if msg is reduced pub = point_from_octets( "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B" ) msg = bytes.fromhex( "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") sig = ( 0x570DD4CA83D4E6317B8EE6BAE83467A1BF419D0767122DE409394414B05080DC, 0xE9EE5F237CBD108EABAE1E37759AE47F8E4203DA3532EB28DB860F33D62D49BD) self.assertTrue(ssa._verify(msg, pub, sig)) e = ssa._e(sig[0], pub, msg) self.assertEqual(ssa._pubkey_recovery(e, sig), pub) # new proposed test: test would fail if msg is reduced pub = point_from_octets( "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B" ) msg = bytes.fromhex( "000008D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A5000000") sig = ( 0x3598678C6C661F02557E2F5614440B53156997936FE54A90961CFCC092EF789D, 0x41E4E4386E54C924251679ADD3D837367EECBFF248A3DE7C2DB4CE52A3D6192A) self.assertTrue(ssa._verify(msg, pub, sig)) e = ssa._e(sig[0], pub, msg) self.assertEqual(ssa._pubkey_recovery(e, sig), pub) # new proposed test: genuine failure pub = point_from_octets( "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B" ) msg = bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000000") sig = ( 0x3598678C6C661F02557E2F5614440B53156997936FE54A90961CFCC092EF789D, 0x41E4E4386E54C924251679ADD3D837367EECBFF248A3DE7C2DB4CE52A3D6192A) self.assertFalse(ssa._verify(msg, pub, sig)) # new proposed test: P = infinite pub = 1, 0 msg = bytes.fromhex( "5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C") sig = ( 0x00DA9B08172A9B6F0466A2DEFD817F2D7AB437E0D253CB5395A963866B3574BE, 0x00880371D01766935B92D2AB4CD5C8A2A5837EC57FED7660773A05F0DE142380) self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 7 # public key not on the curve # impossible to verify with btclib analytics as it at Point conversion self.assertRaises( ValueError, point_from_octets, "03EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34" ) # msg = bytes.fromhex("4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703") # sig = (0x00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C63, 0x02A8DC32E64E86A333F20EF56EAC9BA30B7246D6D25E22ADB8C6BE1AEB08D49D) # self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 8 # Incorrect sig: incorrect R residuosity pub = point_from_octets( "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" ) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") sig = ( 0x2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D, 0xFA16AEE06609280A19B67A24E1977E4697712B5FD2943914ECD5F730901B4AB7) self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 9 # Incorrect sig: negated message hash pub = point_from_octets( "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B" ) msg = bytes.fromhex( "5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C") sig = ( 0x00DA9B08172A9B6F0466A2DEFD817F2D7AB437E0D253CB5395A963866B3574BE, 0xD092F9D860F1776A1F7412AD8A1EB50DACCC222BC8C0E26B2056DF2F273EFDEC) self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 10 # Incorrect sig: negated s value pub = point_from_octets( "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" ) msg = b'\x00' * 32 sig = ( 0x787A848E71043D280C50470E8E1532B2DD5D20EE912A45DBDD2BD1DFBF187EF6, 0x8FCE5677CE7A623CB20011225797CE7A8DE1DC6CCD4F754A47DA6C600E59543C) self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 11 # Incorrect sig: negated public key pub = point_from_octets( "03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" ) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") sig = ( 0x2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D, 0x1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD) self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 12 # sG - eP is infinite. # Test fails in single verification if jacobi(y(inf)) is defined as 1 and x(inf) as 0 pub = point_from_octets( "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" ) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") sig = ( 0x0000000000000000000000000000000000000000000000000000000000000000, 0x9E9D01AF988B5CEDCE47221BFA9B222721F3FA408915444A4B489021DB55775F) self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 13 # sG - eP is infinite. # Test fails in single verification if jacobi(y(inf)) is defined as 1 and x(inf) as 1""" pub = point_from_octets( "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" ) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") sig = ( 0x0000000000000000000000000000000000000000000000000000000000000001, 0xD37DDF0254351836D84B1BD6A795FD5D523048F298C4214D187FE4892947F728) self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 14 # sig[0:32] is not an X coordinate on the curve pub = point_from_octets( "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" ) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") sig = ( 0x4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D, 0x1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD) self.assertFalse(ssa._verify(msg, pub, sig)) # test vector 15 # sig[0:32] is equal to field size pub = point_from_octets( "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" ) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") sig = ( 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2F, 0x1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD) #self.assertRaises(ValueError, ssa._verify, msg, pub, sig) self.assertFalse(ssa._verify(msg, pub, sig)) # test vector 16 # sig[32:64] is equal to curve order pub = point_from_octets( "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" ) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") sig = ( 0x2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141) self.assertRaises(ValueError, ssa._verify, msg, pub, sig)
mprvkey = 1 + secrets.randbelow(ec.n - 1) print(f"\nmaster prvkey: {hex(mprvkey).upper()}") # Master Pubkey: mpubkey = mult(mprvkey, ec.G) print(f"Master Pubkey: {hex(mpubkey[0]).upper()}") print(f" {hex(mpubkey[1]).upper()}") r = secrets.randbits(ec.nlen) print(f"\npublic random number: {hex(r).upper()}") rbytes = r.to_bytes(ec.nsize, "big") nKeys = 3 for i in range(nKeys): ibytes = i.to_bytes(ec.nsize, "big") hd = hf(ibytes + rbytes).digest() offset = int_from_bits(hd, ec.nlen) % ec.n q = (mprvkey + offset) % ec.n Q = mult(q, ec.G, ec) print(f"\nprvkey #{i}: {hex(q).upper()}") print(f"Pubkey #{i}: {hex(Q[0]).upper()}") print(f" {hex(Q[1]).upper()}") # Pubkeys could also be calculated without using prvkeys for i in range(nKeys): ibytes = i.to_bytes(ec.nsize, "big") hd = hf(ibytes + rbytes).digest() offset = int_from_bits(hd, ec.nlen) % ec.n Q = ec.add(mpubkey, mult(offset, ec.G, ec)) assert Q == mult((mprvkey + offset) % ec.n, ec.G, ec)
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)