def do_test(exp_hex, wif, c_wif, public_pair_sec, c_public_pair_sec, address_b58, c_address_b58): secret_exponent = int(exp_hex, 16) sec = binascii.unhexlify(public_pair_sec) c_sec = binascii.unhexlify(c_public_pair_sec) self.assertEqual( secret_exponent_to_wif(secret_exponent, compressed=False), wif) self.assertEqual( secret_exponent_to_wif(secret_exponent, compressed=True), c_wif) exponent, compressed = wif_to_tuple_of_secret_exponent_compressed( wif) self.assertEqual(exponent, secret_exponent) self.assertFalse(compressed) exponent, compressed = wif_to_tuple_of_secret_exponent_compressed( c_wif) self.assertEqual(exponent, secret_exponent) self.assertTrue(compressed) public_pair = public_pair_for_secret_exponent( generator_secp256k1, secret_exponent) pk_public_pair = public_pair_from_sec(sec) compressed = is_sec_compressed(sec) self.assertEqual(pk_public_pair, public_pair) self.assertFalse(is_sec_compressed(sec)) self.assertEqual( public_pair_to_sec(pk_public_pair, compressed=False), sec) pk_public_pair = public_pair_from_sec(c_sec) compressed = is_sec_compressed(c_sec) self.assertEqual(pk_public_pair, public_pair) self.assertTrue(compressed) self.assertEqual( public_pair_to_sec(pk_public_pair, compressed=True), c_sec) bca = public_pair_to_bitcoin_address(pk_public_pair, compressed=True) self.assertEqual(bca, c_address_b58) self.assertEqual( bitcoin_address_to_ripemd160_sha_sec(c_address_b58), public_pair_to_ripemd160_sha_sec(pk_public_pair, compressed=True)) bca = public_pair_to_bitcoin_address(pk_public_pair, compressed=False) self.assertEqual(bca, address_b58) self.assertEqual( bitcoin_address_to_ripemd160_sha_sec(address_b58), public_pair_to_ripemd160_sha_sec(pk_public_pair, compressed=False))
def do_test(exp_hex, wif, c_wif, public_pair_sec, c_public_pair_sec, address_b58, c_address_b58): secret_exponent = int(exp_hex, 16) sec = h2b(public_pair_sec) c_sec = h2b(c_public_pair_sec) self.assertEqual( secret_exponent_to_wif(secret_exponent, compressed=False), wif) self.assertEqual( secret_exponent_to_wif(secret_exponent, compressed=True), c_wif) exponent, compressed = wif_to_tuple_of_secret_exponent_compressed( wif) self.assertEqual(exponent, secret_exponent) self.assertFalse(compressed) exponent, compressed = wif_to_tuple_of_secret_exponent_compressed( c_wif) self.assertEqual(exponent, secret_exponent) self.assertTrue(compressed) public_pair = secret_exponent * secp256k1_generator pk_public_pair = sec_to_public_pair(sec) compressed = is_sec_compressed(sec) self.assertEqual(pk_public_pair, public_pair) self.assertFalse(is_sec_compressed(sec)) self.assertEqual( public_pair_to_sec(pk_public_pair, compressed=False), sec) pk_public_pair = sec_to_public_pair(c_sec) compressed = is_sec_compressed(c_sec) self.assertEqual(pk_public_pair, public_pair) self.assertTrue(compressed) self.assertEqual( public_pair_to_sec(pk_public_pair, compressed=True), c_sec) bca = public_pair_to_bitcoin_address(pk_public_pair, compressed=True) self.assertEqual(bca, c_address_b58) self.assertEqual( bitcoin_address_to_hash160_sec(c_address_b58), public_pair_to_hash160_sec(pk_public_pair, compressed=True)) bca = public_pair_to_bitcoin_address(pk_public_pair, compressed=False) self.assertEqual(bca, address_b58) self.assertEqual( bitcoin_address_to_hash160_sec(address_b58), public_pair_to_hash160_sec(pk_public_pair, compressed=False))
def from_sec(class_, sec): """ Create a key from an sec bytestream (which is an encoding of a public pair). """ public_pair = sec_to_public_pair(sec) return Key(public_pair=public_pair, prefer_uncompressed=not is_sec_compressed(sec))
def pubkey_to_address(pubkey_hex): sec = binascii.unhexlify(pubkey_hex) compressed = encoding.is_sec_compressed(sec) public_pair = encoding.sec_to_public_pair(sec) address_prefix = b'\x6f' if config.TESTNET else b'\x00' return encoding.public_pair_to_bitcoin_address( public_pair, compressed=compressed, address_prefix=address_prefix)
def from_sec(class_, sec, netcode=None): """ Create a key from an sec bytestream (which is an encoding of a public pair). """ public_pair = sec_to_public_pair(sec) return class_(public_pair=public_pair, is_compressed=is_sec_compressed(sec), netcode=netcode)
def add_sec_annotations(a1, data, address_prefix): pair = sec_to_public_pair(data) is_compressed = is_sec_compressed(data) a1.append( "SEC for %scompressed %s" % ("" if is_compressed else "un", public_pair_to_bitcoin_address( pair, compressed=is_compressed, address_prefix=address_prefix)))
def do_test(as_public_pair, as_sec, is_compressed, as_hash160_sec, as_bitcoin_address): self.assertEqual(encoding.sec_to_public_pair(as_sec), as_public_pair) self.assertEqual(encoding.public_pair_to_sec(as_public_pair, compressed=is_compressed), as_sec) self.assertEqual(encoding.is_sec_compressed(as_sec), is_compressed) self.assertEqual(encoding.public_pair_to_hash160_sec(as_public_pair, compressed=is_compressed), as_hash160_sec) self.assertEqual(encoding.hash160_sec_to_bitcoin_address(as_hash160_sec), as_bitcoin_address) self.assertEqual(encoding.public_pair_to_bitcoin_address(as_public_pair, compressed=is_compressed), as_bitcoin_address) self.assertTrue(encoding.is_valid_bitcoin_address(as_bitcoin_address)) bad_address = as_bitcoin_address[:17] + chr(ord(as_bitcoin_address[17]) + 1) + as_bitcoin_address[18:] self.assertFalse(encoding.is_valid_bitcoin_address(bad_address))
def do_test(exp_hex, wif, c_wif, public_pair_sec, c_public_pair_sec, address_b58, c_address_b58): secret_exponent = int(exp_hex, 16) sec = h2b(public_pair_sec) c_sec = h2b(c_public_pair_sec) self.assertEqual(secret_exponent_to_wif(secret_exponent, compressed=False), wif) self.assertEqual(secret_exponent_to_wif(secret_exponent, compressed=True), c_wif) exponent, compressed = wif_to_tuple_of_secret_exponent_compressed(wif) self.assertEqual(exponent, secret_exponent) self.assertFalse(compressed) exponent, compressed = wif_to_tuple_of_secret_exponent_compressed(c_wif) self.assertEqual(exponent, secret_exponent) self.assertTrue(compressed) public_pair = secret_exponent * secp256k1_generator pk_public_pair = sec_to_public_pair(sec) compressed = is_sec_compressed(sec) self.assertEqual(pk_public_pair, public_pair) self.assertFalse(is_sec_compressed(sec)) self.assertEqual(public_pair_to_sec(pk_public_pair, compressed=False), sec) pk_public_pair = sec_to_public_pair(c_sec) compressed = is_sec_compressed(c_sec) self.assertEqual(pk_public_pair, public_pair) self.assertTrue(compressed) self.assertEqual(public_pair_to_sec(pk_public_pair, compressed=True), c_sec) bca = public_pair_to_bitcoin_address(pk_public_pair, compressed=True) self.assertEqual(bca, c_address_b58) self.assertEqual(bitcoin_address_to_hash160_sec(c_address_b58), public_pair_to_hash160_sec(pk_public_pair, compressed=True)) bca = public_pair_to_bitcoin_address(pk_public_pair, compressed=False) self.assertEqual(bca, address_b58) self.assertEqual(bitcoin_address_to_hash160_sec(address_b58), public_pair_to_hash160_sec(pk_public_pair, compressed=False))
def do_test(exp_hex, wif, c_wif, public_pair_sec, c_public_pair_sec, address_b58, c_address_b58): secret_exponent = int(exp_hex, 16) sec = binascii.unhexlify(public_pair_sec) c_sec = binascii.unhexlify(c_public_pair_sec) self.assertEqual(secret_exponent_to_wif(secret_exponent, compressed=False), wif) self.assertEqual(secret_exponent_to_wif(secret_exponent, compressed=True), c_wif) exponent, compressed = wif_to_tuple_of_secret_exponent_compressed(wif) self.assertEqual(exponent, secret_exponent) self.assertFalse(compressed) exponent, compressed = wif_to_tuple_of_secret_exponent_compressed(c_wif) self.assertEqual(exponent, secret_exponent) self.assertTrue(compressed) public_pair = public_pair_for_secret_exponent(generator_secp256k1, secret_exponent) pk_public_pair = public_pair_from_sec(sec) compressed = is_sec_compressed(sec) self.assertEqual(pk_public_pair, public_pair) self.assertFalse(is_sec_compressed(sec)) self.assertEqual(public_pair_to_sec(pk_public_pair, compressed=False), sec) pk_public_pair = public_pair_from_sec(c_sec) compressed = is_sec_compressed(c_sec) self.assertEqual(pk_public_pair, public_pair) self.assertTrue(compressed) self.assertEqual(public_pair_to_sec(pk_public_pair, compressed=True), c_sec) bca = public_pair_to_bitcoin_address(pk_public_pair, compressed=True) self.assertEqual(bca, c_address_b58) self.assertEqual(bitcoin_address_to_ripemd160_sha_sec(c_address_b58), public_pair_to_ripemd160_sha_sec(pk_public_pair, compressed=True)) bca = public_pair_to_bitcoin_address(pk_public_pair, compressed=False) self.assertEqual(bca, address_b58) self.assertEqual(bitcoin_address_to_ripemd160_sha_sec(address_b58), public_pair_to_ripemd160_sha_sec(pk_public_pair, compressed=False))
def address_h160_from_script (script): s = disassemble(script).split(' ') if 'OP_HASH160' in s: p = s.index('OP_HASH160') if len(s) > p+1: return h2b(s[p+1]) elif 'OP_CHECKSIG' in s: p = s.index('OP_CHECKSIG') if len(s[p-1]) in (66, 130): # public key sec = h2b(s[p-1]) return public_pair_to_hash160_sec(sec_to_public_pair(sec), is_sec_compressed(sec)) else: logger.warn("Can't parse address from script: %s" % (s)) return None
def bitcoin_address_for_script(self, is_test=False): try: r = self.match_script_to_templates() if len(r) != 1 or len(r[0]) != 2: return None if r[0][0] == opcodes.OP_PUBKEYHASH: return hash160_sec_to_bitcoin_address(r[0][1], is_test=is_test) if r[0][0] == opcodes.OP_PUBKEY: sec = r[0][1] return public_pair_to_bitcoin_address( sec_to_public_pair(sec), compressed=is_sec_compressed(sec), is_test=is_test) except SolvingError: pass return None
def add_sec_annotations(a1, data, address_prefix): pair = sec_to_public_pair(data) is_compressed = is_sec_compressed(data) a1.append("SEC for %scompressed %s" % ( "" if is_compressed else "un", public_pair_to_bitcoin_address( pair, compressed=is_compressed, address_prefix=address_prefix)))
def pubkey_to_address(pubkey_hex): sec = binascii.unhexlify(pubkey_hex) compressed = encoding.is_sec_compressed(sec) public_pair = encoding.sec_to_public_pair(sec) return encoding.public_pair_to_bitcoin_address(public_pair, compressed=compressed)
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): (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) #convert public key hex into public key pair (sec) try: sec = binascii.unhexlify(public_key_hex) is_compressed = is_sec_compressed(sec) public_key = sec except (EncodingError, binascii.Error): raise exceptions.InputError('Invalid private key.') # 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 {}s at address {}. (Need approximately {} {}.) To spend unconfirmed coins, use the flag `--unconfirmed`.' .format(config.BTC_NAME, 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') return unsigned_tx_hex
def pubkey_to_address(pubkey_hex): sec = binascii.unhexlify(pubkey_hex) compressed = encoding.is_sec_compressed(sec) public_pair = encoding.sec_to_public_pair(sec) address_prefix = b'\x6f' if config.TESTNET else b'\x00' return encoding.public_pair_to_bitcoin_address(public_pair, compressed=compressed, address_prefix=address_prefix)
def transaction (db, 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, self_public_key_hex=None, allow_unconfirmed_inputs=False): block_index = util.last_block(db)['block_index'] (source, destination_outputs, data) = tx_info '''Destinations''' # Destination outputs. # Replace multi‐sig addresses with multi‐sig pubkeys. Check that the # destination output isn’t a dust output. Set null values to dust size. destination_outputs_new = [] for (address, value) in destination_outputs: # Value. if util.is_multisig(address): dust_size = multisig_dust_size else: dust_size = regular_dust_size if value == None: value = dust_size elif value < dust_size: raise exceptions.TransactionError('Destination output is dust.') # Address. util.validate_address(address) if util.is_multisig(address): destination_outputs_new.append((multisig_pubkeyhashes_to_pubkeys(address), value)) else: destination_outputs_new.append((address, value)) destination_outputs = destination_outputs_new destination_btc_out = sum([value for address, value in destination_outputs]) '''Data''' # Data encoding methods (choose and validate). if data: if encoding == 'auto': if len(data) <= config.OP_RETURN_MAX_SIZE: # 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.') # 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 util.enabled('multisig_addresses', block_index): # Protocol change. if encoding == 'pubkeyhash': data_array = list(chunks(data, 20 - 1 - 8)) # Prefix is also a suffix here. elif encoding == 'multisig': data_array = list(chunks(data, (33 * 2) - 1 - 8 - 2 - 2)) # Two pubkeys, minus length byte, minus prefix, minus two nonces, minus two sign bytes else: data = config.PREFIX + data 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)) if 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 = [] # Data outputs. 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 if data: data_output = (data_array, data_value) else: data_output = None data_btc_out = sum([data_value for data_chunk in data_array]) '''Inputs''' # Source. # 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. if source: util.validate_address(source) self_public_key = None if encoding in ('multisig', 'pubkeyhash'): if util.is_multisig(source): a, self_pubkeys, b = util.extract_array(multisig_pubkeyhashes_to_pubkeys(source)) self_public_key = binascii.unhexlify(self_pubkeys[0]) else: if not self_public_key_hex: # If public key was not provided, derive it from the private key. private_key_wif = backend.dumpprivkey(source) self_public_key_hex = private_key_to_public_key(private_key_wif) else: # If public key was provided, check that it matches the source address. if source != script.pubkey_to_pubkeyhash(binascii.unhexlify(self_public_key_hex)): raise InputError('provided public key does not match the source address') # Convert hex public key into binary public key. try: self_public_key = binascii.unhexlify(self_public_key_hex) is_compressed = is_sec_compressed(self_public_key) except (EncodingError, binascii.Error): raise InputError('Invalid private key.') # Calculate collective 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) 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. btc_out = destination_btc_out + data_btc_out 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 BalanceError('Insufficient bitcoins at address {}. (Need approximately {} {}.) To spend unconfirmed coins, use the flag `--unconfirmed`. (Unconfirmed coins cannot be spent from multi‐sig addresses.)'.format(source, total_btc_out / config.UNIT, config.BTC)) '''Finish''' # Change output. if util.is_multisig(source): change_address = multisig_pubkeyhashes_to_pubkeys(source) else: change_address = source if change_quantity: change_output = (change_address, change_quantity) else: change_output = None # Serialise inputs and outputs. unsigned_tx = serialise(block_index, encoding, inputs, destination_outputs, data_output, change_output, dust_return_public_key=self_public_key) unsigned_tx_hex = binascii.hexlify(unsigned_tx).decode('utf-8') # Check that the constructed transaction isn’t doing anything funny. from lib import blocks (desired_source, desired_destination_outputs, desired_data) = tx_info desired_source = util.canonical_address(desired_source) desired_destination = util.canonical_address(desired_destination_outputs[0][0]) if desired_destination_outputs else '' # Include change in destinations for BTC transactions. if change_output and not desired_data and desired_destination != config.UNSPENDABLE: if desired_destination == '': desired_destination = desired_source else: desired_destination += '-{}'.format(desired_source) if desired_data == None: desired_data = b'' parsed_source, parsed_destination, x, y, parsed_data = blocks.get_tx_info2(unsigned_tx_hex) if (desired_source, desired_destination, desired_data) != (parsed_source, parsed_destination, parsed_data): raise exceptions.TransactionError('constructed transaction does not parse correctly') return unsigned_tx_hex
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): (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) #convert public key hex into public key pair (sec) try: sec = binascii.unhexlify(public_key_hex) is_compressed = is_sec_compressed(sec) public_key = sec except (EncodingError, binascii.Error): raise exceptions.InputError('Invalid private key.') # 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 {}s at address {}. (Need approximately {} {}.) To spend unconfirmed coins, use the flag `--unconfirmed`.'.format(config.BTC_NAME, 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') return unsigned_tx_hex
def from_sec(class_, sec, netcode="BTC"): """ Create a key from an sec bytestream (which is an encoding of a public pair). """ public_pair = sec_to_public_pair(sec) return class_(public_pair=public_pair, is_compressed=is_sec_compressed(sec), netcode=netcode)
def transaction (db, 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, self_public_key_hex=None, allow_unconfirmed_inputs=False): block_index = util.last_block(db)['block_index'] (source, destination_outputs, data) = tx_info multisig_source = util.is_multisig(source) # Data encoding methods. if data: if encoding == 'auto': if len(data) <= config.OP_RETURN_MAX_SIZE: # 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. self_public_key = None if encoding in ('multisig', 'pubkeyhash') and not multisig_source: # If no public key was provided, derive from private key. if not self_public_key_hex: # Get private key. private_key_wif = get_private_key(source) # Derive public key. self_public_key_hex = private_key_to_public_key(private_key_wif) #convert public key hex into public key pair (sec) try: sec = binascii.unhexlify(self_public_key_hex) is_compressed = is_sec_compressed(sec) self_public_key = sec except (EncodingError, binascii.Error): raise InputError('Invalid private key.') # 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 destination addresses. destinations = [address for address, value in destination_outputs] for address in destinations + [source]: if address: util.validate_address(address, block_index) # Check that the source is in wallet. if encoding in ('multisig') and not self_public_key and not multisig_source: if not is_mine(source): 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 util.multisig_enabled(block_index): # Protocol change. if encoding == 'pubkeyhash': data_array = list(chunks(data, 20 - 1 - 8)) # Prefix is also a suffix here. elif encoding == 'multisig': data_array = list(chunks(data, (33 * 2) - 1 - 8 - 2 - 2)) # Two pubkeys, minus length byte, minus prefix, minus two nonces, minus two sign bytes else: data = config.PREFIX + data 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)) if 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) 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 BalanceError('Insufficient bitcoins at address {}. (Need approximately {} {}.) To spend unconfirmed coins, use the flag `--unconfirmed`. (Unconfirmed coins cannot be spent from multi‐sig addresses.)'.format(source, total_btc_out / config.UNIT, config.BTC)) # Destination outputs. (Replace multi‐sig addresses with multi‐sig pubkeys.) destination_outputs_new = [] for (destination, value) in destination_outputs: if util.is_multisig(destination): destination_outputs_new.append((multisig_pubkeyhashes_to_pubkeys(destination), value)) else: destination_outputs_new.append((destination, value)) destination_outputs = destination_outputs_new # Data outputs. if data: data_output = (data_array, data_value) else: data_output = None # Change output. (Change address is source address.) if util.is_multisig(source): change_address = multisig_pubkeyhashes_to_pubkeys(source) else: change_address = source if change_quantity: change_output = (change_address, change_quantity) else: change_output = None # Get `self_public_key`, if multi‐sig (for then it’s not passed as an argument). if multisig_source: a, self_pubkeys, b = util.extract_array(multisig_pubkeyhashes_to_pubkeys(source)) self_public_key = binascii.unhexlify(self_pubkeys[0]) # Serialise inputs and outputs. unsigned_tx = serialise(block_index, encoding, inputs, destination_outputs, data_output, change_output, self_public_key=self_public_key) unsigned_tx_hex = binascii.hexlify(unsigned_tx).decode('utf-8') # Check that the constructed transaction isn’t doing anything funny. from lib import blocks (desired_source, desired_destination_outputs, desired_data) = tx_info desired_source = util.canonical_address(desired_source) desired_destination = util.canonical_address(desired_destination_outputs[0][0]) if desired_destination_outputs else '' # Include change in destinations for BTC transactions. if change_output and not desired_data and desired_destination != config.UNSPENDABLE: if desired_destination == '': desired_destination = desired_source else: desired_destination += '-{}'.format(desired_source) if desired_data == None: desired_data = b'' parsed_source, parsed_destination, x, y, parsed_data = blocks.get_tx_info2(unsigned_tx_hex) if (desired_source, desired_destination, desired_data) != (parsed_source, parsed_destination, parsed_data): raise exceptions.TransactionError('constructed transaction does not parse correctly') return unsigned_tx_hex