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 prvkey_from_wif(wif: octets) -> Tuple[int, bool]: """Wallet Import Format to (bytes) private key""" payload = base58.decode_check(wif) if payload[0] != 0x80: raise ValueError("Not a private key WIF: missing leading 0x80") if len(payload) == ec.psize + 2: # compressed WIF compressed = True if payload[ec.psize + 1] != 0x01: # must have a trailing 0x01 raise ValueError("Not a compressed WIF: missing trailing 0x01") prvkey = int_from_octets(payload[1:-1]) elif len(payload) == ec.psize + 1: # uncompressed WIF compressed = False prvkey = int_from_octets(payload[1:]) else: raise ValueError(f"Not a WIF: wrong size ({len(payload)})") if not 0 < prvkey < ec.n: raise ValueError(f"Not a WIF: private key {hex(prvkey)} not in (0, n)") return prvkey, compressed
def kdf(zbytes: bytes, keydatasize: int, ec: Curve, hf) -> bytes: """ ANS-X9.63-KDF - SEC 1 specification source: http://www.secg.org/sec1-v2.pdf, section 3.6.1 """ hsize = hf().digest_size assert keydatasize < hsize * (2**32 - 1), "invalid" counter = 1 counter_bytes = counter.to_bytes(4, 'big') K_temp = [] for i in range((keydatasize + 1) // hsize): K_temp.append(hf(zbytes + counter_bytes).digest()) counter += 1 counter_bytes = counter.to_bytes(4, 'big') i += 1 K_bytes = b''.join(K_temp[i] for i in range(keydatasize // hsize)) K = int_from_octets(K_bytes) >> (keydatasize - hsize) return octets_from_int(K, ec.psize)
def mprv_from_seed(seed: octets, version: octets) -> bytes: """derive the master extended private key from the seed""" if isinstance(version, str): # hex string version = bytes.fromhex(version) if version not in PRV: m = f"invalid private version ({version})" raise ValueError(m) # serialization data xmprv = version # version xmprv += b'\x00' # depth xmprv += b'\x00\x00\x00\x00' # parent pubkey fingerprint xmprv += b'\x00\x00\x00\x00' # child index # actual extended key (key + chain code) derivation if isinstance(seed, str): # hex string seed = bytes.fromhex(seed) hd = HMAC(b"Bitcoin seed", seed, sha512).digest() mprv = int_from_octets(hd[:32]) xmprv += hd[32:] # chain code xmprv += b'\x00' + mprv.to_bytes(32, 'big') # private key return base58.encode_check(xmprv)
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))
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))