Example #1
0
    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)
Example #2
0
 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
Example #3
0
 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
Example #4
0
 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)
Example #5
0
 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()
Example #6
0
    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())
Example #7
0
    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
Example #8
0
    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()