def set_text(widget, text: str): def call(widget, text): widget.setText(text) widget.repaint() widget.setVisible(True) WndUtils.call_in_main_thread(call, widget, text)
def hide_request_character_dialog(self): if self.char_request_dialog and self.char_request_dialog_shown: def hide(): self.char_request_dialog.hide() del self.char_request_dialog self.char_request_dialog = None self.char_request_dialog_shown = False WndUtils.call_in_main_thread(hide)
def get_collateral_tx_address_thread(self, ctrl: CtrlObject, bip44_wallet: Bip44Wallet): utxos = [] break_scanning = False txes_cnt = 0 msg = 'Scanning wallet transactions for 1000 Dash UTXOs.<br>' \ 'This may take a while (<a href="break">break</a>)....' ctrl.dlg_config_fun(dlg_title="Scanning wallet", show_progress_bar=False) ctrl.display_msg_fun(msg) def check_break_scanning(): nonlocal break_scanning if break_scanning: # stop the scanning process if the dialog finishes or the address/bip32path has been found raise BreakFetchTransactionsException() def fetch_txes_feeback(tx_cnt: int): nonlocal msg, txes_cnt txes_cnt += tx_cnt ctrl.display_msg_fun(msg + '<br><br>' + 'Number of transactions fetched so far: ' + str(txes_cnt)) def on_msg_link_activated(link: str): nonlocal break_scanning if link == 'break': break_scanning = True lbl = ctrl.get_msg_label_control() if lbl: def set(): lbl.setOpenExternalLinks(False) lbl.setTextInteractionFlags(lbl.textInteractionFlags() & ~Qt.TextSelectableByMouse) lbl.linkActivated.connect(on_msg_link_activated) lbl.repaint() WndUtils.call_in_main_thread(set) try: bip44_wallet.on_fetch_account_txs_feedback = fetch_txes_feeback bip44_wallet.fetch_all_accounts_txs(check_break_scanning) for utxo in bip44_wallet.list_utxos_for_account( account_id=None, filter_by_satoshis=1e11): utxos.append(utxo) except BreakFetchTransactionsException: return None return utxos
def get_latest_firmware_thread(self, ctr: CtrlObject, hw_model: HWModel): try: fws = hw_intf.get_hw_firmware_web_sources((hw_model, ), only_official=True, only_latest=True) if fws: self.latest_firmwares[hw_model] = fws[0] WndUtils.call_in_main_thread(self.update_ui) else: self.latest_firmwares[ hw_model] = None # mark that we weren't able to read the latest firmware version except Exception as e: logging.error( 'Error while reading the latest version of the hw firmware: ' + str(e))
def ask_for_pin_callback(msg, hide_numbers=True): def dlg(): ui = hw_pin_dlg.HardwareWalletPinDlg(msg, hide_numbers=hide_numbers) if ui.exec_(): return ui.pin else: return None if threading.current_thread() != threading.main_thread(): return WndUtils.call_in_main_thread(dlg) else: return dlg()
def ask_for_pass_callback(): def dlg(): ui = hw_pass_dlg.HardwareWalletPassDlg() if ui.exec_(): return ui.getPassphrase() else: return None if threading.current_thread() != threading.main_thread(): return WndUtils.call_in_main_thread(dlg) else: return dlg()
def ask_for_word_callback(msg: str, wordlist: List[str]) -> str: def dlg(): ui = hw_word_dlg.HardwareWalletWordDlg(msg, wordlist) if ui.exec_(): return ui.get_word() else: return None if threading.current_thread() != threading.main_thread(): return WndUtils.call_in_main_thread(dlg) else: return dlg()
def ask_for_pass_callback(pass_available_on_device: bool = False): def dlg(): ui = hw_pass_dlg.HardwareWalletPassDlg(pass_available_on_device) if ui.exec_(): if ui.getEnterOnDevice(): return PASSPHRASE_ON_DEVICE else: return ui.getPassphrase() else: return None if threading.current_thread() != threading.main_thread(): return WndUtils.call_in_main_thread(dlg) else: return dlg()
def connect_trezor( device_id: Optional[str] = None) -> Optional[MyTrezorClient]: """ Connect to a Trezor device. :param device_id: :return: ref to a trezor client if connection successfull or None if we are sure that no Trezor device connected. """ logging.info('Started function') def get_client() -> Optional[MyTrezorClient]: from trezorlib.transport_hid import HidTransport count = len(HidTransport.enumerate()) if not count: logging.warning('Number of Trezor devices: 0') for d in HidTransport.enumerate(): transport = HidTransport(d) client = MyTrezorClient(transport, ask_for_pin_callback, ask_for_pass_callback) if not device_id or client.features.device_id == device_id: return client else: client.clear_session() client.close() return None # HidTransport.enumerate() has to be called in the main thread - second call from bg thread # causes SIGSEGV client = WndUtils.call_in_main_thread(get_client) if client: logging.info( 'Trezor connected. Firmware version: %s.%s.%s, vendor: %s, initialized: %s, ' 'pp_protection: %s, pp_cached: %s, bootloader_mode: %s ' % (str(client.features.major_version), str(client.features.minor_version), str(client.features.patch_version), str( client.features.vendor), str(client.features.initialized), str(client.features.passphrase_protection), str(client.features.passphrase_cached), str(client.features.bootloader_mode))) return client else: if device_id: msg = 'Cannot connect to the Trezor device with this id: %s.' % device_id else: msg = 'Cannot find any Trezor device.' raise Exception(msg)
def connect_keepkey(passphrase_encoding: Optional[str] = 'NFC', device_id: Optional[str] = None) -> Optional[MyKeepkeyClient]: """ Connect to a Keepkey device. :passphrase_encoding: Allowed values: 'NFC' or 'NFKD'. Note: Keekpey uses NFC encoding for passphrases, which is incompatible with BIP-39 standard (NFKD). This argument gives the possibility to enforce comforming the standard encoding. :return: ref to a keepkey client if connection successfull or None if we are sure that no Keepkey device connected. """ logging.info('Started function') def get_client() -> Optional[MyKeepkeyClient]: from keepkeylib.transport_hid import HidTransport count = len(HidTransport.enumerate()) if not count: logging.warning('Number of Keepkey devices: 0') for d in HidTransport.enumerate(): transport = HidTransport(d) client = MyKeepkeyClient(transport, ask_for_pin_callback, ask_for_pass_callback, passphrase_encoding) if not device_id or client.features.device_id == device_id: return client else: client.clear_session() client.close() return None # HidTransport.enumerate() has to be called in the main thread - second call from bg thread # causes SIGSEGV client = WndUtils.call_in_main_thread(get_client) if client: logging.info('Keepkey connected. Firmware version: %s.%s.%s, vendor: %s, initialized: %s, ' 'pp_protection: %s, pp_cached: %s, bootloader_mode: %s ' % (str(client.features.major_version), str(client.features.minor_version), str(client.features.patch_version), str(client.features.vendor), str(client.features.initialized), str(client.features.passphrase_protection), str(client.features.passphrase_cached), str(client.features.bootloader_mode))) return client else: if device_id: msg = 'Cannot connect to the Keepkey device with this id: .' % device_id else: msg = 'Cannot find any Keepkey device.' raise Exception(msg)
def sign_protx_message_with_hw(self, msg_to_sign) -> str: sig = WndUtils.call_in_main_thread( hw_intf.hw_sign_message, self.main_dlg.hw_session, self.dmn_collateral_tx_address_path, msg_to_sign, 'Click the confirmation button on your hardware wallet to sign the ProTx payload message.' ) if sig.address != self.dmn_collateral_tx_address: log.error( f'Protx payload signature address mismatch. Is: {sig.address}, should be: ' f'{self.dmn_collateral_tx_address}.') raise Exception( f'Protx payload signature address mismatch. Is: {sig.address}, should be: ' f'{self.dmn_collateral_tx_address}.') else: sig_bin = base64.b64encode(sig.signature) payload_sig_str = sig_bin.decode('ascii') return payload_sig_str
def ask_for_password(username, host, message=None): if not SshPassCache.parent_window: raise Exception('SshPassCache not initialized') def query_psw(msg): password, ok = QInputDialog.getText(SshPassCache.parent_window, 'Password Dialog', msg, echo=QLineEdit.Password) return password, ok if not message: message = 'Enter password for ' + username + '@' + host + ':' if threading.current_thread() != threading.main_thread(): password, ok = WndUtils.call_in_main_thread(query_psw, message) else: password, ok = query_psw(message) if not ok: raise UserCancelledConnection return password
def ask_for_martix_element_callback(msg, columns: int = 3, parent_window: Optional[QWidget] = None): # noinspection SqlResolve def dlg(): ui = hw_pin_dlg.HardwareWalletPinDlg( msg, hide_numbers=True, window_title='Enter element of seed word', max_length=1, button_heights=35, parent_window=parent_window, columns=columns) if ui.exec_(): return ui.pin else: return None if threading.current_thread() != threading.main_thread(): return WndUtils.call_in_main_thread(dlg) else: return dlg()
def upload_firmware_thread(self, ctrl: CtrlObject): """ Thread that performs upload of the selected firmware to the hardware wallet device. The device has to be in bootloader mode. """ def add_info(msg: str): def append_message_mainth(msg_: str): t = self.lblUploadFirmwareMessage.text() t += f'<div>{msg}</div>' self.lblUploadFirmwareMessage.setText(t) WndUtils.call_in_main_thread(append_message_mainth, msg) update_ok = False try: self.hw_conn_change_allowed = False add_info('Uploading new firmware.<br><br><b>Click the confirmation button on your hardware wallet device ' 'if necessary.</b>') if self.cur_hw_device.hw_type == HWType.trezor: update_ok = self.cur_hw_device.hw_client.firmware_update(self.selected_firmware_source_web.fingerprint, self.firmware_data) elif self.cur_hw_device.hw_type == HWType.keepkey: update_ok = self.cur_hw_device.hw_client.firmware_update(BytesIO(self.firmware_data)) else: WndUtils.call_in_main_thread(self.go_to_prev_step) raise Exception('Invalid hardware wallet type') if not update_ok: WndUtils.error_msg('Operation failed. Look into the log file for details.') except CancelException: pass except Exception as e: WndUtils.error_msg('Operation failed with the following error: ' + str(e)) finally: self.hw_conn_change_allowed = True self.upload_firmware_thread_obj = None if update_ok: WndUtils.call_in_main_thread(self.go_to_next_step) else: WndUtils.call_in_main_thread(self.go_to_prev_step)
def request_character(self, cur_characters: Optional[str], label: Optional[str] = None) -> int: return WndUtils.call_in_main_thread(self._request_character, cur_characters, label)
def connect_keepkey(passphrase_encoding: Optional[str] = 'NFC', device_id: Optional[str] = None) -> Optional[MyKeepkeyClient]: """ Connect to a Keepkey device. :passphrase_encoding: Allowed values: 'NFC' or 'NFKD'. Note: Keekpey uses NFC encoding for passphrases, which is incompatible with BIP-39 standard (NFKD). This argument gives the possibility to enforce comforming the standard encoding. :return: ref to a keepkey client if connection successfull or None if we are sure that no Keepkey device connected. """ logging.info('Started function') def get_client() -> Optional[MyKeepkeyClient]: hw_clients, exceptions = get_device_list(passphrase_encoding=passphrase_encoding) if not hw_clients: if exceptions: raise exceptions[0] else: selected_client = None if device_id: # we have to select a device with the particular id number for cli in hw_clients: if cli['device_id'] == device_id: selected_client = cli['client'] break else: cli['client'].close() cli['client'] = None else: # we are not forced to automatically select the particular device if len(hw_clients) > 1: hw_names = [a['desc'] for a in hw_clients] selected_index = select_hw_device(None, 'Select Keepkey device', hw_names) if selected_index is not None and (0 <= selected_index < len(hw_clients)): selected_client = hw_clients[selected_index] else: selected_client = hw_clients[0]['client'] # close all the clients but the selected one for cli in hw_clients: if cli['client'] != selected_client: cli['client'].close() cli['client'] = None return selected_client return None # HidTransport.enumerate() has to be called in the main thread - second call from bg thread # causes SIGSEGV client = WndUtils.call_in_main_thread(get_client) if client: logging.info('Keepkey connected. Firmware version: %s.%s.%s, vendor: %s, initialized: %s, ' 'pp_protection: %s, pp_cached: %s, bootloader_mode: %s ' % (str(client.features.major_version), str(client.features.minor_version), str(client.features.patch_version), str(client.features.vendor), str(client.features.initialized), str(client.features.passphrase_protection), str(client.features.passphrase_cached), str(client.features.bootloader_mode))) return client else: if device_id: msg = 'Cannot connect to the Keepkey device with this id: .' % device_id else: msg = 'Cannot find any Keepkey device.' raise Exception(msg)
def connect_trezor( device_id: Optional[str] = None) -> Optional[MyTrezorClient]: """ Connect to a Trezor device. :param device_id: :return: ref to a trezor client if connection successfull or None if we are sure that no Trezor device connected. """ logging.info('Started function') def get_client() -> Optional[MyTrezorClient]: hw_clients, exceptions = get_device_list() if not hw_clients: if exceptions: raise exceptions[0] else: selected_client = None if device_id: # we have to select a device with the particular id number for cli in hw_clients: if cli['device_id'] == device_id: selected_client = cli['client'] break else: cli['client'].close() cli['client'] = None else: # we are not forced to automatically select the particular device if len(hw_clients) > 1: hw_names = [a['desc'] for a in hw_clients] selected_index = select_hw_device(None, 'Select Trezor device', hw_names) if selected_index is not None and (0 <= selected_index < len(hw_clients)): selected_client = hw_clients[selected_index]['client'] else: selected_client = hw_clients[0]['client'] # close all the clients but the selected one for cli in hw_clients: if cli['client'] != selected_client: cli['client'].close() cli['client'] = None return selected_client return None # HidTransport.enumerate() has to be called in the main thread - second call from bg thread # causes SIGSEGV client = WndUtils.call_in_main_thread(get_client) if client: logging.info( 'Trezor connected. Firmware version: %s.%s.%s, vendor: %s, initialized: %s, ' 'pp_protection: %s, pp_cached: %s, bootloader_mode: %s ' % (str(client.features.major_version), str(client.features.minor_version), str(client.features.patch_version), str( client.features.vendor), str(client.features.initialized), str(client.features.passphrase_protection), str(client.features.passphrase_cached), str(client.features.bootloader_mode))) return client else: if device_id: msg = 'Cannot connect to the Trezor device with this id: %s.' % device_id else: msg = 'Cannot find any Trezor device.' raise Exception(msg)
def prepare_firmware_upload_thread(self, ctrl: CtrlObject): """ Thread that performs the steps of preparing the firmware for upload and making sure that the device is in the bootloader mode. """ def add_info(msg: str): def append_message_mainth(msg_: str): t = self.lblPrepareFirmwareMessage.text() t += f'<div>{msg}</div>' self.lblPrepareFirmwareMessage.setText(t) WndUtils.call_in_main_thread(append_message_mainth, msg) def operation_succeeded(): self.set_btn_continue_enabled(True) self.set_btn_back_enabled(True) self.set_btn_cancel_enabled(True) def operation_failed(message: str): WndUtils.error_msg(message) self.set_btn_continue_enabled(False) self.set_btn_back_enabled(True) self.set_btn_cancel_enabled(True) try: firmware_fingerprint = '' if self.hw_firmware_source_type == FirmwareSource.INTERNET: if not self.selected_firmware_source_web: raise Exception('Firmware source not available.') url = self.selected_firmware_source_web.url firmware_fingerprint = self.selected_firmware_source_web.fingerprint file_name = os.path.basename(urllib.parse.urlparse(url).path) f_, ext_ = os.path.splitext(file_name) if f_ and not re.match('.*\d+\.\d+\.\d+.*', f_): # add version string to the name of the file being downloaded file_name = f_ + '-' + self.selected_firmware_source_web.version + ext_ local_file_path = os.path.join(self.app_config.cache_dir, file_name) add_info(f' * Downloading firmware from <a href="{url}">{url}</a>') try: response = urllib.request.Request(url, data=None, headers={'User-Agent': app_defs.BROWSER_USER_AGENT}) f = urllib.request.urlopen(response) self.firmware_data = f.read() except Exception as e: raise Exception('Could not download firmware file ' + url + ': ' + str(e)) try: add_info(' * Saving firmware to a temp file ' + local_file_path) with open(local_file_path, 'wb') as out_fptr: out_fptr.write(self.firmware_data) except Exception as e: pass else: add_info(f' * Reading firmware from ' + self.selected_firmware_source_file) with open(self.selected_firmware_source_file, 'rb') as fptr: self.firmware_data = fptr.read() add_info(' * Verifying firmware') if self.cur_hw_device.hw_type == HWType.trezor: if self.firmware_data[:8] == b'54525a52' or self.firmware_data[:8] == b'54525a56': data = binascii.unhexlify(self.firmware_data) else: data = self.firmware_data self.cur_hw_device.hw_client.validate_firmware(firmware_fingerprint, data) elif self.cur_hw_device.hw_type == HWType.keepkey: if self.firmware_data[:8] == b'4b504b59': data = binascii.unhexlify(self.firmware_data) else: data = self.firmware_data self.cur_hw_device.hw_client.validate_firmware(firmware_fingerprint, data) add_info('<br><b>1. Make sure you have your backup seed on hand as some updates may erase data from your ' 'device.</b><br><br>' '<b>2. Put the device into bootloader mode and press the <Continue> button to upload the ' 'new firmware.</b>') WndUtils.call_in_main_thread(operation_succeeded) except Exception as e: WndUtils.call_in_main_thread(operation_failed, str(e))
def add_info(msg: str): def append_message_mainth(msg_: str): t = self.lblUploadFirmwareMessage.text() t += f'<div>{msg}</div>' self.lblUploadFirmwareMessage.setText(t) WndUtils.call_in_main_thread(append_message_mainth, msg)
def get_collateral_tx_address_thread(self, ctrl: CtrlObject): txes_cnt = 0 msg = '' break_scanning = False ctrl.dlg_config_fun(dlg_title="Validating collateral transaction.", show_progress_bar=False) ctrl.display_msg_fun('Verifying collateral transaction...') def check_break_scanning(): nonlocal break_scanning if self.finishing or break_scanning: # stop the scanning process if the dialog finishes or the address/bip32path has been found raise BreakFetchTransactionsException() def fetch_txes_feeback(tx_cnt: int): nonlocal msg, txes_cnt txes_cnt += tx_cnt ctrl.display_msg_fun(msg + '<br><br>' + 'Number of transactions fetched so far: ' + str(txes_cnt)) def on_msg_link_activated(link: str): nonlocal break_scanning if link == 'break': break_scanning = True try: tx = self.dashd_intf.getrawtransaction(self.dmn_collateral_tx, 1, skip_cache=True) except Exception as e: raise Exception( 'Cannot get the collateral transaction due to the following errror: ' + str(e)) vouts = tx.get('vout') if vouts: if self.dmn_collateral_tx_index < len(vouts): vout = vouts[self.dmn_collateral_tx_index] spk = vout.get('scriptPubKey') if not spk: raise Exception( f'The collateral transaction ({self.dmn_collateral_tx}) output ' f'({self.dmn_collateral_tx_index}) doesn\'t have value in the scriptPubKey ' f'field.') ads = spk.get('addresses') if not ads or len(ads) < 0: raise Exception( 'The collateral transaction output doesn\'t have the Dash address assigned.' ) self.dmn_collateral_tx_address = ads[0] else: raise Exception( f'Transaction {self.dmn_collateral_tx} doesn\'t have output with index: ' f'{self.dmn_collateral_tx_index}') else: raise Exception('Invalid collateral transaction') ctrl.display_msg_fun( 'Verifying the collateral transaction address on your hardware wallet.' ) if not self.main_dlg.connect_hardware_wallet(): return False if self.dmn_collateral_tx_address_path: addr = hw_intf.get_address(self.main_dlg.hw_session, self.dmn_collateral_tx_address_path) msg = '' if addr != self.dmn_collateral_tx_address: log.warning( f'The address returned by the hardware wallet ({addr}) for the BIP32 path ' f'{self.dmn_collateral_tx_address_path} differs from the address stored the mn configuration ' f'(self.dmn_collateral_tx_address). Need to scan wallet for a correct BIP32 path.' ) msg = '<span style="color:red">The BIP32 path of the collateral address from your mn config is incorret.<br></span>' \ f'Trying to find the BIP32 path of the address {self.dmn_collateral_tx_address} in your wallet.' \ f'<br>This may take a while (<a href="break">break</a>)...' self.dmn_collateral_tx_address_path = '' else: msg = 'Looking for a BIP32 path of the Dash address related to the masternode collateral.<br>' \ 'This may take a while (<a href="break">break</a>)....' if not self.dmn_collateral_tx_address_path and not self.finishing: lbl = ctrl.get_msg_label_control() if lbl: def set(): lbl.setOpenExternalLinks(False) lbl.setTextInteractionFlags(lbl.textInteractionFlags() & ~Qt.TextSelectableByMouse) lbl.linkActivated.connect(on_msg_link_activated) lbl.repaint() WndUtils.call_in_main_thread(set) ctrl.display_msg_fun(msg) # fetch the transactions that involved the addresses stored in the wallet - during this # all the used addresses are revealed addr = self.bip44_wallet.scan_wallet_for_address( self.dmn_collateral_tx_address, check_break_scanning, fetch_txes_feeback) if not addr: if not break_scanning: WndUtils.errorMsg( f'Couldn\'t find a BIP32 path of the collateral address ({self.dmn_collateral_tx_address}).' ) return False else: self.dmn_collateral_tx_address_path = addr.bip32_path return True
def finished_with_success(): def call(): self.next_step() WndUtils.call_in_main_thread(call)