def before_send(self): # request billing info before forming the transaction self.billing_info = None self.waiting_dialog = WaitingDialog(self.window, 'please wait...', self.request_billing_info) self.waiting_dialog.start() self.waiting_dialog.wait() if self.billing_info is None: self.window.show_message('Could not contact server') return True return False
def _recv(self, parent): def receiver_thread(): try: with self._audio_interface() as interface: src = interface.recorder() dst = BytesIO() amodem.main.recv(config=self.modem_config, src=src, dst=dst) return dst.getvalue() except Exception: traceback.print_exc() def on_success(blob): if blob: blob = zlib.decompress(blob) print_msg('Received:', repr(blob)) parent.setText(blob) kbps = self.modem_config.modem_bps / 1e3 msg = 'Receiving from Audio MODEM ({0:.1f} kbps)...'.format(kbps) return WaitingDialog(parent=parent, message=msg, run_task=receiver_thread, on_success=on_success)
def _send(self, parent, blob): def sender_thread(): with self._audio_interface() as interface: src = BytesIO(blob) dst = interface.player() amodem.main.send(config=self.modem_config, src=src, dst=dst) print_msg('Sending:', repr(blob)) blob = zlib.compress(blob) kbps = self.modem_config.modem_bps / 1e3 msg = 'Sending to Audio MODEM ({0:.1f} kbps)...'.format(kbps) WaitingDialog(parent, msg, sender_thread)
def _send(self, parent, blob): def sender_thread(): try: with self._audio_interface() as interface: src = BytesIO(blob) dst = interface.player() amodem.send.main(config=self.modem_config, src=src, dst=dst) except Exception: traceback.print_exc() print_msg('Sending:', repr(blob)) blob = zlib.compress(blob) kbps = self.modem_config.modem_bps / 1e3 msg = 'Sending to Audio MODEM ({0:.1f} kbps)...'.format(kbps) return WaitingDialog(parent=parent, message=msg, run_task=sender_thread)
def _recv(self, parent): def receiver_thread(): with self._audio_interface() as interface: src = interface.recorder() dst = BytesIO() amodem.main.recv(config=self.modem_config, src=src, dst=dst) return dst.getvalue() def on_finished(blob): if blob: blob = zlib.decompress(blob) print_msg('Received:', repr(blob)) parent.setText(blob) kbps = self.modem_config.modem_bps / 1e3 msg = 'Receiving from Audio MODEM ({0:.1f} kbps)...'.format(kbps) WaitingDialog(parent, msg, receiver_thread, on_finished)
def settings_dialog(self): self.waiting_dialog = WaitingDialog(self.window, 'please wait...', self.request_billing_info, self.show_settings_dialog) self.waiting_dialog.start()
class Plugin(BasePlugin): wallet = None def __init__(self, x, y): BasePlugin.__init__(self, x, y) electrum.wallet.wallet_types.append( ('twofactor', '2fa', _("Wallet with two-factor authentication"), Wallet_2fa)) self.seed_func = lambda x: bitcoin.is_new_seed(x, SEED_PREFIX) self.billing_info = None def fullname(self): return 'Two Factor Authentication' def description(self): return _("This plugin adds two-factor authentication to your wallet.") + '<br/>'\ + _("For more information, visit") + " <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>" def is_available(self): if self.wallet is None: return True if self.wallet.storage.get('wallet_type') == '2fa': return True return False def requires_settings(self): return True def set_enabled(self, enabled): self.wallet.storage.put('use_' + self.name, enabled) def is_enabled(self): if not self.is_available(): return False if not self.wallet: return True if self.wallet.storage.get('wallet_type') != '2fa': return False if self.wallet.master_private_keys.get('x2/'): return False return True def make_long_id(self, xpub_hot, xpub_cold): return bitcoin.sha256(''.join(sorted([xpub_hot, xpub_cold]))) def get_user_id(self): xpub_hot = self.wallet.master_public_keys["x1/"] xpub_cold = self.wallet.master_public_keys["x2/"] long_id = self.make_long_id(xpub_hot, xpub_cold) short_id = hashlib.sha256(long_id).hexdigest() return long_id, short_id def make_xpub(self, xpub, s): _, _, _, c, cK = deserialize_xkey(xpub) cK2, c2 = bitcoin._CKD_pub(cK, c, s) xpub2 = ("0488B21E" + "00" + "00000000" + "00000000").decode("hex") + c2 + cK2 return EncodeBase58Check(xpub2) def make_billing_address(self, num): long_id, short_id = self.get_user_id() xpub = self.make_xpub(billing_xpub, long_id) _, _, _, c, cK = deserialize_xkey(xpub) cK, c = bitcoin.CKD_pub(cK, c, num) address = public_key_to_bc_address(cK) return address def enable(self): if self.is_enabled(): self.window.show_message( 'Error: Two-factor authentication is already activated on this wallet' ) return self.set_enabled(True) self.window.show_message('Two-factor authentication is enabled.') def create_extended_seed(self, wallet, window): seed = wallet.make_seed() if not window.show_seed(seed, None): return if not window.verify_seed(seed, None, self.seed_func): return password = window.password_dialog() wallet.storage.put('seed_version', wallet.seed_version, True) wallet.storage.put('use_encryption', password is not None, True) words = seed.split() n = len(words) / 2 wallet.add_cosigner_seed(' '.join(words[0:n]), 'x1/', password) wallet.add_cosigner_xpub(' '.join(words[n:]), 'x2/') msg = [ _('Your wallet file is:') + " %s" % os.path.abspath(wallet.storage.path), _('You need to be online in order to complete the creation of your wallet.' ), _('If you generated your seed on an offline computer, click on "%s" to close this window, move your wallet file to an online computer and reopen it with Electrum.' ) % _('Close'), _('If you are online, click on "%s" to continue.') % _('Next') ] return window.question('\n\n'.join(msg), no_label=_('Close'), yes_label=_('Next')) def show_disclaimer(self, wallet, window): msg = [ _("Two-factor authentication is a service provided by TrustedCoin." ) + ' ', _("It uses a multi-signature wallet, where you own 2 of 3 keys.") + ' ', _("The third key is stored on a remote server that signs transactions on your behalf." ) + ' ', _("To use this service, you will need a smartphone with Google Authenticator." ) + '\n\n', _("A small fee will be charged on each transaction that uses the remote server." ) + ' ', _("You may check and modify your billing preferences once the installation is complete." ) + '\n\n', _("Note that your coins are not locked in this service.") + ' ', _("You may withdraw your funds at any time and at no cost, without the remote server, by using the 'restore wallet' option with your wallet seed." ) + '\n\n', _('The next step will generate the seed of your wallet.') + ' ', _('This seed will NOT be saved in your computer, and it must be stored on paper.' ) + ' ', _('To be safe from malware, you may want to do this on an offline computer, and move your wallet later to an online computer.' ) ] icon = QPixmap(':icons/trustedcoin.png') if not window.question(''.join(msg), icon=icon): return False self.wallet = wallet self.set_enabled(True) return True def restore_third_key(self, wallet): long_user_id, short_id = self.get_user_id() xpub3 = self.make_xpub(signing_xpub, long_user_id) wallet.add_master_public_key('x3/', xpub3) @hook def init_qt(self, gui): self.window = gui.main_window self.is_billing = False @hook def do_clear(self): self.is_billing = False @hook def load_wallet(self, wallet): self.trustedcoin_button = StatusBarButton( QIcon(":icons/trustedcoin.png"), _("Network"), self.settings_dialog) self.window.statusBar().addPermanentWidget(self.trustedcoin_button) self.xpub = self.wallet.master_public_keys.get('x1/') self.user_id = self.get_user_id()[1] t = threading.Thread(target=self.request_billing_info) t.setDaemon(True) t.start() @hook def close_wallet(self): self.window.statusBar().removeWidget(self.trustedcoin_button) @hook def get_wizard_action(self, window, wallet, action): if hasattr(self, action): return getattr(self, action) @hook def installwizard_restore(self, window, storage): if storage.get('wallet_type') != '2fa': return seed = window.enter_seed_dialog("Enter your seed", None, func=self.seed_func) if not seed: return wallet = Wallet_2fa(storage) self.wallet = wallet password = window.password_dialog() wallet.add_seed(seed, password) words = seed.split() n = len(words) / 2 wallet.add_cosigner_seed(' '.join(words[0:n]), 'x1/', password) wallet.add_cosigner_seed(' '.join(words[n:]), 'x2/', password) self.restore_third_key(wallet) wallet.create_main_account(password) # disable plugin self.set_enabled(False) return wallet def create_remote_key(self, wallet, window): self.wallet = wallet self.window = window if wallet.storage.get('wallet_type') != '2fa': raise return email = self.accept_terms_of_use(window) if not email: return xpub_hot = wallet.master_public_keys["x1/"] xpub_cold = wallet.master_public_keys["x2/"] # Generate third key deterministically. long_user_id, self.user_id = self.get_user_id() xpub3 = self.make_xpub(signing_xpub, long_user_id) # secret must be sent by the server try: r = server.create(xpub_hot, xpub_cold, email) except socket.error: self.window.show_message('Server not reachable, aborting') return otp_secret = r.get('otp_secret') if not otp_secret: self.window.show_message(_('Error')) return _xpub3 = r['xpubkey_cosigner'] _id = r['id'] try: assert _id == self.user_id, ("user id error", _id, self.user_id) assert xpub3 == _xpub3, ("xpub3 error", xpub3, _xpub3) except Exception as e: self.window.show_message(str(e)) return if not self.setup_google_auth(self.window, _id, otp_secret): return self.wallet.add_master_public_key('x3/', xpub3) return True def need_server(self, tx): from electrum.account import BIP32_Account # Detect if the server is needed long_id, short_id = self.get_user_id() xpub3 = self.wallet.master_public_keys['x3/'] for x in tx.inputs_to_sign(): if x[0:2] == 'ff': xpub, sequence = BIP32_Account.parse_xpubkey(x) if xpub == xpub3: return True return False @hook def send_tx(self, tx): print_error("twofactor:send_tx") if self.wallet.storage.get('wallet_type') != '2fa': return if not self.need_server(tx): print_error("twofactor: xpub3 not needed") self.auth_code = None return self.auth_code = self.auth_dialog() @hook def before_send(self): # request billing info before forming the transaction self.billing_info = None self.waiting_dialog = WaitingDialog(self.window, 'please wait...', self.request_billing_info) self.waiting_dialog.start() self.waiting_dialog.wait() if self.billing_info is None: self.window.show_message('Could not contact server') return True return False @hook def extra_fee(self, tx): if self.billing_info.get('tx_remaining'): return 0 if self.is_billing: return 0 # trustedcoin won't charge if the total inputs is lower than their fee price = int(self.price_per_tx.get(1)) assert price <= 100000 if tx.input_value() < price: print_error("not charging for this tx") return 0 return price @hook def make_unsigned_transaction(self, tx): price = self.extra_fee(tx) if not price: return tx.outputs.append( ('address', self.billing_info['billing_address'], price)) @hook def sign_transaction(self, tx, password): print_error("twofactor:sign") if self.wallet.storage.get('wallet_type') != '2fa': print_error("twofactor: aborting") return self.long_user_id, self.user_id = self.get_user_id() if not self.auth_code: return if tx.is_complete(): return tx_dict = tx.as_dict() raw_tx = tx_dict["hex"] try: r = server.sign(self.user_id, raw_tx, self.auth_code) except Exception as e: tx.error = str(e) return print_error("received answer", r) if not r: return raw_tx = r.get('transaction') tx.update(raw_tx) print_error("twofactor: is complete", tx.is_complete()) def auth_dialog(self): d = QDialog(self.window) d.setModal(1) vbox = QVBoxLayout(d) pw = AmountEdit(None, is_int=True) msg = _('Please enter your Google Authenticator code') vbox.addWidget(QLabel(msg)) grid = QGridLayout() grid.setSpacing(8) grid.addWidget(QLabel(_('Code')), 1, 0) grid.addWidget(pw, 1, 1) vbox.addLayout(grid) vbox.addLayout(ok_cancel_buttons(d)) if not d.exec_(): return return pw.get_amount() def settings_widget(self, window): return EnterButton(_('Settings'), self.settings_dialog) def settings_dialog(self): self.waiting_dialog = WaitingDialog(self.window, 'please wait...', self.request_billing_info, self.show_settings_dialog) self.waiting_dialog.start() def show_settings_dialog(self, success): if not success: self.window.show_message(_('Server not reachable.')) return d = QDialog(self.window) d.setWindowTitle("TrustedCoin Information") d.setMinimumSize(500, 200) vbox = QVBoxLayout(d) hbox = QHBoxLayout() logo = QLabel() logo.setPixmap(QPixmap(":icons/trustedcoin.png")) msg = _('This wallet is protected by TrustedCoin\'s two-factor authentication.') + '<br/>'\ + _("For more information, visit") + " <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>" label = QLabel(msg) label.setOpenExternalLinks(1) hbox.addStretch(10) hbox.addWidget(logo) hbox.addStretch(10) hbox.addWidget(label) hbox.addStretch(10) vbox.addLayout(hbox) vbox.addStretch(10) msg = _( 'TrustedCoin charges a fee per co-signed transaction. You may pay on each transaction (an extra output will be added to your transaction), or you may purchase prepaid transaction using this dialog.' ) + '<br/>' label = QLabel(msg) label.setWordWrap(1) vbox.addWidget(label) vbox.addStretch(10) grid = QGridLayout() vbox.addLayout(grid) v = self.price_per_tx.get(1) grid.addWidget(QLabel(_("Price per transaction (not prepaid):")), 0, 0) grid.addWidget( QLabel( self.window.format_amount(v) + ' ' + self.window.base_unit()), 0, 1) i = 1 for k, v in sorted(self.price_per_tx.items()): if k != 1: grid.addWidget( QLabel("Price for %d prepaid transactions:" % k), i, 0) grid.addWidget( QLabel( self.window.format_amount(v) + ' ' + self.window.base_unit()), i, 1) b = QPushButton(_("Buy")) grid.addWidget(b, i, 2) def on_buy(): d.close() if self.window.pluginsdialog: self.window.pluginsdialog.close() uri = "bitcoin:" + self.billing_info[ 'billing_address'] + "?message=TrustedCoin Prepaid Transactions&amount=" + str( Decimal(v) / 100000000) self.is_billing = True self.window.pay_from_URI(uri) self.window.payto_e.setFrozen(True) self.window.message_e.setFrozen(True) self.window.amount_e.setFrozen(True) b.clicked.connect(on_buy) i += 1 n = self.billing_info.get('tx_remaining', 0) grid.addWidget( QLabel(_("Your wallet has %d prepaid transactions.") % n), i, 0) # tranfer button #def on_transfer(): # server.transfer_credit(self.user_id, recipient, otp, signature_callback) # pass #b = QPushButton(_("Transfer")) #b.clicked.connect(on_transfer) #grid.addWidget(b, 1, 2) #grid.addWidget(QLabel(_("Next Billing Address:")), i, 0) #grid.addWidget(QLabel(self.billing_info['billing_address']), i, 1) vbox.addLayout(close_button(d)) d.exec_() def request_billing_info(self): billing_info = server.get(self.user_id) billing_address = self.make_billing_address( billing_info['billing_index']) assert billing_address == billing_info['billing_address'] self.billing_info = billing_info self.price_per_tx = dict(self.billing_info['price_per_tx']) return True def accept_terms_of_use(self, window): vbox = QVBoxLayout() window.set_layout(vbox) vbox.addWidget(QLabel(_("Terms of Service"))) tos_e = QTextEdit() tos_e.setReadOnly(True) vbox.addWidget(tos_e) vbox.addWidget(QLabel(_("Please enter your e-mail address"))) email_e = QLineEdit() vbox.addWidget(email_e) vbox.addStretch() hbox, accept_button = ok_cancel_buttons2(window, _('Accept')) accept_button.setEnabled(False) vbox.addLayout(hbox) def request_TOS(): tos = server.get_terms_of_service() self.TOS = tos window.emit(SIGNAL('twofactor:TOS')) def on_result(): tos_e.setText(self.TOS) window.connect(window, SIGNAL('twofactor:TOS'), on_result) t = threading.Thread(target=request_TOS) t.setDaemon(True) t.start() regexp = r"[^@]+@[^@]+\.[^@]+" email_e.textChanged.connect(lambda: accept_button.setEnabled( re.match(regexp, email_e.text()) is not None)) email_e.setFocus(True) if not window.exec_(): return email = str(email_e.text()) return email def setup_google_auth(self, window, _id, otp_secret): uri = "otpauth://totp/%s?secret=%s" % ('trustedcoin.com', otp_secret) vbox = QVBoxLayout() window.set_layout(vbox) vbox.addWidget( QLabel("Please scan this QR code in Google Authenticator.")) qrw = QRCodeWidget(uri) vbox.addWidget(qrw, 1) #vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter) hbox = QHBoxLayout() msg = _('Then, enter your Google Authenticator code:') hbox.addWidget(QLabel(msg)) pw = AmountEdit(None, is_int=True) pw.setFocus(True) hbox.addWidget(pw) hbox.addStretch(1) vbox.addLayout(hbox) hbox, b = ok_cancel_buttons2(window, _('Next')) b.setEnabled(False) vbox.addLayout(hbox) pw.textChanged.connect(lambda: b.setEnabled(len(pw.text()) == 6)) window.exec_() otp = pw.get_amount() try: server.auth(_id, otp) except: self.window.show_message('Incorrect password, aborting') return return True
class Plugin(BasePlugin): wallet = None def __init__(self, x, y): BasePlugin.__init__(self, x, y) electrum.wallet.wallet_types.append(('twofactor', '2fa', _("Wallet with two-factor authentication"), Wallet_2fa)) self.seed_func = lambda x: bitcoin.is_new_seed(x, SEED_PREFIX) self.billing_info = None def fullname(self): return 'Two Factor Authentication' def description(self): return _("This plugin adds two-factor authentication to your wallet.") + '<br/>'\ + _("For more information, visit") + " <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>" def is_available(self): if not self.wallet: return False if self.wallet.storage.get('wallet_type') == '2fa': return True return False def requires_settings(self): return True def set_enabled(self, enabled): self.wallet.storage.put('use_' + self.name, enabled) def is_enabled(self): if not self.is_available(): return False if self.wallet.master_private_keys.get('x2/'): return False return True def make_long_id(self, xpub_hot, xpub_cold): return bitcoin.sha256(''.join(sorted([xpub_hot, xpub_cold]))) def get_user_id(self): xpub_hot = self.wallet.master_public_keys["x1/"] xpub_cold = self.wallet.master_public_keys["x2/"] long_id = self.make_long_id(xpub_hot, xpub_cold) short_id = hashlib.sha256(long_id).hexdigest() return long_id, short_id def make_xpub(self, xpub, s): _, _, _, c, cK = deserialize_xkey(xpub) cK2, c2 = bitcoin._CKD_pub(cK, c, s) xpub2 = ("0488B21E" + "00" + "00000000" + "00000000").decode("hex") + c2 + cK2 return EncodeBase58Check(xpub2) def make_billing_address(self, num): long_id, short_id = self.get_user_id() xpub = self.make_xpub(billing_xpub, long_id) _, _, _, c, cK = deserialize_xkey(xpub) cK, c = bitcoin.CKD_pub(cK, c, num) address = public_key_to_bc_address( cK ) return address def enable(self): if self.is_enabled(): self.window.show_message('Error: Two-factor authentication is already activated on this wallet') return self.set_enabled(True) self.window.show_message('Two-factor authentication is enabled.') def create_extended_seed(self, wallet, window): seed = wallet.make_seed() if not window.show_seed(seed, None): return if not window.verify_seed(seed, None, self.seed_func): return password = window.password_dialog() wallet.storage.put('seed_version', wallet.seed_version, True) wallet.storage.put('use_encryption', password is not None, True) words = seed.split() n = len(words)/2 wallet.add_cosigner_seed(' '.join(words[0:n]), 'x1/', password) wallet.add_cosigner_xpub(' '.join(words[n:]), 'x2/') msg = [ _('Your wallet file is:') + " %s"%os.path.abspath(wallet.storage.path), _('You need to be online in order to complete the creation of your wallet.'), _('If you generated your seed on an offline computer, click on "%s" to close this window, move your wallet file to an online computer and reopen it with Electrum.') % _('Close'), _('If you are online, click on "%s" to continue.') % _('Next') ] return window.question('\n\n'.join(msg), no_label=_('Close'), yes_label=_('Next')) def show_disclaimer(self, wallet, window): msg = [ _("Two-factor authentication is a service provided by TrustedCoin.") + ' ', _("It uses a multi-signature wallet, where you own 2 of 3 keys.") + ' ', _("The third key is stored on a remote server that signs transactions on your behalf.") + ' ', _("To use this service, you will need a smartphone with Google Authenticator.") + '\n\n', _("A small fee will be charged on each transaction that uses the remote server.") + ' ', _("You may check and modify your billing preferences once the installation is complete.") + '\n\n', _("Note that your coins are not locked in this service.") + ' ', _("You may withdraw your funds at any time and at no cost, without the remote server, by using the 'restore wallet' option with your wallet seed.") + '\n\n', _('The next step will generate the seed of your wallet.') + ' ', _('This seed will NOT be saved in your computer, and it must be stored on paper.') + ' ', _('To be safe from malware, you may want to do this on an offline computer, and move your wallet later to an online computer.') ] icon = QPixmap(':icons/trustedcoin.png') if not window.question(''.join(msg), icon=icon): return False self.wallet = wallet self.set_enabled(True) return True def restore_third_key(self, wallet): long_user_id, short_id = self.get_user_id() xpub3 = self.make_xpub(signing_xpub, long_user_id) wallet.add_master_public_key('x3/', xpub3) @hook def init_qt(self, gui): self.window = gui.main_window self.is_billing = False @hook def do_clear(self): self.is_billing = False @hook def load_wallet(self, wallet): self.trustedcoin_button = StatusBarButton( QIcon(":icons/trustedcoin.png"), _("Network"), self.settings_dialog) self.window.statusBar().addPermanentWidget(self.trustedcoin_button) self.xpub = self.wallet.master_public_keys.get('x1/') self.user_id = self.get_user_id()[1] t = threading.Thread(target=self.request_billing_info) t.setDaemon(True) t.start() @hook def close_wallet(self): self.window.statusBar().removeWidget(self.trustedcoin_button) @hook def get_wizard_action(self, window, wallet, action): if hasattr(self, action): return getattr(self, action) @hook def installwizard_restore(self, window, storage): if storage.get('wallet_type') != '2fa': return seed = window.enter_seed_dialog("Enter your seed", None, func=self.seed_func) if not seed: return wallet = Wallet_2fa(storage) self.wallet = wallet password = window.password_dialog() wallet.add_seed(seed, password) words = seed.split() n = len(words)/2 wallet.add_cosigner_seed(' '.join(words[0:n]), 'x1/', password) wallet.add_cosigner_seed(' '.join(words[n:]), 'x2/', password) self.restore_third_key(wallet) wallet.create_main_account(password) # disable plugin self.set_enabled(False) return wallet def create_remote_key(self, wallet, window): self.wallet = wallet self.window = window if wallet.storage.get('wallet_type') != '2fa': raise return email = self.accept_terms_of_use(window) if not email: return xpub_hot = wallet.master_public_keys["x1/"] xpub_cold = wallet.master_public_keys["x2/"] # Generate third key deterministically. long_user_id, self.user_id = self.get_user_id() xpub3 = self.make_xpub(signing_xpub, long_user_id) # secret must be sent by the server try: r = server.create(xpub_hot, xpub_cold, email) except socket.error: self.window.show_message('Server not reachable, aborting') return otp_secret = r.get('otp_secret') if not otp_secret: self.window.show_message(_('Error')) return _xpub3 = r['xpubkey_cosigner'] _id = r['id'] try: assert _id == self.user_id, ("user id error", _id, self.user_id) assert xpub3 == _xpub3, ("xpub3 error", xpub3, _xpub3) except Exception as e: self.window.show_message(str(e)) return if not self.setup_google_auth(self.window, _id, otp_secret): return self.wallet.add_master_public_key('x3/', xpub3) return True def need_server(self, tx): from electrum.account import BIP32_Account # Detect if the server is needed long_id, short_id = self.get_user_id() xpub3 = self.wallet.master_public_keys['x3/'] for x in tx.inputs_to_sign(): if x[0:2] == 'ff': xpub, sequence = BIP32_Account.parse_xpubkey(x) if xpub == xpub3: return True return False @hook def send_tx(self, tx): print_error("twofactor:send_tx") if self.wallet.storage.get('wallet_type') != '2fa': return if not self.need_server(tx): print_error("twofactor: xpub3 not needed") self.auth_code = None return self.auth_code = self.auth_dialog() @hook def before_send(self): # request billing info before forming the transaction self.billing_info = None self.waiting_dialog = WaitingDialog(self.window, 'please wait...', self.request_billing_info) self.waiting_dialog.start() self.waiting_dialog.wait() if self.billing_info is None: self.window.show_message('Could not contact server') return True return False @hook def extra_fee(self, tx): if self.billing_info.get('tx_remaining'): return 0 if self.is_billing: return 0 # trustedcoin won't charge if the total inputs is lower than their fee price = int(self.price_per_tx.get(1)) assert price <= 100000 if tx.input_value() < price: print_error("not charging for this tx") return 0 return price @hook def make_unsigned_transaction(self, tx): price = self.extra_fee(tx) if not price: return tx.outputs.append(('address', self.billing_info['billing_address'], price)) @hook def sign_transaction(self, tx, password): print_error("twofactor:sign") if self.wallet.storage.get('wallet_type') != '2fa': print_error("twofactor: aborting") return self.long_user_id, self.user_id = self.get_user_id() if not self.auth_code: return if tx.is_complete(): return tx_dict = tx.as_dict() raw_tx = tx_dict["hex"] try: r = server.sign(self.user_id, raw_tx, self.auth_code) except Exception as e: tx.error = str(e) return print_error( "received answer", r) if not r: return raw_tx = r.get('transaction') tx.update(raw_tx) print_error("twofactor: is complete", tx.is_complete()) def auth_dialog(self ): d = QDialog(self.window) d.setModal(1) vbox = QVBoxLayout(d) pw = AmountEdit(None, is_int = True) msg = _('Please enter your Google Authenticator code') vbox.addWidget(QLabel(msg)) grid = QGridLayout() grid.setSpacing(8) grid.addWidget(QLabel(_('Code')), 1, 0) grid.addWidget(pw, 1, 1) vbox.addLayout(grid) vbox.addLayout(ok_cancel_buttons(d)) if not d.exec_(): return return pw.get_amount() def settings_widget(self, window): return EnterButton(_('Settings'), self.settings_dialog) def settings_dialog(self): self.waiting_dialog = WaitingDialog(self.window, 'please wait...', self.request_billing_info, self.show_settings_dialog) self.waiting_dialog.start() def show_settings_dialog(self, success): if not success: self.window.show_message(_('Server not reachable.')) return d = QDialog(self.window) d.setWindowTitle("TrustedCoin Information") d.setMinimumSize(500, 200) vbox = QVBoxLayout(d) hbox = QHBoxLayout() logo = QLabel() logo.setPixmap(QPixmap(":icons/trustedcoin.png")) msg = _('This wallet is protected by TrustedCoin\'s two-factor authentication.') + '<br/>'\ + _("For more information, visit") + " <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>" label = QLabel(msg) label.setOpenExternalLinks(1) hbox.addStretch(10) hbox.addWidget(logo) hbox.addStretch(10) hbox.addWidget(label) hbox.addStretch(10) vbox.addLayout(hbox) vbox.addStretch(10) msg = _('TrustedCoin charges a fee per co-signed transaction. You may pay on each transaction (an extra output will be added to your transaction), or you may purchase prepaid transaction using this dialog.') + '<br/>' label = QLabel(msg) label.setWordWrap(1) vbox.addWidget(label) vbox.addStretch(10) grid = QGridLayout() vbox.addLayout(grid) v = self.price_per_tx.get(1) grid.addWidget(QLabel(_("Price per transaction (not prepaid):")), 0, 0) grid.addWidget(QLabel(self.window.format_amount(v) + ' ' + self.window.base_unit()), 0, 1) i = 1 for k, v in sorted(self.price_per_tx.items()): if k!=1: grid.addWidget(QLabel("Price for %d prepaid transactions:"%k), i, 0) grid.addWidget(QLabel(self.window.format_amount(v) + ' ' + self.window.base_unit()), i, 1) b = QPushButton(_("Buy")) grid.addWidget(b, i, 2) def on_buy(): d.close() if self.window.pluginsdialog: self.window.pluginsdialog.close() uri = "bitcoin:" + self.billing_info['billing_address'] + "?message=TrustedCoin Prepaid Transactions&amount="+str(Decimal(v)/100000000) self.is_billing = True self.window.pay_from_URI(uri) self.window.payto_e.setFrozen(True) self.window.message_e.setFrozen(True) self.window.amount_e.setFrozen(True) b.clicked.connect(on_buy) i += 1 n = self.billing_info.get('tx_remaining', 0) grid.addWidget(QLabel(_("Your wallet has %d prepaid transactions.")%n), i, 0) # tranfer button #def on_transfer(): # server.transfer_credit(self.user_id, recipient, otp, signature_callback) # pass #b = QPushButton(_("Transfer")) #b.clicked.connect(on_transfer) #grid.addWidget(b, 1, 2) #grid.addWidget(QLabel(_("Next Billing Address:")), i, 0) #grid.addWidget(QLabel(self.billing_info['billing_address']), i, 1) vbox.addLayout(close_button(d)) d.exec_() def request_billing_info(self): billing_info = server.get(self.user_id) billing_address = self.make_billing_address(billing_info['billing_index']) assert billing_address == billing_info['billing_address'] self.billing_info = billing_info self.price_per_tx = dict(self.billing_info['price_per_tx']) return True def accept_terms_of_use(self, window): vbox = QVBoxLayout() window.set_layout(vbox) vbox.addWidget(QLabel(_("Terms of Service"))) tos_e = QTextEdit() tos_e.setReadOnly(True) vbox.addWidget(tos_e) vbox.addWidget(QLabel(_("Please enter your e-mail address"))) email_e = QLineEdit() vbox.addWidget(email_e) vbox.addStretch() hbox, accept_button = ok_cancel_buttons2(window, _('Accept')) accept_button.setEnabled(False) vbox.addLayout(hbox) def request_TOS(): tos = server.get_terms_of_service() self.TOS = tos window.emit(SIGNAL('twofactor:TOS')) def on_result(): tos_e.setText(self.TOS) window.connect(window, SIGNAL('twofactor:TOS'), on_result) t = threading.Thread(target=request_TOS) t.setDaemon(True) t.start() regexp = r"[^@]+@[^@]+\.[^@]+" email_e.textChanged.connect(lambda: accept_button.setEnabled(re.match(regexp,email_e.text()) is not None)) email_e.setFocus(True) if not window.exec_(): return email = str(email_e.text()) return email def setup_google_auth(self, window, _id, otp_secret): uri = "otpauth://totp/%s?secret=%s"%('trustedcoin.com', otp_secret) vbox = QVBoxLayout() window.set_layout(vbox) vbox.addWidget(QLabel("Please scan this QR code in Google Authenticator.")) qrw = QRCodeWidget(uri) vbox.addWidget(qrw, 1) #vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter) hbox = QHBoxLayout() msg = _('Then, enter your Google Authenticator code:') hbox.addWidget(QLabel(msg)) pw = AmountEdit(None, is_int = True) pw.setFocus(True) hbox.addWidget(pw) hbox.addStretch(1) vbox.addLayout(hbox) hbox, b = ok_cancel_buttons2(window, _('Next')) b.setEnabled(False) vbox.addLayout(hbox) pw.textChanged.connect(lambda: b.setEnabled(len(pw.text())==6)) window.exec_() otp = pw.get_amount() try: server.auth(_id, otp) except: self.window.show_message('Incorrect password, aborting') return return True