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
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
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
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
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
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