def child(self, i: int) -> 'Xpub': hardened = i >= 1 << 31 if hardened: raise KeyDerivationError( 'Cannot derive a hardened key from an extended public key') I = hmac.new(key=self.code, msg=self.keydata() + int_to_bytes(i).rjust(4, b'\x00'), digestmod=hashlib.sha512).digest() I_L, I_R = I[:32], I[32:] key = PrivateKey(I_L).to_public().point + self.key.point ret_code = I_R path = self.path + f'/{i}' # TODO add point at infinity check return Xpub(PublicKey(key), ret_code, depth=self.depth + 1, i=i, parent=self.fingerprint(), path=path, addresstype=self.type.value)
def test_signing(self): tx_ids = [ 'f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16', '12b5633bad1f9c167d523ad1aa1947b2732a865bf5414eab2f9e5ae5d5c191ba', # P2PK # 'a38d3393a32d06fe842b35ebd68aa2b6a1ccbabbbc244f67462a10fd8c81dba5', # coinbase 'a8d60051745755be5b13ba3ecedc1540fbb66e95ab15e76b4d871fd7c2b68794', # segwit 'fff2525b8931402dd09222c50775608f75787bd2b87e56995a7bdd30f79702c4', 'ee475443f1fbfff84ffba43ba092a70d291df233bd1428f3d09f7bd1a6054a1f', '7edb32d4ffd7a385b763c7a8e56b6358bcd729e747290624e18acdbe6209fc45', # 1-of-1 multisig '001bf3d87e417c63eee64bd466d0a6eec9770a3b336368062e9db730cf5db922', # Spends P2SH 2-of-3 multisig '5a0ce1166ff8e6800416b1aa25f1577e233f230bd21204a6505fa6ee5a9c5fc6', 'ef27d32f7f0c645daec3071c203399783555d84cfe92bfe61583a464a260df0b', # 24 inputs 7 outputs '454e575aa1ed4427985a9732d753b37dc711675eb7c977637b1eea7f600ed214', # sends to P2SH and P2WSH 'eba5e1e668e0d47dc28c7fff686a7f680e334e1f9740fd90f0aed3d5e9c4114a', # spends P2WSH 'e5c95e9b3c8e81bf9fc4da9f069e5c40fa38cdcc0067b5706b517878298a6f7f', # non standard sequence 'e694da982e1a725e3524c622932f6159a328194a9201588783393c35ac852732' # P2SH-P2WSH ] private = PrivateKey.random() for tx_id in tx_ids: tx = Transaction.get(tx_id) for inp in tx.inputs: if inp.ref().type() not in (TX.P2WSH, TX.P2SH): self.assertTrue(inp.is_signed()) inp.clear() self.assertFalse(inp.is_signed()) inp.sign(private) self.assertTrue(inp.is_signed()) self.assertFalse(tx.verify(inp.tx_index))
def child(self, i: int) -> 'Xprv': hardened = i >= 1 << 31 if hardened: I = hmac.new(key=self.code, msg=self.keydata() + int_to_bytes(i).rjust(4, b'\x00'), digestmod=hashlib.sha512).digest() else: I = hmac.new(key=self.code, msg=self.key.to_public().encode(compressed=True) + int_to_bytes(i).rjust(4, b'\x00'), digestmod=hashlib.sha512).digest() I_L, I_R = bytes_to_int(I[:32]), I[32:] key = (I_L + self.key.int()) % CURVE.N if I_L >= CURVE.N or key == 0: return self.child(i + 1) ret_code = I_R if hardened: path = self.path + f'/{i - 2**31}h' else: path = self.path + f'/{i}' return Xprv(PrivateKey.from_int(key), ret_code, depth=self.depth + 1, i=i, parent=self.fingerprint(), path=path, addresstype=self.type.value)
def test_wif(self): payload = {'Random': 'Random'} data = urllib.parse.urlencode(payload).encode('ascii') req = urllib.request.Request(self.url, data) with urllib.request.urlopen(req) as response: html = HtmlPrivKey() html.feed(response.read().decode('utf-8')) wif, private = html.wif, html.private my_private = PrivateKey.from_wif(wif) my_wif = PrivateKey.from_hex(private).wif() self.assertEqual(my_private.hex().lower(), private.lower()) self.assertEqual(my_wif, wif)
def from_seed(cls, seed: Union[bytes, str], addresstype='P2PKH') -> 'Xprv': if isinstance(seed, str): seed = hex_to_bytes(seed) assert 16 <= len(seed) <= 64, 'Seed should be between 128 and 512 bits' I = hmac.new(key=b"Bitcoin seed", msg=seed, digestmod=hashlib.sha512).digest() I_L, I_R = I[:32], I[32:] if bytes_to_int(I_L) == 0 or bytes_to_int(I_L) > CURVE.N: raise KeyDerivationError key, code = PrivateKey(I_L), I_R return cls(key, code, addresstype=addresstype)
def test_encoding(self): for vector in self.vectors: prv_hex = vector['prv'] if not prv_hex: continue prv = PrivateKey.from_hex(prv_hex) pubkey_actual = prv.to_public() pubkey = PublicKey.from_hex(vector['pub']) self.assertEqual(pubkey_actual.hex(compact=True), pubkey.hex(compact=True)) msg = vector['msg'] aux = vector['aux'] signature = msg.sign_schnorr(prv, aux=aux) self.assertEqual(signature.hex(), vector['sig'].lower())
def test_p2pkh(self): """https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses#See_Also""" payload = {'Random': 'Random'} data = urllib.parse.urlencode(payload).encode('ascii') req = urllib.request.Request(self.url, data) with urllib.request.urlopen(req) as response: html = HtmlLegacyAddress() html.feed(response.read().decode('utf-8')) private, public, address = html.private, html.public, html.address my_pubkey = PrivateKey.from_hex(private).to_public() self.assertEqual(public.lower(), my_pubkey.hex()) self.assertEqual(pubkey_to_address(my_pubkey), address) self.assertEqual(address_type(address), ADDRESS.P2PKH)
def deserialize(cls, bts: bytes) -> 'ExtendedKey': def read(n): nonlocal bts data, bts = bts[:n], bts[n:] return data net = read(4) is_private = net in network('extended_prv').values() is_public = net in network('extended_pub').values() assert is_public ^ is_private, f'Invalid network bytes : {bytes_to_hex(net)}' address_lookup = { val: key for key, val in (network('extended_prv') if is_private else network('extended_pub')).items() } constructor = Xprv if is_private else Xpub depth = bytes_to_int(read(1)) assert depth in range(256), f'Invalid depth : {depth}' fingerprint = read(4) i = bytes_to_int(read(4)) if depth == 0: i = None path = None else: ih = f'{i}' if i < 2**31 else f"{i - 2**31}h" path = '/'.join([constructor.root_path] + ['x' for _ in range(depth - 1)] + [ih]) code = read(32) key = read(33) key = PrivateKey(key) if is_private else PublicKey.decode(key) assert not bts, 'Leftover bytes' return constructor(key, code, depth=depth, i=i, parent=fingerprint, path=path, addresstype=address_lookup[net])
def test_compression(self): prv = PrivateKey.random() self.assertEqual(PrivateKey.from_wif(prv.wif(compressed=False)), prv) self.assertEqual(PrivateKey.from_wif(prv.wif(compressed=True)), prv)