示例#1
0
def transaction(tx_info,
                encoding,
                exact_fee=None,
                fee_provided=0,
                unittest=False,
                public_key_hex=None,
                allow_unconfirmed_inputs=False):

    (source, destination_outputs, data) = tx_info

    if exact_fee and not isinstance(exact_fee, int):
        raise exceptions.TransactionError('Exact fees must be in satoshis.')
    if not isinstance(fee_provided, int):
        raise exceptions.TransactionError('Fee provided must be in satoshis.')
    if encoding not in ('pubkeyhash', 'multisig', 'opreturn'):
        raise exceptions.TransactionError('Unknown encoding‐scheme.')

    # If public key is necessary for construction of (unsigned) transaction,
    # either use the public key provided, or derive it from a private key
    # retrieved from wallet.
    public_key = None
    if encoding in ('multisig', 'pubkeyhash'):
        # get the address type
        pdb.set_trace()
        p2sh = get_address_type(source)
        if p2sh:
            temp = rpc('validateaddress', [source])
            redeemscript = temp['hex']
        else:
            # If no public key was provided, derive from private key.
            if not public_key_hex:
                # Get private key.
                if unittest:
                    private_key_wif = 'cPdUqd5EbBWsjcG9xiL1hz8bEyGFiz4SW99maU9JgpL9TEcxUf3j'
                else:
                    private_key_wif = rpc('dumpprivkey', [source])

                # Derive public key.
                public_key_hex = private_key_to_public_key(private_key_wif)

            pubkeypair = bitcoin_utils.parse_as_public_pair(public_key_hex)
            if not pubkeypair:
                raise exceptions.InputError('Invalid private key.')
            public_key = public_pair_to_sec(pubkeypair, compressed=True)

    # Protocol change.
    if encoding == 'pubkeyhash' and get_block_count(
    ) < 293000 and not config.TESTNET:
        raise exceptions.TransactionError(
            'pubkeyhash encoding unsupported before block 293000')

    if config.PREFIX == config.UNITTEST_PREFIX: unittest = True

    # Validate source and all destination addresses.
    destinations = [address for address, value in destination_outputs]
    for address in destinations + [source]:
        if address:
            try:
                base58_decode(address, config.ADDRESSVERSION,
                              config.ADDRESSVERSION_MULTISIG)
            except Exception:  # TODO
                raise exceptions.AddressError('Invalid Bitcoin address:',
                                              address)

    # Check that the source is in wallet.
    if not unittest and encoding in ('multisig') and not public_key:
        if not rpc('validateaddress', [source])['ismine']:
            raise exceptions.AddressError('Not one of your Bitcoin addresses:',
                                          source)

    # Check that the destination output isn't a dust output.
    # Set null values to dust size.
    new_destination_outputs = []
    for address, value in destination_outputs:
        if encoding == 'multisig':
            if value == None: value = config.MULTISIG_DUST_SIZE
            if not value >= config.MULTISIG_DUST_SIZE:
                raise exceptions.TransactionError(
                    'Destination output is below the dust target value.')
        else:
            if value == None: value = config.REGULAR_DUST_SIZE
            if not value >= config.REGULAR_DUST_SIZE:
                raise exceptions.TransactionError(
                    'Destination output is below the dust target value.')
        new_destination_outputs.append((address, value))
    destination_outputs = new_destination_outputs

    # Divide data into chunks.
    if data:

        def chunks(l, n):
            """ Yield successive n‐sized chunks from l.
            """
            for i in range(0, len(l), n):
                yield l[i:i + n]

        if encoding == 'pubkeyhash':
            data_array = list(chunks(data + config.PREFIX,
                                     20 - 1))  # Prefix is also a suffix here.
        elif encoding == 'multisig':
            data_array = list(chunks(data, 33 - 1))
        elif encoding == 'opreturn':
            data_array = list(chunks(data, 80))
            assert len(
                data_array
            ) == 1  # Only one OP_RETURN output currently supported (messages should all be shorter than 80 bytes, at the moment).
    else:
        data_array = []

    # Calculate total BTC to be sent.
    btc_out = 0
    if encoding == 'multisig': data_value = config.MULTISIG_DUST_SIZE
    elif encoding == 'opreturn': data_value = config.OP_RETURN_VALUE
    else: data_value = config.REGULAR_DUST_SIZE  # Pay‐to‐PubKeyHash
    btc_out = sum([data_value for data_chunk in data_array])
    btc_out += sum([value for address, value in destination_outputs])

    # Get size of outputs.
    if encoding == 'multisig': data_output_size = 81  # 71 for the data
    elif encoding == 'opreturn': data_output_size = 90  # 80 for the data
    else: data_output_size = 25 + 9  # Pay‐to‐PubKeyHash (25 for the data?)
    outputs_size = ((25 + 9) * len(destination_outputs)) + (len(data_array) *
                                                            data_output_size)

    # Get inputs.
    unspent = get_unspent_txouts(source, normalize=True, unittest=unittest)
    unspent = sort_unspent_txouts(unspent, allow_unconfirmed_inputs)

    inputs, btc_in = [], 0
    change_quantity = 0
    sufficient_funds = False
    final_fee = config.FEE_PER_KB
    for coin in unspent:
        inputs.append(coin)
        btc_in += round(coin['amount'] * config.UNIT)

        # If exact fee is specified, use that. Otherwise, calculate size of tx and base fee on that (plus provide a minimum fee for selling BTC).
        if exact_fee:
            final_fee = exact_fee
        else:
            size = 181 * len(inputs) + outputs_size + 10
            necessary_fee = (int(size / 10000) + 1) * config.FEE_PER_KB
            final_fee = max(fee_provided, necessary_fee)
            assert final_fee >= 1 * config.FEE_PER_KB

        # Check if good.
        change_quantity = btc_in - (btc_out + final_fee)
        if change_quantity == 0 or change_quantity >= config.REGULAR_DUST_SIZE:  # If change is necessary, must not be a dust output.
            sufficient_funds = True
            break
    if not sufficient_funds:
        # Approximate needed change, fee by with most recently calculated quantities.
        total_btc_out = btc_out + max(change_quantity, 0) + final_fee
        raise exceptions.BalanceError(
            'Insufficient bitcoins at address {}. (Need approximately {} BTC.)'
            .format(source, total_btc_out / config.UNIT))

    # Construct outputs.
    if data: data_output = (data_array, data_value)
    else: data_output = None
    if change_quantity: change_output = (source, change_quantity)
    else: change_output = None

    # Serialise inputs and outputs.
    transaction = serialise(encoding,
                            inputs,
                            destination_outputs,
                            data_output,
                            change_output,
                            source=source,
                            public_key=public_key)
    unsigned_tx_hex = binascii.hexlify(transaction).decode('utf-8')
    return unsigned_tx_hex
示例#2
0
def transaction (tx_info, encoding, unittest=False, public_key_hex=None, allow_unconfirmed_inputs=False):

    if len(tx_info) == 3:
        source, destination_outputs, data = tx_info
        fee_provided = 0
    else:
        source, destination_outputs, data, fee_provided = tx_info
    if not isinstance(fee_provided, int):
        raise exceptions.TransactionError('Fee provided must be in satoshis')
    if encoding not in ('pubkeyhash', 'multisig', 'opreturn'):
        raise exceptions.TransactionError('Unknown encoding‐scheme.')

    # If public key is necessary for construction of (unsigned) transaction,
    # either use the public key provided, or derive it from a private key
    # retrieved from wallet.
    public_key = None
    if encoding in ('multisig', 'pubkeyhash'):
        # If no public key was provided, derive from private key.
        if not public_key_hex:
            # Get private key.
            if unittest:
                private_key_wif = 'cPdUqd5EbBWsjcG9xiL1hz8bEyGFiz4SW99maU9JgpL9TEcxUf3j'
            else:
                private_key_wif = rpc('dumpprivkey', [source])

            # Derive public key.
            public_key_hex = private_key_to_public_key(private_key_wif)
            
        pubkeypair = bitcoin_utils.parse_as_public_pair(public_key_hex)
        public_key = public_pair_to_sec(pubkeypair, compressed=True)

    # Protocol change.
    if encoding == 'pubkeyhash' and get_block_count() < 293000 and not config.TESTNET:
        raise exceptions.TransactionError('pubkeyhash encoding unsupported before block 293000')
    
    if config.PREFIX == config.UNITTEST_PREFIX: unittest = True

    # Validate source and all destination addresses.
    destinations = [address for address, value in destination_outputs]
    for address in destinations + [source]:
        if address:
            try:
                base58_decode(address, config.ADDRESSVERSION)
            except Exception:   # TODO
                raise exceptions.AddressError('Invalid Bitcoin address:', address)

    # Check that the source is in wallet.
    if not unittest and encoding in ('multisig') and not public_key:
        if not rpc('validateaddress', [source])['ismine']:
            raise exceptions.AddressError('Not one of your Bitcoin addresses:', source)

    # Check that the destination output isn't a dust output.
    # Set null values to dust size.
    new_destination_outputs = []
    for address, value in destination_outputs:
        if encoding == 'multisig':
            if value == None: value = config.MULTISIG_DUST_SIZE
            if not value >= config.MULTISIG_DUST_SIZE:
                raise exceptions.TransactionError('Destination output is below the dust target value.')
        else:
            if value == None: value = config.REGULAR_DUST_SIZE
            if not value >= config.REGULAR_DUST_SIZE:
                raise exceptions.TransactionError('Destination output is below the dust target value.')
        new_destination_outputs.append((address, value))
    destination_outputs = new_destination_outputs

    # Divide data into chunks.
    if data:
        def chunks(l, n):
            """ Yield successive n‐sized chunks from l.
            """
            for i in range(0, len(l), n): yield l[i:i+n]
        if encoding == 'pubkeyhash':
            data_array = list(chunks(data + config.PREFIX, 20 - 1)) # Prefix is also a suffix here.
        elif encoding == 'multisig':
            data_array = list(chunks(data, 33 - 1))
        elif encoding == 'opreturn':
            data_array = list(chunks(data, 80))
            assert len(data_array) == 1 # Only one OP_RETURN output currently supported (messages should all be shorter than 80 bytes, at the moment).
    else:
        data_array = []

    # Calculate total BTC to be sent.
    btc_out = 0
    if encoding == 'multisig': data_value = config.MULTISIG_DUST_SIZE
    elif encoding == 'opreturn': data_value = config.OP_RETURN_VALUE
    else: data_value = config.REGULAR_DUST_SIZE # Pay‐to‐PubKeyHash
    btc_out = sum([data_value for data_chunk in data_array])
    btc_out += sum([value for address, value in destination_outputs])

    # Get size of outputs.
    if encoding == 'multisig': data_output_size = 81        # 71 for the data
    elif encoding == 'opreturn': data_output_size = 90      # 80 for the data
    else: data_output_size = 25 + 9                         # Pay‐to‐PubKeyHash (25 for the data?)
    outputs_size = ((25 + 9) * len(destination_outputs)) + (len(data_array) * data_output_size)

    # Get inputs.
    unspent = get_unspent_txouts(source, normalize=True, unittest=unittest, allow_unconfirmed_inputs=allow_unconfirmed_inputs)

    inputs, btc_in = [], 0
    change_quantity = 0
    sufficient_funds = False
    final_fee = config.FEE_PER_KB
    for coin in sorted(unspent,key=lambda x:input_value_weight(x['amount'])):
        inputs.append(coin)
        btc_in += round(coin['amount'] * config.UNIT)
        size = 181 * len(inputs) + outputs_size + 10
        necessary_fee = (int(size / 10000) + 1) * config.FEE_PER_KB
        final_fee = max(fee_provided, necessary_fee)
        change_quantity = btc_in - (btc_out + final_fee)
        if change_quantity == 0 or change_quantity >= config.REGULAR_DUST_SIZE: # If change is necessary, must not be a dust output.
            sufficient_funds = True
            break
    if not sufficient_funds:
        # Approximate needed change, fee by with most recently calculated quantities.
        total_btc_out = btc_out + max(change_quantity, 0) + final_fee
        raise exceptions.BalanceError('Insufficient bitcoins at address {}. (Need approximately {} BTC.)'.format(source, total_btc_out / config.UNIT))

    # Construct outputs.
    if data: data_output = (data_array, data_value)
    else: data_output = None
    if change_quantity: change_output = (source, change_quantity)
    else: change_output = None

    # Serialise inputs and outputs.
    transaction = serialise(encoding, inputs, destination_outputs, data_output, change_output, source=source, public_key=public_key)
    unsigned_tx_hex = binascii.hexlify(transaction).decode('utf-8')
    return unsigned_tx_hex
示例#3
0
def serialise(inputs,
              destination_output=None,
              data_output=None,
              change_output=None,
              source=None,
              multisig=False):
    s = (1).to_bytes(4, byteorder='little')  # Version

    # Number of inputs.
    s += var_int(int(len(inputs)))

    # List of Inputs.
    for i in range(len(inputs)):
        txin = inputs[i]
        s += binascii.unhexlify(bytes(txin['txid'],
                                      'utf-8'))[::-1]  # TxOutHash
        s += txin['vout'].to_bytes(4, byteorder='little')  # TxOutIndex

        script = str.encode(txin['scriptPubKey'])
        s += var_int(int(len(script)))  # Script length
        s += script  # Script
        s += b'\xff' * 4  # Sequence

    # Number of outputs.
    n = 0
    if destination_output: n += 1
    if data_output:
        data_array, value = data_output
        for data_chunk in data_array:
            n += 1
    else:
        data_array = []
    if change_output: n += 1
    s += var_int(n)

    # Destination output.
    if destination_output:
        address, value = destination_output
        pubkeyhash = base58_decode(address, config.ADDRESSVERSION)
        s += value.to_bytes(8, byteorder='little')  # Value
        script = OP_DUP  # OP_DUP
        script += OP_HASH160  # OP_HASH160
        script += op_push(20)  # Push 0x14 bytes
        script += pubkeyhash  # pubKeyHash
        script += OP_EQUALVERIFY  # OP_EQUALVERIFY
        script += OP_CHECKSIG  # OP_CHECKSIG
        s += var_int(int(len(script)))  # Script length
        s += script

    # Data output.
    for data_chunk in data_array:
        data_array, value = data_output  # DUPE
        s += value.to_bytes(8, byteorder='little')  # Value

        if multisig:
            # Get source public key (either provided as a string or derived from a private key in the wallet).
            if isinstance(multisig, str):
                pubkeypair = bitcoin_utils.parse_as_public_pair(multisig)
                source_pubkey = public_pair_to_sec(pubkeypair, compressed=True)
            else:
                if config.PREFIX == config.UNITTEST_PREFIX:
                    private_key_wif = 'cPdUqd5EbBWsjcG9xiL1hz8bEyGFiz4SW99maU9JgpL9TEcxUf3j'
                else:
                    private_key_wif = rpc('dumpprivkey', [source])
                if private_key_wif[0] == 'c': testnet = True
                else: testnet = False
                secret_exponent, compressed = wif_to_tuple_of_secret_exponent_compressed(
                    private_key_wif, is_test=testnet)
                public_pair = public_pair_for_secret_exponent(
                    generator_secp256k1, secret_exponent)
                source_pubkey = public_pair_to_sec(public_pair,
                                                   compressed=compressed)

            # Get data (fake) public key.
            pad_length = 33 - 1 - len(data_chunk)
            assert pad_length >= 0
            data_pubkey = bytes([len(data_chunk)
                                 ]) + data_chunk + (pad_length * b'\x00')

            script = OP_1  # OP_1
            script += op_push(
                len(source_pubkey))  # Push bytes of source public key
            script += source_pubkey  # Source public key
            script += op_push(
                len(data_pubkey))  # Push bytes of data chunk (fake) public key
            script += data_pubkey  # Data chunk (fake) public key
            script += OP_2  # OP_2
            script += OP_CHECKMULTISIG  # OP_CHECKMULTISIG
        else:
            script = OP_RETURN  # OP_RETURN
            script += op_push(len(
                data_chunk))  # Push bytes of data chunk (NOTE: OP_SMALLDATA?)
            script += data_chunk  # Data chunk
        s += var_int(int(len(script)))  # Script length
        s += script

    # Change output.
    if change_output:
        address, value = change_output
        pubkeyhash = base58_decode(address, config.ADDRESSVERSION)
        s += value.to_bytes(8, byteorder='little')  # Value
        script = OP_DUP  # OP_DUP
        script += OP_HASH160  # OP_HASH160
        script += op_push(20)  # Push 0x14 bytes
        script += pubkeyhash  # pubKeyHash
        script += OP_EQUALVERIFY  # OP_EQUALVERIFY
        script += OP_CHECKSIG  # OP_CHECKSIG
        s += var_int(int(len(script)))  # Script length
        s += script

    s += (0).to_bytes(4, byteorder='little')  # LockTime
    return s
示例#4
0
def serialise (encoding, inputs, destination_outputs, data_output=None, change_output=None, source=None, pubkey=None):
    s  = (1).to_bytes(4, byteorder='little')                # Version

    # Number of inputs.
    s += var_int(int(len(inputs)))

    # List of Inputs.
    for i in range(len(inputs)):
        txin = inputs[i]
        s += binascii.unhexlify(bytes(txin['txid'], 'utf-8'))[::-1]         # TxOutHash
        s += txin['vout'].to_bytes(4, byteorder='little')   # TxOutIndex

        script = binascii.unhexlify(bytes(txin['scriptPubKey'], 'utf-8'))
        s += var_int(int(len(script)))                      # Script length
        s += script                                         # Script
        s += b'\xff' * 4                                    # Sequence

    # Number of outputs.
    n = 0
    n += len(destination_outputs)
    if data_output:
        data_array, value = data_output
        for data_chunk in data_array: n += 1
    else:
        data_array = []
    if change_output: n += 1
    s += var_int(n)

    # Destination output.
    for address, value in destination_outputs:
        pubkeyhash = base58_decode(address, config.ADDRESSVERSION)
        s += value.to_bytes(8, byteorder='little')          # Value
        script = OP_DUP                                     # OP_DUP
        script += OP_HASH160                                # OP_HASH160
        script += op_push(20)                               # Push 0x14 bytes
        script += pubkeyhash                                # pubKeyHash
        script += OP_EQUALVERIFY                            # OP_EQUALVERIFY
        script += OP_CHECKSIG                               # OP_CHECKSIG
        s += var_int(int(len(script)))                      # Script length
        s += script

    # Data output.
    for data_chunk in data_array:
        data_array, value = data_output # DUPE
        s += value.to_bytes(8, byteorder='little')        # Value

        # Get source public key (either provided as a string or derived from a private key in the wallet).
        if encoding in ('multisig', 'pubkeyhash'):
            if pubkey:
                pubkeypair = bitcoin_utils.parse_as_public_pair(pubkey)
                source_pubkey = public_pair_to_sec(pubkeypair, compressed=True)
            else:
                if config.PREFIX == config.UNITTEST_PREFIX:
                    private_key_wif = 'cPdUqd5EbBWsjcG9xiL1hz8bEyGFiz4SW99maU9JgpL9TEcxUf3j'
                else:
                    private_key_wif = rpc('dumpprivkey', [source])
                if private_key_wif[0] == 'c': testnet = True
                else: testnet = False
                secret_exponent, compressed = wif_to_tuple_of_secret_exponent_compressed(private_key_wif, is_test=testnet)
                public_pair = public_pair_for_secret_exponent(generator_secp256k1, secret_exponent)
                source_pubkey = public_pair_to_sec(public_pair, compressed=compressed)

        if encoding == 'multisig':
            # Get data (fake) public key.
            pad_length = 33 - 1 - len(data_chunk)
            assert pad_length >= 0
            data_pubkey = bytes([len(data_chunk)]) + data_chunk + (pad_length * b'\x00')
            # Construct script.
            script = OP_1                                   # OP_1
            script += op_push(len(source_pubkey))           # Push bytes of source public key
            script += source_pubkey                         # Source public key
            script += op_push(len(data_pubkey))             # Push bytes of data chunk (fake) public key
            script += data_pubkey                           # Data chunk (fake) public key
            script += OP_2                                  # OP_2
            script += OP_CHECKMULTISIG                      # OP_CHECKMULTISIG
        elif encoding == 'opreturn':
            script = OP_RETURN                              # OP_RETURN
            script += op_push(len(data_chunk))              # Push bytes of data chunk (NOTE: OP_SMALLDATA?)
            script += data_chunk                            # Data chunk
        elif encoding == 'pubkeyhash':
            pad_length = 20 - 1 - len(data_chunk)
            assert pad_length >= 0
            obj1 = ARC4.new(binascii.unhexlify(inputs[0]['txid']))  # Arbitrary, easy‐to‐find, unique key.
            pubkeyhash = bytes([len(data_chunk)]) + data_chunk + (pad_length * b'\x00')
            pubkeyhash_encrypted = obj1.encrypt(pubkeyhash)
            # Construct script.
            script = OP_DUP                                     # OP_DUP
            script += OP_HASH160                                # OP_HASH160
            script += op_push(20)                               # Push 0x14 bytes
            script += pubkeyhash_encrypted                      # pubKeyHash
            script += OP_EQUALVERIFY                            # OP_EQUALVERIFY
            script += OP_CHECKSIG                               # OP_CHECKSIG
        else:
            raise exceptions.TransactionError('Unknown encoding‐scheme.')

        s += var_int(int(len(script)))                      # Script length
        s += script

    # Change output.
    if change_output:
        address, value = change_output
        pubkeyhash = base58_decode(address, config.ADDRESSVERSION)
        s += value.to_bytes(8, byteorder='little')          # Value
        script = OP_DUP                                     # OP_DUP
        script += OP_HASH160                                # OP_HASH160
        script += op_push(20)                               # Push 0x14 bytes
        script += pubkeyhash                                # pubKeyHash
        script += OP_EQUALVERIFY                            # OP_EQUALVERIFY
        script += OP_CHECKSIG                               # OP_CHECKSIG
        s += var_int(int(len(script)))                      # Script length
        s += script

    s += (0).to_bytes(4, byteorder='little')                # LockTime
    return s
示例#5
0
def transaction (tx_info, encoding='auto', fee_per_kb=config.DEFAULT_FEE_PER_KB,
                 regular_dust_size=config.DEFAULT_REGULAR_DUST_SIZE,
                 multisig_dust_size=config.DEFAULT_MULTISIG_DUST_SIZE,
                 op_return_value=config.DEFAULT_OP_RETURN_VALUE, exact_fee=None,
                 fee_provided=0, public_key_hex=None,
                 allow_unconfirmed_inputs=False, armory=False):

    (source, destination_outputs, data) = tx_info

    # Data encoding methods.
    if data:
        if encoding == 'auto':
            if len(data) <= 40:
                # encoding = 'opreturn'
                encoding = 'multisig'   # BTCGuild isn’t mining OP_RETURN?!
            else:
                encoding = 'multisig'

        if encoding not in ('pubkeyhash', 'multisig', 'opreturn'):
            raise exceptions.TransactionError('Unknown encoding‐scheme.')

    if exact_fee and not isinstance(exact_fee, int):
        raise exceptions.TransactionError('Exact fees must be in satoshis.')
    if not isinstance(fee_provided, int):
        raise exceptions.TransactionError('Fee provided must be in satoshis.')

    # If public key is necessary for construction of (unsigned) transaction,
    # either use the public key provided, or derive it from a private key
    # retrieved from wallet.
    public_key = None
    if encoding in ('multisig', 'pubkeyhash'):
        # If no public key was provided, derive from private key.
        if not public_key_hex:
            # Get private key.
            if config.UNITTEST:
                private_key_wif = config.UNITTEST_PRIVKEY[source]
            else:
                private_key_wif = rpc('dumpprivkey', [source])

            # Derive public key.
            public_key_hex = private_key_to_public_key(private_key_wif)

        pubkeypair = bitcoin_utils.parse_as_public_pair(public_key_hex)
        if not pubkeypair:
            raise exceptions.InputError('Invalid private key.')
        public_key = public_pair_to_sec(pubkeypair, compressed=True)

    # Protocol change.
    if encoding == 'pubkeyhash' and get_block_count() < 293000 and not config.TESTNET:
        raise exceptions.TransactionError('pubkeyhash encoding unsupported before block 293000')

    # Validate source and all destination addresses.
    destinations = [address for address, value in destination_outputs]
    for address in destinations + [source]:
        if address:
            try:
                base58_decode(address, config.ADDRESSVERSION)
            except Exception:   # TODO
                raise exceptions.AddressError('Invalid Bitcoin address:', address)

    # Check that the source is in wallet.
    if not config.UNITTEST and encoding in ('multisig') and not public_key:
        if not rpc('validateaddress', [source])['ismine']:
            raise exceptions.AddressError('Not one of your Bitcoin addresses:', source)

    # Check that the destination output isn't a dust output.
    # Set null values to dust size.
    new_destination_outputs = []
    for address, value in destination_outputs:
        if encoding == 'multisig':
            if value == None: value = multisig_dust_size
            if not value >= multisig_dust_size:
                raise exceptions.TransactionError('Destination output is below the dust target value.')
        else:
            if value == None: value = regular_dust_size
            if not value >= regular_dust_size:
                raise exceptions.TransactionError('Destination output is below the dust target value.')
        new_destination_outputs.append((address, value))
    destination_outputs = new_destination_outputs

    # Divide data into chunks.
    if data:
        def chunks(l, n):
            """ Yield successive n‐sized chunks from l.
            """
            for i in range(0, len(l), n): yield l[i:i+n]
        if encoding == 'pubkeyhash':
            data_array = list(chunks(data + config.PREFIX, 20 - 1)) # Prefix is also a suffix here.
        elif encoding == 'multisig':
            data_array = list(chunks(data, 33 - 1))
        elif encoding == 'opreturn':
            data_array = list(chunks(data, config.OP_RETURN_MAX_SIZE))
            assert len(data_array) == 1 # Only one OP_RETURN output currently supported (OP_RETURN messages should all be shorter than 40 bytes, at the moment).
    else:
        data_array = []

    # Calculate total BTC to be sent.
    btc_out = 0
    if encoding == 'multisig': data_value = multisig_dust_size
    elif encoding == 'opreturn': data_value = op_return_value
    else: data_value = regular_dust_size # Pay‐to‐PubKeyHash
    btc_out = sum([data_value for data_chunk in data_array])
    btc_out += sum([value for address, value in destination_outputs])

    # Get size of outputs.
    if encoding == 'multisig': data_output_size = 81        # 71 for the data
    elif encoding == 'opreturn': data_output_size = 90      # 80 for the data
    else: data_output_size = 25 + 9                         # Pay‐to‐PubKeyHash (25 for the data?)
    outputs_size = ((25 + 9) * len(destination_outputs)) + (len(data_array) * data_output_size)

    # Get inputs.
    unspent = get_unspent_txouts(source, normalize=True)
    unspent = sort_unspent_txouts(unspent, allow_unconfirmed_inputs)
    logging.debug('Sorted UTXOs: {}'.format([print_coin(coin) for coin in unspent]))

    inputs, btc_in = [], 0
    change_quantity = 0
    sufficient_funds = False
    final_fee = fee_per_kb
    for coin in unspent:
        logging.debug('New input: {}'.format(print_coin(coin)))
        inputs.append(coin)
        btc_in += round(coin['amount'] * config.UNIT)

        # If exact fee is specified, use that. Otherwise, calculate size of tx and base fee on that (plus provide a minimum fee for selling BTC).
        if exact_fee:
            final_fee = exact_fee
        else:
            size = 181 * len(inputs) + outputs_size + 10
            necessary_fee = (int(size / 1000) + 1) * fee_per_kb
            final_fee = max(fee_provided, necessary_fee)
            assert final_fee >= 1 * fee_per_kb

        # Check if good.
        change_quantity = btc_in - (btc_out + final_fee)
        logging.debug('Change quantity: {} BTC'.format(change_quantity / config.UNIT))
        if change_quantity == 0 or change_quantity >= regular_dust_size: # If change is necessary, must not be a dust output.
            sufficient_funds = True
            break
    if not sufficient_funds:
        # Approximate needed change, fee by with most recently calculated quantities.
        total_btc_out = btc_out + max(change_quantity, 0) + final_fee
        raise exceptions.BalanceError('Insufficient bitcoins at address {}. (Need approximately {} {}.) To spend unconfirmed coins, use the flag `--unconfirmed`.'.format(source, total_btc_out / config.UNIT, config.BTC))

    # Construct outputs.
    if data: data_output = (data_array, data_value)
    else: data_output = None
    if change_quantity: change_output = (source, change_quantity)
    else: change_output = None

    # Serialise inputs and outputs.
    unsigned_tx = serialise(encoding, inputs, destination_outputs, data_output, change_output, source=source, public_key=public_key)
    unsigned_tx_hex = binascii.hexlify(unsigned_tx).decode('utf-8')

    # bip-0010
    try:
        if armory:
            txdp = []
            dpid = base58_encode(hashlib.sha256(unsigned_tx).digest())[:8]
            txdp.append(('-----BEGIN-TRANSACTION-' + dpid + '-----').ljust(80,'-'))

            magic_bytes = binascii.hexlify(config.MAGIC_BYTES).decode('utf-8')
            varIntTxSize = binascii.hexlify(len(unsigned_tx).to_bytes(2, byteorder='big')).decode('utf-8')
            txdp.append('_TXDIST_{}_{}_{}'.format(magic_bytes, dpid, varIntTxSize))
            tx_list = unsigned_tx_hex
            for coin in inputs:
                tx_list += get_raw_transaction(coin['txid'], json=False)
            for byte in range(0,len(tx_list),80):
                txdp.append(tx_list[byte:byte+80] )

            for index, coin in enumerate(inputs):
                index_fill = str(index).zfill(2)
                value = '{0:.8f}'.format(coin['amount'])
                txdp.append('_TXINPUT_{}_{}'.format(index_fill, value))

            txdp.append(('-----END-TRANSACTION-' + dpid + '-----').ljust(80,'-'))
            unsigned_tx_hex = '\n'.join(txdp)
    except Exception as e:
        print(e)

    return unsigned_tx_hex
示例#6
0
def serialise (inputs, destination_output=None, data_output=None, change_output=None, multisig=False, source=None, unsigned=False):
    assert not (multisig and unsigned is True)
    s  = (1).to_bytes(4, byteorder='little')                # Version

    # Number of inputs.
    s += var_int(int(len(inputs)))

    # List of Inputs.
    for i in range(len(inputs)):
        txin = inputs[i]
        s += binascii.unhexlify(bytes(txin['txid'], 'utf-8'))[::-1]         # TxOutHash
        s += txin['vout'].to_bytes(4, byteorder='little')   # TxOutIndex

        if not unsigned:
            # No signature.
            script = b''
        else:
            #pubkeyhash = base58_decode(source, config.ADDRESSVERSION)
            #script = OP_DUP                                     # OP_DUP
            #script += OP_HASH160                                # OP_HASH160
            #script += op_push(20)                               # Push 0x14 bytes
            #script += pubkeyhash                                # pubKeyHash
            #script += OP_EQUALVERIFY                            # OP_EQUALVERIFY
            #script += OP_CHECKSIG                               # OP_CHECKSIG
            script = str.encode(txin['scriptPubKey'])

        s += var_int(int(len(script)))                      # Script length
        s += script                                         # Script
        s += b'\xff' * 4                                    # Sequence

    # Number of outputs.
    n = 0
    if destination_output: n += 1
    if data_output:
        data_array, value = data_output
        for data_chunk in data_array: n += 1
    else:
        data_array = []
    if change_output: n += 1
    s += var_int(n)

    # Destination output.
    if destination_output:
        address, value = destination_output
        pubkeyhash = base58_decode(address, config.ADDRESSVERSION)
        s += value.to_bytes(8, byteorder='little')          # Value
        script = OP_DUP                                     # OP_DUP
        script += OP_HASH160                                # OP_HASH160
        script += op_push(20)                               # Push 0x14 bytes
        script += pubkeyhash                                # pubKeyHash
        script += OP_EQUALVERIFY                            # OP_EQUALVERIFY
        script += OP_CHECKSIG                               # OP_CHECKSIG
        s += var_int(int(len(script)))                      # Script length
        s += script

    # Data output.
    for data_chunk in data_array:
        data_array, value = data_output # DUPE
        s += (value).to_bytes(8, byteorder='little')        # Value

        if multisig:
            # Get source public key.
            if unsigned:
                assert isinstance(unsigned, str)
                pubkeypair = bitcoin_utils.parse_as_public_pair(unsigned)
                source_pubkey = public_pair_to_sec(pubkeypair, compressed=True)
            else:
                if config.PREFIX == config.UNITTEST_PREFIX:
                    private_key_wif = 'cPdUqd5EbBWsjcG9xiL1hz8bEyGFiz4SW99maU9JgpL9TEcxUf3j'
                else:
                    private_key_wif = rpc('dumpprivkey', [source])
                if private_key_wif[0] == 'c': testnet = True
                else: testnet = False
                secret_exponent, compressed = wif_to_tuple_of_secret_exponent_compressed(private_key_wif, is_test=testnet)
                public_pair = public_pair_for_secret_exponent(generator_secp256k1, secret_exponent)
                source_pubkey = public_pair_to_sec(public_pair, compressed=compressed)

            # Get data (fake) public key.
            pad_length = 33 - 1 - len(data_chunk)
            assert pad_length >= 0
            data_pubkey = bytes([len(data_chunk)]) + data_chunk + (pad_length * b'\x00')

            script = OP_1                                   # OP_1
            script += op_push(len(source_pubkey))           # Push bytes of source public key
            script += source_pubkey                         # Source public key
            script += op_push(len(data_pubkey))             # Push bytes of data chunk (fake) public key
            script += data_pubkey                           # Data chunk (fake) public key
            script += OP_2                                  # OP_2
            script += OP_CHECKMULTISIG                      # OP_CHECKMULTISIG
        else:
            script = OP_RETURN                              # OP_RETURN
            script += op_push(len(data_chunk))              # Push bytes of data chunk (NOTE: OP_SMALLDATA?)
            script += data_chunk                            # Data chunk
        s += var_int(int(len(script)))                      # Script length
        s += script

    # Change output.
    if change_output:
        address, value = change_output
        pubkeyhash = base58_decode(address, config.ADDRESSVERSION)
        s += value.to_bytes(8, byteorder='little')          # Value
        script = OP_DUP                                     # OP_DUP
        script += OP_HASH160                                # OP_HASH160
        script += op_push(20)                               # Push 0x14 bytes
        script += pubkeyhash                                # pubKeyHash
        script += OP_EQUALVERIFY                            # OP_EQUALVERIFY
        script += OP_CHECKSIG                               # OP_CHECKSIG
        s += var_int(int(len(script)))                      # Script length
        s += script

    s += (0).to_bytes(4, byteorder='little')                # LockTime
    return s