def prepare_transfer_tx(hw_session: HwSessionInfo, utxos_to_spend: List[dict], dest_addresses: List[Tuple[str, int, str]], tx_fee): """ Creates a signed transaction. :param hw_session: :param utxos_to_spend: list of utxos to send :param dest_address: destination (Dash) address :param tx_fee: transaction fee :return: tuple (serialized tx, total transaction amount in satoshis) """ insight_network = 'insight_dash' if hw_session.app_config.is_testnet(): insight_network += '_testnet' dash_network = hw_session.app_config.dash_network tx_api = MyTxApiInsight(insight_network, '', hw_session.zyrkd_intf, hw_session.app_config.cache_dir) client = hw_session.hw_client client.set_tx_api(tx_api) inputs = [] outputs = [] inputs_amount = 0 for utxo_index, utxo in enumerate(utxos_to_spend): if not utxo.get('bip32_path', None): raise Exception('No BIP32 path for UTXO ' + utxo['txid']) address_n = client.expand_path(clean_bip32_path(utxo['bip32_path'])) it = proto_types.TxInputType(address_n=address_n, prev_hash=binascii.unhexlify(utxo['txid']), prev_index=utxo['outputIndex']) inputs.append(it) inputs_amount += utxo['satoshis'] outputs_amount = 0 for addr, amount, bip32_path in dest_addresses: outputs_amount += amount if addr[0] in dash_utils.get_chain_params(dash_network).B58_PREFIXES_SCRIPT_ADDRESS: stype = proto_types.PAYTOSCRIPTHASH logging.debug('Transaction type: PAYTOSCRIPTHASH' + str(stype)) elif addr[0] in dash_utils.get_chain_params(dash_network).B58_PREFIXES_PUBKEY_ADDRESS: stype = proto_types.PAYTOADDRESS logging.debug('Transaction type: PAYTOADDRESS ' + str(stype)) else: raise Exception('Invalid prefix of the destination address.') if bip32_path: address_n = client.expand_path(bip32_path) else: address_n = None ot = proto_types.TxOutputType( address=addr if address_n is None else None, address_n=address_n, amount=amount, script_type=stype ) outputs.append(ot) if outputs_amount + tx_fee != inputs_amount: raise Exception('Transaction validation failure: inputs + fee != outputs') signed = client.sign_tx(hw_session.app_config.hw_coin_name, inputs, outputs) logging.info('Signed transaction') return signed[1], inputs_amount
def sign_message(hw_session: HwSessionInfo, bip32path, message): client = hw_session.hw_client address_n = client.expand_path(clean_bip32_path(bip32path)) try: return client.sign_message(hw_session.app_config.hw_coin_name, address_n, message) except CallException as e: if e.args and len(e.args) >= 2 and e.args[1].lower().find('cancelled') >= 0: raise CancelException('Cancelled') else: raise
def sign_message(hw_client, hw_coin_name: str, bip32path: str, message: str): address_n = hw_client.expand_path(clean_bip32_path(bip32path)) try: return hw_client.sign_message(hw_coin_name, address_n, message) except CallException as e: if e.args and len( e.args) >= 2 and e.args[1].lower().find('cancelled') >= 0: raise CancelException('Cancelled') else: raise
def get_address_and_pubkey(client, bip32_path, show_display=False): bip32_path = clean_bip32_path(bip32_path) bip32_path.strip() if bip32_path.lower().find('m/') >= 0: bip32_path = bip32_path[2:] nodedata = client.getWalletPublicKey(bip32_path, showOnScreen=show_display) return { 'address': nodedata.get('address').decode('utf-8'), 'publicKey': compress_public_key(nodedata.get('publicKey')) }
def get_address_and_pubkey(hw_session: HWSessionBase, bip32_path, show_display=False): bip32_path = clean_bip32_path(bip32_path) bip32_path.strip() if bip32_path.lower().find('m/') >= 0: bip32_path = bip32_path[2:] nodedata = hw_session.hw_client.getWalletPublicKey( bip32_path, showOnScreen=show_display) addr = _ledger_extract_address(nodedata.get('address')) return { 'address': addr, 'publicKey': compress_public_key(nodedata.get('publicKey')) }
def prepare_transfer_tx(main_ui, utxos_to_spend, dest_address, tx_fee): """ Creates a signed transaction. :param main_ui: Main window for configuration data :param utxos_to_spend: list of utxos to send :param dest_address: destination (Dash) address :param tx_fee: transaction fee :return: tuple (serialized tx, total transaction amount in satoshis) """ tx_api = MyTxApiInsight('insight_dash', None, main_ui.dashd_intf, main_ui.config.cache_dir) client = main_ui.hw_client client.set_tx_api(tx_api) inputs = [] outputs = [] amt = 0 for utxo in utxos_to_spend: if not utxo.get('bip32_path', None): raise Exception('No BIP32 path for UTXO ' + utxo['txid']) address_n = client.expand_path(clean_bip32_path(utxo['bip32_path'])) it = proto_types.TxInputType(address_n=address_n, prev_hash=binascii.unhexlify( utxo['txid']), prev_index=utxo['outputIndex']) inputs.append(it) amt += utxo['satoshis'] amt -= tx_fee amt = int(amt) # check if dest_address is a Dash address or a script address and then set appropriate script_type # https://github.com/dashpay/dash/blob/master/src/chainparams.cpp#L140 if dest_address.startswith('7'): stype = proto_types.PAYTOSCRIPTHASH else: stype = proto_types.PAYTOADDRESS ot = proto_types.TxOutputType(address=dest_address, amount=amt, script_type=stype) outputs.append(ot) signed = client.sign_tx('Dash', inputs, outputs) return signed[1], amt
def get_xpub(client, bip32_path): bip32_path = clean_bip32_path(bip32_path) bip32_path.strip() if bip32_path.lower().find('m/') >= 0: bip32_path = bip32_path[2:] path_n = bip32_path_string_to_n(bip32_path) parent_bip32_path = bip32_path_n_to_string(path_n[:-1]) depth = len(path_n) index = path_n[-1] nodedata = client.getWalletPublicKey(bip32_path) pubkey = compress(nodedata.get('publicKey')) chaincode = nodedata.get('chainCode') parent_nodedata = client.getWalletPublicKey(parent_bip32_path) parent_pubkey = compress(parent_nodedata['publicKey']) parent_fingerprint = bin_hash160(parent_pubkey)[:4] xpub_raw = bytes.fromhex('0488b21e') + depth.to_bytes(1, 'big') + parent_fingerprint + index.to_bytes(4, 'big') + \ chaincode + pubkey xpub = Base58.check_encode(xpub_raw) return xpub
def sign_tx(hw_session: HwSessionInfo, utxos_to_spend: List[wallet_common.UtxoType], tx_outputs: List[wallet_common.TxOutputType], tx_fee): client = hw_session.hw_client rawtransactions = {} decodedtransactions = {} # Each of the UTXOs will become an input in the new transaction. For each of those inputs, create # a Ledger's 'trusted input', that will be used by the the device to sign a transaction. trusted_inputs = [] # arg_inputs: list of dicts # { # 'locking_script': <Locking script of the UTXO used as an input. Used in the process of signing # transaction.>, # 'outputIndex': <index of the UTXO within the previus transaction>, # 'txid': <hash of the previus transaction>, # 'bip32_path': <BIP32 path of the HW key controlling UTXO's destination>, # 'pubkey': <Public key obtained from the HW using the bip32_path.> # 'signature' <Signature obtained as a result of processing the input. It will be used as a part of the # unlocking script.> # } # Why do we need a locking script of the previous transaction? When hashing a new transaction before creating its # signature, all placeholders for input's unlocking script has to be filled with locking script of the # corresponding UTXO. Look here for the details: # https://klmoney.wordpress.com/bitcoin-dissecting-transactions-part-2-building-a-transaction-by-hand) arg_inputs = [] # A dictionary mapping bip32 path to a pubkeys obtained from the Ledger device - used to avoid # reading it multiple times for the same bip32 path bip32_to_address = {} # read previous transactins for utxo in utxos_to_spend: if utxo.txid not in rawtransactions: tx = hw_session.crownd_intf.getrawtransaction(utxo.txid, 1, skip_cache=False) if tx and tx.get('hex'): tx_raw = tx.get('hex') else: tx_raw = hw_session.crownd_intf.getrawtransaction( utxo.txid, 0, skip_cache=False) if tx_raw: rawtransactions[utxo.txid] = tx_raw decodedtransactions[utxo.txid] = tx amount = 0 starting = True for idx, utxo in enumerate(utxos_to_spend): amount += utxo.satoshis raw_tx = rawtransactions.get(utxo.txid) if not raw_tx: raise Exception("Can't find raw transaction for txid: " + utxo.txid) else: raw_tx = bytearray.fromhex(raw_tx) # parse the raw transaction, so that we can extract the UTXO locking script we refer to prev_transaction = bitcoinTransaction(raw_tx) data = decodedtransactions[utxo.txid] dip2_type = data.get("type", 0) if data['version'] == 3 and dip2_type != 0: # It's a DIP2 special TX with payload if "extraPayloadSize" not in data or "extraPayload" not in data: raise ValueError("Payload data missing in DIP2 transaction") if data["extraPayloadSize"] * 2 != len(data["extraPayload"]): raise ValueError( "extra_data_len (%d) does not match calculated length (%d)" % (data["extraPayloadSize"], len(data["extraPayload"]) * 2)) prev_transaction.extra_data = crown_utils.num_to_varint( data["extraPayloadSize"]) + bytes.fromhex(data["extraPayload"]) else: prev_transaction.extra_data = bytes() utxo_tx_index = utxo.output_index if utxo_tx_index < 0 or utxo_tx_index > len(prev_transaction.outputs): raise Exception('Incorrent value of outputIndex for UTXO %s' % str(idx)) trusted_input = client.getTrustedInput(prev_transaction, utxo_tx_index) trusted_inputs.append(trusted_input) bip32_path = utxo.bip32_path bip32_path = clean_bip32_path(bip32_path) pubkey = bip32_to_address.get(bip32_path) if not pubkey: pubkey = compress_public_key( client.getWalletPublicKey(bip32_path)['publicKey']) bip32_to_address[bip32_path] = pubkey pubkey_hash = bitcoin.bin_hash160(pubkey) # verify if the public key hash of the wallet's bip32 path is the same as specified in the UTXO locking script # if they differ, signature and public key we produce and are going to include in the unlocking script won't # match the locking script conditions - transaction will be rejected by the network pubkey_hash_from_script = extract_pkh_from_locking_script( prev_transaction.outputs[utxo_tx_index].script) if pubkey_hash != pubkey_hash_from_script: logging.error( "Error: different public key hashes for the BIP32 path %s (UTXO %s) and the UTXO locking " "script. Your signed transaction will not be validated by the network." % (bip32_path, str(idx))) arg_inputs.append({ 'locking_script': prev_transaction.outputs[utxo.output_index].script, 'pubkey': pubkey, 'bip32_path': bip32_path, 'outputIndex': utxo.output_index, 'txid': utxo.txid }) amount -= int(tx_fee) amount = int(amount) new_transaction = bitcoinTransaction( ) # new transaction object to be used for serialization at the last stage new_transaction.version = bytearray([0x01, 0x00, 0x00, 0x00]) for out in tx_outputs: output = bitcoinOutput() output.script = compose_tx_locking_script( out.address, hw_session.app_config.crown_network) output.amount = int.to_bytes(out.satoshis, 8, byteorder='little') new_transaction.outputs.append(output) # join all outputs - will be used by Ledger for sigining transaction all_outputs_raw = new_transaction.serializeOutputs() # sign all inputs on Ledger and add inputs in the new_transaction object for serialization for idx, new_input in enumerate(arg_inputs): client.startUntrustedTransaction(starting, idx, trusted_inputs, new_input['locking_script']) client.finalizeInputFull(all_outputs_raw) sig = client.untrustedHashSign(new_input['bip32_path'], lockTime=0) new_input['signature'] = sig input = bitcoinInput() input.prevOut = bytearray.fromhex(new_input['txid'])[::-1] + \ int.to_bytes(new_input['outputIndex'], 4, byteorder='little') input.script = bytearray([len(sig)]) + sig + bytearray( [0x21]) + new_input['pubkey'] input.sequence = bytearray([0xFF, 0xFF, 0xFF, 0xFF]) new_transaction.inputs.append(input) starting = False new_transaction.lockTime = bytearray([0, 0, 0, 0]) tx_raw = bytearray(new_transaction.serialize()) return tx_raw, amount
def sign_message(hw_session: HwSessionInfo, bip32_path, message): client = hw_session.hw_client # Ledger doesn't accept characters other that ascii printable: # https://ledgerhq.github.io/btchip-doc/bitcoin-technical.html#_sign_message message = message.encode('ascii', 'ignore') bip32_path = clean_bip32_path(bip32_path) ok = False for i in range(1, 4): info = client.signMessagePrepare(bip32_path, message) if info['confirmationNeeded'] and info['confirmationType'] == 34: if i == 1 or \ WndUtils.queryDlg('Another application (such as Ledger Wallet Bitcoin app) has probably taken over ' 'the communication with the Ledger device.' '\n\nTo continue, close that application and click the <b>Retry</b> button.' '\nTo cancel, click the <b>Abort</b> button', buttons=QMessageBox.Retry | QMessageBox.Abort, default_button=QMessageBox.Retry, icon=QMessageBox.Warning) == QMessageBox.Retry: # we need to reconnect the device; first, we'll try to reconnect to HW without closing the intefering # application; it it doesn't help we'll display a message requesting the user to close the app hw_session.hw_disconnect() if hw_session.hw_connect(): client = hw_session.hw_client else: raise Exception('Hardware wallet reconnect error.') else: break else: ok = True break if not ok: raise CancelException('Cancelled') try: signature = client.signMessageSign() except Exception as e: logging.exception('Exception while signing message with Ledger Nano S') raise Exception( 'Exception while signing message with Ledger Nano S. Details: ' + str(e)) try: pubkey = client.getWalletPublicKey(bip32_path) except Exception as e: logging.exception( 'Could not get public key for BIP32 path from Ledger Nano S') raise Exception( 'Could not get public key for BIP32 path from Ledger Nano S. Details: ' + str(e)) if len(signature) > 4: r_length = signature[3] r = signature[4:4 + r_length] if len(signature) > 4 + r_length + 1: s_length = signature[4 + r_length + 1] if len(signature) > 4 + r_length + 2: s = signature[4 + r_length + 2:] if r_length == 33: r = r[1:] if s_length == 33: s = s[1:] else: logging.error( 'client.signMessageSign() returned invalid response (code 3): ' + signature.hex()) raise Exception('Invalid signature returned (code 3).') else: logging.error( 'client.signMessageSign() returned invalid response (code 2): ' + signature.hex()) raise Exception('Invalid signature returned (code 2).') else: logging.error( 'client.signMessageSign() returned invalid response (code 1): ' + signature.hex()) raise Exception('Invalid signature returned (code 1).') return MessageSignature( pubkey.get('address').decode('ascii'), bytes(chr(27 + 4 + (signature[0] & 0x01)), "utf-8") + r + s)
def sign_message(hw_session: HwSessionInfo, bip32path, message): client = hw_session.hw_client address_n = client.expand_path(clean_bip32_path(bip32path)) return client.sign_message(hw_session.app_config.hw_coin_name, address_n, message)
def prepare_transfer_tx(main_ui, utxos_to_spend, dest_address, tx_fee, rawtransactions): client = main_ui.hw_client # Each of the UTXOs will become an input in the new transaction. For each of those inputs, create # a Ledger's 'trusted input', that will be used by the the device to sign a transaction. trusted_inputs = [] # arg_inputs: list of dicts # { # 'locking_script': <Locking script of the UTXO used as an input. Used in the process of signing # transaction.>, # 'outputIndex': <index of the UTXO within the previus transaction>, # 'txid': <hash of the previus transaction>, # 'bip32_path': <BIP32 path of the HW key controlling UTXO's destination>, # 'pubkey': <Public key obtained from the HW using the bip32_path.> # 'signature' <Signature obtained as a result of processing the input. It will be used as a part of the # unlocking script.> # } # Why do we need a locking script of the previous transaction? When hashing a new transaction before creating its # signature, all placeholders for input's unlocking script has to be filled with locking script of the # corresponding UTXO. Look here for the details: # https://klmoney.wordpress.com/bitcoin-dissecting-transactions-part-2-building-a-transaction-by-hand) arg_inputs = [] # A dictionary mapping bip32 path to a pubkeys obtained from the Ledger device - used to avoid # reading it multiple times for the same bip32 path bip32_to_address = {} amount = 0 starting = True for idx, utxo in enumerate(utxos_to_spend): amount += utxo['satoshis'] raw_tx = bytearray.fromhex(rawtransactions[utxo['txid']]) if not raw_tx: raise Exception("Can't find raw transaction for txid: " + rawtransactions[utxo['txid']]) # parse the raw transaction, so that we can extract the UTXO locking script we refer to prev_transaction = bitcoinTransaction(raw_tx) utxo_tx_index = utxo['outputIndex'] if utxo_tx_index < 0 or utxo_tx_index > len(prev_transaction.outputs): raise Exception('Incorrent value of outputIndex for UTXO %s' % str(idx)) trusted_input = client.getTrustedInput(prev_transaction, utxo_tx_index) trusted_inputs.append(trusted_input) bip32_path = utxo['bip32_path'] bip32_path = clean_bip32_path(bip32_path) pubkey = bip32_to_address.get(bip32_path) if not pubkey: pubkey = compress_public_key( client.getWalletPublicKey(bip32_path)['publicKey']) bip32_to_address[bip32_path] = pubkey pubkey_hash = bitcoin.bin_hash160(pubkey) # verify if the public key hash of the wallet's bip32 path is the same as specified in the UTXO locking script # if they differ, signature and public key we produce and are going to include in the unlocking script won't # match the locking script conditions - transaction will be rejected by the network pubkey_hash_from_script = extract_pkh_from_locking_script( prev_transaction.outputs[utxo_tx_index].script) if pubkey_hash != pubkey_hash_from_script: logging.error( "Error: different public key hashes for the BIP32 path %s (UTXO %s) and the UTXO locking " "script. Your signed transaction will not be validated by the network." % (bip32_path, str(idx))) arg_inputs.append({ 'locking_script': prev_transaction.outputs[utxo['outputIndex']].script, 'pubkey': pubkey, 'bip32_path': bip32_path, 'outputIndex': utxo['outputIndex'], 'txid': utxo['txid'] }) amount -= int(tx_fee) amount = int(amount) arg_outputs = [{ 'address': dest_address, 'valueSat': amount }] # there will be multiple outputs soon new_transaction = bitcoinTransaction( ) # new transaction object to be used for serialization at the last stage new_transaction.version = bytearray([0x01, 0x00, 0x00, 0x00]) for o in arg_outputs: output = bitcoinOutput() output.script = compose_tx_locking_script(o['address']) output.amount = int.to_bytes(o['valueSat'], 8, byteorder='little') new_transaction.outputs.append(output) # join all outputs - will be used by Ledger for sigining transaction all_outputs_raw = new_transaction.serializeOutputs() # sign all inputs on Ledger and add inputs in the new_transaction object for serialization for idx, new_input in enumerate(arg_inputs): client.startUntrustedTransaction(starting, idx, trusted_inputs, new_input['locking_script']) client.finalizeInputFull(all_outputs_raw) sig = client.untrustedHashSign(new_input['bip32_path'], lockTime=0) new_input['signature'] = sig input = bitcoinInput() input.prevOut = bytearray.fromhex(new_input['txid'])[::-1] + \ int.to_bytes(new_input['outputIndex'], 4, byteorder='little') input.script = bytearray([len(sig)]) + sig + bytearray( [0x21]) + new_input['pubkey'] input.sequence = bytearray([0xFF, 0xFF, 0xFF, 0xFF]) new_transaction.inputs.append(input) starting = False new_transaction.lockTime = bytearray([0, 0, 0, 0]) tx_raw = bytearray(new_transaction.serialize()) return tx_raw, amount
def sign_tx(hw_session: HwSessionInfo, utxos_to_spend: List[wallet_common.UtxoType], tx_outputs: List[wallet_common.TxOutputType], tx_fee): """ Creates a signed transaction. :param hw_session: :param utxos_to_spend: list of utxos to send :param tx_outputs: list of transaction outputs :param tx_fee: transaction fee :return: tuple (serialized tx, total transaction amount in satoshis) """ insight_network = 'insight_fix' if hw_session.app_config.is_testnet(): insight_network += '_testnet' fix_network = hw_session.app_config.fix_network tx_api = MyTxApiInsight(insight_network, '', hw_session.fixd_intf, hw_session.app_config.tx_cache_dir) client = hw_session.hw_client client.set_tx_api(tx_api) inputs = [] outputs = [] inputs_amount = 0 for utxo_index, utxo in enumerate(utxos_to_spend): if not utxo.bip32_path: raise Exception('No BIP32 path for UTXO ' + utxo.txid) address_n = client.expand_path(clean_bip32_path(utxo.bip32_path)) it = proto_types.TxInputType(address_n=address_n, prev_hash=binascii.unhexlify(utxo.txid), prev_index=utxo.output_index) inputs.append(it) inputs_amount += utxo.satoshis outputs_amount = 0 for out in tx_outputs: outputs_amount += out.satoshis if out.address[0] in fix_utils.get_chain_params( fix_network).B58_PREFIXES_SCRIPT_ADDRESS: stype = proto_types.PAYTOSCRIPTHASH logging.debug('Transaction type: PAYTOSCRIPTHASH' + str(stype)) elif out.address[0] in fix_utils.get_chain_params( fix_network).B58_PREFIXES_PUBKEY_ADDRESS: stype = proto_types.PAYTOADDRESS logging.debug('Transaction type: PAYTOADDRESS ' + str(stype)) else: raise Exception('Invalid prefix of the destination address.') if out.bip32_path: address_n = client.expand_path(out.bip32_path) else: address_n = None ot = proto_types.TxOutputType( address=out.address if address_n is None else None, address_n=address_n, amount=out.satoshis, script_type=stype) outputs.append(ot) if outputs_amount + tx_fee != inputs_amount: raise Exception( 'Transaction validation failure: inputs + fee != outputs') signed = client.sign_tx(hw_session.app_config.hw_coin_name, inputs, outputs) logging.info('Signed transaction') return signed[1], inputs_amount
def sign_message(main_ui, bip32path, message): client = main_ui.hw_client address_n = client.expand_path(clean_bip32_path(bip32path)) return client.sign_message('Dash', address_n, message)