def restore(self, t): if t == 'standard': text = self.enter_seed_dialog(MSG_ENTER_ANYTHING, None) if not text: return password = self.password_dialog() if Wallet.is_seed(text) or Wallet.is_xprv(text) or Wallet.is_private_key(text) else None wallet = Wallet.from_text(text, password, self.storage) elif re.match('(\d+)of(\d+)', t): n = int(re.match('(\d+)of(\d+)', t).group(2)) key_list = self.multi_seed_dialog(n - 1) if not key_list: return password = self.password_dialog() if any(map(lambda x: Wallet.is_seed(x) or Wallet.is_xprv(x), key_list)) else None wallet = Wallet.from_multisig(key_list, password, self.storage, t) else: self.storage.put('wallet_type', t, False) # call the constructor to load the plugin (side effect) Wallet(self.storage) wallet = always_hook('installwizard_restore', self, self.storage) if not wallet: util.print_error("no wallet") return # create first keys offline self.waiting_dialog(wallet.synchronize) return wallet
def close_wallet(self): print_error("keepkey: clear session") if self.client: self.client.clear_session() self.client.transport.close() self.client = None self.wallet = None
def give_error(self, message, clear_client=False): print_error(message) if not self.signing: self.handler.show_error(message) else: self.signing = False if clear_client: self.client = None raise Exception(message)
def load_theme(self): """Load theme retrieved from wallet file.""" try: theme_prefix, theme_path = self.themes[self.theme_name] except KeyError: util.print_error("Theme not found!", self.theme_name) return full_theme_path = "%s/%s/style.css" % (theme_prefix, theme_path) with open(full_theme_path) as style_file: qApp.setStyleSheet(style_file.read())
def give_error(self, message, clear_client = False): print_error(message) if not self.signing: QMessageBox.warning(QDialog(), _('Warning'), _(message), _('OK')) else: self.signing = False if clear_client and self.client is not None: self.client.bad = True self.device_checked = False raise Exception(message)
def give_error(self, message, clear_client=False): print_error(message) if not self.signing: QMessageBox.warning(QDialog(), _('Warning'), _(message), _('OK')) else: self.signing = False if clear_client and self.client is not None: self.client.bad = True self.device_checked = False raise Exception(message)
def hid_send_plain(self, msg): reply = "" try: self.dbb_hid.write('\0' + bytearray(msg) + '\0' * (self.hidBufSize - len(msg))) r = [] while len(r) < self.hidBufSize: r = r + self.dbb_hid.read(self.hidBufSize) r = str(bytearray(r)).rstrip(' \t\r\n\0') r = r.replace("\0", '') reply = json.loads(r) except Exception as e: print_error('Exception caught ' + str(e)) return reply
def hid_send_encrypt(self, msg): reply = "" try: secret = Hash(self.password) msg = EncodeAES(secret, msg) reply = self.hid_send_plain(msg) if 'ciphertext' in reply: reply = DecodeAES(secret, ''.join(reply["ciphertext"])) reply = json.loads(reply) if 'error' in reply: self.password = None except Exception as e: print_error('Exception caught ' + str(e)) return reply
def set_dark_theme_if_needed(self): use_dark_theme = self.config.get('qt_gui_color_theme', 'default') == 'dark' if use_dark_theme: try: import qdarkstyle self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) except BaseException as e: use_dark_theme = False print_error('Error setting dark theme: {}'.format(e)) # Even if we ourselves don't set the dark theme, # the OS/window manager/etc might set *a dark theme*. # Hence, try to choose colors accordingly: ColorScheme.update_from_widget(QWidget(), force_dark=use_dark_theme)
def restore(self, t): if t == 'standard': text = self.enter_seed_dialog(MSG_ENTER_ANYTHING, None) if not text: return if Wallet.is_xprv(text): password = self.password_dialog() wallet = Wallet.from_xprv(text, password, self.storage) elif Wallet.is_old_mpk(text): wallet = Wallet.from_old_mpk(text, self.storage) elif Wallet.is_xpub(text): wallet = Wallet.from_xpub(text, self.storage) elif Wallet.is_address(text): wallet = Wallet.from_address(text, self.storage) elif Wallet.is_private_key(text): password = self.password_dialog() wallet = Wallet.from_private_key(text, password, self.storage) elif Wallet.is_seed(text): password = self.password_dialog() wallet = Wallet.from_seed(text, password, self.storage) else: raise BaseException('unknown wallet type') elif re.match('(\d+)of(\d+)', t): n = int(re.match('(\d+)of(\d+)', t).group(2)) key_list = self.multi_seed_dialog(n - 1) if not key_list: return password = self.password_dialog() if any( map(lambda x: Wallet.is_seed(x) or Wallet.is_xprv(x), key_list)) else None wallet = Wallet.from_multisig(key_list, password, self.storage, t) else: self.storage.put('wallet_type', t, False) # call the constructor to load the plugin (side effect) Wallet(self.storage) wallet = always_hook('installwizard_restore', self, self.storage) if not wallet: util.print_error("no wallet") return # create first keys offline self.waiting_dialog(wallet.synchronize) return wallet
def sign_transaction(self, tx, password): # Build a PSBT in memory, upload it for signing. # - we can also work offline (without paired device present) if tx.is_complete(): return client = self.get_client() assert client.dev.master_fingerprint == self.ckcc_xfp raw_psbt = self.build_psbt(tx) #open('debug.psbt', 'wb').write(out_fd.getvalue()) try: try: self.handler.show_message("Authorize Transaction...") client.sign_transaction_start(raw_psbt, True) while 1: # How to kill some time, without locking UI? time.sleep(0.250) resp = client.sign_transaction_poll() if resp is not None: break rlen, rsha = resp # download the resulting txn. new_raw = client.download_file(rlen, rsha) finally: self.handler.finished() except (CCUserRefused, CCBusyError) as exc: print_error('[coldcard]', 'Did not sign:', str(exc)) self.handler.show_error(str(exc)) return except BaseException as e: traceback.print_exc(file=sys.stderr) self.give_error(e, True) return # trust the coldcard to re-searilize final product right? tx.update(bh2u(new_raw))
def use_tor_proxy(self, use_it): if not use_it: self.proxy_cb.setChecked(False) else: socks5_mode_index = self.proxy_mode.findText('SOCKS5') if socks5_mode_index == -1: print_error("[network_dialog] can't find proxy_mode 'SOCKS5'") return self.proxy_mode.setCurrentIndex(socks5_mode_index) self.proxy_host.setText("127.0.0.1") self.proxy_port.setText(str(self.tor_proxy[1])) self.proxy_user.setText("") self.proxy_password.setText("") self.tor_cb.setChecked(True) self.proxy_cb.setChecked(True) self.check_disable_proxy(use_it) self.set_proxy()
def run(self): self.is_running = True while self.is_running: if not self.keyhash: time.sleep(2) continue if not self.message: try: self.message = server.get(self.keyhash) except Exception as e: util.print_error("cannot contact cosigner pool") time.sleep(30) continue if self.message: self.parent.win.emit(SIGNAL("cosigner:receive")) # poll every 30 seconds time.sleep(30)
def get_xpub(self, bip32_path, xtype): assert xtype in ColdcardPlugin.SUPPORTED_XTYPES print_error('[coldcard]', 'Derive xtype = %r' % xtype) xpub = self.dev.send_recv(CCProtocolPacker.get_xpub(bip32_path), timeout=5000) # TODO handle timeout? # change type of xpub to the requested type try: __, depth, fingerprint, child_number, c, cK = deserialize_xpub( xpub) except InvalidMasterKeyVersionBytes: raise Exception( _('Invalid xpub magic. Make sure your {} device is set to the correct chain.' ).format(self.device)) from None if xtype != 'standard': xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number) return xpub
def hid_send_plain(self, msg): reply = "" try: serial_number = self.dbb_hid.get_serial_number_string() if "v2.0." in serial_number or "v1." in serial_number: hidBufSize = 4096 self.dbb_hid.write('\0' + msg + '\0' * (hidBufSize - len(msg))) r = bytearray() while len(r) < hidBufSize: r += bytearray(self.dbb_hid.read(hidBufSize)) else: self.hid_send_frame(msg) r = self.hid_read_frame() r = r.rstrip(b' \t\r\n\0') r = r.replace(b"\0", b'') r = to_string(r, 'utf8') reply = json.loads(r) except Exception as e: print_error('Exception caught ' + str(e)) return reply
def get_xpub(self, bip32_path): self.checkDevice() # bip32_path is of the form 44'/17'/1' # S-L-O-W - we don't handle the fingerprint directly, so compute # it manually from the previous node # This only happens once so it's bearable #self.get_client() # prompt for the PIN before displaying the dialog if necessary #self.handler.show_message("Computing master public key") try: splitPath = bip32_path.split('/') if splitPath[0] == 'm': splitPath = splitPath[1:] bip32_path = bip32_path[2:] fingerprint = 0 if len(splitPath) > 1: prevPath = "/".join(splitPath[0:len(splitPath) - 1]) nodeData = self.dongleObject.getWalletPublicKey(prevPath) publicKey = compress_public_key(nodeData['publicKey']) h = hashlib.new('ripemd160') h.update(hashlib.sha256(publicKey).digest()) fingerprint = unpack(">I", h.digest()[0:4])[0] nodeData = self.dongleObject.getWalletPublicKey(bip32_path) publicKey = compress_public_key(nodeData['publicKey']) depth = len(splitPath) lastChild = splitPath[len(splitPath) - 1].split('\'') if len(lastChild) == 1: childnum = int(lastChild[0]) else: childnum = 0x80000000 | int(lastChild[0]) xpub = bitcoin.serialize_xpub(0, str(nodeData['chainCode']), str(publicKey), depth, self.i4b(fingerprint), self.i4b(childnum)) return xpub except Exception as e: print_error(e) return None
def verify_connection(self, expected_xfp, expected_xpub): ex = (expected_xfp, expected_xpub) if self._expected_device == ex: # all is as expected return if ((self._expected_device is not None) or (self.dev.master_fingerprint != expected_xfp) or (self.dev.master_xpub != expected_xpub)): # probably indicating programing error, not hacking raise RuntimeError( "Expecting 0x%08x but that's not whats connected?!" % expected_xfp) # check signature over session key # - mitm might have lied about xfp and xpub up to here # - important that we use value capture at wallet creation time, not some value # we read over USB today self.dev.check_mitm(expected_xpub=expected_xpub) self._expected_device = ex print_error("[coldcard]", "Successfully verified against MiTM")
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import webbrowser import datetime from electrum_grs.address_synchronizer import TX_HEIGHT_LOCAL from .util import * from electrum_grs.i18n import _ from electrum_grs.util import block_explorer_URL, profiler, print_error, TxMinedStatus try: from electrum_grs.plot import plot_history, NothingToPlotException except: print_error("qt/history_list: could not import electrum_grs.plot. This feature needs matplotlib to be installed.") plot_history = None # note: this list needs to be kept in sync with another in kivy TX_ICONS = [ "unconfirmed.png", "warning.png", "unconfirmed.png", "offline_tx.png", "clock1.png", "clock2.png", "clock3.png", "clock4.png", "clock5.png", "confirmed.png", ]
def give_error(message): print_error(message) raise Exception(message)
def start_new_window(self, path, uri, app_is_starting=False): '''Raises the window for the wallet if it is open. Otherwise opens the wallet and creates a new window for it''' try: wallet = self.daemon.load_wallet(path, None) except BaseException as e: traceback.print_exc(file=sys.stdout) d = QMessageBox(QMessageBox.Warning, _('Error'), _('Cannot load wallet') + ' (1):\n' + str(e)) d.exec_() if app_is_starting: # do not return so that the wizard can appear wallet = None else: return if not wallet: storage = WalletStorage(path, manual_upgrades=True) wizard = InstallWizard(self.config, self.app, self.plugins, storage) try: wallet = wizard.run_and_get_wallet(self.daemon.get_wallet) except UserCancelled: pass except GoBack as e: print_error('[start_new_window] Exception caught (GoBack)', e) except (WalletFileException, BitcoinException) as e: traceback.print_exc(file=sys.stderr) d = QMessageBox(QMessageBox.Warning, _('Error'), _('Cannot load wallet') + ' (2):\n' + str(e)) d.exec_() return finally: wizard.terminate() if not wallet: return if not self.daemon.get_wallet(wallet.storage.path): # wallet was not in memory wallet.start_threads(self.daemon.network) self.daemon.add_wallet(wallet) try: for w in self.windows: if w.wallet.storage.path == wallet.storage.path: w.bring_to_top() return w = self.create_window_for_wallet(wallet) except BaseException as e: traceback.print_exc(file=sys.stdout) d = QMessageBox( QMessageBox.Warning, _('Error'), _('Cannot create window for wallet') + ':\n' + str(e)) d.exec_() return if uri: w.pay_to_URI(uri) w.bring_to_top() w.setWindowState(w.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) # this will activate the window w.activateWindow() return w
def sign_transaction(self, tx, password): if tx.is_complete(): return try: p2shTransaction = False derivations = self.get_tx_derivations(tx) 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'] in ['p2sh']: p2shTransaction = True 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 = Hash(tx.serialize_preimage(i).decode('hex')).encode('hex') hasharray_i = {'hash': inputHash, 'keypath': inputPath} hasharray.append(hasharray_i) break else: self.give_error("No matching x_key for sign_transaction") # should never happen # Sanity check if p2shTransaction: for txinput in tx.inputs(): if txinput['type'] != 'p2sh': self.give_error("P2SH / regular input mixed in same transaction not supported") # should never happen # Build pubkeyarray from outputs (unused because echo for smart verification not implemented) if not p2shTransaction: 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) # 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": {"meta":"%s", "data":%s, "checkpub":%s} }' % \ (Hash(tx.serialize()).encode('hex'), json.dumps(hashes), json.dumps(pubkeyarray)) 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 steps > 1: self.handler.show_message(_("Signing large transaction. Please be patient ...\r\n\r\n" \ "To continue, touch the Digital Bitbox's blinking light for 3 seconds. " \ "(Touch " + str(step + 1) + " of " + str(int(steps)) + ")\r\n\r\n" \ "To cancel, briefly touch the blinking light or wait for the timeout.\r\n\r\n")) else: self.handler.show_message(_("Signing transaction ...\r\n\r\n" \ "To continue, touch the Digital Bitbox's blinking light for 3 seconds.\r\n\r\n" \ "To cancel, briefly touch the blinking light or wait for the timeout.")) reply = dbb_client.hid_send_encrypt(msg) # Send twice, first returns an echo for smart verification (not implemented) self.handler.clear_dialog() if 'error' in reply: 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 = filter(None, txin['signatures']) if len(signatures) == num: break # txin is complete ii = txin['pubkeys'].index(pubkey) signed = dbb_signatures[i] if signed['pubkey'] != pubkey: continue sig_r = int(signed['sig'][:64], 16) sig_s = int(signed['sig'][64:], 16) sig = sigencode_der(sig_r, sig_s, generator_secp256k1.order()) txin['signatures'][ii] = sig.encode('hex') tx._inputs[i] = txin except BaseException as e: self.give_error(e, True) else: print_error("Transaction is_complete", tx.is_complete()) tx.raw = tx.serialize()
def show_error(self, msg, blocking=False): print_error(msg)
import platform from electrum_grs.plugin import BasePlugin, hook from electrum_grs.gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog from electrum_grs.util import print_msg, print_error from electrum_grs.i18n import _ from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import (QComboBox, QGridLayout, QLabel, QPushButton) try: import amodem.audio import amodem.main import amodem.config print_error('Audio MODEM is available.') amodem.log.addHandler(amodem.logging.StreamHandler(sys.stderr)) amodem.log.setLevel(amodem.logging.INFO) except ImportError: amodem = None print_error('Audio MODEM is not found.') class Plugin(BasePlugin): def __init__(self, parent, config, name): BasePlugin.__init__(self, parent, config, name) if self.is_available(): self.modem_config = amodem.config.slowest() self.library_name = {'Linux': 'libportaudio.so'}[platform.system()] def is_available(self):
def update_status(self, b): print_error('hw device status', b)
def run(self, action, wallet_type): if action in ['create', 'restore']: if wallet_type == 'multisig': wallet_type = self.multisig_choice() if not wallet_type: return elif wallet_type == 'hardware': hardware_wallets = map( lambda x: (x[1], x[2]), filter(lambda x: x[0] == 'hardware', electrum_grs.wallet.wallet_types)) wallet_type = self.choice(_("Hardware Wallet"), 'Select your hardware wallet', hardware_wallets) if not wallet_type: return elif wallet_type == 'twofactor': wallet_type = '2fa' if action == 'create': self.storage.put('wallet_type', wallet_type, False) if action is None: return if action == 'restore': wallet = self.restore(wallet_type) if not wallet: return action = None else: wallet = Wallet(self.storage) action = wallet.get_action() # fixme: password is only needed for multiple accounts password = None # load wallet in plugins always_hook('installwizard_load_wallet', wallet, self) while action is not None: util.print_error("installwizard:", wallet, action) if action == 'create_seed': lang = self.config.get('language') seed = wallet.make_seed(lang) if not self.show_seed(seed, None): return if not self.verify_seed(seed, None): return password = self.password_dialog() wallet.add_seed(seed, password) wallet.create_master_keys(password) elif action == 'add_cosigners': n = int(re.match('(\d+)of(\d+)', wallet.wallet_type).group(2)) xpub1 = wallet.master_public_keys.get("x1/") r = self.multi_mpk_dialog(xpub1, n - 1) if not r: return for i, xpub in enumerate(r): wallet.add_master_public_key("x%d/" % (i + 2), xpub) elif action == 'create_accounts': wallet.create_main_account(password) self.waiting_dialog(wallet.synchronize) else: f = always_hook('get_wizard_action', self, wallet, action) if not f: raise BaseException('unknown wizard action', action) r = f(wallet, self) if not r: return # next action action = wallet.get_action() if self.network: if self.network.interfaces: self.network_dialog() else: QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK')) self.network.stop() self.network = None # start wallet threads wallet.start_threads(self.network) if action == 'restore': self.waiting_dialog( lambda: wallet.restore(self.waiting_label.setText)) if self.network: msg = _("Recovery successful") if wallet.is_found() else _( "No transactions found for this seed") else: msg = _( "This wallet was restored offline. It may contain more addresses than displayed." ) QMessageBox.information(None, _('Information'), msg, _('OK')) return wallet
def run_wallet_type(self, action, wallet_type): if action in ['create', 'restore']: if wallet_type == 'multisig': wallet_type = self.multisig_choice() if not wallet_type: return elif wallet_type == 'hardware': hardware_wallets = [] for item in electrum_grs.wallet.wallet_types: t, name, description, loader = item if t == 'hardware': try: p = loader() except: util.print_error("cannot load plugin for:", name) continue if p: hardware_wallets.append((name, description)) wallet_type = self.choice(_("Hardware Wallet"), 'Select your hardware wallet', hardware_wallets) if not wallet_type: return elif wallet_type == 'twofactor': wallet_type = '2fa' if action == 'create': self.storage.put('wallet_type', wallet_type, False) if action is None: return if action == 'restore': wallet = self.restore(wallet_type) if not wallet: return action = None else: wallet = Wallet(self.storage) action = wallet.get_action() # fixme: password is only needed for multiple accounts password = None # load wallet in plugins always_hook('installwizard_load_wallet', wallet, self) while action is not None: util.print_error("installwizard:", wallet, action) if action == 'create_seed': lang = self.config.get('language') seed = wallet.make_seed(lang) if not self.show_seed(seed, None): return self.app.clipboard().clear() if not self.verify_seed(seed, None): return password = self.password_dialog() wallet.add_seed(seed, password) wallet.create_master_keys(password) elif action == 'add_cosigners': n = int(re.match('(\d+)of(\d+)', wallet.wallet_type).group(2)) xpub1 = wallet.master_public_keys.get("x1/") r = self.multi_mpk_dialog(xpub1, n - 1) if not r: return for i, xpub in enumerate(r): wallet.add_master_public_key("x%d/"%(i+2), xpub) elif action == 'create_accounts': wallet.create_main_account(password) self.waiting_dialog(wallet.synchronize) else: f = always_hook('get_wizard_action', self, wallet, action) if not f: raise BaseException('unknown wizard action', action) r = f(wallet, self) if not r: return # next action action = wallet.get_action() if self.network: # show network dialog if config does not exist if self.config.get('server') is None: self.network_dialog() else: QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK')) # start wallet threads wallet.start_threads(self.network) if action == 'restore': self.waiting_dialog(lambda: wallet.wait_until_synchronized(self.waiting_label.setText)) if self.network: msg = _("Recovery successful") if wallet.is_found() else _("No transactions found for this seed") else: msg = _("This wallet was restored offline. It may contain more addresses than displayed.") QMessageBox.information(None, _('Information'), msg, _('OK')) return wallet
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 = Hash( 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(Hash(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()