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 get_seed(self): password = None if self.wallet.has_keystore_encryption(): password = self.password_dialog(parent=self.d.parent()) if not password: raise UserCancelled() keystore = self.wallet.get_keystore() if not keystore or not keystore.has_seed(): return self.extension = bool(keystore.get_passphrase(password)) return keystore.get_seed(password)
def check_device_dialog(self): match = re.search(r'v([0-9])+\.[0-9]+\.[0-9]+', self.dbb_hid.get_serial_number_string()) if match is None: raise Exception("error detecting firmware version") major_version = int(match.group(1)) if major_version < MIN_MAJOR_VERSION: raise Exception("Please upgrade to the newest firmware using the BitBox Desktop app: https://shiftcrypto.ch/start") # Set password if fresh device if self.password is None and not self.dbb_has_password(): if not self.setupRunning: return False # A fresh device cannot connect to an existing wallet msg = _("An uninitialized Digital Bitbox is detected.") + " " + \ _("Enter a new password below.") + "\n\n" + \ _("REMEMBER THE PASSWORD!") + "\n\n" + \ _("You cannot access your coins or a backup without the password.") + "\n" + \ _("A backup is saved automatically when generating a new wallet.") if self.password_dialog(msg): reply = self.hid_send_plain(b'{"password":"******"}') else: return False # Get password from user if not yet set msg = _("Enter your Digital Bitbox password:"******"led":"blink"}') if 'error' in reply: self.password = None if reply['error']['code'] == 109: msg = _("Incorrect password entered.") + "\n\n" + \ reply['error']['message'] + "\n\n" + \ _("Enter your Digital Bitbox password:"******"Unexpected error occurred.") + "\n\n" + \ reply['error']['message'] + "\n\n" + \ _("Enter your Digital Bitbox password:"******"device":"info"}') if reply['device']['id'] != "": self.recover_or_erase_dialog() # Already seeded else: self.seed_device_dialog() # Seed if not initialized self.mobile_pairing_dialog() return self.isInitialized
def f(method): import threading settings = self.request_trezor_init_settings( wizard, method, device_id) 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 run_upgrades(self, storage): path = storage.path if storage.requires_split(): self.hide() msg = _( "The wallet '{}' contains multiple accounts, which are no longer supported since SmartCryptoTech Electrum Wallet 1.0.\n\n" "Do you want to split your wallet into multiple files?" ).format(path) if not self.question(msg): return file_list = '\n'.join(storage.split_accounts()) msg = _('Your accounts have been moved to' ) + ':\n' + file_list + '\n\n' + _( 'Do you want to delete the old file') + ':\n' + path if self.question(msg): os.remove(path) self.show_warning(_('The file was removed')) # raise now, to avoid having the old storage opened raise UserCancelled() action = storage.get_action() if action and storage.requires_upgrade(): raise WalletFileException( 'Incomplete wallet files cannot be upgraded.') if action: self.hide() msg = _("The file '{}' contains an incompletely created wallet.\n" "Do you want to complete its creation now?").format(path) if not self.question(msg): if self.question( _("Do you want to delete '{}'?").format(path)): os.remove(path) self.show_warning(_('The file was removed')) return self.show() self.data = storage.db.data # FIXME self.run(action) for k, v in self.data.items(): storage.put(k, v) storage.write() return if storage.requires_upgrade(): self.upgrade_storage(storage)
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(binascii.unhexlify(tx.serialize_preimage(i))) hasharray_i = {'hash': to_hexstr(inputHash), '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 o in tx.outputs(): assert o.type == TYPE_ADDRESS info = tx.output_info.get(o.address) if info is not None: index = info.address_index 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_to_network() 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"] = to_hexstr(sha256d(tx_dbb_serialized)) 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 = list(filter(None, txin['signatures'])) if len(signatures) == num: break # txin is complete ii = txin['pubkeys'].index(pubkey) signed = dbb_signatures[i] if 'recid' in signed: # firmware > v2.1.1 recid = int(signed['recid'], 16) s = binascii.unhexlify(signed['sig']) h = inputhasharray[i] pk = ecc.ECPubkey.from_sig_string(s, recid, h) pk = pk.get_public_key_hex(compressed=True) elif 'pubkey' in signed: # firmware <= v2.1.1 pk = signed['pubkey'] if pk != pubkey: continue sig_r = int(signed['sig'][:64], 16) sig_s = int(signed['sig'][64:], 16) sig = ecc.der_sig_from_r_and_s(sig_r, sig_s) sig = to_hexstr(sig) + '01' tx.add_signature_to_txin(i, ii, sig) except UserCancelled: raise except BaseException as e: self.give_error(e, True) else: print_error("Transaction is_complete", tx.is_complete()) tx.raw = tx.serialize()
def select_storage( self, path, get_wallet_from_daemon) -> Tuple[str, Optional[WalletStorage]]: vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(QLabel(_('Wallet') + ':')) self.name_e = QLineEdit() hbox.addWidget(self.name_e) button = QPushButton(_('Choose...')) hbox.addWidget(button) vbox.addLayout(hbox) self.msg_label = QLabel('') vbox.addWidget(self.msg_label) hbox2 = QHBoxLayout() self.pw_e = QLineEdit('', self) self.pw_e.setFixedWidth(150) self.pw_e.setEchoMode(2) self.pw_label = QLabel(_('Password') + ':') hbox2.addWidget(self.pw_label) hbox2.addWidget(self.pw_e) hbox2.addStretch() vbox.addLayout(hbox2) self.set_layout(vbox, title=_('SmartCryptoTech Electrum Wallet')) self.temp_storage = WalletStorage(path, manual_upgrades=True) wallet_folder = os.path.dirname(self.temp_storage.path) def on_choose(): path, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) if path: self.name_e.setText(path) def on_filename(filename): path = os.path.join(wallet_folder, filename) wallet_from_memory = get_wallet_from_daemon(path) try: if wallet_from_memory: self.temp_storage = wallet_from_memory.storage else: self.temp_storage = WalletStorage(path, manual_upgrades=True) self.next_button.setEnabled(True) except BaseException: traceback.print_exc(file=sys.stderr) self.temp_storage = None self.next_button.setEnabled(False) user_needs_to_enter_password = False if self.temp_storage: if not self.temp_storage.file_exists(): msg =_("This file does not exist.") + '\n' \ + _("Press 'Next' to create this wallet, or choose another file.") elif not wallet_from_memory: if self.temp_storage.is_encrypted_with_user_pw(): msg = _("This file is encrypted with a password.") + '\n' \ + _('Enter your password or choose another file.') user_needs_to_enter_password = True elif self.temp_storage.is_encrypted_with_hw_device(): msg = _("This file is encrypted using a hardware device.") + '\n' \ + _("Press 'Next' to choose device to decrypt.") else: msg = _("Press 'Next' to open this wallet.") else: msg = _("This file is already open in memory.") + "\n" \ + _("Press 'Next' to create/focus window.") else: msg = _('Cannot read file') self.msg_label.setText(msg) if user_needs_to_enter_password: self.pw_label.show() self.pw_e.show() self.pw_e.setFocus() else: self.pw_label.hide() self.pw_e.hide() button.clicked.connect(on_choose) self.name_e.textChanged.connect(on_filename) n = os.path.basename(self.temp_storage.path) self.name_e.setText(n) while True: if self.loop.exec_() != 2: # 2 = next raise UserCancelled if self.temp_storage.file_exists( ) and not self.temp_storage.is_encrypted(): break if not self.temp_storage.file_exists(): break wallet_from_memory = get_wallet_from_daemon(self.temp_storage.path) if wallet_from_memory: raise WalletAlreadyOpenInMemory(wallet_from_memory) if self.temp_storage.file_exists( ) and self.temp_storage.is_encrypted(): if self.temp_storage.is_encrypted_with_user_pw(): password = self.pw_e.text() try: self.temp_storage.decrypt(password) break except InvalidPassword as e: QMessageBox.information(None, _('Error'), str(e)) continue except BaseException as e: traceback.print_exc(file=sys.stdout) QMessageBox.information(None, _('Error'), str(e)) raise UserCancelled() elif self.temp_storage.is_encrypted_with_hw_device(): try: self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET, storage=self.temp_storage) except InvalidPassword as e: QMessageBox.information( None, _('Error'), _('Failed to decrypt using this hardware device.') + '\n' + _('If you use a passphrase, make sure it is correct.' )) self.reset_stack() return self.select_storage(path, get_wallet_from_daemon) except BaseException as e: traceback.print_exc(file=sys.stdout) QMessageBox.information(None, _('Error'), str(e)) raise UserCancelled() if self.temp_storage.is_past_initial_decryption(): break else: raise UserCancelled() else: raise Exception('Unexpected encryption version') return self.temp_storage.path, ( self.temp_storage if self.temp_storage.file_exists() else None)