from io import BytesIO
from json import dumps

import random
import requests
import zmq

from ecc import PrivateKey, S256Point, Signature
from helper import (
    decode_base58,
    double_sha256,
    encode_varint,
    hash160,
    int_to_little_endian,
    little_endian_to_int,
    p2pkh_script,
    p2sh_script,
    read_varint,
    SIGHASH_ALL,
)
from script import Script


class LibBitcoinClient:

    context = zmq.Context()
    mainnet_socket = None
    testnet_socket = None
    cache = {}

    @classmethod
    def get_socket(cls, testnet=False):
        if testnet:
            if cls.testnet_socket is None:
                cls.testnet_socket = cls.context.socket(zmq.REQ)
                cls.testnet_socket.connect(
                    'tcp://testnet.libbitcoin.net:19091')
            return cls.testnet_socket
        else:
            if cls.mainnet_socket is None:
                cls.mainnet_socket = cls.context.socket(zmq.REQ)
                cls.mainnet_socket.connect(
                    'tcp://mainnet.libbitcoin.net:9091')
            return cls.mainnet_socket


class Tx(LibBitcoinClient):

    default_version = 1
    default_hash_type = 1
    cache = {}
    p2pkh_prefixes = (b'\x00', b'\x6f')
    p2sh_prefixes = (b'\x05', b'\xc4')
    testnet_prefixes = (b'\x6f', b'\xc4')
    scale = 100000000
    num_bytes = 25
    fee = 2500
    insight = 'https://btc-bitcore6.trezor.io/api'
    seeds = None

    def __init__(self, version, tx_ins, tx_outs, locktime, testnet=False):
        self.version = version
        self.tx_ins = tx_ins
        self.tx_outs = tx_outs
        self.locktime = locktime
        self.testnet = testnet
        self._hash_prevouts = None
        self._hash_sequence = None
        self._hash_outputs = None

    def __repr__(self):
        tx_ins = ''
        for tx_in in self.tx_ins:
            tx_ins += tx_in.__repr__() + '\n'
        tx_outs = ''
        for tx_out in self.tx_outs:
            tx_outs += tx_out.__repr__() + '\n'
        return '{}\nversion: {}\ntx_ins:\n{}\ntx_outs:\n{}\nlocktime: {}\n'.format(
            self.hash().hex(),
            self.version,
            tx_ins,
            tx_outs,
            self.locktime,
        )

    def hash(self):
        return double_sha256(self.serialize())[::-1]

    def id(self):
        return self.hash().hex()

    @classmethod
    def get_address_data(cls, addr):
        b58 = decode_base58(addr, num_bytes=cls.num_bytes)
        prefix = b58[:-20]
        h160 = b58[-20:]
        testnet = prefix in cls.testnet_prefixes
        if prefix in cls.p2pkh_prefixes:
            script_pubkey = Script.parse(p2pkh_script(h160))
        elif prefix in cls.p2sh_prefixes:
            script_pubkey = Script.parse(p2sh_script(h160))
        else:
            raise RuntimeError('unknown type of address {} {}'.format(addr, prefix))
        return {
            'testnet': testnet,
            'h160': h160,
            'script_pubkey': script_pubkey,
        }

    @classmethod
    def fetch_address_utxos(cls, address, at_block_height=None):
        # grab all unspent transaction outputs as of block block_height
        # if block_height is None, we include all utxos
        address_data = cls.get_address_data(address)
        serialized_script_pubkey = address_data['script_pubkey'].serialize()
        socket = cls.get_socket(address_data['testnet'])
        nonce = int_to_little_endian(random.randint(0, 2**32), 4)
        msg = b'blockchain.fetch_history3'
        socket.send(msg, zmq.SNDMORE)
        socket.send(nonce, zmq.SNDMORE)
        socket.send(address_data['h160'] + b'\x00\x00\x00\x00')
        response_msg = socket.recv()
        response_nonce = socket.recv()
        if response_msg != msg or response_nonce != nonce:
            raise RuntimeError('received wrong msg: {}'.format(
                response_msg.decode('ascii')))
        response = socket.recv()
        response_code = little_endian_to_int(response[:4])
        if response_code != 0:
            raise RuntimeError('got code from server: {}'.format(response_code))
        response = response[4:]
        receives = []
        spent = set()
        while len(response) > 0:
            kind = response[0]
            prev_tx = response[1:33]
            prev_index = response[33:37]
            block_height = little_endian_to_int(response[37:41])
            if kind == 0:
                value = little_endian_to_int(response[41:49])
                if at_block_height is None or block_height <= at_block_height:
                    receives.append([prev_tx, prev_index, value])
            else:
                if at_block_height is None or block_height <= at_block_height:
                    spent.add(little_endian_to_int(response[41:49]))
            response = response[49:]
        utxos = []
        tx_mask = 0xffffffffffff8000
        index_mask = 0x7fff
        for prev_tx, prev_index, value in receives:
            tx_upper_49_bits = (little_endian_to_int(prev_tx) >> 12*8) & tx_mask
            index_lower_15_bits = little_endian_to_int(prev_index) & index_mask
            key = tx_upper_49_bits | index_lower_15_bits
            if key not in spent:
                utxos.append([serialized_script_pubkey, prev_tx[::-1], little_endian_to_int(prev_index), value])
        return utxos

    @classmethod
    def get_all_utxos(cls, addrs):
        utxos = []
        for addr in addrs:
            # look up utxos for each address
            utxos.extend(cls.fetch_address_utxos(addr))
        return utxos

    @classmethod
    def spend_tx(cls, wifs, utxos, destination_addr, fee=540, segwit=False):
        destination_address_data = cls.get_address_data(destination_addr)
        testnet = destination_address_data['testnet']
        if testnet:
            prefix = cls.testnet_prefixes[0]
        else:
            prefix = cls.p2pkh_prefixes[0]
        tx_ins = []
        sequence = 0xffffffff
        priv_lookup = {}
        total = 0
        for wif in wifs:
            priv_key = PrivateKey.parse(wif)
            if segwit:
                addr = priv_key.point.segwit_address()
            else:
                addr = priv_key.point.address(priv_key.compressed, prefix=prefix)
            # look up utxos for each address
            address_data = cls.get_address_data(addr)
            script_pubkey = address_data['script_pubkey']
            spk = script_pubkey.serialize()
            priv_lookup[spk] = priv_key
        if not utxos:
            raise RuntimeError('fetch utxos first')
        for serialized_script_pubkey, prev_tx, prev_index, value in utxos:
            tx_ins.append(TxIn(prev_tx, prev_index, b'', sequence, value=value, script_pubkey=serialized_script_pubkey))
            total += value
        num_tx_ins = len(tx_ins)
        if num_tx_ins == 0:
            raise RuntimeError('nothing to spend')
        script_pubkey = destination_address_data['script_pubkey']
        tx_out = TxOut(total - fee, script_pubkey.serialize())
        tx = cls(cls.default_version, tx_ins, [tx_out], 0, testnet=testnet)
        for index, tx_in in enumerate(tx_ins):
            priv_key = priv_lookup[tx_in.script_pubkey().serialize()]
            if segwit:
                sec = priv_key.point.sec(True)
                redeem_script = Script([0, hash160(sec)]).serialize()
            else:
                redeem_script = None
            tx.sign_input(
                index,
                priv_key,
                cls.default_hash_type,
                compressed=priv_key.compressed,
                redeem_script=redeem_script,
            )
        if not tx.verify():
            raise RuntimeError('failed validation')
        return tx

    @classmethod
    def spend_all_tx(cls, private_keys, destination_addr, fee, segwit, utxos):
        destination_address_data = cls.get_address_data(destination_addr)
        testnet = destination_address_data['testnet']
        if testnet:
            if segwit:
                prefix = cls.p2sh_prefixes[1]
            else:
                prefix = cls.p2pkh_prefixes[1]
        else:
            if segwit:
                prefix = cls.p2sh_prefixes[0]
            else:
                prefix = cls.p2pkh_prefixes[0]
        tx_ins = []
        sequence = 0xffffffff
        priv_lookup = {}
        total = 0
        for private_key in private_keys:
            if segwit:
                addr = private_key.point.segwit_address(prefix=prefix)
            else:
                addr = private_key.point.address(private_key.compressed, prefix=prefix)
            address_data = cls.get_address_data(addr)
            script_pubkey = address_data['script_pubkey']
            priv_lookup[script_pubkey.serialize()] = private_key
        for serialized_script_pubkey, prev_tx, prev_index, value in utxos:
            private_key = priv_lookup[serialized_script_pubkey]
            if segwit:
                script_sig = private_key.segwit_redeem_script()
            else:
                script_sig = b''
            tx_in = TxIn(
                prev_tx,
                prev_index,
                script_sig,
                sequence,
                value=value,
                script_pubkey=serialized_script_pubkey,
            )
            tx_ins.append(tx_in)
            total += value
        num_tx_ins = len(tx_ins)
        if num_tx_ins == 0:
            return
        if total - fee < 0:
            return
        script_pubkey = destination_address_data['script_pubkey']
        print('{}: {} to {}'.format(cls, (total - fee) / cls.scale, destination_addr))
        tx_out = TxOut(total - fee, script_pubkey.serialize())
        tx = cls(cls.default_version, tx_ins, [tx_out], 0, testnet=testnet)
        for index, tx_in in enumerate(tx_ins):
            private_key = priv_lookup[tx_in.script_pubkey().serialize()]
            if segwit:
                sec = private_key.point.sec(True)
                redeem_script = Script([0, hash160(sec)]).serialize()
            else:
                redeem_script = None
            if not tx.sign_input(
                index,
                private_key,
                cls.default_hash_type,
                compressed=private_key.compressed,
                redeem_script=redeem_script,
            ):
                raise RuntimeError('sign and verify do different things')
        if not tx.verify():
            raise RuntimeError('failed validation')
        return tx

    @classmethod
    def parse(cls, s):
        '''Takes a byte stream and parses the transaction at the start
        return a Tx object
        '''
        # s.read(n) will return n bytes
        # version has 4 bytes, little-endian, interpret as int
        version = little_endian_to_int(s.read(4))
        # num_inputs is a varint, use read_varint(s)
        num_inputs = read_varint(s)
        # if we have a segwit marker, we need to parse in another way
        if num_inputs == 0:
            return cls.parse_segwit(s, version)
        # each input needs parsing
        inputs = []
        for _ in range(num_inputs):
            inputs.append(TxIn.parse(s))
        # num_outputs is a varint, use read_varint(s)
        num_outputs = read_varint(s)
        # each output needs parsing
        outputs = []
        for _ in range(num_outputs):
            outputs.append(TxOut.parse(s))
        # locktime is 4 bytes, little-endian
        locktime = little_endian_to_int(s.read(4))
        # return an instance of the class (cls(...))
        return cls(version, inputs, outputs, locktime)

    @classmethod
    def parse_segwit(cls, s, version):
        '''Takes a byte stream and parses the segwit transaction in middle
        return a Tx object
        '''
        marker = s.read(1)
        if marker != b'\x01':
            raise RuntimeError('Not a segwit transaction {}'.format(marker))
        # num_inputs is a varint, use read_varint(s)
        num_inputs = read_varint(s)
        # each input needs parsing
        tx_ins = []
        for _ in range(num_inputs):
            tx_ins.append(TxIn.parse(s))
        # num_outputs is a varint, use read_varint(s)
        num_outputs = read_varint(s)
        # each output needs parsing
        tx_outs = []
        for _ in range(num_outputs):
            tx_outs.append(TxOut.parse(s))
        # now parse the witness program
        for tx_in in tx_ins:
            num_elements = read_varint(s)
            elements = [num_elements]
            for _ in range(num_elements):
                element_len = read_varint(s)
                elements.append(s.read(element_len))
            tx_in.witness_program = Script(elements).serialize()
        # locktime is 4 bytes, little-endian
        locktime = little_endian_to_int(s.read(4))
        # return an instance of the class (cls(...))
        return cls(version, tx_ins, tx_outs, locktime)

    def is_segwit(self):
        for tx_in in self.tx_ins:
            if tx_in.is_segwit():
                return True
        return False

    def serialize(self):
        '''Returns the byte serialization of the transaction'''
        if self.is_segwit():
            return self.serialize_segwit()
        # serialize version (4 bytes, little endian)
        result = int_to_little_endian(self.version, 4)
        # encode_varint on the number of inputs
        result += encode_varint(len(self.tx_ins))
        # iterate inputs
        for tx_in in self.tx_ins:
            # serialize each input
            result += tx_in.serialize()
        # encode_varint on the number of inputs
        result += encode_varint(len(self.tx_outs))
        # iterate outputs
        for tx_out in self.tx_outs:
            # serialize each output
            result += tx_out.serialize()
        # serialize locktime (4 bytes, little endian)
        result += int_to_little_endian(self.locktime, 4)
        return result

    def serialize_segwit(self):
        '''Returns the byte serialization of the transaction'''
        # serialize version (4 bytes, little endian)
        result = int_to_little_endian(self.version, 4)
        # segwit marker '0001'
        result += b'\x00\x01'
        # encode_varint on the number of inputs
        result += encode_varint(len(self.tx_ins))
        # iterate inputs
        for tx_in in self.tx_ins:
            # serialize each input
            result += tx_in.serialize()
        # encode_varint on the number of inputs
        result += encode_varint(len(self.tx_outs))
        # iterate outputs
        for tx_out in self.tx_outs:
            # serialize each output
            result += tx_out.serialize()
        # add the witness data
        for tx_in in self.tx_ins:
            result += tx_in.witness_program
        # serialize locktime (4 bytes, little endian)
        result += int_to_little_endian(self.locktime, 4)
        return result

    def fee(self):
        '''Returns the fee of this transaction in satoshi'''
        # initialize input sum and output sum
        input_sum, output_sum = 0, 0
        # iterate through inputs
        for tx_in in self.tx_ins:
            # for each input get the value and add to input sum
            input_sum += tx_in.value()
        # iterate through outputs
        for tx_out in self.tx_outs:
            # for each output get the amount and add to output sum
            output_sum += tx_out.amount
        # return input sum - output sum
        return input_sum - output_sum

    def hash_prevouts(self):
        if self._hash_prevouts is None:
            all_prevouts = b''
            all_sequence = b''
            for tx_in in self.tx_ins:
                all_prevouts += tx_in.prev_tx[::-1] + int_to_little_endian(tx_in.prev_index, 4)
                all_sequence += int_to_little_endian(tx_in.sequence, 4)
            self._hash_prevouts = double_sha256(all_prevouts)
            self._hash_sequence = double_sha256(all_sequence)
        return self._hash_prevouts

    def hash_sequence(self):
        if self._hash_sequence is None:
            self.hash_prevouts()  # this should calculate self._hash_prevouts
        return self._hash_sequence

    def hash_outputs(self):
        if self._hash_outputs is None:
            all_outputs = b''
            for tx_out in self.tx_outs:
                all_outputs += tx_out.serialize()
            self._hash_outputs = double_sha256(all_outputs)
        return self._hash_outputs

    def sig_hash_preimage_bip143(self, input_index, hash_type, redeem_script=None):
        '''Returns the integer representation of the hash that needs to get
        signed for index input_index'''
        tx_in = self.tx_ins[input_index]
        # per BIP143 spec
        s = int_to_little_endian(self.version, 4)
        s += self.hash_prevouts() + self.hash_sequence()
        s += tx_in.prev_tx[::-1] + int_to_little_endian(tx_in.prev_index, 4)
        if tx_in.is_segwit() or redeem_script:
            if redeem_script:
                h160 = redeem_script[-20:]
            else:
                h160 = tx_in.redeem_script()[-20:]
            ser = p2pkh_script(h160)
        else:
            ser = tx_in.script_pubkey().serialize()
        s += bytes([len(ser)]) + ser  # script pubkey
        s += int_to_little_endian(tx_in.value(), 8)
        s += int_to_little_endian(tx_in.sequence, 4)
        s += self.hash_outputs()
        s += int_to_little_endian(self.locktime, 4)
        s += int_to_little_endian(hash_type, 4)
        return s

    def sig_hash_bip143(self, input_index, hash_type, redeem_script=None):
        s = self.sig_hash_preimage_bip143(input_index, hash_type, redeem_script=redeem_script)
        return int.from_bytes(double_sha256(s), 'big')

    def sig_hash(self, input_index, hash_type):
        '''Returns the integer representation of the hash that needs to get
        signed for index input_index'''
        # create a transaction serialization where
        # all the input script_sigs are blanked out
        alt_tx_ins = []
        for tx_in in self.tx_ins:
            alt_tx_ins.append(TxIn(
                prev_tx=tx_in.prev_tx,
                prev_index=tx_in.prev_index,
                script_sig=b'',
                sequence=tx_in.sequence,
                value=tx_in.value(),
                script_pubkey=tx_in.script_pubkey().serialize(),
            ))
        # replace the input's scriptSig with the scriptPubKey
        signing_input = alt_tx_ins[input_index]
        script_pubkey = signing_input.script_pubkey(self.testnet)
        sig_type = script_pubkey.type()
        if sig_type == 'p2pkh':
            signing_input.script_sig = script_pubkey
        elif sig_type == 'p2sh':
            current_input = self.tx_ins[input_index]
            signing_input.script_sig = Script.parse(
                current_input.redeem_script())
        else:
            raise RuntimeError('not a valid sig_type: {}'.format(sig_type))
        alt_tx = self.__class__(
            version=self.version,
            tx_ins=alt_tx_ins,
            tx_outs=self.tx_outs,
            locktime=self.locktime,
        )
        # add the hash_type
        result = alt_tx.serialize()
        result += int_to_little_endian(hash_type, 4)
        return int.from_bytes(double_sha256(result), 'big')

    def verify_input(self, input_index):
        '''Returns whether the input has a valid signature'''
        # get the relevant input
        tx_in = self.tx_ins[input_index]
        # get the number of signatures required. This is available in tx_in.script_sig.num_sigs_required()
        sigs_required = tx_in.script_sig.num_sigs_required()
        # iterate over the sigs required and check each signature
        for sig_num in range(sigs_required):
            # get the point from the sec format
            sec = tx_in.sec_pubkey(index=sig_num)
            # get the sec_pubkey at current signature index
            point = S256Point.parse(sec)
            # get the der sig and hash_type from input
            # get the der_signature at current signature index
            der, hash_type = tx_in.der_signature(index=sig_num)
            # get the signature from der format
            signature = Signature.parse(der)
            # get the hash to sign
            if tx_in.is_segwit():
                h160 = hash160(tx_in.script_sig.redeem_script())
                if h160 != tx_in.script_pubkey(self.testnet).elements[1]:
                    return False
                pubkey_h160 = tx_in.script_sig.redeem_script()[-20:]
                if pubkey_h160 != point.h160():
                    return False
                z = self.sig_hash_bip143(input_index, hash_type)
            else:
                z = self.sig_hash(input_index, hash_type)
            # use point.verify on the hash to sign and signature
            if not point.verify(z, signature):
                return False
        return True

    def sign_input(self, input_index, private_key, hash_type, compressed=True, redeem_script=None):
        '''Signs the input using the private key'''
        # get the hash to sign
        tx_in = self.tx_ins[input_index]
        if redeem_script:
            z = self.sig_hash_bip143(input_index, hash_type, redeem_script=redeem_script)
        else:
            z = self.sig_hash(input_index, hash_type)
        # get der signature of z from private key
        der = private_key.sign(z).der()
        # append the hash_type to der (use bytes([hash_type]))
        sig = der + bytes([hash_type])
        # calculate the sec
        sec = private_key.point.sec(compressed=compressed)
        if redeem_script:
            # witness program 0
            tx_in.script_sig = Script([redeem_script])
            tx_in.witness_program = Script([2, sig, sec]).serialize()
        else:
            # initialize a new script with [sig, sec] as the elements
            # change input's script_sig to new script
            tx_in.script_sig = Script([sig, sec])
        # return whether sig is valid using self.verify_input
        return self.verify_input(input_index)

    def is_coinbase(self):
        '''Returns whether this transaction is a coinbase transaction or not'''
        # check that there is exactly 1 input
        if len(self.tx_ins) != 1:
            return False
        # grab the first input
        first_input = self.tx_ins[0]
        # check that first input prev_tx is b'\x00' * 32 bytes
        if first_input.prev_tx != b'\x00' * 32:
            return False
        # check that first input prev_index is 0xffffffff
        if first_input.prev_index != 0xffffffff:
            return False
        return True

    def coinbase_height(self):
        '''Returns the height of the block this coinbase transaction is in
        Returns None if this transaction is not a coinbase transaction
        '''
        # if this is NOT a coinbase transaction, return None
        if not self.is_coinbase():
            return None
        # grab the first input
        first_input = self.tx_ins[0]
        # grab the first element of the script_sig (.script_sig.elements[0])
        first_element = first_input.script_sig.elements[0]
        # convert the first element from little endian to int
        return little_endian_to_int(first_element)

    def verify(self):
        for i in range(len(self.tx_ins)):
            if not self.verify_input(i):
                return False
        return True

    def sign(self, private_key, compressed=True):
        for i in range(len(self.tx_ins)):
            if not self.sign_input(
                    i,
                    private_key,
                    self.default_hash_type,
                    compressed=compressed,
            ):
                raise RuntimeError('signing failed')

    def send_insight(self):
        if self.insight is None:
            return
        url = '{}/tx/send'.format(self.insight)
        data = dumps({'rawtx': self.serialize().hex()})
        r = requests.post(url, data=data, headers={'Content-Type': 'application/json'})
        return r.text



class BTXTx(Tx):
    fork_block = 492820
    default_version = 2
    fee = 200000
    magic = b'\xf9\xbe\xb4\xd9'
    port = 8555
    seeds = ("37.120.190.76", "37.120.186.85", "185.194.140.60", "188.71.223.206", "185.194.142.122")
    insight = None

    @classmethod
    def fetch_address_utxos(cls, address):
        api_key = 'e86ce04b6888'
        url = 'https://chainz.cryptoid.info/btx/api.dws?q=unspent&active={}&key={}'.format(
            address, api_key)
        result = requests.get(url).json()
        address_data = cls.get_address_data(address)
        serialized_script_pubkey = address_data['script_pubkey'].serialize()
        print(result)
        utxos = []
        for item in result['unspent_outputs']:
            utxos.append([serialized_script_pubkey, bytes.fromhex(item['tx_hash']), item['tx_ouput_n'], int(item['value'])])
        return utxos


class ForkTx(Tx):
    fork_block = 0

    @classmethod
    def fetch_address_utxos(cls, address):
        return super().fetch_address_utxos(address, at_block_height=cls.fork_block)


class BTCPTx(Tx):
    default_hash_type = 0x41
    fork_id = 42 << 8
    p2pkh_prefixes = (b'\x13\x25',)
    p2sh_prefixes = (b'\x13\xaf',)
    num_bytes = 26
    fee = 20000
    insight = 'https://explorer.btcprivate.org/api'

    @classmethod
    def fetch_address_utxos(cls, address):
        url = 'https://explorer.btcprivate.org/api/addr/{}/utxo'.format(
            address)
        result = requests.get(url).json()
        address_data = cls.get_address_data(address)
        serialized_script_pubkey = address_data['script_pubkey'].serialize()
        utxos = []
        for item in result:
            utxos.append([serialized_script_pubkey, bytes.fromhex(item['txid']), item['vout'], int(item['satoshis'])])
        return utxos
        

    def sig_hash(self, input_index, hash_type):
        '''Returns the integer representation of the hash that needs to get
        signed for index input_index'''
        # create a transaction serialization where
        # all the input script_sigs are blanked out
        alt_tx_ins = []
        for tx_in in self.tx_ins:
            alt_tx_ins.append(TxIn(
                prev_tx=tx_in.prev_tx,
                prev_index=tx_in.prev_index,
                script_sig=b'',
                sequence=tx_in.sequence,
                value=tx_in.value(),
                script_pubkey=tx_in.script_pubkey().serialize(),
            ))
        # replace the input's scriptSig with the scriptPubKey
        signing_input = alt_tx_ins[input_index]
        signing_input.script_sig = signing_input.script_pubkey(self.testnet)
        alt_tx = self.__class__(
            version=self.version,
            tx_ins=alt_tx_ins,
            tx_outs=self.tx_outs,
            locktime=self.locktime,
        )
        # add the hash_type
        result = alt_tx.serialize()
        result += int_to_little_endian(hash_type | self.fork_id, 4)
        return int.from_bytes(double_sha256(result), 'big')

    def sign_input(self, input_index, private_key, hash_type, compressed=True, redeem_script=None):
        '''Signs the input using the private key'''
        # get the hash to sign
        tx_in = self.tx_ins[input_index]
        if redeem_script:
            h160 = Script.parse(redeem_script).elements[1]
            tx_in._script_pubkey = Script.parse(p2pkh_script(h160))
        z = self.sig_hash(input_index, hash_type)
        # get der signature of z from private key
        der = private_key.sign(z).der()
        # append the hash_type to der (use bytes([hash_type]))
        sig = der + bytes([hash_type])
        # calculate the sec
        sec = private_key.point.sec(compressed=compressed)
        if redeem_script:
            tx_in.script_sig = Script([sig, sec, redeem_script])
        else:
            tx_in.script_sig = Script([sig, sec])
        return self.verify_input(input_index)


class B2XTx(ForkTx):
    fork_block = 501451
    fork_id = 0
    default_hash_type = 0x31
    fee = 20000
    magic = b'\xf4\xb2\xb5\xd8'
    port = 8333
    seeds = ("node1.b2x-segwit.io", "node2.b2x-segwit.io", "node3.b2x-segwit.io")
    insight = None

    def sig_hash(self, input_index, hash_type):
        '''Returns the integer representation of the hash that needs to get
        signed for index input_index'''
        # create a transaction serialization where
        # all the input script_sigs are blanked out
        alt_tx_ins = []
        for tx_in in self.tx_ins:
            alt_tx_ins.append(TxIn(
                prev_tx=tx_in.prev_tx,
                prev_index=tx_in.prev_index,
                script_sig=b'',
                sequence=tx_in.sequence,
                value=tx_in.value(),
                script_pubkey=tx_in.script_pubkey().serialize(),
            ))
        # replace the input's scriptSig with the scriptPubKey
        signing_input = alt_tx_ins[input_index]
        script_pubkey = signing_input.script_pubkey(self.testnet)
        sig_type = script_pubkey.type()
        if sig_type == 'p2pkh':
            signing_input.script_sig = script_pubkey
        elif sig_type == 'p2sh':
            current_input = self.tx_ins[input_index]
            signing_input.script_sig = Script.parse(
                current_input.redeem_script())
        else:
            raise RuntimeError('not a valid sig_type: {}'.format(sig_type))
        alt_tx = self.__class__(
            version=self.version,
            tx_ins=alt_tx_ins,
            tx_outs=self.tx_outs,
            locktime=self.locktime,
        )
        # add the hash_type
        result = alt_tx.serialize()
        result += int_to_little_endian(hash_type << 1, 4)
        return int.from_bytes(double_sha256(result), 'big')

    def sig_hash_preimage_bip143(self, input_index, hash_type, redeem_script=None):
        '''Returns the integer representation of the hash that needs to get
        signed for index input_index'''
        tx_in = self.tx_ins[input_index]
        # per BIP143 spec
        s = int_to_little_endian(self.version, 4)
        s += self.hash_prevouts() + self.hash_sequence()
        s += tx_in.prev_tx[::-1] + int_to_little_endian(tx_in.prev_index, 4)
        if tx_in.is_segwit() or redeem_script:
            if redeem_script:
                h160 = redeem_script[-20:]
            else:
                h160 = tx_in.redeem_script()[-20:]
            ser = p2pkh_script(h160)
        else:
            ser = tx_in.script_pubkey().serialize()
        s += bytes([len(ser)]) + ser  # script pubkey
        s += int_to_little_endian(tx_in.value(), 8)
        s += int_to_little_endian(tx_in.sequence, 4)
        s += self.hash_outputs()
        s += int_to_little_endian(self.locktime, 4)
        s += int_to_little_endian(hash_type << 1, 4)
        return s
    

class LBTCTx(ForkTx):
    fork_block = 499999
    fork_id = 0
    magic = b'\xf9\xbe\xb3\xd7'
    port = 9333
    seeds = ("seed9.lbtc.io", "seed8.lbtc.io", "seed10.lbtc.io")
    default_version = 0xff01
    fee = 200000
    insight = None


class BCHTx(ForkTx):
    fork_block = 478558
    fork_id = 0
    default_hash_type = 0x41
    insight = 'https://bch-bitcore2.trezor.io/api'
    fee = 540

    def sig_hash_preimage_bip143(self, input_index, hash_type, redeem_script=None):
        '''Returns the integer representation of the hash that needs to get
        signed for index input_index'''
        tx_in = self.tx_ins[input_index]
        # per BIP143 spec
        s = int_to_little_endian(self.version, 4)
        s += self.hash_prevouts() + self.hash_sequence()
        s += tx_in.prev_tx[::-1] + int_to_little_endian(tx_in.prev_index, 4)
        if tx_in.is_segwit() or redeem_script:
            if redeem_script:
                h160 = redeem_script[-20:]
            else:
                h160 = tx_in.redeem_script()[-20:]
            ser = p2pkh_script(h160)
        else:
            ser = tx_in.script_pubkey().serialize()
        s += bytes([len(ser)]) + ser  # script pubkey
        s += int_to_little_endian(tx_in.value(), 8)
        s += int_to_little_endian(tx_in.sequence, 4)
        s += self.hash_outputs()
        s += int_to_little_endian(self.locktime, 4)
        s += int_to_little_endian(hash_type | self.fork_id, 4)
        return s

    def verify_input(self, input_index):
        '''Returns whether the input has a valid signature'''
        # get the relevant input
        tx_in = self.tx_ins[input_index]
        # get the sec_pubkey at current signature index
        point = S256Point.parse(tx_in.sec_pubkey())
        # get the der sig and hash_type from input
        # get the der_signature at current signature index
        der, hash_type = tx_in.der_signature()
        # get the signature from der format
        signature = Signature.parse(der)
        # get the hash to sign
        z = self.sig_hash_bip143(input_index, hash_type)
        # use point.verify on the hash to sign and signature
        if not point.verify(z, signature):
            return False
        return True

    def sign_input(self, input_index, private_key, hash_type, compressed=True, redeem_script=None):
        '''Signs the input using the private key'''
        # get the hash to sign
        z = self.sig_hash_bip143(input_index, hash_type)
        # get der signature of z from private key
        der = private_key.sign(z).der()
        # append the hash_type to der (use bytes([hash_type]))
        sig = der + bytes([hash_type])
        # calculate the sec
        sec = private_key.point.sec(compressed=compressed)
        # initialize a new script with [sig, sec] as the elements
        script_sig = Script([sig, sec])
        # change input's script_sig to new script
        self.tx_ins[input_index].script_sig = script_sig
        # return whether sig is valid using self.verify_input
        return self.verify_input(input_index)

    def sign(self, private_key, compressed=True):
        hash_type = self.default_hash_type
        for i in range(len(self.tx_ins)):
            if not self.sign_input(
                    i, private_key, hash_type, compressed=compressed):
                raise RuntimeError('signing failed')


class BTGTx(BCHTx):
    fork_block = 491407
    fork_id = 79 << 8
    p2pkh_prefixes = (b'\x26', b'\x6f', b'\x00')
    p2sh_prefixes = (b'\x17', b'\xc4', b'\x05')
    insight = 'https://btg-bitcore2.trezor.io/api'
    fee = 5000

    def sign_input(self, input_index, private_key, hash_type, compressed=True, redeem_script=None):
        '''Signs the input using the private key'''
        # get the hash to sign
        z = self.sig_hash_bip143(input_index, hash_type, redeem_script=redeem_script)
        # get der signature of z from private key
        der = private_key.sign(z).der()
        # append the hash_type to der (use bytes([hash_type]))
        sig = der + bytes([hash_type])
        # calculate the sec
        sec = private_key.point.sec(compressed=compressed)
        tx_in = self.tx_ins[input_index]
        if redeem_script:
            tx_in.script_sig = Script([redeem_script])
            tx_in.witness_program = Script([2, sig, sec]).serialize()
        else:
            # initialize a new script with [sig, sec] as the elements
            script_sig = Script([sig, sec])
            # change input's script_sig to new script
            tx_in.script_sig = script_sig
        # return whether sig is valid using self.verify_input
        return self.verify_input(input_index)


class BCITx(BTGTx):
    fork_block = 505083
    fork_id = 79 << 8
    default_hash_type = 0x41
    p2pkh_prefixes = (b'\x66',)
    p2sh_prefixes = (b'\x17',)
    insight = 'https://explorer.bitcoininterest.io/api'
    magic = b'\xed\xe4\xfe\x26'
    port = 8331
    seeds = ("74.208.166.57", "216.250.117.221")
    fee = 20000

class BTPTx(BTGTx):
    fork_block = 499345
    fork_id = 80 << 8
    default_hash_type = 0x41
    p2pkh_prefixes = (b'\x38',)
    p2sh_prefixes = (b'\x05',)
    insight = 'http://exp.btceasypay.com/insight-api'
    fee = 20000
    scale = 10000000

    
class BTVTx(BTGTx):
    fork_block = 505050
    fork_id = 50 << 8
    default_hash_type = 0x41
    fee = 20000
    magic = b'\xf9\x50\x50\x50'
    port = 8333
    seeds = ("seed1.bitvote.one", "seed2.bitvote.one", "seed3.bitvote.one")
    insight = 'https://block.bitvote.one/insight-api'

    def sig_hash(self, input_index, hash_type):
        '''Returns the integer representation of the hash that needs to get
        signed for index input_index'''
        # create a transaction serialization where
        # all the input script_sigs are blanked out
        alt_tx_ins = []
        for tx_in in self.tx_ins:
            alt_tx_ins.append(TxIn(
                prev_tx=tx_in.prev_tx,
                prev_index=tx_in.prev_index,
                script_sig=b'',
                sequence=tx_in.sequence,
                value=tx_in.value(),
                script_pubkey=tx_in.script_pubkey().serialize(),
            ))
        # replace the input's scriptSig with the scriptPubKey
        signing_input = alt_tx_ins[input_index]
        script_pubkey = signing_input.script_pubkey(self.testnet)
        sig_type = script_pubkey.type()
        if sig_type == 'p2pkh':
            signing_input.script_sig = script_pubkey
        elif sig_type == 'p2sh':
            current_input = self.tx_ins[input_index]
            signing_input.script_sig = Script.parse(
                current_input.redeem_script())
        else:
            raise RuntimeError('not a valid sig_type: {}'.format(sig_type))
        alt_tx = self.__class__(
            version=self.version,
            tx_ins=alt_tx_ins,
            tx_outs=self.tx_outs,
            locktime=self.locktime,
        )
        # add the hash_type
        result = alt_tx.serialize()
        result += int_to_little_endian(hash_type | self.fork_id, 4)
        return int.from_bytes(double_sha256(result), 'big')

    def sign_input(self, input_index, private_key, hash_type, compressed=True, redeem_script=None):
        '''Signs the input using the private key'''
        # get the hash to sign
        tx_in = self.tx_ins[input_index]
        if redeem_script:
            z = self.sig_hash_bip143(input_index, hash_type, redeem_script=redeem_script)
        else:
            z = self.sig_hash(input_index, hash_type)
        # get der signature of z from private key
        der = private_key.sign(z).der()
        # append the hash_type to der (use bytes([hash_type]))
        sig = der + bytes([hash_type])
        # calculate the sec
        sec = private_key.point.sec(compressed=compressed)
        if redeem_script:
            # witness program 0
            tx_in.script_sig = Script([redeem_script])
            tx_in.witness_program = Script([2, sig, sec]).serialize()
        else:
            # initialize a new script with [sig, sec] as the elements
            # change input's script_sig to new script
            tx_in.script_sig = Script([sig, sec])
        # return whether sig is valid using self.verify_input
        return self.verify_input(input_index)

    def verify_input(self, input_index):
        '''Returns whether the input has a valid signature'''
        # get the relevant input
        tx_in = self.tx_ins[input_index]
        # get the number of signatures required. This is available in tx_in.script_sig.num_sigs_required()
        sigs_required = tx_in.script_sig.num_sigs_required()
        # iterate over the sigs required and check each signature
        for sig_num in range(sigs_required):
            # get the point from the sec format
            sec = tx_in.sec_pubkey(index=sig_num)
            # get the sec_pubkey at current signature index
            point = S256Point.parse(sec)
            # get the der sig and hash_type from input
            # get the der_signature at current signature index
            der, hash_type = tx_in.der_signature(index=sig_num)
            # get the signature from der format
            signature = Signature.parse(der)
            # get the hash to sign
            if tx_in.is_segwit():
                h160 = hash160(tx_in.script_sig.redeem_script())
                if h160 != tx_in.script_pubkey(self.testnet).elements[1]:
                    return False
                pubkey_h160 = tx_in.script_sig.redeem_script()[-20:]
                if pubkey_h160 != point.h160():
                    return False
                z = self.sig_hash_bip143(input_index, hash_type)
            else:
                z = self.sig_hash(input_index, hash_type)
            # use point.verify on the hash to sign and signature
            if not point.verify(z, signature):
                return False
        return True


class BCA(BTGTx):
    fork_block = 505888
    fork_id = 93 << 8
    default_hash_type = 0x41
    fee = 20000
    magic = b'\x4f\xc1\x1d\xe8'
    port = 7333
    seeds = ("seed.bitcoinatom.io", "seed.bitcoin-atom.org", "seed.bitcoinatom.net")
    insight = None

    
class BCXTx(BTGTx):
    fork_block = 498888
    default_version = 2
    default_hash_type = 0x11
    fork_id = 0
    p2pkh_prefixes = (b'\x4b', b'\x41', b'\x00')
    p2sh_prefixes = (b'\x3f', b'\xc4', b'\x05')
    scale = 10000
    magic = b'\x11\x05\xbc\xf9'
    port = 9003
    seeds = ("192.169.227.48", "120.92.119.221", "120.92.89.254", "120.131.5.173", "120.92.117.145", "192.169.153.174", "192.169.154.185", "166.227.117.163")
    fee = 200000
    insight = None


class BTFTx(BTGTx):
    fork_block = 500000
    fork_id = 70 << 8
    p2pkh_prefixes = (b'\x24', b'\x60')
    p2sh_prefixes = (b'\x28', b'\x65')
    fee = 200000
    magic = b'\xfa\xe2\xd4\xe6'
    port = 8346
    seeds = ("a.btf.hjy.cc", "b.btf.hjy.cc", "c.btf.hjy.cc", "d.btf.hjy.cc", "e.btf.hjy.cc", "f.btf.hjy.cc")
    insight = None


class BTWTx(BTGTx):
    fork_block = 499777
    fork_id = 87 << 8
    p2pkh_prefixes = (b'\x49', b'\x87')
    p2sh_prefixes = (b'\x1f', b'\x59')
    scale = 10000
    fee = 200000
    magic = b'\xf8\x62\x74\x77'
    port = 8357
    seeds = ("47.52.250.221", "47.91.237.5")
    insight = None


class BCDTx(ForkTx):
    fork_block = 495866
    default_version = 12
    default_block_hash = bytes.fromhex('c51159637a85160ed5c726fb0df68e14352b495e4c57444d4d427bbc68db0551')
    scale = 10000000
    fee = 20000
    magic = b'\xbd\xde\xb4\xd9'
    port = 7117
    seeds = ("seed1.dns.btcd.io", "seed2.dns.btcd.io", "seed3.dns.btcd.io", "seed4.dns.btcd.io", "seed5.dns.btcd.io", "seed6.dns.btcd.io")
    insight = None

    def __init__(self, version, tx_ins, tx_outs, locktime, prev_block_hash=None, testnet=False):
        super().__init__(version, tx_ins, tx_outs, locktime, testnet=False)
        if prev_block_hash is None:
            self.prev_block_hash = self.default_block_hash
        else:
            self.prev_block_hash = prev_block_hash

    @classmethod
    def parse(cls, s):
        '''Takes a byte stream and parses the transaction at the start
        return a Tx object
        '''
        # s.read(n) will return n bytes
        # version has 4 bytes, little-endian, interpret as int
        version = little_endian_to_int(s.read(4))
        prev_block_hash = s.read(32)[::-1]
        # num_inputs is a varint, use read_varint(s)
        num_inputs = read_varint(s)
        # each input needs parsing
        inputs = []
        for _ in range(num_inputs):
            inputs.append(TxIn.parse(s))
        # num_outputs is a varint, use read_varint(s)
        num_outputs = read_varint(s)
        # each output needs parsing
        outputs = []
        for _ in range(num_outputs):
            outputs.append(TxOut.parse(s))
        # locktime is 4 bytes, little-endian
        locktime = little_endian_to_int(s.read(4))
        # return an instance of the class (cls(...))
        return cls(version, inputs, outputs, locktime, prev_block_hash=prev_block_hash)

    def serialize_segwit(self):
        '''Returns the byte serialization of the transaction'''
        # serialize version (4 bytes, little endian)
        result = int_to_little_endian(self.version, 4)
        # previous block hash
        result += self.prev_block_hash[::-1]
        # segwit marker '0001'
        result += b'\x00\x01'
        # encode_varint on the number of inputs
        result += encode_varint(len(self.tx_ins))
        # iterate inputs
        for tx_in in self.tx_ins:
            # serialize each input
            result += tx_in.serialize()
        # encode_varint on the number of inputs
        result += encode_varint(len(self.tx_outs))
        # iterate outputs
        for tx_out in self.tx_outs:
            # serialize each output
            result += tx_out.serialize()
        # add the witness data
        for tx_in in self.tx_ins:
            result += tx_in.witness_program
        # serialize locktime (4 bytes, little endian)
        result += int_to_little_endian(self.locktime, 4)
        return result

    def serialize(self):
        '''Returns the byte serialization of the transaction'''
        if self.is_segwit():
            return self.serialize_segwit()
        # serialize version (4 bytes, little endian)
        result = int_to_little_endian(self.version, 4)
        # previous block hash
        result += self.prev_block_hash[::-1]
        # encode_varint on the number of inputs
        result += encode_varint(len(self.tx_ins))
        # iterate inputs
        for tx_in in self.tx_ins:
            # serialize each input
            result += tx_in.serialize()
        # encode_varint on the number of inputs
        result += encode_varint(len(self.tx_outs))
        # iterate outputs
        for tx_out in self.tx_outs:
            # serialize each output
            result += tx_out.serialize()
        # serialize locktime (4 bytes, little endian)
        result += int_to_little_endian(self.locktime, 4)
        return result

    def sig_hash_preimage_bip143(self, input_index, hash_type, redeem_script=None):
        '''Returns the integer representation of the hash that needs to get
        signed for index input_index'''
        tx_in = self.tx_ins[input_index]
        # per BIP143 spec
        s = int_to_little_endian(self.version, 4)
        s += self.prev_block_hash[::-1]
        s += self.hash_prevouts() + self.hash_sequence()
        s += tx_in.prev_tx[::-1] + int_to_little_endian(tx_in.prev_index, 4)
        if tx_in.is_segwit() or redeem_script:
            if redeem_script:
                h160 = redeem_script[-20:]
            else:
                h160 = tx_in.redeem_script()[-20:]
            ser = p2pkh_script(h160)
        else:
            ser = tx_in.script_pubkey().serialize()
        s += bytes([len(ser)]) + ser  # script pubkey
        s += int_to_little_endian(tx_in.value(), 8)
        s += int_to_little_endian(tx_in.sequence, 4)
        s += self.hash_outputs()
        s += int_to_little_endian(self.locktime, 4)
        s += int_to_little_endian(hash_type, 4)
        return s

    def sig_hash(self, input_index, hash_type):
        '''Returns the integer representation of the hash that needs to get
        signed for index input_index'''
        # create a transaction serialization where
        # all the input script_sigs are blanked out
        alt_tx_ins = []
        for tx_in in self.tx_ins:
            alt_tx_ins.append(TxIn(
                prev_tx=tx_in.prev_tx,
                prev_index=tx_in.prev_index,
                script_sig=b'',
                sequence=tx_in.sequence,
                value=tx_in.value(),
                script_pubkey=tx_in.script_pubkey().serialize(),
            ))
        # replace the input's scriptSig with the scriptPubKey
        signing_input = alt_tx_ins[input_index]
        script_pubkey = signing_input.script_pubkey(self.testnet)
        sig_type = script_pubkey.type()
        if sig_type == 'p2pkh':
            signing_input.script_sig = script_pubkey
        elif sig_type == 'p2sh':
            current_input = self.tx_ins[input_index]
            signing_input.script_sig = Script.parse(
                current_input.redeem_script())
        else:
            raise RuntimeError('no valid sig_type')
        alt_tx = self.__class__(
            version=self.version,
            tx_ins=alt_tx_ins,
            tx_outs=self.tx_outs,
            locktime=self.locktime,
            prev_block_hash=self.prev_block_hash,
        )
        # add the hash_type
        result = alt_tx.serialize()
        result += int_to_little_endian(hash_type, 4)
        return int.from_bytes(double_sha256(result), 'big')


class SBTCTx(ForkTx):
    fork_block = 498888
    default_version = 2
    default_hash_type = 0x41
    sighash_append = b'\x04sbtc'
    fee = 20000
    magic = b'\xf9\xbe\xb4\xd9'
    port = 8334
    seeds = ("seed.superbtca.com", "seed.superbtca.info", "seed.superbtc.org")
    insight = None

    def sig_hash_preimage_bip143(self, input_index, hash_type, redeem_script=None):
        '''Returns the integer representation of the hash that needs to get
        signed for index input_index'''
        tx_in = self.tx_ins[input_index]
        # per BIP143 spec
        s = int_to_little_endian(self.version, 4)
        s += self.hash_prevouts() + self.hash_sequence()
        s += tx_in.prev_tx[::-1] + int_to_little_endian(tx_in.prev_index, 4)
        if tx_in.is_segwit() or redeem_script:
            if redeem_script:
                h160 = redeem_script[-20:]
            else:
                h160 = tx_in.redeem_script()[-20:]
            ser = p2pkh_script(h160)
        else:
            ser = tx_in.script_pubkey().serialize()
        s += bytes([len(ser)]) + ser  # script pubkey
        s += int_to_little_endian(tx_in.value(), 8)
        s += int_to_little_endian(tx_in.sequence, 4)
        s += self.hash_outputs()
        s += int_to_little_endian(self.locktime, 4)
        s += int_to_little_endian(hash_type, 4)
        s += self.sighash_append
        return s

    def sig_hash(self, input_index, hash_type):
        '''Returns the integer representation of the hash that needs to get
        signed for index input_index'''
        # create a transaction serialization where
        # all the input script_sigs are blanked out
        alt_tx_ins = []
        for tx_in in self.tx_ins:
            alt_tx_ins.append(TxIn(
                prev_tx=tx_in.prev_tx,
                prev_index=tx_in.prev_index,
                script_sig=b'',
                sequence=tx_in.sequence,
                value=tx_in.value(),
                script_pubkey=tx_in.script_pubkey().serialize(),
            ))
        # replace the input's scriptSig with the scriptPubKey
        signing_input = alt_tx_ins[input_index]
        script_pubkey = signing_input.script_pubkey(self.testnet)
        sig_type = script_pubkey.type()
        if sig_type == 'p2pkh':
            signing_input.script_sig = script_pubkey
        elif sig_type == 'p2sh':
            current_input = self.tx_ins[input_index]
            signing_input.script_sig = Script.parse(
                current_input.redeem_script())
        else:
            raise RuntimeError('no valid sig_type')
        alt_tx = self.__class__(
            version=self.version,
            tx_ins=alt_tx_ins,
            tx_outs=self.tx_outs,
            locktime=self.locktime,
        )
        # add the hash_type
        result = alt_tx.serialize()
        result += int_to_little_endian(hash_type, 4)
        result += self.sighash_append
        return int.from_bytes(double_sha256(result), 'big')

    def sign(self, private_key, compressed=True):
        hash_type = 0x40 | SIGHASH_ALL
        for i in range(len(self.tx_ins)):
            if not self.sign_input(i, private_key, hash_type, compressed=compressed):
                raise RuntimeError('signing failed')


class TxIn(LibBitcoinClient):

    def __init__(self, prev_tx, prev_index, script_sig, sequence, witness_program=b'\x00', value=None, script_pubkey=None):
        self.prev_tx = prev_tx
        self.prev_index = prev_index
        self.script_sig = Script.parse(script_sig)
        self.sequence = sequence
        self.witness_program = witness_program
        self._value = value
        if script_pubkey is None:
            self._script_pubkey = None
        else:
            self._script_pubkey = Script.parse(script_pubkey)

    def __repr__(self):
        return '{}:{}'.format(self.prev_tx.hex(), self.prev_index)

    @classmethod
    def parse(cls, s):
        '''Takes a byte stream and parses the tx_input at the start
        return a TxIn object
        '''
        # s.read(n) will return n bytes
        # prev_tx is 32 bytes, little endian
        prev_tx = s.read(32)[::-1]
        # prev_index is 4 bytes, little endian, interpret as int
        prev_index = little_endian_to_int(s.read(4))
        # script_sig is a variable field (length followed by the data)
        # get the length by using read_varint(s)
        script_sig_length = read_varint(s)
        script_sig = s.read(script_sig_length)
        # sequence is 4 bytes, little-endian, interpret as int
        sequence = little_endian_to_int(s.read(4))
        # return an instance of the class (cls(...))
        return cls(prev_tx, prev_index, script_sig, sequence)

    def serialize(self):
        '''Returns the byte serialization of the transaction input'''
        # serialize prev_tx, little endian
        result = self.prev_tx[::-1]
        # serialize prev_index, 4 bytes, little endian
        result += int_to_little_endian(self.prev_index, 4)
        # get the scriptSig ready (use self.script_sig.serialize())
        raw_script_sig = self.script_sig.serialize()
        # encode_varint on the length of the scriptSig
        result += encode_varint(len(raw_script_sig))
        # add the scriptSig
        result += raw_script_sig
        # serialize sequence, 4 bytes, little endian
        result += int_to_little_endian(self.sequence, 4)
        return result

    def fetch_tx(self, testnet=False):
        if self.prev_tx not in self.cache:
            socket = self.get_socket(testnet=testnet)
            nonce = int_to_little_endian(random.randint(0, 2**32), 4)
            msg = b'blockchain.fetch_transaction2'
            socket.send(msg, zmq.SNDMORE)
            socket.send(nonce, zmq.SNDMORE)
            socket.send(self.prev_tx[::-1])
            response_msg = socket.recv()
            response_nonce = socket.recv()
            if response_msg != msg or response_nonce != nonce:
                raise RuntimeError('received wrong msg: {}'.format(
                    response_msg.decode('ascii')))
            response_tx = socket.recv()
            response_code = little_endian_to_int(response_tx[:4])
            if response_code != 0:
                raise RuntimeError('got code from server: {}'.format(response_code))
            stream = BytesIO(response_tx[4:])
            self.cache[self.prev_tx] = Tx.parse(stream)
        return self.cache[self.prev_tx]

    def value(self, testnet=False):
        '''Get the outpoint value by looking up the tx hash on libbitcoin server
        Returns the amount in satoshi
        '''
        if self._value is None:
            # use self.fetch_tx to get the transaction
            tx = self.fetch_tx(testnet=testnet)
            # get the output at self.prev_index
            # get the amount property
            self._value = tx.tx_outs[self.prev_index].amount
        return self._value

    def script_pubkey(self, testnet=False):
        '''Get the scriptPubKey by looking up the tx hash on libbitcoin server
        Returns the binary scriptpubkey
        '''
        if self._script_pubkey is None:
            # use self.fetch_tx to get the transaction
            tx = self.fetch_tx(testnet=testnet)
            # get the output at self.prev_index
            # get the script_pubkey property
            self._script_pubkey = tx.tx_outs[self.prev_index].script_pubkey
        return self._script_pubkey

    def der_signature(self, index=0):
        '''returns a DER format signature and hash_type if the script_sig
        has a signature'''
        if self.is_segwit():
            signature = self.witness_program[2:-34]
        else:
            signature = self.script_sig.der_signature(index=index)
        # last byte is the hash_type, rest is the signature
        return signature[:-1], signature[-1]

    def sec_pubkey(self, index=0):
        '''returns the SEC format public if the script_sig has one'''
        if self.is_segwit():
            return self.witness_program[-33:]
        else:
            return self.script_sig.sec_pubkey(index=index)

    def redeem_script(self):
        '''return the Redeem Script if there is one'''
        return self.script_sig.redeem_script()

    def is_segwit(self):
        if self.script_sig.type() != 'p2sh sig':
            return False
        redeem_script_raw = self.script_sig.redeem_script()
        if not redeem_script_raw:
            return False
        redeem_script = Script.parse(redeem_script_raw)
        return redeem_script.elements[0] == 0 and \
            type(redeem_script.elements[1]) == bytes and \
            len(redeem_script.elements[1]) == 20


class TxOut:

    def __init__(self, amount, script_pubkey):
        self.amount = amount
        self.script_pubkey = Script.parse(script_pubkey)

    def __repr__(self):
        return '{}:{}'.format(self.amount, self.script_pubkey.address())

    @classmethod
    def parse(cls, s):
        '''Takes a byte stream and parses the tx_output at the start
        return a TxOut object
        '''
        # s.read(n) will return n bytes
        # amount is 8 bytes, little endian, interpret as int
        amount = little_endian_to_int(s.read(8))
        # script_pubkey is a variable field (length followed by the data)
        # get the length by using read_varint(s)
        script_pubkey_length = read_varint(s)
        script_pubkey = s.read(script_pubkey_length)
        # return an instance of the class (cls(...))
        return cls(amount, script_pubkey)

    def serialize(self):
        '''Returns the byte serialization of the transaction output'''
        # serialize amount, 8 bytes, little endian
        result = int_to_little_endian(self.amount, 8)
        # get the scriptPubkey ready (use self.script_pubkey.serialize())
        raw_script_pubkey = self.script_pubkey.serialize()
        # encode_varint on the length of the scriptPubkey
        result += encode_varint(len(raw_script_pubkey))
        # add the scriptPubKey
        result += raw_script_pubkey
        return result