def settings_dialog(self, window): d = WindowModalDialog(window, _("Email settings")) d.setMinimumSize(500, 200) vbox = QVBoxLayout(d) vbox.addWidget(QLabel(_('Server hosting your email acount'))) grid = QGridLayout() vbox.addLayout(grid) grid.addWidget(QLabel('Server (IMAP)'), 0, 0) server_e = QLineEdit() server_e.setText(self.imap_server) grid.addWidget(server_e, 0, 1) grid.addWidget(QLabel('Username'), 1, 0) username_e = QLineEdit() username_e.setText(self.username) grid.addWidget(username_e, 1, 1) grid.addWidget(QLabel('Password'), 2, 0) password_e = QLineEdit() password_e.setText(self.password) grid.addWidget(password_e, 2, 1) vbox.addStretch() vbox.addLayout(Buttons(CloseButton(d), OkButton(d))) if not d.exec_(): return server = str(server_e.text()) self.config.set_key('email_server', server) username = str(username_e.text()) self.config.set_key('email_username', username) password = str(password_e.text()) self.config.set_key('email_password', password)
def multisig_dialog(self, run_next): cw = CosignWidget(2, 2) m_edit = QSlider(Qt.Horizontal, self) n_edit = QSlider(Qt.Horizontal, self) n_edit.setMinimum(2) n_edit.setMaximum(15) m_edit.setMinimum(1) m_edit.setMaximum(2) n_edit.setValue(2) m_edit.setValue(2) n_label = QLabel() m_label = QLabel() grid = QGridLayout() grid.addWidget(n_label, 0, 0) grid.addWidget(n_edit, 0, 1) grid.addWidget(m_label, 1, 0) grid.addWidget(m_edit, 1, 1) def on_m(m): m_label.setText(_('Require %d signatures')%m) cw.set_m(m) def on_n(n): n_label.setText(_('From %d cosigners')%n) cw.set_n(n) m_edit.setMaximum(n) n_edit.valueChanged.connect(on_n) m_edit.valueChanged.connect(on_m) on_n(2) on_m(2) vbox = QVBoxLayout() vbox.addWidget(cw) vbox.addWidget(WWLabel(_("Choose the number of signatures needed to unlock funds in your wallet:"))) vbox.addLayout(grid) self.exec_layout(vbox, _("Multi-Signature Wallet")) m = int(m_edit.value()) n = int(n_edit.value()) return (m, n)
def create_masternode_conf_tab(self): """Create the tab used to import masternode.conf files.""" desc = ' '.join(['You can use this form to import your masternode.conf file.', 'This file is usually located in the same directory that your wallet file is in.', 'If you just need to import your masternode\'s private key, use the regular process for importing a key.']) desc = QLabel(_(desc)) desc.setWordWrap(True) import_filename_edit = QLineEdit() import_filename_edit.setPlaceholderText(_('Enter the path to your masternode.conf')) import_select_file = QPushButton(_('Select File...')) hbox = QHBoxLayout() hbox.addWidget(import_filename_edit, stretch=1) hbox.addWidget(import_select_file) import_conf_button = QPushButton(_('Import')) vbox = QVBoxLayout() vbox.addWidget(desc) vbox.addLayout(hbox) vbox.addLayout(util.Buttons(import_conf_button)) vbox.addStretch(1) def select_import_file(): text = QFileDialog.getOpenFileName(None, _('Select a file to import'), '', '*.conf') if text: import_filename_edit.setText(text) import_select_file.clicked.connect(select_import_file) def do_import_file(): path = str(import_filename_edit.text()) self.import_masternode_conf(path) import_conf_button.clicked.connect(do_import_file) w = QWidget() w.setLayout(vbox) return w
def scan_for_outputs(self, include_frozen): """Scan for 1000 PAC outputs. If one or more is found, populate the list and enable the sign button. """ self.valid_outputs_list.clear() exclude_frozen = not include_frozen coins = self.manager.get_masternode_outputs(exclude_frozen=exclude_frozen) if len(coins) > 0: self.valid_outputs_list.add_outputs(coins) else: self.status_edit.setText(_('No 1000 PAC outputs were found.')) self.status_edit.setStyleSheet(util.RED_FG)
def dbb_load_backup(self, show_msg=True): backups = self.hid_send_encrypt('{"backup":"list"}') if 'error' in backups: raise Exception(backups['error']['message']) try: f = self.handler.win.query_choice(_("Choose a backup file:"), backups['backup']) except Exception: return False # Back button pushed key = self.backup_password_dialog() if key is None: raise Exception('Canceled by user') key = self.stretch_key(key) if show_msg: self.handler.show_message(_("Loading backup...\r\n\r\n" \ "To continue, touch the Digital Bitbox's light for 3 seconds.\r\n\r\n" \ "To cancel, briefly touch the light or wait for the timeout.")) msg = '{"seed":{"source": "backup", "key": "%s", "filename": "%s"}}' % ( key, backups['backup'][f]) hid_reply = self.hid_send_encrypt(msg) self.handler.clear_dialog() if 'error' in hid_reply: raise Exception(hid_reply['error']['message']) return True
def pw_changed(self): password = self.new_pw.text() if password: colors = { "Weak": "Red", "Medium": "Blue", "Strong": "Green", "Very Strong": "Green" } strength = check_password_strength(password) label = (_("Password Strength") + ": " + "<font color=" + colors[strength] + ">" + strength + "</font>") else: label = "" self.pw_strength.setText(label)
def seed_warning_msg(seed): return ''.join([ "<p>", _("Please save these %d words on paper (order is important). "), _("This seed will allow you to recover your wallet in case " "of computer failure."), "</p>", "<b>" + _("WARNING") + ":</b>", "<ul>", "<li>" + _("Never disclose your seed.") + "</li>", "<li>" + _("Never type it on a website.") + "</li>", "<li>" + _("Do not store it electronically.") + "</li>", "</ul>" ]) % len(seed.split())
def address_field(addresses): hbox = QHBoxLayout() address_e = QLineEdit() if addresses: address_e.setText(addresses[0]) def func(): i = addresses.index(str(address_e.text())) + 1 i = i % len(addresses) address_e.setText(addresses[i]) button = QPushButton(_('Address')) button.clicked.connect(func) hbox.addWidget(button) hbox.addWidget(address_e) return hbox, address_e
def change_homescreen(): from PIL import Image # FIXME dialog = QFileDialog(self, _("Choose Homescreen")) filename = dialog.getOpenFileName() if filename: im = Image.open(str(filename)) if im.size != (hs_cols, hs_rows): raise Exception('Image must be 64 x 128 pixels') im = im.convert('1') pix = im.load() img = '' for j in range(hs_rows): for i in range(hs_cols): img += '1' if pix[i, j] else '0' img = ''.join(chr(int(img[i:i + 8], 2)) for i in range(0, len(img), 8)) invoke_client('change_homescreen', img)
def set_mapper_index(self, row): """Set the row that the data widget mapper should use.""" self.status_edit.clear() self.status_edit.setStyleSheet(util.BLACK_FG) self.mapper.setCurrentIndex(row) mn = self.dialog.masternodes_widget.masternode_for_row(row) # Disable the sign button if the masternode can't be signed (for whatever reason). status_text = '%s can be activated' % mn.alias can_sign = True try: self.manager.check_can_sign_masternode(mn.alias) except Exception as e: status_text = str(e) can_sign = False self.status_edit.setText(_(status_text)) self.sign_button.setEnabled(can_sign)
def create_sign_announce_tab(self): desc = ' '.join(['You can sign a Masternode Announce message to activate your masternode.', 'First, ensure that all the required data has been entered for this masternode.', 'Then, click "Activate Masternode" to activate your masternode.', ]) desc = QLabel(_(desc)) desc.setWordWrap(True) self.sign_announce_widget = SignAnnounceWidget(self) vbox = QVBoxLayout() vbox.addWidget(desc) vbox.addWidget(self.sign_announce_widget) vbox.addStretch(1) w = QWidget() w.setLayout(vbox) return w
def item_changed(self, item): if item is None: return if not self.isItemSelected(item): return addr = str(item.text(1)) req = self.wallet.receive_requests[addr] expires = age(req['time'] + req['exp']) if req.get('exp') else _('Never') amount = req['amount'] message = self.wallet.labels.get(addr, '') self.parent.receive_address_e.setText(addr) self.parent.receive_message_e.setText(message) self.parent.receive_amount_e.setAmount(amount) self.parent.expires_combo.hide() self.parent.expires_label.show() self.parent.expires_label.setText(expires) self.parent.new_request_button.setEnabled(True)
def passphrase_dialog(self, msg, confirm): # If confirm is true, require the user to enter the passphrase twice parent = self.top_level_window() if confirm: d = PasswordDialog(parent, None, msg, PW_PASSPHRASE) confirmed, p, passphrase = d.run() else: d = WindowModalDialog(parent, _("Enter Passphrase")) pw = QLineEdit() pw.setEchoMode(2) pw.setMinimumWidth(200) vbox = QVBoxLayout() vbox.addWidget(WWLabel(msg)) vbox.addWidget(pw) vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) d.setLayout(vbox) passphrase = unicode(pw.text()) if d.exec_() else None self.passphrase = passphrase self.done.set()
def create_proposal_tx(self, proposal, pw, callback): """Create and sign the proposal fee transaction.""" result = [False] def tx_thread(): return self.parent.masternode_manager.create_proposal_tx( proposal.proposal_name, pw, save=False) def on_tx_made(tx): result[0] = tx def on_done(): callback(proposal, result[0]) self.waiting_dialog = util.WaitingDialog(self, _('Creating Transaction...'), tx_thread, on_tx_made, on_done) self.waiting_dialog.start()
def refresh_headers(self): headers = [ '', '', _('Date'), _('Description'), _('Amount'), _('Balance') ] fx = self.parent.fx if fx and fx.show_history(): headers.extend( ['%s ' % fx.ccy + _('Amount'), '%s ' % fx.ccy + _('Balance')]) self.update_headers(headers)
def sign_message(self, sequence, message, password): self.signing = True # prompt for the PIN before displaying the dialog if necessary client = self.get_client() address_path = self.get_derivation()[2:] + "/%d/%d"%sequence self.handler.show_message("Signing message ...") try: info = self.get_client().signMessagePrepare(address_path, message) pin = "" if info['confirmationNeeded']: pin = self.handler.get_auth( info ) # does the authenticate dialog and returns pin if not pin: raise UserWarning(_('Cancelled by user')) pin = str(pin).encode() signature = self.get_client().signMessageSign(pin) except BTChipException, e: if e.sw == 0x6a80: self.give_error("Unfortunately, this message cannot be signed by the Ledger wallet. Only alphanumerical messages shorter than 140 characters are supported. Please remove any extra characters (tab, carriage return) and retry.") else: self.give_error(e, True)
def __init__(self, parent, address): WindowModalDialog.__init__(self, parent, _("Address")) self.address = address self.parent = parent self.config = parent.config self.wallet = parent.wallet self.app = parent.app self.saved = True self.setMinimumWidth(700) vbox = QVBoxLayout() self.setLayout(vbox) vbox.addWidget(QLabel(_("Address:"))) self.addr_e = ButtonsLineEdit(self.address) self.addr_e.addCopyButton(self.app) self.addr_e.addButton(":icons/qrcode.png", self.show_qr, _("Show QR Code")) self.addr_e.setReadOnly(True) vbox.addWidget(self.addr_e) try: pubkeys = self.wallet.get_public_keys(address) except BaseException as e: pubkeys = None if pubkeys: vbox.addWidget(QLabel(_("Public keys") + ':')) for pubkey in pubkeys: pubkey_e = ButtonsLineEdit(pubkey) pubkey_e.addCopyButton(self.app) vbox.addWidget(pubkey_e) try: redeem_script = self.wallet.pubkeys_to_redeem_script(pubkeys) except BaseException as e: redeem_script = None if redeem_script: vbox.addWidget(QLabel(_("Redeem Script") + ':')) redeem_e = ShowQRTextEdit(text=redeem_script) redeem_e.addCopyButton(self.app) vbox.addWidget(redeem_e) vbox.addWidget(QLabel(_("History"))) self.hw = HistoryList(self.parent) self.hw.get_domain = self.get_domain vbox.addWidget(self.hw) vbox.addLayout(Buttons(CloseButton(self))) self.format_amount = self.parent.format_amount self.hw.update()
def settings_dialog(self, window): wallet = window.parent().wallet d = WindowModalDialog(window, _("Label Settings")) hbox = QHBoxLayout() hbox.addWidget(QLabel("Label sync options:")) upload = ThreadedButton("Force upload", partial(self.push_thread, wallet), partial(self.done_processing, d)) download = ThreadedButton("Force download", partial(self.pull_thread, wallet, True), partial(self.done_processing, d)) vbox = QVBoxLayout() vbox.addWidget(upload) vbox.addWidget(download) hbox.addLayout(vbox) vbox = QVBoxLayout(d) vbox.addLayout(hbox) vbox.addSpacing(20) vbox.addLayout(Buttons(OkButton(d))) return bool(d.exec_())
def show_restore(self, wallet, network): # FIXME: these messages are shown after the install wizard is # finished and the window closed. On MacOSX they appear parented # with a re-appeared ghost install wizard window... if network: def task(): wallet.wait_until_synchronized() if wallet.is_found(): msg = _("Recovery successful") else: msg = _("No transactions found for this seed") self.emit(QtCore.SIGNAL('synchronized'), msg) self.connect(self, QtCore.SIGNAL('synchronized'), self.show_message) t = threading.Thread(target = task) t.daemon = True t.start() else: msg = _("This wallet was restored offline. It may " "contain more addresses than displayed.") self.show_message(msg)
def __init__(self, config, app, plugins, storage): BaseWizard.__init__(self, config, storage) QDialog.__init__(self, None) self.setWindowTitle('Electrum-PAC - ' + _('Install Wizard')) self.app = app self.config = config # Set for base base class self.plugins = plugins self.language_for_seed = config.get('language') self.setMinimumSize(600, 400) self.connect(self, QtCore.SIGNAL('accept'), self.accept) self.title = QLabel() self.main_widget = QWidget() self.back_button = QPushButton(_("Back"), self) self.back_button.setText( _('Back') if self.can_go_back() else _('Cancel')) self.next_button = QPushButton(_("Next"), self) self.next_button.setDefault(True) self.logo = QLabel() self.please_wait = QLabel(_("Please wait...")) self.please_wait.setAlignment(Qt.AlignCenter) self.icon_filename = None self.loop = QEventLoop() self.rejected.connect(lambda: self.loop.exit(0)) self.back_button.clicked.connect(lambda: self.loop.exit(1)) self.next_button.clicked.connect(lambda: self.loop.exit(2)) outer_vbox = QVBoxLayout(self) inner_vbox = QVBoxLayout() inner_vbox = QVBoxLayout() inner_vbox.addWidget(self.title) inner_vbox.addWidget(self.main_widget) inner_vbox.addStretch(1) inner_vbox.addWidget(self.please_wait) inner_vbox.addStretch(1) icon_vbox = QVBoxLayout() icon_vbox.addWidget(self.logo) icon_vbox.addStretch(1) hbox = QHBoxLayout() hbox.addLayout(icon_vbox) hbox.addSpacing(5) hbox.addLayout(inner_vbox) hbox.setStretchFactor(inner_vbox, 1) outer_vbox.addLayout(hbox) outer_vbox.addLayout(Buttons(self.back_button, self.next_button)) self.set_icon(':icons/electrum-PAC.png') self.show() self.raise_() self.refresh_gui() # Need for QT on MacOSX. Lame.
def submit_waiting_proposals(self): """Submit the proposals that are ready to the network.""" # Submit the proposals that are ready. results = [('', '', False)] * len(self.unsubmitted_proposals) def submit_thread(): for i, (proposal_name, txid) in enumerate(self.unsubmitted_proposals): errmsg, success = self.parent.masternode_manager.submit_proposal( proposal_name, save=False) results[i] = (proposal_name, errmsg, success) if success: print_error('Sucessfully submitted proposal "%s"' % proposal_name) else: print_error('Failed to submit proposal "%s": %s' % (proposal_name, errmsg)) return results def on_done(): msg = '' for proposal_name, errmsg, success in results: if success: msg += '<b>' + proposal_name + '</b>' + _( ': submitted successfully.') else: msg += '<b>' + proposal_name + '</b>' + _( ': failed! "%s"' % errmsg) msg += '\n' QMessageBox.information(self, _('Results'), msg) self.update_unsubmitted_proposals() self.parent.masternode_manager.save() self.waiting_dialog = util.WaitingDialog(self, _('Submitting Proposals...'), submit_thread, on_complete=on_done) self.waiting_dialog.start()
def on_update(self): inv_list = self.parent.invoices.unpaid_invoices() self.clear() for pr in inv_list: key = pr.get_id() status = self.parent.invoices.get_status(key) requestor = pr.get_requestor() exp = pr.get_expiration_date() date_str = format_time(exp) if exp else _('Never') item = QTreeWidgetItem([ date_str, requestor, pr.memo, self.parent.format_amount(pr.get_amount(), whitespaces=True), pr_tooltips.get(status, '') ]) item.setIcon(4, QIcon(pr_icons.get(status))) item.setData(0, Qt.UserRole, key) item.setFont(1, QFont(MONOSPACE_FONT)) item.setFont(3, QFont(MONOSPACE_FONT)) self.addTopLevelItem(item) self.setCurrentItem(self.topLevelItem(0)) self.setVisible(len(inv_list)) self.parent.invoices_label.setVisible(len(inv_list))
def on_receive(self, keyhash, message): self.print_error("signal arrived for", keyhash) for key, _hash, window in self.keys: if _hash == keyhash: break else: self.print_error("keyhash not found") return wallet = window.wallet if wallet.has_password(): password = window.password_dialog( 'An encrypted transaction was retrieved from cosigning pool.\nPlease enter your password to decrypt it.' ) if not password: return else: password = None if not window.question( _("An encrypted transaction was retrieved from cosigning pool.\nDo you want to open it now?" )): return xprv = wallet.keystore.get_master_private_key(password) if not xprv: return try: k = bitcoin.deserialize_xprv(xprv)[-1].encode('hex') EC = bitcoin.EC_KEY(k.decode('hex')) message = EC.decrypt_message(message) except Exception as e: traceback.print_exc(file=sys.stdout) window.show_message(str(e)) return self.listener.clear(keyhash) tx = transaction.Transaction(message) show_transaction(tx, window, prompt_if_unsaved=True)
def _initialize_device(self, settings, method, device_id, wizard, handler): item, label, pin_protection, passphrase_protection = settings if method == TIM_RECOVER and self.device == 'TREZOR': # Warn user about firmware lameness handler.show_error( _("You will be asked to enter 24 words regardless of your " "seed's actual length. If you enter a word incorrectly or " "misspell it, you cannot change it or go back - you will need " "to start again from the beginning.\n\nSo please enter " "the words carefully!")) language = 'english' devmgr = self.device_manager() client = devmgr.client_by_id(device_id) if method == TIM_NEW: strength = 64 * (item + 2) # 128, 192 or 256 u2f_counter = 0 skip_backup = False client.reset_device(True, strength, passphrase_protection, pin_protection, label, language, u2f_counter, skip_backup) elif method == TIM_RECOVER: word_count = 6 * (item + 2) # 12, 18 or 24 client.step = 0 client.recovery_device(word_count, passphrase_protection, pin_protection, label, language) elif method == TIM_MNEMONIC: pin = pin_protection # It's the pin, not a boolean client.load_device_by_mnemonic(str(item), pin, passphrase_protection, label, language) else: pin = pin_protection # It's the pin, not a boolean client.load_device_by_xprv(item, pin, passphrase_protection, label, language) wizard.loop.exit(0)
def update_unsubmitted_proposals(self): """Update the list of unsubmitted proposals.""" self.unsubmitted_proposals = [] for p in self.parent.masternode_manager.proposals: if p.fee_txid and not p.submitted and not p.rejected: confirmations, timestamp = self.parent.wallet.get_confirmations( p.fee_txid) if confirmations < BUDGET_FEE_CONFIRMATIONS: continue item = (p.proposal_name, p.fee_txid) if item not in self.unsubmitted_proposals: self.unsubmitted_proposals.append(item) can_submit = len(self.unsubmitted_proposals) > 0 self.submit_ready_proposals_button.setEnabled(can_submit) num = len(self.unsubmitted_proposals) noun = 'proposal%s' % ('' if num == 1 else 's') article = 'is' if num == 1 else 'are' self.ready_proposals.setText( str(num) + _(' %s %s ready to be submitted.' % (noun, article))) self.ready_proposals.setVisible(can_submit)
def f(b): if b: msg = ' '.join([ '<b>' + _('Warning') + '</b>' + ': ', _('BIP39 seeds may not be supported in the future.'), '<br/><br/>', _('As technology matures, PAC address generation may change.' ), _('However, BIP39 seeds do not include a version number.' ), _('As a result, it is not possible to infer your wallet type from a BIP39 seed.' ), '<br/><br/>', _('We do not guarantee that BIP39 seeds will be supported in future versions of Electrum-PAC.' ), _('We recommend to use seeds generated by Electrum-PAC or compatible wallets.' ), ]) #self.parent.show_warning(msg) self.seed_type_label.setVisible(not b) self.is_seed = (lambda x: bool(x)) if b else self.saved_is_seed self.on_edit()
def send(self, window, addr): from electrum_PAC import paymentrequest r = window.wallet.receive_requests.get(addr) message = r.get('memo', '') if r.get('signature'): pr = paymentrequest.serialize_request(r) else: pr = paymentrequest.make_request(self.config, r) if not pr: return recipient, ok = QtGui.QInputDialog.getText(window, 'Send request', 'Email invoice to:') if not ok: return recipient = str(recipient) payload = pr.SerializeToString() self.print_error('sending mail to', recipient) try: self.processor.send(recipient, message, payload) except BaseException as e: window.show_message(str(e)) return window.show_message(_('Request sent.'))
def create_menu(self, position): menu = QMenu() selected = self.selectedItems() if not selected: menu.addAction(_("New contact"), lambda: self.parent.new_contact_dialog()) menu.addAction(_("Import file"), lambda: self.parent.import_contacts()) else: names = [unicode(item.text(0)) for item in selected] keys = [unicode(item.text(1)) for item in selected] column = self.currentColumn() column_title = self.headerItem().text(column) column_data = '\n'.join([unicode(item.text(column)) for item in selected]) menu.addAction(_("Copy %s")%column_title, lambda: self.parent.app.clipboard().setText(column_data)) if column in self.editable_columns: menu.addAction(_("Edit %s")%column_title, lambda: self.editItem(item, column)) menu.addAction(_("Pay to"), lambda: self.parent.payto_contacts(keys)) menu.addAction(_("Delete"), lambda: self.parent.delete_contacts(keys)) URLs = [block_explorer_URL(self.config, 'addr', key) for key in filter(is_address, keys)] if URLs: menu.addAction(_("View on block explorer"), lambda: map(webbrowser.open, URLs)) run_hook('create_contact_menu', menu, selected) menu.exec_(self.viewport().mapToGlobal(position))
def __init__(self, parent=None): super(PrevOutWidget, self).__init__(parent) self.vin = {} self.hash_edit = QLineEdit() self.hash_edit.setPlaceholderText(_('The TxID of your 1000 PAC output')) self.index_edit = QLineEdit() self.index_edit.setPlaceholderText(_('The output number of your 1000 PAC output')) self.address_edit = QLineEdit() self.address_edit.setPlaceholderText(_('The address that 1000 PAC was sent to')) # Collection of fields so that it's easier to act on them all at once. self.fields = (self.hash_edit, self.index_edit, self.address_edit) for i in self.fields: i.setFont(QFont(util.MONOSPACE_FONT)) form = QFormLayout() form.setContentsMargins(0, 0, 0, 0) form.addRow(_('TxID:'), self.hash_edit) form.addRow(_('Output Index:'), self.index_edit) form.addRow(_('Address:'), self.address_edit) self.setLayout(form)
def sign_transaction(self, tx, password): if tx.is_complete(): return try: p2shTransaction = False 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'] 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')) hasharray_i = { 'hash': inputHash.encode('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 # 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 'recid' in signed: # firmware > v2.1.1 recid = int(signed['recid'], 16) s = signed['sig'].decode('hex') h = inputhasharray[i] pk = MyVerifyingKey.from_signature(s, recid, h, curve=SECP256k1) pk = point_to_ser(pk.pubkey.point, True).encode('hex') 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 = sigencode_der(sig_r, sig_s, generator_secp256k1.order()) txin['signatures'][ii] = sig.encode('hex') + '01' 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()