#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    Virtualchain
    ~~~~~
    copyright: (c) 2014-2015 by Halfmoon Labs, Inc.
    copyright: (c) 2016 by Blockstack.org
    
    This file is part of Virtualchain
    
    Virtualchain is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    Virtualchain is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with Virtualchain.  If not, see <http://www.gnu.org/licenses/>.
"""

import os
import re
import copy
import binascii
import simplejson
import keylib
from six import int2byte, b, integer_types
from decimal import *

from .keys import btc_script_hex_to_address, btc_is_singlesig, btc_is_multisig, \
        btc_script_deserialize, btc_script_serialize, btc_is_singlesig_segwit, btc_is_multisig_segwit, btc_get_singlesig_privkey, \
        btc_make_p2sh_p2wpkh_redeem_script, btc_make_p2sh_p2wsh_redeem_script

from .opcodes import *
from ....lib import encoding, ecdsalib, hashing, merkle
from ....lib.config import get_features

import traceback

from ....lib.config import get_logger
log = get_logger('virtualchain')

# signature modes
SIGHASH_ALL = 1
SIGHASH_NONE = 2
SIGHASH_SINGLE = 3
SIGHASH_ANYONECANPAY = 128

# bitcoin constants 
UINT_MAX = 4294967295


def read_as_int(ptr, buf, bytez):
    ptr[0] += bytez
    if ptr[0] > len(buf):
        raise ValueError("Invalid transaction: tried to parse {} bytes of {} offset in {}".format(bytez, ptr[0] - bytez, buf.encode('hex')))

    ret = encoding.decode( buf[ ptr[0]-bytez:ptr[0] ][::-1], 256 )
    return ret


def read_var_int(ptr, buf):
    ptr[0] += 1
    if ptr[0] > len(buf):
        raise ValueError("Invalid transaction: tried to parse {} byte of {} offset in {}".format(1, ptr[0] - 1, buf.encode('hex')))

    val = encoding.from_byte_to_int( buf[ ptr[0]-1 ] )
    if val < 253:
        return val

    ret = read_as_int(ptr, buf, pow(2, val - 252))
    return ret


def read_bytes(ptr, buf, bytez):
    ptr[0] += bytez
    if ptr[0] > len(buf):
        raise ValueError("Invalid transaction: tried to parse {} bytes of {} offset in {}".format(bytez, ptr[0] - bytez, buf.encode('hex')))

    ret = buf[ ptr[0]-bytez:ptr[0] ]
    return ret


def peek_bytes(ptr, buf, bytez):
    if ptr[0] + bytez > len(buf):
        raise ValueError("Invalid transaction: tried to parse {} bytes of {} offset in {}".format(bytez, ptr[0], buf.encode('hex')))

    ret = buf[ ptr[0]:ptr[0]+bytez ]
    return ret


def read_var_string(ptr, buf):
    size = read_var_int(ptr, buf)
    return read_bytes(ptr, buf, size)


def read_tx_body(ptr, tx):
    """
    Returns {'ins': [...], 'outs': [...]}
    """
    _obj = {"ins": [], "outs": [], 'locktime': None}

    # number of inputs
    ins = read_var_int(ptr, tx)

    # all inputs
    for i in range(ins):
        _obj["ins"].append({
            "outpoint": {
                "hash": read_bytes(ptr, tx, 32)[::-1],
                "index": read_as_int(ptr, tx, 4)
            },
            "script": read_var_string(ptr, tx),
            "sequence": read_as_int(ptr, tx, 4)
        })

    # number of outputs
    outs = read_var_int(ptr, tx)

    # all outputs
    for i in range(outs):
        _obj["outs"].append({
            "value": read_as_int(ptr, tx, 8),
            "script": read_var_string(ptr, tx)
        })

    return _obj


def read_tx_witnesses(ptr, tx, num_witnesses):
    """
    Returns an array of witness scripts.
    Each witness will be a bytestring (i.e. encoding the witness script)
    """
    witnesses = []
    for i in xrange(0, num_witnesses):

        witness_stack_len = read_var_int(ptr, tx)
        witness_stack = []

        for j in xrange(0, witness_stack_len):

            stack_item = read_var_string(ptr, tx)
            witness_stack.append(stack_item)
             
        witness_script = btc_witness_script_serialize(witness_stack).decode('hex')
        witnesses.append(witness_script)

    return witnesses


def make_var_string(string):
    """
    Make a var-string (a var-int with the length, concatenated with the data)
    Return the hex-encoded string
    """
    s = None
    if isinstance(string, str) and re.match('^[0-9a-fA-F]*$', string):
        # convert from hex to bin, safely
        s = binascii.unhexlify(string)
    else:
        s = string[:]

    buf = encoding.num_to_var_int(len(s)) + s
    return buf.encode('hex')


def _btc_witness_serialize_unit(unit):
    """
    Encode one item of a BTC witness script
    Return the encoded item (as a string)

    Returns a byte string with the encoded unit

    Based on code from pybitcointools (https://github.com/vbuterin/pybitcointools)
    by Vitalik Buterin
    """
    
    if isinstance(unit, int):
        # pass literal
        return encoding.from_int_to_byte(unit)

    elif unit is None:
        # None means OP_0
        return b'\x00'

    else:
        # return as a varint-prefixed string
        return make_var_string(unit)


def btc_witness_script_serialize(_stack):
    """
    Given a deserialized witness script stack (i.e. the input-specific witness, as an array of
    Nones, ints, and strings), turn it back into a hex-encoded script
    """
    stack = _stack
    if encoding.json_is_base(_stack, 16):
        # hex-to-bin all hex strings 
        stack = encoding.json_changebase(_stack, lambda x: binascii.unhexlify(x))

    return encoding.safe_hexlify(_btc_witness_serialize_unit(len(stack)) + ''.join(map(lambda stack_unit: _btc_witness_serialize_unit(stack_unit), stack)))


def btc_witness_script_deserialize(_script):
    """
    Given a hex-encoded serialized witness script, turn it into a witness stack
    (i.e. an array of Nones, ints, and strings)
    """

    script = None
    if isinstance(_script, str) and re.match('^[0-9a-fA-F]*$', _script):
        # convert from hex to bin, safely
        script = binascii.unhexlify(_script)
    else:
        script = _script[:]

    # pointer to byte offset in _script (as an array due to Python scoping rules)
    ptr = [0]

    witness_stack_len = read_var_int(ptr, script)
    witness_stack = []

    for _ in xrange(0, witness_stack_len):

        stack_item = read_var_string(ptr, script)
        witness_stack.append(stack_item)

    return witness_stack


def btc_tx_deserialize(_tx, **blockchain_opts):
    """
    Given a hex-encoded transaction, decode it into an object
    with the following structure:
    {
        ins: [
            {
                outpoint: { hash: ..., index: ... }, 
                script: ...,
                sequence: ...,
                witness_script: ...,        # not included if not segwit
            }, ...
        ]
        outs: [
            {
                value: ...,
                script: ...
            }, ...
        ],
        version: ..., 
        locktime: ...
    }
    
    Derived from pybitcointools (https://github.com/vbuterin/pybitcointools) written by Vitalik Buterin
    Throws an exception if there are remaining bytes
    """

    tx = None
    if isinstance(_tx, str) and re.match('^[0-9a-fA-F]*$', _tx):
        # convert from hex to bin, safely
        tx = binascii.unhexlify(_tx)
    else:
        tx = _tx[:]

    # pointer to byte offset in _tx (as an array due to Python scoping rules)
    ptr = [0]
    
    # top-level tx
    obj = {"ins": [], "outs": [], 'version': None, 'locktime': None}

    # get version
    obj["version"] = read_as_int(ptr, tx, 4)

    # segwit? (bip143)
    # 5th byte will be 0 and 6th byte will be flags (nonzero) if so
    bip143 = peek_bytes(ptr, tx, 2)
    if ord(bip143[0]) == 0 and ord(bip143[1]) != 0:
        # segwit
        # consume marker
        read_bytes(ptr, tx, 2)

        # get the rest of the body
        body = read_tx_body(ptr, tx)
        obj['ins'] = body['ins']
        obj['outs'] = body['outs']
     
        # read witnesses for each input
        witness_scripts = read_tx_witnesses(ptr, tx, len(obj['ins']))
      
        if len(witness_scripts) != len(obj['ins']):
            raise ValueError('Invald number of witnesses in {}'.format(_tx))

        for i in xrange(0, len(witness_scripts)):
            obj['ins'][i]['witness_script'] = witness_scripts[i]
        
    else:
        # non-segwit
        body = read_tx_body(ptr, tx)
        obj['ins'] = body['ins']
        obj['outs'] = body['outs']

    # locktime
    obj["locktime"] = read_as_int(ptr, tx, 4)

    if not ptr[0] == len(tx):
        # log.warning('Did not parse entire tx ({} bytes remaining)'.format(len(tx) - ptr[0]))
        raise ValueError('Did not parse entire tx ({} bytes remaining)'.format(len(tx) - ptr[0]))

    # hexlify each byte field 
    obj = encoding.json_changebase(obj, lambda x: encoding.safe_hexlify(x))
    return obj


def btc_tx_serialize(_txobj):
    """
    Given a transaction dict returned by btc_tx_deserialize, convert it back into a
    hex-encoded byte string.

    Derived from code written by Vitalik Buterin in pybitcointools (https://github.com/vbuterin/pybitcointools)
    """
    
    # output buffer
    o = []
    txobj = None
    if encoding.json_is_base(_txobj, 16):
        # txobj is built from hex strings already.  deserialize them 
        txobj = encoding.json_changebase(_txobj, lambda x: binascii.unhexlify(x))
    else:
        txobj = copy.deepcopy(_txobj)

    # version
    o.append(encoding.encode(txobj["version"], 256, 4)[::-1])

    # do we have any witness scripts?
    have_witness = False
    for inp in txobj['ins']:
        if inp.has_key('witness_script') and len(inp['witness_script']) > 0:
            have_witness = True
            break

    if have_witness:
        # add segwit marker 
        o.append('\x00\x01')
    
    # number of inputs
    o.append(encoding.num_to_var_int(len(txobj["ins"])))

    # all inputs
    for inp in txobj["ins"]:
        # input tx hash
        o.append(inp["outpoint"]["hash"][::-1])

        # input tx outpoint
        o.append(encoding.encode(inp["outpoint"]["index"], 256, 4)[::-1])

        # input scriptsig
        script = inp.get('script')
        if not script:
            script = bytes()

        scriptsig = encoding.num_to_var_int(len(script)) + script
        o.append(scriptsig)

        # sequence
        o.append(encoding.encode(inp.get("sequence", UINT_MAX - 1), 256, 4)[::-1])

    # number of outputs
    o.append(encoding.num_to_var_int(len(txobj["outs"])))

    # all outputs
    for out in txobj["outs"]:
        # value
        o.append(encoding.encode(out["value"], 256, 8)[::-1])

        # scriptPubKey
        scriptpubkey = encoding.num_to_var_int(len(out['script'])) + out['script']
        o.append(scriptpubkey)

    # add witnesses 
    if have_witness:
        for inp in txobj['ins']:
            witness_script = inp.get('witness_script')
            if not witness_script:
                witness_script = '\x00'

            o.append(witness_script)

    # locktime
    o.append(encoding.encode(txobj["locktime"], 256, 4)[::-1])

    # full string
    ret = ''.join( encoding.json_changebase(o, lambda x: encoding.safe_hexlify(x)) )
    return ret


def btc_bitcoind_tx_is_coinbase( tx ):
    """
    Is a transaction a coinbase transaction?
    tx is a bitcoind-given transaction structure
    """
    for inp in tx['vin']:
        if 'coinbase' in inp.keys():
            return True 

    return False


def btc_bitcoind_tx_serialize( tx ):
     """
     Convert a *Bitcoind*-given transaction into its hex string.

     tx format is {'vin': [...], 'vout': [...], 'locktime': ..., 'version': ...},
     with the same formatting rules as getrawtransaction.
     
     (in particular, each value in vout is a Decimal, in BTC)
     """
     tx_ins = []
     tx_outs = []

     try:
         for inp in tx['vin']:
             next_inp = {
                "outpoint": {
                   "index": int(inp['vout']),
                   "hash": str(inp['txid'])
                }
             }
             if 'sequence' in inp:
                 next_inp['sequence'] = int(inp['sequence'])
             else:
                 next_inp['sequence'] = UINT_MAX

             if 'scriptSig' in inp:
                 next_inp['script'] = str(inp['scriptSig']['hex'])
             else:
                 next_inp['script'] = ""

             if 'txinwitness' in inp:
                 next_inp['witness_script'] = btc_witness_script_serialize(inp['txinwitness'])

             tx_ins.append(next_inp)

         for out in tx['vout']:
         
             assert out['value'] < 1000, "High transaction value\n%s" % simplejson.dumps(tx, indent=4, sort_keys=True)
             next_out = {
                'value': int(Decimal(out['value'] * 10**8)),
                'script': str(out['scriptPubKey']['hex'])
             }
             tx_outs.append(next_out)

         tx_fields = {
            "locktime": int(tx['locktime']),
            "version": int(tx['version']),
            "ins": tx_ins,
            "outs": tx_outs
         }

         tx_serialized = btc_tx_serialize( tx_fields )
         return str(tx_serialized)

     except KeyError, ke:
         if btc_bitcoind_tx_is_coinbase(tx) and 'hex' in tx.keys():
             tx_serialized = tx['hex']
             return str(tx_serialized)

         log.error("Key error in:\n%s" % simplejson.dumps(tx, indent=4, sort_keys=True))
         traceback.print_exc()
         raise ke


def btc_tx_is_segwit( tx_serialized ):
    """
    Is this serialized (hex-encoded) transaction a segwit transaction?
    """
    marker_offset = 4       # 5th byte is the marker byte
    flag_offset = 5         # 6th byte is the flag byte
    
    marker_byte_string = tx_serialized[2*marker_offset:2*(marker_offset+1)]
    flag_byte_string = tx_serialized[2*flag_offset:2*(flag_offset+1)]

    if marker_byte_string == '00' and flag_byte_string != '00':
        # segwit (per BIP144)
        return True
    else:
        return False


def btc_tx_witness_strip( tx_serialized ):
    """
    Strip the witness information from a serialized transaction
    """
    if not btc_tx_is_segwit(tx_serialized):
        # already strippped
        return tx_serialized
     
    tx = btc_tx_deserialize(tx_serialized)
    for inp in tx['ins']:
        del inp['witness_script']

    tx_stripped = btc_tx_serialize(tx)
    return tx_stripped


def btc_tx_get_hash( tx_serialized, hashcode=None ):
    """
    Make a transaction hash (txid) from a hex tx, optionally along with a sighash.
    This DOES NOT WORK for segwit transactions
    """
    if btc_tx_is_segwit(tx_serialized):
        raise ValueError('Segwit transaction: {}'.format(tx_serialized))

    tx_bin = binascii.unhexlify(tx_serialized)
    if hashcode:
        return binascii.hexlify( hashing.bin_double_sha256(tx_bin + encoding.encode(int(hashcode), 256, 4)[::-1]) )

    else:
        return binascii.hexlify( hashing.bin_double_sha256(tx_bin)[::-1] )


def btc_tx_script_to_asm( script_hex ):
    """
    Decode a script into assembler
    """
    if len(script_hex) == 0:
        return ""

    try:
        script_array = btc_script_deserialize(script_hex)
    except:
        log.error("Failed to convert '%s' to assembler" % script_hex)
        raise

    script_tokens = []
    for token in script_array:
        if token is None:
            token = 0

        token_name = None

        if type(token) in [int,long]:
            token_name = OPCODE_NAMES.get(token, None)
            if token_name is None:
                token_name = str(token)
        
        else:
            token_name = token

        script_tokens.append(token_name)

    return " ".join(script_tokens)


def btc_tx_output_has_data(output, **blockchain_opts):
    """
    Does this output have user data?
    @output must be an element from the 'outs' list in btc_tx_deserialize()
    """
    return btc_tx_output_script_has_data(output['script'], **blockchain_opts)


def btc_tx_output_script_has_data(output_script, **blockchain_opts):
    """
    Does a btc output script have data? i.e. is it an OP_RETURN?
    The script must be hex-encoded
    """
    if len(output_script) < 2:
        return False

    return int(output_script[0:2], 16) == OPCODE_VALUES['OP_RETURN']


def btc_tx_extend(partial_tx_hex, new_inputs, new_outputs, **blockchain_opts):
    """
    Given an unsigned serialized transaction, add more inputs and outputs to it.
    @new_inputs and @new_outputs will be virtualchain-formatted:
    * new_inputs[i] will have {'outpoint': {'index':..., 'hash':...}, 'script':..., 'witness_script': ...}
    * new_outputs[i] will have {'script':..., 'value':... (in fundamental units, e.g. satoshis!)}
    """

    # recover tx
    tx = btc_tx_deserialize(partial_tx_hex)
    tx_inputs, tx_outputs = tx['ins'], tx['outs']
    locktime, version = tx['locktime'], tx['version']

    tx_inputs += new_inputs
    tx_outputs += new_outputs

    new_tx = {
        'ins': tx_inputs,
        'outs': tx_outputs,
        'locktime': locktime,
        'version': version,
    }

    new_unsigned_tx = btc_tx_serialize(new_tx)
    return new_unsigned_tx


def btc_tx_der_encode_integer(r):
    """
    Return a DER-encoded integer

    Based on code from python-ecdsa (https://github.com/warner/python-ecdsa)
    by Brian Warner.  Subject to the MIT license.
    """
    # borrowed from python-ecdsa
    if r < 0:
        raise ValueError('cannot support negative numbers')

    h = ("%x" % r).encode()
    if len(h) % 2:
        h = b("0") + h
    s = binascii.unhexlify(h)
    num = s[0] if isinstance(s[0], integer_types) else ord(s[0])
    if num <= 0x7f:
        return b("\x02") + int2byte(len(s)) + s
    else:
        # DER integers are two's complement, so if the first byte is
        # 0x80-0xff then we need an extra 0x00 byte to prevent it from
        # looking negative.
        return b("\x02") + int2byte(len(s)+1) + b("\x00") + s


def btc_tx_der_encode_length(l):
    """
    Return a DER-encoded length field

    Based on code from python-ecdsa (https://github.com/warner/python-ecdsa)
    by Brian Warner.  Subject to the MIT license.
    """
    if l < 0:
        raise ValueError("length cannot be negative")

    if l < 0x80:
        return int2byte(l)
    s = ("%x" % l).encode()
    if len(s) % 2:
        s = b("0") + s
    s = binascii.unhexlify(s)
    llen = len(s)
    return int2byte(0x80 | llen) + s


def btc_tx_der_encode_sequence(*encoded_pieces):
    """
    Return a DER-encoded sequence

    Based on code from python-ecdsa (https://github.com/warner/python-ecdsa)
    by Brian Warner.  Subject to the MIT license.
    """
    # borrowed from python-ecdsa
    total_len = sum([len(p) for p in encoded_pieces])
    return b('\x30') + btc_tx_der_encode_length(total_len) + b('').join(encoded_pieces)


def btc_tx_der_encode_signature(r, s):
    """
    Return a DER-encoded signature as a 2-item sequence

    Based on code from python-ecdsa (https://github.com/warner/python-ecdsa)
    by Brian Warner.  Subject to the MIT license.
    """
    # borrowed from python-ecdsa
    return btc_tx_der_encode_sequence(btc_tx_der_encode_integer(r), btc_tx_der_encode_integer(s))


def btc_tx_sighash( tx, idx, script, hashcode=SIGHASH_ALL):
    """
    Calculate the sighash of a non-segwit transaction.

    If it's SIGHASH_NONE, then digest the inputs but no outputs 
    If it's SIGHASH_SINGLE, then digest all inputs and all outputs up to i (excluding values and scripts), and fully digest the ith input and output
    If it's (something) | SIGHASH_ANYONECANPAY, then only digest the ith input.
    
    Return the double-sha256 digest of the relevant fields.

    THIS DOES NOT WORK WITH SEGWIT OUTPUTS

    Adapted from https://github.com/vbuterin/pybitcointools, by Vitalik Buterin
    """

    txobj = btc_tx_deserialize(tx)

    idx = int(idx)
    hashcode = int(hashcode)
    newtx = copy.deepcopy(txobj)

    # remove all scriptsigs in all inputs, except for the ith input's scriptsig.
    # the other inputs will be 'partially signed', except for SIGHASH_ANYONECANPAY mode.
    for i in xrange(0, len(newtx['ins'])):
        newtx['ins'][i]["script"] = ''
        if i == idx:
            if newtx['ins'][i].has_key('witness_script') and newtx['ins'][i]['witness_script']:
                raise ValueError('this method does not handle segwit inputs')

        if newtx['ins'][i].has_key('witness_script'):
            del newtx['ins'][i]['witness_script']

    newtx["ins"][idx]["script"] = script

    if (hashcode & 0x1f) == SIGHASH_NONE:
        # don't care about the outputs with this signature
        newtx["outs"] = []
        for inp in newtx['ins']:
            inp['sequence'] = 0

    elif (hashcode & 0x1f) == SIGHASH_SINGLE:
        # only signing for this input.
        # all outputs after this input will not be signed.
        # all outputs before this input will be partially signed (but not their values or scripts)
        if len(newtx['ins']) > len(newtx['outs']):
            raise ValueError('invalid hash code: {} inputs but {} outputs'.format(len(newtx['ins']), len(newtx['outs'])))

        newtx["outs"] = newtx["outs"][:len(newtx["ins"])]
        for out in newtx["outs"][:len(newtx["ins"]) - 1]:
            out['value'] = 2**64 - 1
            out['script'] = ""

    elif (hashcode & SIGHASH_ANYONECANPAY) != 0:
        # only going to sign this specific input, and nothing else
        newtx["ins"] = [newtx["ins"][idx]]
   
    signing_tx = btc_tx_serialize(newtx)
    sighash = btc_tx_get_hash( signing_tx, hashcode )
    return sighash


def btc_tx_sighash_segwit(tx, i, prevout_amount, prevout_script, hashcode=SIGHASH_ALL):
    """
    Calculate the sighash for a segwit transaction, according to bip143
    """
    txobj = btc_tx_deserialize(tx)

    hash_prevouts = encoding.encode(0, 256, 32)
    hash_sequence = encoding.encode(0, 256, 32)
    hash_outputs = encoding.encode(0, 256, 32)

    if (hashcode & SIGHASH_ANYONECANPAY) == 0:
        prevouts = ''
        for inp in txobj['ins']:
            prevouts += hashing.reverse_hash(inp['outpoint']['hash'])
            prevouts += encoding.encode(inp['outpoint']['index'], 256, 4)[::-1].encode('hex')

        hash_prevouts = hashing.bin_double_sha256(prevouts.decode('hex'))

        # print 'prevouts: {}'.format(prevouts)

    if (hashcode & SIGHASH_ANYONECANPAY) == 0 and (hashcode & 0x1f) != SIGHASH_SINGLE and (hashcode & 0x1f) != SIGHASH_NONE:
        sequences = ''
        for inp in txobj['ins']:
            sequences += encoding.encode(inp['sequence'], 256, 4)[::-1].encode('hex')

        hash_sequence = hashing.bin_double_sha256(sequences.decode('hex'))

        # print 'sequences: {}'.format(sequences)

    if (hashcode & 0x1f) != SIGHASH_SINGLE and (hashcode & 0x1f) != SIGHASH_NONE:
        outputs = ''
        for out in txobj['outs']:
            outputs += encoding.encode(out['value'], 256, 8)[::-1].encode('hex')
            outputs += make_var_string(out['script'])

        hash_outputs = hashing.bin_double_sha256(outputs.decode('hex'))

        # print 'outputs: {}'.format(outputs)

    elif (hashcode & 0x1f) == SIGHASH_SINGLE and i < len(txobj['outs']):
        outputs = ''
        outputs += encoding.encode(txobj['outs'][i]['value'], 256, 8)[::-1].encode('hex')
        outputs += make_var_string(txobj['outs'][i]['script'])

        hash_outputs = hashing.bin_double_sha256(outputs.decode('hex'))

        # print 'outputs: {}'.format(outputs)

    # print 'hash_prevouts: {}'.format(hash_prevouts.encode('hex'))
    # print 'hash_sequence: {}'.format(hash_sequence.encode('hex'))
    # print 'hash_outputs: {}'.format(hash_outputs.encode('hex'))
    # print 'prevout_script: {}'.format(prevout_script)
    # print 'prevout_amount: {}'.format(prevout_amount)

    sighash_preimage = ''
    sighash_preimage += encoding.encode(txobj['version'], 256, 4)[::-1].encode('hex')
    sighash_preimage += hash_prevouts.encode('hex')
    sighash_preimage += hash_sequence.encode('hex')

    # this input's prevout, script, amount, and sequence
    sighash_preimage += hashing.reverse_hash(txobj['ins'][i]['outpoint']['hash'])
    sighash_preimage += encoding.encode(txobj['ins'][i]['outpoint']['index'], 256, 4)[::-1].encode('hex')
    sighash_preimage += make_var_string(prevout_script)
    sighash_preimage += encoding.encode(prevout_amount, 256, 8)[::-1].encode('hex')
    sighash_preimage += encoding.encode(txobj['ins'][i]['sequence'], 256, 4)[::-1].encode('hex')

    sighash_preimage += hash_outputs.encode('hex')
    sighash_preimage += encoding.encode(txobj['locktime'], 256, 4)[::-1].encode('hex')
    sighash_preimage += encoding.encode(hashcode, 256, 4)[::-1].encode('hex')

    sighash = hashing.bin_double_sha256(sighash_preimage.decode('hex')).encode('hex')

    # print 'sighash_preimage: {}'.format(sighash_preimage)
    # print 'sighash: {}'.format(sighash)

    return sighash
    

def btc_tx_make_input_signature(tx, idx, prevout_script, privkey_str, hashcode):
    """
    Sign a single input of a transaction, given the serialized tx,
    the input index, the output's scriptPubkey, and the hashcode.

    tx must be a hex-encoded string
    privkey_str must be a hex-encoded private key

    Return the hex signature.

    THIS DOES NOT WORK WITH SEGWIT TRANSACTIONS
    """
    if btc_tx_is_segwit(tx):
        raise ValueError('tried to use the standard sighash to sign a segwit transaction')

    pk = ecdsalib.ecdsa_private_key(str(privkey_str))
    priv = pk.to_hex()

    # get the parts of the tx we actually need to sign
    sighash = btc_tx_sighash(tx, idx, prevout_script, hashcode)
    # print 'non-segwit sighash: {}'.format(sighash)

    # sign using uncompressed private key
    pk_uncompressed_hex, pubk_uncompressed_hex = ecdsalib.get_uncompressed_private_and_public_keys(priv)
    sigb64 = ecdsalib.sign_digest( sighash, priv )

    # sanity check 
    # assert ecdsalib.verify_digest( txhash, pubk_uncompressed_hex, sigb64 )

    sig_r, sig_s = ecdsalib.decode_signature(sigb64)
    sig_bin = btc_tx_der_encode_signature(sig_r, sig_s)
    sig = sig_bin.encode('hex') + encoding.encode(hashcode, 16, 2)

    return sig


def btc_tx_make_input_signature_segwit(tx, idx, prevout_amount, prevout_script, privkey_str, hashcode):
    """
    Sign a single input of a transaction, given the serialized tx,
    the input index, the output's scriptPubkey, and the hashcode.

    tx must be a hex-encoded string
    privkey_str must be a hex-encoded private key

    Return the hex signature.
    """
    # always compressed
    if len(privkey_str) == 64:
        privkey_str += '01'

    pk = ecdsalib.ecdsa_private_key(str(privkey_str))
    pubk = pk.public_key()
    
    priv = pk.to_hex()

    # must always be compressed
    pub = keylib.key_formatting.compress(pubk.to_hex())
    sighash = btc_tx_sighash_segwit(tx, idx, prevout_amount, prevout_script, hashcode=hashcode)
    
    # sign using uncompressed private key
    # pk_uncompressed_hex, pubk_uncompressed_hex = ecdsalib.get_uncompressed_private_and_public_keys(priv)
    sigb64 = ecdsalib.sign_digest( sighash, priv )

    # sanity check 
    # assert ecdsalib.verify_digest( txhash, pubk_uncompressed_hex, sigb64 )

    sig_r, sig_s = ecdsalib.decode_signature(sigb64)
    sig_bin = btc_tx_der_encode_signature(sig_r, sig_s)
    sig = sig_bin.encode('hex') + encoding.encode(hashcode, 16, 2)

    # print 'segwit signature: {}'.format(sig)
    return sig


def btc_tx_sign_multisig(tx, idx, redeem_script, private_keys, hashcode=SIGHASH_ALL):
    """
    Sign a p2sh multisig input (not segwit!).

    @tx must be a hex-encoded tx

    Return the signed transaction
    """
    
    from .multisig import parse_multisig_redeemscript

    # sign in the right order.  map all possible public keys to their private key
    txobj = btc_tx_deserialize(str(tx))

    privs = {}
    for pk in private_keys:
        pubk = ecdsalib.ecdsa_private_key(pk).public_key().to_hex()

        compressed_pubkey = keylib.key_formatting.compress(pubk)
        uncompressed_pubkey = keylib.key_formatting.decompress(pubk)

        privs[compressed_pubkey] = pk
        privs[uncompressed_pubkey] = pk

    m, public_keys = parse_multisig_redeemscript(str(redeem_script))

    used_keys, sigs = [], []
    for public_key in public_keys:
        if public_key not in privs:
            continue

        if len(used_keys) == m:
            break

        if public_key in used_keys:
            raise ValueError('Tried to reuse key in redeem script: {}'.format(public_key))

        pk_str = privs[public_key]
        used_keys.append(public_key)

        sig = btc_tx_make_input_signature(tx, idx, redeem_script, pk_str, hashcode)
        sigs.append(sig)

    if len(used_keys) != m:
        raise ValueError('Missing private keys (used {}, required {})'.format(len(used_keys), m))

    txobj["ins"][idx]["script"] = btc_script_serialize([None] + sigs + [redeem_script])
    return btc_tx_serialize(txobj)


def btc_tx_sign_multisig_segwit(tx, idx, prevout_amount, witness_script, private_keys, hashcode=SIGHASH_ALL, hashcodes=None, native=False):
    """
    Sign a native p2wsh or p2sh-p2wsh multisig input.

    @tx must be a hex-encoded tx

    Return the signed transaction
    """

    from .multisig import parse_multisig_redeemscript

    if hashcodes is None:
        hashcodes = [hashcode] * len(private_keys)

    txobj = btc_tx_deserialize(str(tx))
    privs = {}
    for pk in private_keys:
        pubk = ecdsalib.ecdsa_private_key(pk).public_key().to_hex()

        compressed_pubkey = keylib.key_formatting.compress(pubk)
        privs[compressed_pubkey] = pk

    m, public_keys = parse_multisig_redeemscript(witness_script)

    used_keys, sigs = [], []
    for i, public_key in enumerate(public_keys):
        if public_key not in privs:
            continue

        if len(used_keys) == m:
            break

        if public_key in used_keys:
            raise ValueError('Tried to reuse key in witness script: {}'.format(public_key))

        pk_str = privs[public_key]
        used_keys.append(public_key)

        sig = btc_tx_make_input_signature_segwit(tx, idx, prevout_amount, witness_script, pk_str, hashcodes[i])
        sigs.append(sig)

        # print ''

    if len(used_keys) != m:
        raise ValueError('Missing private keys (used {}, required {})'.format(len(used_keys), m))
   
    if native: 
        # native p2wsh
        txobj['ins'][idx]['witness_script'] = btc_witness_script_serialize([None] + sigs + [witness_script]) 

        # print 'segwit multisig: native p2wsh: witness script {}'.format(txobj['ins'][idx]['witness_script'])

    else:
        # p2sh-p2wsh
        redeem_script = btc_make_p2sh_p2wsh_redeem_script(witness_script)
        txobj['ins'][idx]['script'] = redeem_script
        txobj['ins'][idx]['witness_script'] = btc_witness_script_serialize([None] + sigs + [witness_script]) 
        
        # print 'segwit multisig: p2sh p2wsh: witness script {}'.format(txobj['ins'][idx]['witness_script'])
        # print 'segwit multisig: p2sh p2wsh: redeem script {}'.format(txobj['ins'][idx]['script'])
    
    return btc_tx_serialize(txobj)


def btc_tx_sign(tx_hex, idx, prevout_script, prevout_amount, private_key_info, scriptsig_type, hashcode=SIGHASH_ALL, hashcodes=None, redeem_script=None, witness_script=None):
    """
    Insert a scriptsig for an input that will later be spent by a p2pkh, p2pk, or p2sh scriptPubkey.

    @private_key_info is either a single private key, or a dict with 'redeem_script' and 'private_keys' defined.

    @redeem_script, if given, must NOT start with the varint encoding its length.
    However, it must otherwise be a hex string

    Return the transaction with the @idx'th scriptSig filled in.
    """
    new_tx = None
    
    # print 'sign input {} as {}'.format(idx, scriptsig_type)

    if scriptsig_type in ['p2pkh', 'p2pk']:
        if not btc_is_singlesig(private_key_info):
            raise ValueError('Need only one private key for {}'.format(scriptsig_type))

        pk = ecdsalib.ecdsa_private_key(str(private_key_info))
        pubk = pk.public_key()
        pub = pubk.to_hex()

        sig = btc_tx_make_input_signature(tx_hex, idx, prevout_script, private_key_info, hashcode)

        # print 'non-segwit sig: {}'.format(sig)
        # print 'non-segwit pubk: {}'.format(pub)

        # NOTE: sig and pub need to be hex-encoded
        txobj = btc_tx_deserialize(str(tx_hex))

        if scriptsig_type == 'p2pkh':
            # scriptSig + scriptPubkey is <signature> <pubkey> OP_DUP OP_HASH160 <pubkeyhash> OP_EQUALVERIFY OP_CHECKSIG
            txobj['ins'][idx]['script'] = btc_script_serialize([sig, pub])

        else:
            # p2pk
            # scriptSig + scriptPubkey is <signature> <pubkey> OP_CHECKSIG
            txobj['ins'][idx]['script'] = btc_script_serialize([sig])

        new_tx = btc_tx_serialize(txobj)

    elif scriptsig_type == 'p2wpkh' or scriptsig_type == 'p2sh-p2wpkh':
        # must be a segwit singlesig bundle
        if not btc_is_singlesig_segwit(private_key_info):
            raise ValueError('Keys are not for p2wpkh or p2sh-p2wpkh')

        privkey_str = str(btc_get_singlesig_privkey(private_key_info))
        pk = ecdsalib.ecdsa_private_key(privkey_str)
        pubk = pk.public_key()
        pub = keylib.key_formatting.compress(pubk.to_hex())

        # special bip141 rule: this is always 0x1976a914{20-byte-pubkey-hash}88ac
        prevout_script_sighash = '76a914' + hashing.bin_hash160(pub.decode('hex')).encode('hex') + '88ac'

        sig = btc_tx_make_input_signature_segwit(tx_hex, idx, prevout_amount, prevout_script_sighash, privkey_str, hashcode)

        txobj = btc_tx_deserialize(str(tx_hex))
        txobj['ins'][idx]['witness_script'] = btc_witness_script_serialize([sig, pub])

        if scriptsig_type == 'p2wpkh':
            # native
            # NOTE: sig and pub need to be hex-encoded
            txobj['ins'][idx]['script'] = ''

            if redeem_script:
                # goes in scriptSig 
                txobj['ins'][idx]['script'] = btc_script_serialize([redeem_script])

        else:
            # p2sh-p2wpkh
            redeem_script = btc_make_p2sh_p2wpkh_redeem_script(pub)
            txobj['ins'][idx]['script'] = redeem_script

            # print 'scriptsig {} from {} is {}'.format(pub, privkey_str, txobj['ins'][idx]['script'])

        new_tx = btc_tx_serialize(txobj)

        # print 'scriptsig type: {}'.format(scriptsig_type)
        # print 'segwit scriptsig: {}'.format(txobj['ins'][idx]['script'])
        # print 'segwit witness script: {}'.format(txobj['ins'][idx]['witness_script'])

    elif scriptsig_type == 'p2wsh' or scriptsig_type == 'p2sh-p2wsh':
        # only support p2wsh for multisig purposes at this time
        if not btc_is_multisig_segwit(private_key_info):
            raise ValueError('p2wsh requires a multisig key bundle')

        native = None
        if scriptsig_type == 'p2wsh':
            native = True
        else:
            native = False

        new_tx = btc_tx_sign_multisig_segwit(tx_hex, idx, prevout_amount, private_key_info['redeem_script'], private_key_info['private_keys'], hashcode=hashcode, hashcodes=hashcodes, native=native)
       
        txobj = btc_tx_deserialize(new_tx)

        # print 'segwit scriptsig: {}'.format(txobj['ins'][idx]['script'])
        # print 'segwit witness script: {}'.format(txobj['ins'][idx]['witness_script'])

    elif scriptsig_type == 'p2sh':
    
        if not redeem_script:
            # p2sh multisig
            if not btc_is_multisig(private_key_info):
                raise ValueError('No redeem script given, and not a multisig key bundle')

            new_tx = btc_tx_sign_multisig(tx_hex, idx, private_key_info['redeem_script'], private_key_info['private_keys'], hashcode=hashcode)

        else:
            # NOTE: sig and pub need to be hex-encoded
            txobj = btc_tx_deserialize(str(tx_hex))

            # scriptSig + scriptPubkey is <redeem script> OP_HASH160 <script hash> OP_EQUAL
            txobj['ins'][idx]['script'] = btc_script_serialize([redeem_script]) 
            new_tx = btc_tx_serialize(txobj)

    else:
        raise ValueError("Unknown script type {}".format(scriptsig_type))

    return new_tx


def btc_script_classify(scriptpubkey, private_key_info=None):
    """
    Classify a scriptpubkey, optionally also using the private key info that will generate the corresponding scriptsig/witness
    Return None if not known (nonstandard)
    """
    if scriptpubkey.startswith("76a914") and scriptpubkey.endswith("88ac") and len(scriptpubkey) == 50:
        return 'p2pkh'

    elif scriptpubkey.startswith("a914") and scriptpubkey.endswith("87") and len(scriptpubkey) == 46:
        # maybe p2sh-p2wpkh or p2sh-p2wsh?
        if private_key_info:
            if btc_is_singlesig_segwit(private_key_info):
                return 'p2sh-p2wpkh'
            elif btc_is_multisig_segwit(private_key_info):
                return 'p2sh-p2wsh'

        return 'p2sh'

    elif scriptpubkey.startswith('0014') and len(scriptpubkey) == 44:
        return 'p2wpkh'

    elif scriptpubkey.startswith('0020') and len(scriptpubkey) == 68:
        return 'p2wsh'

    script_tokens = btc_script_deserialize(scriptpubkey)
    if len(script_tokens) == 0:
        return None

    if script_tokens[0] == OPCODE_VALUES['OP_RETURN']:
        return "nulldata"

    elif script_tokens[-1] == OPCODE_VALUES['OP_CHECKMULTISIG']:
        return "multisig"

    elif len(script_tokens) == 2 and script_tokens[-1] == OPCODE_VALUES["OP_CHECKSIG"]:
        return "p2pk"

    return None


def btc_privkey_scriptsig_classify(private_key_info):
    """
    What kind of scriptsig can this private key make?
    """
    if btc_is_singlesig(private_key_info):
        return 'p2pkh'

    if btc_is_multisig(private_key_info):
        return 'p2sh'

    if btc_is_singlesig_segwit(private_key_info):
        return 'p2sh-p2wpkh'

    if btc_is_multisig_segwit(private_key_info):
        return 'p2sh-p2wsh'

    return None


def btc_tx_sign_input(tx, idx, prevout_script, prevout_amount, private_key_info, hashcode=SIGHASH_ALL, hashcodes=None, segwit=None, scriptsig_type=None, redeem_script=None, witness_script=None, **blockchain_opts):
    """
    Sign a particular input in the given transaction.
    @private_key_info can either be a private key, or it can be a dict with 'redeem_script' and 'private_keys' defined

    Returns the tx with the signed input
    """
    if segwit is None:
        segwit = get_features('segwit')
    
    if scriptsig_type is None:
        scriptsig_type = btc_privkey_scriptsig_classify(private_key_info)
    
    if scriptsig_type in ['p2wpkh', 'p2wsh', 'p2sh-p2wpkh', 'p2sh-p2wsh'] and not segwit:
        raise ValueError("Segwit is not enabled, but {} is a segwit scriptsig type".format(prevout_script))

    return btc_tx_sign(tx, idx, prevout_script, prevout_amount, private_key_info, scriptsig_type, hashcode=hashcode, hashcodes=hashcodes, redeem_script=redeem_script, witness_script=witness_script)


def btc_tx_sign_all_unsigned_inputs(private_key_info, prev_outputs, unsigned_tx_hex, scriptsig_type=None, segwit=None, **blockchain_opts):
    """
    Sign all unsigned inputs with a given key.
    Use the given outputs to fund them.

    @private_key_info: either a hex private key, or a dict with 'private_keys' and 'redeem_script'
    defined as keys.
    @prev_outputs: a list of {'out_script': xxx, 'value': xxx} that are in 1-to-1 correspondence with the unsigned inputs in the tx ('value' is in satoshis)
    @unsigned_hex_tx: hex transaction with unsigned inputs

    Returns: signed hex transaction
    """
    if segwit is None:
        segwit = get_features('segwit')
    
    txobj = btc_tx_deserialize(unsigned_tx_hex)
    inputs = txobj['ins']
    
    if scriptsig_type is None:
        scriptsig_type = btc_privkey_scriptsig_classify(private_key_info)

    tx_hex = unsigned_tx_hex
    prevout_index = 0
    
    # import json
    # print ''
    # print 'transaction:\n{}'.format(json.dumps(btc_tx_deserialize(unsigned_tx_hex), indent=4, sort_keys=True))
    # print 'prevouts:\n{}'.format(json.dumps(prev_outputs, indent=4, sort_keys=True))
    # print ''

    for i, inp in enumerate(inputs):
        do_witness_script = segwit
        if inp.has_key('witness_script'):
            do_witness_script = True

        elif segwit:
            # all inputs must receive a witness script, even if it's empty 
            inp['witness_script'] = ''

        if (inp['script'] and len(inp['script']) > 0) or (inp.has_key('witness_script') and len(inp['witness_script']) > 0):
            continue

        if prevout_index >= len(prev_outputs):
            raise ValueError("Not enough prev_outputs ({} given, {} more prev-outputs needed)".format(len(prev_outputs), len(inputs) - prevout_index))

        # tx with index i signed with privkey
        tx_hex = btc_tx_sign_input(str(unsigned_tx_hex), i, prev_outputs[prevout_index]['out_script'], prev_outputs[prevout_index]['value'], private_key_info, segwit=do_witness_script, scriptsig_type=scriptsig_type)
        unsigned_tx_hex = tx_hex
        prevout_index += 1

    return tx_hex


def block_header_serialize( inp ):
    """
    Given block header information, serialize it and return the hex hash.

    inp has:
    * version (int)
    * prevhash (str)
    * merkle_root (str)
    * timestamp (int)
    * bits (int)
    * nonce (int)

    Based on code from pybitcointools (https://github.com/vbuterin/pybitcointools)
    by Vitalik Buterin
    """
    
    # concatenate to form header
    o = encoding.encode(inp['version'], 256, 4)[::-1] + \
        inp['prevhash'].decode('hex')[::-1] + \
        inp['merkle_root'].decode('hex')[::-1] + \
        encoding.encode(inp['timestamp'], 256, 4)[::-1] + \
        encoding.encode(inp['bits'], 256, 4)[::-1] + \
        encoding.encode(inp['nonce'], 256, 4)[::-1]

    # get (reversed) hash
    h = hashing.bin_sha256(hashing.bin_sha256(o))[::-1].encode('hex')
    assert h == inp['hash'], (hashing.bin_sha256(o).encode('hex'), inp['hash'])

    return o.encode('hex')


def block_header_to_hex( block_data, prev_hash ):
    """
    Calculate the hex form of a block's header, given its getblock information from bitcoind.
    """
    header_info = {
       "version": block_data['version'],
       "prevhash": prev_hash,
       "merkle_root": block_data['merkleroot'],
       "timestamp": block_data['time'],
       "bits": int(block_data['bits'], 16),
       "nonce": block_data['nonce'],
       "hash": block_data['hash']
    }

    return block_header_serialize(header_info)


def block_header_verify( block_data, prev_hash, block_hash ):
    """
    Verify whether or not bitcoind's block header matches the hash we expect.
    """
    serialized_header = block_header_to_hex( block_data, prev_hash )
    candidate_hash_bin_reversed = hashing.bin_double_sha256(binascii.unhexlify(serialized_header))
    candidate_hash = binascii.hexlify( candidate_hash_bin_reversed[::-1] )

    return block_hash == candidate_hash


def block_verify( block_data ):
    """
    Given block data (a dict with 'merkleroot' hex string and 'tx' list of hex strings--i.e.
    a block compatible with bitcoind's getblock JSON RPC method), verify that the
    transactions are consistent.

    Return True on success
    Return False if not.
    """
    
    # verify block data txs 
    m = merkle.MerkleTree( block_data['tx'] )
    root_hash = str(m.root())

    return root_hash == str(block_data['merkleroot'])


def btc_tx_output_parse_script( scriptpubkey ):
    """
    Given the hex representation of a script,
    turn it into a nice, easy-to-read dict.
    The dict will have:
    * asm: the disassembled script as a string
    * hex: the raw hex (given as an argument)
    * type: the type of script

    Optionally, it will have:
    * addresses: a list of addresses the script represents (if applicable)
    * reqSigs: the number of required signatures (if applicable)
    """
    
    script_type = None
    reqSigs = None
    addresses = []
   
    script_type = btc_script_classify(scriptpubkey)
    script_tokens = btc_script_deserialize(scriptpubkey)

    if script_type in ['p2pkh']:
        script_type = "pubkeyhash"
        reqSigs = 1
        addr = btc_script_hex_to_address(scriptpubkey)
        if not addr:
            raise ValueError("Failed to parse scriptpubkey address")

        addresses = [addr]

    elif script_type in ['p2sh', 'p2sh-p2wpkh', 'p2sh-p2wsh']:
        script_type = "scripthash"
        reqSigs = 1
        addr = btc_script_hex_to_address(scriptpubkey)
        if not addr:
            raise ValueError("Failed to parse scriptpubkey address")

        addresses = [addr]

    elif script_type == 'p2pk':
        script_type = "pubkey"
        reqSigs = 1

    elif script_type is None:
        script_type = "nonstandard"

    ret = {
        "asm": btc_tx_script_to_asm(scriptpubkey),
        "hex": scriptpubkey,
        "type": script_type
    }

    if addresses is not None:
        ret['addresses'] = addresses

    if reqSigs is not None:
        ret['reqSigs'] = reqSigs

    # print 'parse script {}: {}'.format(scriptpubkey, ret)

    return ret