def test_key_deployment(self): """GEC 2: Test Vectors for SEC 1, section 4.1 http://read.pudn.com/downloads168/doc/772358/TestVectorsforSEC%201-gec2.pdf """ # 4.1.1 # ec = secp160r1 # hf = sha1 # 4.1.2 dU = 971761939728640320549601132085879836204587084162 self.assertEqual(format(dU, str(ec.psize) + 'x'), 'aa374ffc3ce144e6b073307972cb6d57b2a4e982') QU = mult(dU, ec.G, ec) self.assertEqual(QU, (466448783855397898016055842232266600516272889280, 1110706324081757720403272427311003102474457754220)) self.assertEqual( octets_from_point(QU, True, ec).hex(), '0251b4496fecc406ed0e75a24a3c03206251419dc0') # 4.1.3 dV = 399525573676508631577122671218044116107572676710 self.assertEqual(format(dV, str(ec.psize) + 'x'), '45fb58a92a17ad4b15101c66e74f277e2b460866') QV = mult(dV, ec.G, ec) self.assertEqual(QV, (420773078745784176406965940076771545932416607676, 221937774842090227911893783570676792435918278531)) self.assertEqual( octets_from_point(QV, True, ec).hex(), '0349b41e0e9c0369c2328739d90f63d56707c6e5bc') # expected results z_exp = 1155982782519895915997745984453282631351432623114 zstr = 'ca7c0f8c3ffa87a96e1b74ac8e6af594347bb40a' size = 20 keying_data_exp = '744ab703f5bc082e59185f6d049d2d367db245c2' # 4.1.4 z, _ = mult(dU, QV, ec) # x coordinate only self.assertEqual(z, z_exp) self.assertEqual(format(z, str(ec.psize) + 'x'), zstr) keyingdata = dh.ansi_x963_kdf(octets_from_int(z, ec.psize), size, ec, hf) self.assertEqual(keyingdata.hex(), keying_data_exp) # 4.1.5 z, _ = mult(dV, QU, ec) # x coordinate only self.assertEqual(z, z_exp) self.assertEqual(format(z, str(ec.psize) + 'x'), zstr) keyingdata = dh.ansi_x963_kdf(octets_from_int(z, ec.psize), size, ec, hf) self.assertEqual(keyingdata.hex(), keying_data_exp)
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 xpub_from_xprv(xprv: octets) -> bytes: """Neutered Derivation (ND) Computation of the extended public key corresponding to an extended private key (“neutered” as it removes the ability to sign transactions) """ xprv = base58.decode_check(xprv, 78) if xprv[45] != 0: raise ValueError("extended key is not a private one") i = PRV.index(xprv[:4]) # serialization data xpub = PUB[i] # version # unchanged serialization data xpub += xprv[4:5] # depth xpub += xprv[5:9] # parent pubkey fingerprint xpub += xprv[9:13] # child index xpub += xprv[13:45] # chain code p = int_from_octets(xprv[46:]) P = mult(ec, p, ec.G) xpub += octets_from_point(ec, P, True) # public key return base58.encode_check(xpub)
def test_gec(self): """ GEC 2: Test Vectors for SEC 1, section 2 http://read.pudn.com/downloads168/doc/772358/TestVectorsforSEC%201-gec2.pdf """ # 2.1.1 Scheme setup ec = secp160r1 hf = sha1 # 2.1.2 Key Deployment for U dU = 971761939728640320549601132085879836204587084162 self.assertEqual(format(dU, str(ec.psize) + 'x'), 'aa374ffc3ce144e6b073307972cb6d57b2a4e982') QU = mult(ec, dU, ec.G) self.assertEqual(QU, (466448783855397898016055842232266600516272889280, 1110706324081757720403272427311003102474457754220)) self.assertEqual( octets_from_point(ec, QU, True).hex(), '0251b4496fecc406ed0e75a24a3c03206251419dc0') # 2.1.3 Signing Operation for U msg = 'abc'.encode() k = 702232148019446860144825009548118511996283736794 exp_sig = (0xCE2873E5BE449563391FEB47DDCBA2DC16379191, 0x3480EC1371A091A464B31CE47DF0CB8AA2D98B54) sig = dsa.sign(ec, hf, msg, dU, k) r, s = sig self.assertEqual(r, exp_sig[0]) self.assertIn(s, (exp_sig[1], ec.n - exp_sig[1])) # 2.1.4 Verifying Operation for V self.assertTrue(dsa.verify(ec, hf, msg, QU, sig)) self.assertTrue(dsa._verify(ec, hf, msg, QU, sig))
def test_p2wpkh_address(self): # https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki # leading/trailing spaces should be tolerated pub = " 0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" addr = b'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4' self.assertEqual(addr, p2wpkh_address(pub)) addr = b'tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx' self.assertEqual(addr, p2wpkh_address(pub, 'testnet')) # http://bitcoinscri.pt/pages/segwit_native_p2wpkh_address pub = "02530c548d402670b13ad8887ff99c294e67fc18097d236d57880c69261b42def7" addr = b'bc1qg9stkxrszkdqsuj92lm4c7akvk36zvhqw7p6ck' self.assertEqual(addr, p2wpkh_address(pub)) _, _, wp = _decode(addr) self.assertEqual(bytes(wp), hash160(pub)) # Uncompressed pubkey uncompr_pub = octets_from_point(point_from_octets(pub, ec), False, ec) self.assertRaises(ValueError, p2wpkh_address, uncompr_pub) # p2wpkh_address(uncompr_pub) # Wrong pubkey size: 34 instead of 33 self.assertRaises(ValueError, p2wpkh_address, pub + '00') # p2wpkh_address(pub + '00') # Witness program length (21) is not 20 self.assertRaises(ValueError, _p2wpkh_address, hash160(pub) + b'\x00', True, "mainnet")
def test_p2pkh_address_from_pubkey(self): # https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses pub = '0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352' addr = p2pkh_address(pub) self.assertEqual(addr, b'1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs') _, _, hash2 = h160_from_base58_address(addr) self.assertEqual(hash2, hash160(pub)) uncompressed_pub = octets_from_point(point_from_octets(pub, ec), False, ec) addr = p2pkh_address(uncompressed_pub) self.assertEqual(addr, b'16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM') _, _, hash2 = h160_from_base58_address(addr) self.assertEqual(hash2, hash160(uncompressed_pub)) # trailing/leading spaces in string pub = ' 0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352' addr = p2pkh_address(pub) self.assertEqual(addr, b'1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs') _, _, hash2 = h160_from_base58_address(addr) self.assertEqual(hash2, hash160(pub)) pub = '0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352 ' addr = p2pkh_address(pub) self.assertEqual(addr, b'1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs')
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 _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 test_all_curves(self): for ec in all_curves: self.assertEqual(mult(ec, 0, ec.G), Inf) self.assertEqual(mult(ec, 0, ec.G), Inf) self.assertEqual(mult(ec, 1, ec.G), ec.G) self.assertEqual(mult(ec, 1, ec.G), ec.G) Gy_odd = ec.y_odd(ec.G[0], True) self.assertEqual(Gy_odd % 2, 1) Gy_even = ec.y_odd(ec.G[0], False) self.assertEqual(Gy_even % 2, 0) self.assertTrue(ec.G[1] in (Gy_odd, Gy_even)) Gbytes = octets_from_point(ec, ec.G, True) G2 = point_from_octets(ec, Gbytes) self.assertEqual(ec.G, G2) Gbytes = octets_from_point(ec, ec.G, False) G2 = point_from_octets(ec, Gbytes) self.assertEqual(ec.G, G2) P = ec.add(Inf, ec.G) self.assertEqual(P, ec.G) P = ec.add(ec.G, Inf) self.assertEqual(P, ec.G) P = ec.add(Inf, Inf) self.assertEqual(P, Inf) P = ec.add(ec.G, ec.G) self.assertEqual(P, mult(ec, 2, ec.G)) P = mult(ec, ec.n-1, ec.G) self.assertEqual(ec.add(P, ec.G), Inf) self.assertEqual(mult(ec, ec.n, ec.G), Inf) self.assertEqual(mult(ec, 0, Inf), Inf) self.assertEqual(mult(ec, 1, Inf), Inf) self.assertEqual(mult(ec, 25, Inf), Inf) ec_repr = repr(ec) if ec in low_card_curves or ec.psize < 24: ec_repr = ec_repr[:-1] + ", False)" ec2 = eval(ec_repr) self.assertEqual(str(ec), str(ec2))
def address_from_pubkey(Q: Point, compressed: bool, version: bytes = b'\x00') -> bytes: """Public key to (bytes) address""" # also check that the Point is on curve pubkey = octets_from_point(ec, Q, compressed) # FIXME: this is mainnet only vh160 = version + _h160(pubkey) return base58.encode_check(vh160)
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 _get_msg_format(msg: bytes, pubk_rings: Dict[int, List[Point]]) -> bytes: m = msg rings = len(pubk_rings) for i in range(rings): keys = len(pubk_rings[i]) for j in range(keys): P = pubk_rings[i][j] Pbytes = octets_from_point(ec, P, True) m += Pbytes return hf(m).digest()
def _tweak(c: bytes, ec: Curve, hf, k: int) -> Tuple[Point, int]: """tweak kG returns: - point kG to tweak - tweaked private key k + h(kG||c), the corresponding pubkey is a commitment to kG, c """ R = mult(ec, k, ec.G) e = hf(octets_from_point(ec, R, True) + c).digest() e = int.from_bytes(e, 'big') return R, (e + k) % ec.n
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 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 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))
# ==master ext private key== # depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ... depth = b'\x00' # This is ser32(i) for i in xi = xpar/i, with xi the key being serialized. (0x00000000 if master key) child_number = b'\x00\x00\x00\x00' # the fingerprint of the parent's public key (0x00000000 if master key) fingerprint = b'\x00\x00\x00\x00' idf = depth + fingerprint + child_number # master private key, master public key, chain code hd = HMAC(b"Bitcoin seed", seed.to_bytes(seed_bytes, 'big'), sha512).digest() qbytes = hd[:32] p = int(qbytes.hex(), 16) % ec.n qbytes = b'\x00' + p.to_bytes(32, 'big') Q = mult(p, ec.G) Qbytes = octets_from_point(Q, True) chain_code = hd[32:] #extended keys ext_prv = encode(xprv + idf + chain_code + qbytes) print("\nm") print(ext_prv) ext_pub = encode(xpub + idf + chain_code + Qbytes) print("M") print(ext_pub) assert ext_prv == b"xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6", "failure" assert ext_pub == b"xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13", "failure" # ==first (0) hardened child== depth = b'\x01' child_n = 0 + 0x80000000 #hardened
def test_octets2point(self): for ec in all_curves: Q = mult(ec, ec._p, ec.G) # just a random point, not Inf Q_bytes = b'\x03' if Q[1] & 1 else b'\x02' Q_bytes += Q[0].to_bytes(ec.psize, "big") R = point_from_octets(ec, Q_bytes) self.assertEqual(R, Q) self.assertEqual(octets_from_point(ec, R, True), Q_bytes) Q_hex_str = Q_bytes.hex() R = point_from_octets(ec, Q_hex_str) self.assertEqual(R, Q) Q_bytes = b'\x04' + Q[0].to_bytes(ec.psize, "big") Q_bytes += Q[1].to_bytes(ec.psize, "big") R = point_from_octets(ec, Q_bytes) self.assertEqual(R, Q) self.assertEqual(octets_from_point(ec, R, False), Q_bytes) Q_hex_str = Q_bytes.hex() R = point_from_octets(ec, Q_hex_str) self.assertEqual(R, Q) # infinity point self.assertEqual(point_from_octets(ec, b'\x00'), Inf) self.assertEqual(octets_from_point(ec, Inf, True), b'\x00') self.assertEqual(octets_from_point(ec, Inf, False), b'\x00') Inf_hex_str = b'\x00'.hex() self.assertEqual(point_from_octets(ec, Inf_hex_str), Inf) # scalar in point multiplication can be int, str, or bytes t = tuple() self.assertRaises(TypeError, mult, ec, t, ec.G) # not a compressed point Q_bytes = b'\x01' * (ec.psize+1) self.assertRaises(ValueError, point_from_octets, ec, Q_bytes) # not a point Q_bytes += b'\x01' self.assertRaises(ValueError, point_from_octets, ec, Q_bytes) # not an uncompressed point Q_bytes = b'\x01' * 2 * (ec.psize+1) self.assertRaises(ValueError, point_from_octets, ec, Q_bytes) # invalid x coordinate ec = secp256k1 x = 0xEEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34 xstr = format(x, '32X') self.assertRaises(ValueError, point_from_octets, ec, "03" + xstr) self.assertRaises(ValueError, point_from_octets, ec, "04" + xstr + xstr) self.assertRaises(ValueError, octets_from_point, ec, (x, x), True) self.assertRaises(ValueError, octets_from_point, ec, (x, x), False) # Point must be a tuple[int, int] P = x, x, x self.assertRaises(ValueError, ec.is_on_curve, P) # y-coordinate not in (0, p) P = x, ec._p+1 self.assertRaises(ValueError, ec.is_on_curve, P)
def ckd(xparentkey: octets, index: Union[octets, int]) -> bytes: """Child Key Derivation (CDK) Key derivation is normal if the extended parent key is public or child_index is less than 0x80000000. Key derivation is hardened if the extended parent key is private and child_index is not less than 0x80000000. """ if isinstance(index, int): index = index.to_bytes(4, 'big') elif isinstance(index, str): # hex string index = bytes.fromhex(index) if len(index) != 4: raise ValueError(f"a 4 bytes int is required, not {len(index)}") xparent = base58.decode_check(xparentkey, 78) version = xparent[:4] # serialization data xkey = version # version xkey += (xparent[4] + 1).to_bytes(1, 'big') # (increased) depth if (version in PUB): if xparent[45] not in (2, 3): # not a compressed public key raise ValueError("version/key mismatch in extended parent key") Parent_bytes = xparent[45:] Parent = point_from_octets(ec, Parent_bytes) xkey += _h160(Parent_bytes)[:4] # parent pubkey fingerprint if index[0] >= 0x80: raise ValueError("no private/hardened derivation from pubkey") xkey += index # child index parent_chain_code = xparent[13:45] # normal derivation # actual extended key (key + chain code) derivation h = HMAC(parent_chain_code, Parent_bytes + index, sha512).digest() offset = int.from_bytes(h[:32], 'big') Offset = mult(ec, offset, ec.G) Child = ec.add(Parent, Offset) Child_bytes = octets_from_point(ec, Child, True) xkey += h[32:] # chain code xkey += Child_bytes # public key elif (version in PRV): if xparent[45] != 0: # not a private key raise ValueError("version/key mismatch in extended parent key") parent = int.from_bytes(xparent[46:], 'big') Parent = mult(ec, parent, ec.G) Parent_bytes = octets_from_point(ec, Parent, True) xkey += _h160(Parent_bytes)[:4] # parent pubkey fingerprint xkey += index # child index # actual extended key (key + chain code) derivation parent_chain_code = xparent[13:45] if (index[0] < 0x80): # normal derivation h = HMAC(parent_chain_code, Parent_bytes + index, sha512).digest() else: # hardened derivation h = HMAC(parent_chain_code, xparent[45:] + index, sha512).digest() offset = int.from_bytes(h[:32], 'big') child = (parent + offset) % ec.n child_bytes = b'\x00' + child.to_bytes(32, 'big') xkey += h[32:] # chain code xkey += child_bytes # private key else: raise ValueError("invalid extended key version") return base58.encode_check(xkey)
# depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ... depth = b'\x00' # This is ser32(i) for i in xi = xpar/i, with xi the key being serialized. (0x00000000 if master key) child_number = b'\x00\x00\x00\x00' # the fingerprint of the parent's public key (0x00000000 if master key) fingerprint = b'\x00\x00\x00\x00' idf = depth + fingerprint + child_number # master private key, master public key, chain code hd = HMAC(b"Bitcoin seed", seed.to_bytes(seed_bytes, byteorder='big'), sha512).digest() qbytes = hd[:32] q = int(qbytes.hex(), 16) % ec.n qbytes = b'\x00' + q.to_bytes(32, byteorder='big') Q = mult(ec, q, ec.G) Qbytes = octets_from_point(ec, Q, True) chain_code = hd[32:] #extended keys ext_prv = encode_check(xprv + idf + chain_code + qbytes) print("\nm") print(ext_prv) ext_pub = encode_check(xpub + idf + chain_code + Qbytes) print("M") print(ext_pub) assert ext_prv == b"xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", "failure" assert ext_pub == b"xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", "failure" # ==first (0) hardened child== depth = b'\x01' child_n = 0 + 0x80000000 #hardened
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_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))