def _copy_electron_cash_wallets(self): """ Work out whether we should show UI to offer to copy the user's Electron Cash wallets to their ElectrumSV wallet directory, and if so, show it and give them the chance. """ # If the user has ElectrumSV wallets already, we do not offer to copy the one's # Electron Cash has. esv_wallets_dir = os.path.join(app_state.config.electrum_path(), "wallets") if len(self._list_user_wallets(esv_wallets_dir)) > 0: return ec_wallets_dir = get_electron_cash_user_dir(esv_wallets_dir) ec_wallet_count = len(self._list_user_wallets(ec_wallets_dir)) # If the user does not have Electron Cash wallets to copy, there's no point in offering. if ec_wallet_count == 0: return vbox, file_list = self._create_copy_electron_cash_wallets_layout( ec_wallets_dir) self._set_standard_layout(vbox, title=_('Import Electron Cash wallets')) v = self.loop.exec_() # Cancel, exit application. if v == -1: raise UserCancelled() if v != 2: raise GoBack() self._do_copy_electron_cash_wallets(file_list, esv_wallets_dir, ec_wallets_dir)
def yes_no_question(self, msg) -> int: self.done.clear() self.yes_no_signal.emit(msg) self.done.wait() if self._ok == -1: raise UserCancelled() return self._ok
def query_choice(self, msg: str, labels: Iterable[str]) -> Optional[int]: self.done.clear() self.query_signal.emit(msg, labels) self.done.wait() if self._choice is None: raise UserCancelled() return self._choice
def callback_Failure(self, msg): # BaseClient's unfortunate call() implementation forces us to # raise exceptions on failure in order to unwind the stack. # However, making the user acknowledge they cancelled # gets old very quickly, so we suppress those. The NotInitialized # one is misnamed and indicates a passphrase request was cancelled. if msg.code in (self.types.Failure_PinCancelled, self.types.Failure_ActionCancelled, self.types.Failure_NotInitialized): raise UserCancelled() raise RuntimeError(msg.message)
def f(method): import threading settings = self.request_trezor_init_settings(wizard, method, model) t = threading.Thread(target=self._initialize_device_safe, args=(settings, method, device_id, wizard, handler)) t.setDaemon(True) t.start() exit_code = wizard.loop.exec_() if exit_code != 0: # this method (initialize_device) was called with the expectation # of leaving the device in an initialized state when finishing. # signal that this is not the case: raise UserCancelled()
def sign_transaction(self, tx: Transaction, password: str) -> None: if tx.is_complete(): return try: p2pkhTransaction = True inputhasharray = [] hasharray = [] pubkeyarray = [] # Build hasharray from inputs for txin in tx.inputs: if txin.type() != ScriptType.P2PKH: p2pkhTransaction = False for x_pubkey in txin.x_pubkeys: if self.is_signature_candidate(x_pubkey): key_derivation = x_pubkey.bip32_path() assert len(key_derivation) == 2 inputPath = "%s/%d/%d" % (self.get_derivation(), *key_derivation) inputHash = tx.preimage_hash(txin) hasharray_i = { 'hash': inputHash.hex(), 'keypath': inputPath } hasharray.append(hasharray_i) inputhasharray.append(inputHash) break else: self.give_error("No matching x_key for sign_transaction" ) # should never happen # Build pubkeyarray from annotated change outputs. # The user is on their own if they have unannotated non-change self-outputs. for txout in tx.outputs: if txout.x_pubkeys: for xpubkey in [ xpk for xpk in txout.x_pubkeys if self.is_signature_candidate(xpk) ]: key_path_text = compose_chain_string( xpubkey.derivation_path())[1:] changePath = self.get_derivation( ) + key_path_text # "/1/0", no "m" pubkeyarray.append({ 'pubkey': xpubkey.to_public_key().to_hex(), 'keypath': changePath, }) # Special serialization of the unsigned transaction for # the mobile verification app. # At the moment, verification only works for p2pkh transactions. if p2pkhTransaction: class CustomTXSerialization(Transaction): @classmethod def input_script(self, txin, estimate_size=False): type_ = txin.type() if type_ == ScriptType.P2PKH: return Transaction.get_preimage_script(txin) if type_ == ScriptType.MULTISIG_P2SH: # Multisig verification has partial support, but is # disabled. This is the expected serialization though, so we # leave it here until we activate it. return '00' + push_script( Transaction.get_preimage_script(txin)) raise RuntimeError(f'unsupported type {type_}') tx_dbb_serialized = CustomTXSerialization.from_hex( tx.serialize()).serialize() else: # We only need this for the signing echo / verification. tx_dbb_serialized = None # Build sign command dbb_signatures: List[Dict[str, Any]] = [] steps = math.ceil(1.0 * len(hasharray) / self.maxInputs) for step in range(int(steps)): hashes = hasharray[step * self.maxInputs:(step + 1) * self.maxInputs] msg_data: Dict[str, Any] = { "sign": { "data": hashes, "checkpub": pubkeyarray, }, } if tx_dbb_serialized is not None: msg_data["sign"]["meta"] = sha256d(tx_dbb_serialized).hex() msg = json.dumps(msg_data).encode('ascii') assert self.plugin is not None dbb_client: DigitalBitbox_Client = self.plugin.get_client(self) if not dbb_client.is_paired(): raise Exception("Could not sign transaction.") reply = dbb_client.hid_send_encrypt(msg) if 'error' in reply: raise Exception(reply['error']['message']) if 'echo' not in reply: raise Exception("Could not sign transaction.") if self.plugin.is_mobile_paired( ) and tx_dbb_serialized is not None: reply['tx'] = tx_dbb_serialized self.plugin.comserver_post_notification(reply) if steps > 1: self.handler.show_message( _("Signing large transaction. Please be patient ...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for " "3 seconds.") + " " + _("(Touch {} of {})").format((step + 1), steps) + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout." ) + "\n\n") else: self.handler.show_message( _("Signing transaction...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for " "3 seconds.") + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout." )) # Send twice, first returns an echo for smart verification reply = dbb_client.hid_send_encrypt(msg) self.handler.finished() if 'error' in reply: if reply["error"].get('code') in (600, 601): # aborted via LED short touch or timeout raise UserCancelled() raise Exception(reply['error']['message']) if 'sign' not in reply: raise Exception("Could not sign transaction.") dbb_signatures.extend(reply['sign']) # Fill signatures if len(dbb_signatures) != len(tx.inputs): raise RuntimeError("Incorrect number of transactions signed") for txin, siginfo, pre_hash in zip(tx.inputs, dbb_signatures, inputhasharray): if txin.is_complete(): continue for pubkey_index, x_pubkey in enumerate(txin.x_pubkeys): compact_sig = bytes.fromhex(siginfo['sig']) if 'recid' in siginfo: # firmware > v2.1.1 recid = int(siginfo['recid'], 16) recoverable_sig = compact_sig + bytes([recid]) pk = PublicKey.from_recoverable_signature( recoverable_sig, pre_hash, None) elif 'pubkey' in siginfo: # firmware <= v2.1.1 pk = PublicKey.from_hex(siginfo['pubkey']) if pk != x_pubkey.to_public_key(): continue full_sig = (compact_signature_to_der(compact_sig) + bytes([Transaction.nHashType() & 255])) txin.signatures[pubkey_index] = full_sig except UserCancelled: raise except Exception as e: self.give_error(e, True) else: logger.debug("Transaction is_complete %s", tx.is_complete())
def _copy_electron_cash_wallets(self): """ Work out whether we should show UI to offer to copy the user's Electron Cash wallets to their ElectrumSV wallet directory, and if so, show it and give them the chance. """ def ignore_wallet_file(wallet_path): if os.path.isdir(wallet_path): return True if wallet_path.startswith("."): return True return False def count_user_wallets(wallets_path): if os.path.exists(wallets_path): filenames = [ filename for filename in os.listdir(wallets_path) if not ignore_wallet_file( os.path.join(wallets_path, filename)) ] return len(filenames) return 0 # If the user has ElectrumSV wallets already, we do not offer to copy the one's # Electron Cash has. esv_wallets_dir = os.path.join(platform.user_dir(), "wallets") if count_user_wallets(esv_wallets_dir) > 0: return ec_wallets_dir = get_electron_cash_user_dir(esv_wallets_dir) ec_wallet_count = count_user_wallets(ec_wallets_dir) # If the user does not have Electron Cash wallets to copy, there's no point in offering. if ec_wallet_count == 0: return def update_summary_label(): selection_count = len(file_list.selectedItems()) if selection_count == 0: summary_label.setText( _("No wallets are selected / will be copied.")) elif selection_count == 1: summary_label.setText( _("1 wallet is selected / will be copied.")) else: summary_label.setText( _("%d wallets are selected / will be copied.") % selection_count) wallet_filenames = sorted(os.listdir(ec_wallets_dir), key=lambda s: s.lower()) file_list = QListWidget() file_list.setSelectionMode(QAbstractItemView.ExtendedSelection) for filename in wallet_filenames: if not ignore_wallet_file(os.path.join(ec_wallets_dir, filename)): file_list.addItem(QListWidgetItem(filename)) file_list.itemSelectionChanged.connect(update_summary_label) vbox = QVBoxLayout() introduction_label = QLabel( _("Your Electron Cash wallet directory was found. If you want ElectrumSV to import " "any of them on your behalf, select the ones you want copied from the list below " "before clicking the Next button.")) introduction_label.setWordWrap(True) vbox.setSpacing(20) vbox.addWidget(introduction_label) vbox.addWidget(file_list) summary_label = QLabel() update_summary_label() vbox.addWidget(summary_label) self._set_standard_layout(vbox, title=_('Import Electron Cash wallets')) v = self.loop.exec_() # Cancel, exit application. if v == -1: raise UserCancelled() if v != 2: raise GoBack() # If the user selected any files, then we copy them before exiting to the next page. for item in file_list.selectedItems(): filename = item.text() source_path = os.path.join(ec_wallets_dir, filename) target_path = os.path.join(esv_wallets_dir, filename) try: shutil.copyfile(source_path, target_path) except shutil.Error: # For now we ignore copy errors. pass
def sign_transaction(self, tx, password): if tx.is_complete(): return try: p2pkhTransaction = True derivations = self.get_tx_derivations(tx) inputhasharray = [] hasharray = [] pubkeyarray = [] # Build hasharray from inputs for i, txin in enumerate(tx.inputs()): if txin['type'] == 'coinbase': self.give_error("Coinbase not supported") # should never happen if txin['type'] != 'p2pkh': p2pkhTransaction = False for x_pubkey in txin['x_pubkeys']: if x_pubkey in derivations: index = derivations.get(x_pubkey) inputPath = "%s/%d/%d" % (self.get_derivation(), index[0], index[1]) inputHash = sha256d(bytes.fromhex(tx.serialize_preimage(i))) hasharray_i = {'hash': inputHash.hex(), 'keypath': inputPath} hasharray.append(hasharray_i) inputhasharray.append(inputHash) break else: self.give_error("No matching x_key for sign_transaction") # should never happen # Build pubkeyarray from outputs for _type, address, amount in tx.outputs(): assert _type == TYPE_ADDRESS info = tx.output_info.get(address) if info is not None: index, xpubs, m = info changePath = self.get_derivation() + "/%d/%d" % index changePubkey = self.derive_pubkey(index[0], index[1]) pubkeyarray_i = {'pubkey': changePubkey, 'keypath': changePath} pubkeyarray.append(pubkeyarray_i) # Special serialization of the unsigned transaction for # the mobile verification app. # At the moment, verification only works for p2pkh transactions. if p2pkhTransaction: class CustomTXSerialization(Transaction): @classmethod def input_script(self, txin, estimate_size=False): if txin['type'] == 'p2pkh': return Transaction.get_preimage_script(txin) if txin['type'] == 'p2sh': # Multisig verification has partial support, but is # disabled. This is the expected serialization though, so we # leave it here until we activate it. return '00' + push_script(Transaction.get_preimage_script(txin)) raise Exception("unsupported type %s" % txin['type']) tx_dbb_serialized = CustomTXSerialization(tx.serialize()).serialize() else: # We only need this for the signing echo / verification. tx_dbb_serialized = None # Build sign command dbb_signatures = [] steps = math.ceil(1.0 * len(hasharray) / self.maxInputs) for step in range(int(steps)): hashes = hasharray[step * self.maxInputs : (step + 1) * self.maxInputs] msg = { "sign": { "data": hashes, "checkpub": pubkeyarray, }, } if tx_dbb_serialized is not None: msg["sign"]["meta"] = sha256d(tx_dbb_serialized).hex() msg = json.dumps(msg).encode('ascii') dbb_client = self.plugin.get_client(self) if not dbb_client.is_paired(): raise Exception("Could not sign transaction.") reply = dbb_client.hid_send_encrypt(msg) if 'error' in reply: raise Exception(reply['error']['message']) if 'echo' not in reply: raise Exception("Could not sign transaction.") if self.plugin.is_mobile_paired() and tx_dbb_serialized is not None: reply['tx'] = tx_dbb_serialized self.plugin.comserver_post_notification(reply) if steps > 1: self.handler.show_message( _("Signing large transaction. Please be patient ...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for " "3 seconds.") + " " + _("(Touch {} of {})").format((step + 1), steps) + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout.") + "\n\n") else: self.handler.show_message( _("Signing transaction...") + "\n\n" + _("To continue, touch the Digital Bitbox's blinking light for " "3 seconds.") + "\n\n" + _("To cancel, briefly touch the blinking light or wait for the timeout.")) # Send twice, first returns an echo for smart verification reply = dbb_client.hid_send_encrypt(msg) self.handler.finished() if 'error' in reply: if reply["error"].get('code') in (600, 601): # aborted via LED short touch or timeout raise UserCancelled() raise Exception(reply['error']['message']) if 'sign' not in reply: raise Exception("Could not sign transaction.") dbb_signatures.extend(reply['sign']) # Fill signatures if len(dbb_signatures) != len(tx.inputs()): raise Exception("Incorrect number of transactions signed.") # Should never occur for i, txin in enumerate(tx.inputs()): num = txin['num_sig'] for pubkey in txin['pubkeys']: signatures = [sig for sig in txin['signatures'] if sig] if len(signatures) == num: break # txin is complete ii = txin['pubkeys'].index(pubkey) siginfo = dbb_signatures[i] compact_sig = bytes.fromhex(siginfo['sig']) if 'recid' in siginfo: # firmware > v2.1.1 recid = int(siginfo['recid'], 16) recoverable_sig = compact_sig + bytes([recid]) h = inputhasharray[i] pk = PublicKey.from_recoverable_signature(recoverable_sig, h, None) pk = pk.to_hex(compressed=True) elif 'pubkey' in siginfo: # firmware <= v2.1.1 pk = siginfo['pubkey'] if pk != pubkey: continue sig = (compact_signature_to_der(compact_sig) + bytes([Transaction.nHashType() & 255])) tx.add_signature_to_txin(i, ii, sig.hex()) except UserCancelled: raise except Exception as e: self.give_error(e, True) else: logger.debug("Transaction is_complete %s", tx.is_complete()) tx.raw = tx.serialize()