def make_outkey_vin_txid(txid, vout): global UTXO_P2SH_ENCODING_LOCKS_CACHE if (txid, vout) not in UTXO_P2SH_ENCODING_LOCKS_CACHE: txhex = backend.getrawtransaction(txid, verbose=False) UTXO_P2SH_ENCODING_LOCKS_CACHE[(txid, vout)] = make_outkey_vin(txhex, vout) return UTXO_P2SH_ENCODING_LOCKS_CACHE[(txid, vout)]
def list_tx(db, block_hash, block_index, block_time, tx_hash, tx_index, tx_hex=None): assert type(tx_hash) == str cursor = db.cursor() # Edge case: confirmed tx_hash also in mempool cursor.execute('''SELECT * FROM transactions WHERE tx_hash = ?''', (tx_hash,)) transactions = list(cursor) if transactions: return tx_index # Get the important details about each transaction. if tx_hex is None: tx_hex = backend.getrawtransaction(tx_hash) source, destination, btc_amount, fee, data = get_tx_info(tx_hex) # For mempool if block_hash == None: block_hash = config.MEMPOOL_BLOCK_HASH block_index = config.MEMPOOL_BLOCK_INDEX backend.extract_addresses([tx_hash,]) # prepare cache for backend.unconfirmed_transactions(). else: assert block_index == util.CURRENT_BLOCK_INDEX if source and (data or destination == config.UNSPENDABLE): logger.debug('Saving transaction: {}'.format(tx_hash)) cursor.execute('''INSERT INTO transactions( tx_index, tx_hash, block_index, block_hash, block_time, source, destination, btc_amount, fee, data) VALUES(?,?,?,?,?,?,?,?,?,?)''', (tx_index, tx_hash, block_index, block_hash, block_time, source, destination, btc_amount, fee, data) ) cursor.close() return tx_index + 1 else: logger.debug('Skipping transaction: {}'.format(tx_hash)) return tx_index
def test_search_raw_transactions_output(): txs = backend.search_raw_transactions(ADDR[0], unconfirmed=True) tx = txs[0] tx = backend.getrawtransaction('02f95716d3c93a1e81b926d9d8d5f05b6f382c115d9ecf0dd0bc9514b0e08649', verbose=True) pprint.pprint(tx) # general assert tx['txid'] == '02f95716d3c93a1e81b926d9d8d5f05b6f382c115d9ecf0dd0bc9514b0e08649' assert tx['confirmations'] == 6 assert tx['hex'] == '0100000001a7f84ec59ff69951f5dc732c77199e177ab608b030f449899a81f13c921d01f4010000001976a9146c39ee7c8f3a5ffa6121b0304a7a0de9d3d9a15288acffffffff0200a3e111000000006951210282b886c087eb37dc8182f14ba6cc3e9485ed618b95804d44aecc17c300b585b0210378ee11c3fb97054877a809ce083db292b16d971bcdc6aa4c8f92087133729d8b21037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b83353ae38847ee2000000001976a9146c39ee7c8f3a5ffa6121b0304a7a0de9d3d9a15288ac00000000' assert tx['size'] == 224 assert tx['version'] == 1 assert tx['locktime'] == 0 assert tx['time'] == None # not mocked yet assert tx['blocktime'] == None # not mocked yet assert tx['blockhash'] == None # not mocked yet # vin assert tx['vin'] == [] # not mocked yet # vout 0 assert tx['vout'][0]['value'] == 3.0 assert tx['vout'][0]['n'] == 0 assert tx['vout'][0]['scriptPubKey']['addresses'] == ['mn6q3dS2EnDUx3bmyWc6D4szJNVGtaR7zc', 'mnfAHmddVibnZNSkh8DvKaQoiEfNsxjXzH', 'mqPCfvqTfYctXMUfmniXeG2nyaN8w6tPmj'] assert tx['vout'][0]['scriptPubKey']['hex'] == '51210282b886c087eb37dc8182f14ba6cc3e9485ed618b95804d44aecc17c300b585b0210378ee11c3fb97054877a809ce083db292b16d971bcdc6aa4c8f92087133729d8b21037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b83353ae' assert tx['vout'][0]['scriptPubKey']['asm'] == '1 0282b886c087eb37dc8182f14ba6cc3e9485ed618b95804d44aecc17c300b585b0 0378ee11c3fb97054877a809ce083db292b16d971bcdc6aa4c8f92087133729d8b 037af2e06061b54cdfe3657bbc8496d69000b822e2db0c86ccbe376346a700b833 3 OP_CHECKMULTISIG' assert tx['vout'][0]['scriptPubKey']['type'] == 'multisig' # vout 1 assert tx['vout'][1]['value'] == 37.999422 assert tx['vout'][1]['n'] == 1 assert tx['vout'][1]['scriptPubKey']['addresses'] == ['mqPCfvqTfYctXMUfmniXeG2nyaN8w6tPmj'] assert tx['vout'][1]['scriptPubKey']['hex'] == '76a9146c39ee7c8f3a5ffa6121b0304a7a0de9d3d9a15288ac' assert tx['vout'][1]['scriptPubKey']['asm'] == 'OP_DUP OP_HASH160 6c39ee7c8f3a5ffa6121b0304a7a0de9d3d9a152 OP_EQUALVERIFY OP_CHECKSIG' assert tx['vout'][1]['scriptPubKey']['type'] == 'pubkeyhash'
def list_tx(db, block_hash, block_index, block_time, tx_hash, tx_index): assert type(tx_hash) == str # Get the important details about each transaction. tx_hex = backend.getrawtransaction(tx_hash) source, destination, btc_amount, fee, data = get_tx_info(tx_hex) # For mempool if block_hash == None: block_hash = config.MEMPOOL_BLOCK_HASH block_index = config.MEMPOOL_BLOCK_INDEX backend.extract_addresses( tx_hash) # prepare cache for backend.unconfirmed_transactions(). else: assert block_index == util.CURRENT_BLOCK_INDEX if source and (data or destination == config.UNSPENDABLE): logger.debug('Saving transaction: {}'.format(tx_hash)) cursor = db.cursor() cursor.execute( '''INSERT INTO transactions( tx_index, tx_hash, block_index, block_hash, block_time, source, destination, btc_amount, fee, data) VALUES(?,?,?,?,?,?,?,?,?,?)''', (tx_index, tx_hash, block_index, block_hash, block_time, source, destination, btc_amount, fee, data)) cursor.close() return tx_index + 1 else: logger.debug('Skipping transaction: {}'.format(tx_hash)) return tx_index
def get_tx_info2(tx_hex, block_parser=None): """Get multisig transaction info. The destinations, if they exists, always comes before the data output; the change, if it exists, always comes after. """ # Decode transaction binary. ctx = backend.deserialize(tx_hex) def arc4_decrypt(cyphertext): '''Un‐obfuscate. Initialise key once per attempt.''' key = ARC4.new(ctx.vin[0].prevout.hash[::-1]) return key.decrypt(cyphertext) def get_opreturn(asm): if len(asm) == 2 and asm[0] == 'OP_RETURN': pubkeyhash = asm[1] if type(pubkeyhash) == bytes: return pubkeyhash raise DecodeError('invalid OP_RETURN') def decode_opreturn(asm): chunk = get_opreturn(asm) chunk = arc4_decrypt(chunk) if chunk[:len(config.PREFIX)] == config.PREFIX: # Data destination, data = None, chunk[len(config.PREFIX):] else: raise DecodeError('unrecognised OP_RETURN output') return destination, data def decode_checksig(asm): pubkeyhash = script.get_checksig(asm) chunk = arc4_decrypt(pubkeyhash) if chunk[1:len(config.PREFIX) + 1] == config.PREFIX: # Data # Padding byte in each output (instead of just in the last one) so that encoding methods may be mixed. Also, it’s just not very much data. chunk_length = chunk[0] chunk = chunk[1:chunk_length + 1] destination, data = None, chunk[len(config.PREFIX):] else: # Destination pubkeyhash = binascii.hexlify(pubkeyhash).decode('utf-8') destination, data = script.base58_check_encode(pubkeyhash, config.ADDRESSVERSION), None return destination, data def decode_checkmultisig(asm): pubkeys, signatures_required = script.get_checkmultisig(asm) chunk = b'' for pubkey in pubkeys[:-1]: # (No data in last pubkey.) chunk += pubkey[1:-1] # Skip sign byte and nonce byte. chunk = arc4_decrypt(chunk) if chunk[1:len(config.PREFIX) + 1] == config.PREFIX: # Data # Padding byte in each output (instead of just in the last one) so that encoding methods may be mixed. Also, it’s just not very much data. chunk_length = chunk[0] chunk = chunk[1:chunk_length + 1] destination, data = None, chunk[len(config.PREFIX):] else: # Destination pubkeyhashes = [script.pubkey_to_pubkeyhash(pubkey) for pubkey in pubkeys] destination, data = script.construct_array(signatures_required, pubkeyhashes, len(pubkeyhashes)), None return destination, data # Ignore coinbase transactions. if ctx.is_coinbase(): raise DecodeError('coinbase transaction') # Get destinations and data outputs. destinations, btc_amount, fee, data = [], 0, 0, b'' for vout in ctx.vout: # Fee is the input values minus output values. output_value = vout.nValue fee -= output_value # Ignore transactions with invalid script. try: asm = script.get_asm(vout.scriptPubKey) except CScriptInvalidError as e: raise DecodeError(e) if asm[0] == 'OP_RETURN': new_destination, new_data = decode_opreturn(asm) elif asm[-1] == 'OP_CHECKSIG': new_destination, new_data = decode_checksig(asm) elif asm[-1] == 'OP_CHECKMULTISIG': new_destination, new_data = decode_checkmultisig(asm) else: raise DecodeError('unrecognised output type') assert not (new_destination and new_data) assert new_destination != None or new_data != None # `decode_*()` should never return `None, None`. if util.enabled('null_data_check'): if new_data == []: raise DecodeError('new destination is `None`') # All destinations come before all data. if not data and not new_data and destinations != [config.UNSPENDABLE,]: destinations.append(new_destination) btc_amount += output_value else: if new_destination: # Change. break else: # Data. data += new_data # Only look for source if data were found or destination is `UNSPENDABLE`, # for speed. if not data and destinations != [config.UNSPENDABLE,]: raise BTCOnlyError('no data and not unspendable') # Collect all (unique) source addresses. sources = [] for vin in ctx.vin[:]: # Loop through inputs. # Get the full transaction data for this input transaction. if block_parser: vin_tx = block_parser.read_raw_transaction(ib2h(vin.prevout.hash)) vin_ctx = backend.deserialize(vin_tx['__data__']) else: vin_tx = backend.getrawtransaction(ib2h(vin.prevout.hash)) vin_ctx = backend.deserialize(vin_tx) vout = vin_ctx.vout[vin.prevout.n] fee += vout.nValue asm = script.get_asm(vout.scriptPubKey) if asm[-1] == 'OP_CHECKSIG': new_source, new_data = decode_checksig(asm) if new_data or not new_source: raise DecodeError('data in source') elif asm[-1] == 'OP_CHECKMULTISIG': new_source, new_data = decode_checkmultisig(asm) if new_data or not new_source: raise DecodeError('data in source') else: raise DecodeError('unrecognised source type') # Collect unique sources. if new_source not in sources: sources.append(new_source) sources = '-'.join(sources) destinations = '-'.join(destinations) return sources, destinations, btc_amount, round(fee), data
def get_tx_info1(tx_hex, block_index, block_parser=None): """Get singlesig transaction info. The destination, if it exists, always comes before the data output; the change, if it exists, always comes after. """ ctx = backend.deserialize(tx_hex) def get_pubkeyhash(scriptpubkey): asm = script.get_asm(scriptpubkey) if len(asm) != 5 or asm[0] != 'OP_DUP' or asm[1] != 'OP_HASH160' or asm[3] != 'OP_EQUALVERIFY' or asm[4] != 'OP_CHECKSIG': return False return asm[2] def get_address(scriptpubkey): pubkeyhash = get_pubkeyhash(scriptpubkey) if not pubkeyhash: return False pubkeyhash = binascii.hexlify(pubkeyhash).decode('utf-8') address = script.base58_check_encode(pubkeyhash, config.ADDRESSVERSION) # Test decoding of address. if address != config.UNSPENDABLE and binascii.unhexlify(bytes(pubkeyhash, 'utf-8')) != script.base58_check_decode(address, config.ADDRESSVERSION): return False return address # Fee is the input values minus output values. fee = 0 # Get destination output and data output. destination, btc_amount, data = None, None, b'' pubkeyhash_encoding = False for vout in ctx.vout: fee -= vout.nValue # Sum data chunks to get data. (Can mix OP_RETURN and multi-sig.) asm = script.get_asm(vout.scriptPubKey) if len(asm) == 2 and asm[0] == 'OP_RETURN': # OP_RETURN if type(asm[1]) != bytes: continue data_chunk = asm[1] data += data_chunk elif len(asm) == 5 and asm[0] == 1 and asm[3] == 2 and asm[4] == 'OP_CHECKMULTISIG': # Multi-sig if type(asm[2]) != bytes: continue data_pubkey = asm[2] data_chunk_length = data_pubkey[0] # No ord() necessary. data_chunk = data_pubkey[1:data_chunk_length + 1] data += data_chunk elif len(asm) == 5 and (block_index >= 293000 or config.TESTNET): # Protocol change. # Be strict. pubkeyhash = get_pubkeyhash(vout.scriptPubKey) if not pubkeyhash: continue if ctx.is_coinbase(): raise DecodeError('coinbase transaction') obj1 = ARC4.new(ctx.vin[0].prevout.hash[::-1]) data_pubkey = obj1.decrypt(pubkeyhash) if data_pubkey[1:9] == config.PREFIX or pubkeyhash_encoding: pubkeyhash_encoding = True data_chunk_length = data_pubkey[0] # No ord() necessary. data_chunk = data_pubkey[1:data_chunk_length + 1] if data_chunk[-8:] == config.PREFIX: data += data_chunk[:-8] break else: data += data_chunk # Destination is the first output before the data. if not destination and not btc_amount and not data: address = get_address(vout.scriptPubKey) if address: destination = address btc_amount = vout.nValue # Check for, and strip away, prefix (except for burns). if destination == config.UNSPENDABLE: pass elif data[:len(config.PREFIX)] == config.PREFIX: data = data[len(config.PREFIX):] else: raise DecodeError('no prefix') # Only look for source if data were found or destination is UNSPENDABLE, for speed. if not data and destination != config.UNSPENDABLE: raise BTCOnlyError('no data and not unspendable') # Collect all possible source addresses; ignore coinbase transactions and anything but the simplest Pay‐to‐PubkeyHash inputs. source_list = [] for vin in ctx.vin[:]: # Loop through input transactions. if vin.prevout.is_null(): raise DecodeError('coinbase transaction') # Get the full transaction data for this input transaction. if block_parser: vin_tx = block_parser.read_raw_transaction(ib2h(vin.prevout.hash)) vin_ctx = backend.deserialize(vin_tx['__data__']) else: vin_tx = backend.getrawtransaction(ib2h(vin.prevout.hash)) vin_ctx = backend.deserialize(vin_tx) vout = vin_ctx.vout[vin.prevout.n] fee += vout.nValue address = get_address(vout.scriptPubKey) if not address: raise DecodeError('invalid scriptpubkey') else: source_list.append(address) # Require that all possible source addresses be the same. if all(x == source_list[0] for x in source_list): source = source_list[0] else: source = None return source, destination, btc_amount, fee, data
def getrawtransaction(tx_hash, verbose=False, skip_missing=False): return backend.getrawtransaction(tx_hash, verbose=verbose, skip_missing=skip_missing)
def get_tx_info2(tx_hex, block_parser=None): """Get multisig transaction info. The destinations, if they exists, always comes before the data output; the change, if it exists, always comes after. """ # Decode transaction binary. ctx = backend.deserialize(tx_hex) def arc4_decrypt(cyphertext): '''Un‐obfuscate. Initialise key once per attempt.''' key = ARC4.new(ctx.vin[0].prevout.hash[::-1]) return key.decrypt(cyphertext) def get_opreturn(asm): if len(asm) == 2 and asm[0] == 'OP_RETURN': pubkeyhash = asm[1] if type(pubkeyhash) == bytes: return pubkeyhash raise DecodeError('invalid OP_RETURN') def decode_opreturn(asm): chunk = get_opreturn(asm) chunk = arc4_decrypt(chunk) if chunk[:len(config.PREFIX)] == config.PREFIX: # Data destination, data = None, chunk[len(config.PREFIX):] else: raise DecodeError('unrecognised OP_RETURN output') return destination, data def decode_checksig(asm): pubkeyhash = script.get_checksig(asm) chunk = arc4_decrypt(pubkeyhash) if chunk[1:len(config.PREFIX) + 1] == config.PREFIX: # Data # Padding byte in each output (instead of just in the last one) so that encoding methods may be mixed. Also, it’s just not very much data. chunk_length = chunk[0] chunk = chunk[1:chunk_length + 1] destination, data = None, chunk[len(config.PREFIX):] else: # Destination pubkeyhash = binascii.hexlify(pubkeyhash).decode('utf-8') destination, data = script.base58_check_encode( pubkeyhash, config.ADDRESSVERSION), None return destination, data def decode_checkmultisig(asm): pubkeys, signatures_required = script.get_checkmultisig(asm) chunk = b'' for pubkey in pubkeys[:-1]: # (No data in last pubkey.) chunk += pubkey[1:-1] # Skip sign byte and nonce byte. chunk = arc4_decrypt(chunk) if chunk[1:len(config.PREFIX) + 1] == config.PREFIX: # Data # Padding byte in each output (instead of just in the last one) so that encoding methods may be mixed. Also, it’s just not very much data. chunk_length = chunk[0] chunk = chunk[1:chunk_length + 1] destination, data = None, chunk[len(config.PREFIX):] else: # Destination pubkeyhashes = [ script.pubkey_to_pubkeyhash(pubkey) for pubkey in pubkeys ] destination, data = script.construct_array(signatures_required, pubkeyhashes, len(pubkeyhashes)), None return destination, data # Ignore coinbase transactions. if ctx.is_coinbase(): raise DecodeError('coinbase transaction') # Get destinations and data outputs. destinations, btc_amount, fee, data = [], 0, 0, b'' for vout in ctx.vout: # Fee is the input values minus output values. output_value = vout.nValue fee -= output_value asm = script.get_asm(vout.scriptPubKey) if asm[0] == 'OP_RETURN': new_destination, new_data = decode_opreturn(asm) elif asm[-1] == 'OP_CHECKSIG': new_destination, new_data = decode_checksig(asm) elif asm[-1] == 'OP_CHECKMULTISIG': new_destination, new_data = decode_checkmultisig(asm) else: raise DecodeError('unrecognised output type') assert not (new_destination and new_data) assert new_destination != None or new_data != None # All destinations come before all data. if not data and not new_data and destinations != [ config.UNSPENDABLE, ]: destinations.append(new_destination) btc_amount += output_value else: if new_destination: # Change. break else: # Data. data += new_data # Only look for source if data were found or destination is `UNSPENDABLE`, # for speed. if not data and destinations != [ config.UNSPENDABLE, ]: raise BTCOnlyError('no data and not unspendable') # Collect all (unique) source addresses. sources = [] for vin in ctx.vin[:]: # Loop through inputs. # Get the full transaction data for this input transaction. if block_parser: vin_tx = block_parser.read_raw_transaction(ib2h(vin.prevout.hash)) vin_ctx = backend.deserialize(vin_tx['__data__']) else: vin_tx = backend.getrawtransaction(ib2h(vin.prevout.hash)) vin_ctx = backend.deserialize(vin_tx) vout = vin_ctx.vout[vin.prevout.n] fee += vout.nValue asm = script.get_asm(vout.scriptPubKey) if asm[-1] == 'OP_CHECKSIG': new_source, new_data = decode_checksig(asm) if new_data or not new_source: raise DecodeError('data in source') elif asm[-1] == 'OP_CHECKMULTISIG': new_source, new_data = decode_checkmultisig(asm) if new_data or not new_source: raise DecodeError('data in source') else: raise DecodeError('unrecognised source type') # Collect unique sources. if new_source not in sources: sources.append(new_source) sources = '-'.join(sources) destinations = '-'.join(destinations) return sources, destinations, btc_amount, round(fee), data
def get_tx_info1(tx_hex, block_index, block_parser=None): """Get singlesig transaction info. The destination, if it exists, always comes before the data output; the change, if it exists, always comes after. """ ctx = backend.deserialize(tx_hex) def get_pubkeyhash(scriptpubkey): asm = script.get_asm(scriptpubkey) if len(asm ) != 5 or asm[0] != 'OP_DUP' or asm[1] != 'OP_HASH160' or asm[ 3] != 'OP_EQUALVERIFY' or asm[4] != 'OP_CHECKSIG': return False return asm[2] def get_address(scriptpubkey): pubkeyhash = get_pubkeyhash(scriptpubkey) if not pubkeyhash: return False pubkeyhash = binascii.hexlify(pubkeyhash).decode('utf-8') address = script.base58_check_encode(pubkeyhash, config.ADDRESSVERSION) # Test decoding of address. if address != config.UNSPENDABLE and binascii.unhexlify( bytes(pubkeyhash, 'utf-8')) != script.base58_check_decode( address, config.ADDRESSVERSION): return False return address # Fee is the input values minus output values. fee = 0 # Get destination output and data output. destination, btc_amount, data = None, None, b'' pubkeyhash_encoding = False for vout in ctx.vout: fee -= vout.nValue # Sum data chunks to get data. (Can mix OP_RETURN and multi-sig.) asm = script.get_asm(vout.scriptPubKey) if len(asm) == 2 and asm[0] == 'OP_RETURN': # OP_RETURN if type(asm[1]) != bytes: continue data_chunk = asm[1] data += data_chunk elif len(asm) == 5 and asm[0] == 1 and asm[3] == 2 and asm[ 4] == 'OP_CHECKMULTISIG': # Multi-sig if type(asm[2]) != bytes: continue data_pubkey = asm[2] data_chunk_length = data_pubkey[0] # No ord() necessary. data_chunk = data_pubkey[1:data_chunk_length + 1] data += data_chunk elif len(asm) == 5 and (block_index >= 293000 or config.TESTNET): # Protocol change. # Be strict. pubkeyhash = get_pubkeyhash(vout.scriptPubKey) if not pubkeyhash: continue if ctx.is_coinbase(): raise DecodeError('coinbase transaction') obj1 = ARC4.new(ctx.vin[0].prevout.hash[::-1]) data_pubkey = obj1.decrypt(pubkeyhash) if data_pubkey[1:9] == config.PREFIX or pubkeyhash_encoding: pubkeyhash_encoding = True data_chunk_length = data_pubkey[0] # No ord() necessary. data_chunk = data_pubkey[1:data_chunk_length + 1] if data_chunk[-8:] == config.PREFIX: data += data_chunk[:-8] break else: data += data_chunk # Destination is the first output before the data. if not destination and not btc_amount and not data: address = get_address(vout.scriptPubKey) if address: destination = address btc_amount = vout.nValue # Check for, and strip away, prefix (except for burns). if destination == config.UNSPENDABLE: pass elif data[:len(config.PREFIX)] == config.PREFIX: data = data[len(config.PREFIX):] else: raise DecodeError('no prefix') # Only look for source if data were found or destination is UNSPENDABLE, for speed. if not data and destination != config.UNSPENDABLE: raise BTCOnlyError('no data and not unspendable') # Collect all possible source addresses; ignore coinbase transactions and anything but the simplest Pay‐to‐PubkeyHash inputs. source_list = [] for vin in ctx.vin[:]: # Loop through input transactions. if vin.prevout.is_null(): raise DecodeError('coinbase transaction') # Get the full transaction data for this input transaction. if block_parser: vin_tx = block_parser.read_raw_transaction(ib2h(vin.prevout.hash)) vin_ctx = backend.deserialize(vin_tx['__data__']) else: vin_tx = backend.getrawtransaction(ib2h(vin.prevout.hash)) vin_ctx = backend.deserialize(vin_tx) vout = vin_ctx.vout[vin.prevout.n] fee += vout.nValue address = get_address(vout.scriptPubKey) if not address: raise DecodeError('invalid scriptpubkey') else: source_list.append(address) # Require that all possible source addresses be the same. if all(x == source_list[0] for x in source_list): source = source_list[0] else: source = None return source, destination, btc_amount, fee, data
def getrawtransaction(tx_hash, verbose=False): return backend.getrawtransaction(tx_hash, verbose=verbose)