def address_type(addr): if addr.startswith(('1', '2', '3', 'm', 'n')): try: address = base58.decode(addr).rjust(25, b'\x00') except Base58DecodeError as e: raise InvalidAddress(f"{addr} : {e}") from None payload, checksum = address[:-4], address[-4:] version_byte, digest = payload[0:1], payload[1:].rjust(20, b'\x00') if len(digest) != 20: raise InvalidAddress(f"{addr} : Bad Payload") from None if sha256(sha256(payload))[:4] != checksum: raise InvalidAddress(f"{addr} : Invalid checksum") from None try: return {network('keyhash'): ADDRESS.P2PKH, network('scripthash'): ADDRESS.P2SH}[version_byte] except KeyError: raise InvalidAddress(f"{addr} : Invalid version byte") from None elif addr.startswith(network('hrp')): try: witness_version, witness_program = bech32.decode(network('hrp'), addr) except Bech32DecodeError as e: raise InvalidAddress(f"{addr} : {e}") from None if not witness_version == 0x00: raise InvalidAddress(f"{addr} : Invalid witness version") from None if len(witness_program) == 20: return ADDRESS.P2WPKH elif len(witness_program) == 32: return ADDRESS.P2WSH else: raise InvalidAddress(f"{addr} : Invalid witness program") from None else: raise InvalidAddress(f"{addr} : Invalid leading character") from None
def utxos(self): if self._outputs is None: import urllib.request import json url = network('utxo_url').format(address=self.address) req = urllib.request.Request(url) outputs = [] try: with urllib.request.urlopen(req) as resp: data = json.loads(resp.read().decode()) except HTTPError as e: resp = e.read().decode() if resp == 'No free outputs to spend': self._outputs = [] else: raise UpstreamError(resp) else: for item in data['unspent_outputs']: out = Output(value=item['value'], script=hex_to_bytes(item['script'])) out.parent_id = hex_to_bytes(item['tx_hash_big_endian']) out.tx_index = item['tx_output_n'] outputs.append(out) self._outputs = outputs return self._outputs
def wif(self, compressed=False) -> str: from cryptotools.BTC import base58, sha256 from cryptotools.BTC.network import network extended = network('wif') + self.bytes() + (b'\x01' if compressed else b'') hashed = sha256(sha256(extended)) checksum = hashed[:4] return base58.encode(extended + checksum)
def serialize(self): version = network('extended_pub')[self.type] depth = int_to_bytes(self.depth) child = bytes(4) if self.is_master() else int_to_bytes(self.i).rjust( 4, b'\x00') return version + depth + self.parent + child + self.code + self.keydata( )
def get_address(script): """Extracts the address from a scriptPubkey""" script = hex_to_bytes(script) if isinstance(script, str) else script stype = get_type(script) if stype == TX.P2SH: data = script[2:22] version = network('scripthash') return hashed_payload_to_address(version + data) elif stype == TX.P2PKH: data = script[3:23] version = network('keyhash') return hashed_payload_to_address(version + data) elif stype in (TX.P2WSH, TX.P2WPKH): witver = version_byte(script) witprog = witness_program(script) return bech32.encode(network('hrp'), witver, witprog) elif stype == TX.P2PK: return "N/A" raise ValidationError(f"Unknown script type: {bytes_to_hex(script)}")
def from_wif(cls, wif: str) -> 'PrivateKey': from cryptotools.BTC import base58, sha256 from cryptotools.BTC.network import network bts = base58.decode(wif) network_byte, key, checksum = bts[0:1], bts[1:-4], bts[-4:] assert sha256(sha256(network_byte + key))[:4] == checksum, 'Invalid Checksum' assert network_byte == network('wif'), 'Invalid Network byte' if key.endswith(b'\x01'): key = key[:-1] compressed = True # TODO else: compressed = False # TODO return cls(key)
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 broadcast(self): import urllib.request import urllib.parse url = network('broadcast_url') payload = {'tx': self.hex()} data = urllib.parse.urlencode(payload).encode('ascii') req = urllib.request.Request(url, data) try: with urllib.request.urlopen(req) as response: resp = response.read() except HTTPError as e: resp = e.read() return resp.decode().strip('\n')
def _receive(self, value: int): """Creates an output that sends to this address""" addr_type = self.type() output = Output(value=value, script=b'') if addr_type == ADDRESS.P2PKH: address = base58.decode(self.address).rjust(25, b'\x00') keyhash = address[1:-4] output.script = OP.DUP.byte + OP.HASH160.byte + push(keyhash) + OP.EQUALVERIFY.byte + OP.CHECKSIG.byte elif addr_type == ADDRESS.P2SH: address = base58.decode(self.address).rjust(25, b'\x00') scripthash = address[1:-4] output.script = OP.HASH160.byte + push(scripthash) + OP.EQUAL.byte elif addr_type in (ADDRESS.P2WPKH, ADDRESS.P2WSH): witness_version, witness_program = bech32.decode(network('hrp'), self.address) output.script = OP(bytes_to_int(witness_byte(witness_version))).byte + push(bytes(witness_program)) else: raise ValidationError(f"Cannot create output of type {addr_type}") return output
def get(cls, txhash): """Construct a transaction from it's tx id by getting the raw data from blockchain.info""" import urllib.request from urllib.error import HTTPError if isinstance(txhash, bytes): txhash = bytes_to_hex(txhash) url = network('rawtx_url').format(txid=txhash) req = urllib.request.Request(url) sleep(0.1) try: with urllib.request.urlopen(req) as resp: try: return cls.from_hex(resp.read().decode()) except SerializationError as e: e.txhash = txhash raise e except HTTPError as e: resp = e.read().decode() raise UpstreamError(resp)
def pubkey_to_bech32(pub: PublicKey, witver: int) -> str: """https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#witness-program""" witprog = hash160(pub.encode(compressed=True)) return bech32.encode(network('hrp'), witver, witprog)
def script_to_bech32(script: bytes, witver: int) -> str: """https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#witness-program""" witprog = sha256(script) return bech32.encode(network('hrp'), witver, witprog)
def script_to_bech32(script: bytes, witver: int) -> str: """https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#witness-program""" witprog = sha256(script) return bech32.encode(network('hrp'), witver, witprog) def pubkey_to_bech32(pub: PublicKey, witver: int) -> str: """https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#witness-program""" witprog = hash160(pub.encode(compressed=True)) return bech32.encode(network('hrp'), witver, witprog) key_to_addr_versions = { ADDRESS.P2PKH: lambda pub: legacy_address(pub, version_byte=network('keyhash')), # 'P2WPKH': partial(pubkey_to_p2wpkh, version_byte=0x06, witver=0x00), # WAS REPLACED BY BIP 173 ADDRESS.P2WPKH_P2SH: lambda pub: legacy_address(witness_byte(witver=0) + push(hash160(pub.encode(compressed=True))), version_byte=network('scripthash')), ADDRESS.P2WPKH: partial(pubkey_to_bech32, witver=0x00), } script_to_addr_versions = { ADDRESS.P2SH: lambda script: legacy_address(script, version_byte=network('scripthash')), # 'P2WSH': partial(script_to_p2wsh, version_byte=0x0A, witver=0x00), # WAS REPLACED BY BIP 173 ADDRESS.P2WSH_P2SH: lambda script: legacy_address(witness_byte(witver=0) + push(sha256(script)), version_byte=network('scripthash')), ADDRESS.P2WSH: partial(script_to_bech32, witver=0x00), } def pubkey_to_address(pub: PublicKey, version='P2PKH') -> str: converter = key_to_addr_versions[ADDRESS(version.upper())]