URI: 
       tplugin for TrustedCoin - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 95586643a4389d900e791aadcee7292a51373945
   DIR parent 2b42f054a954aa7c10cb3f8a4c0cc399bc523104
  HTML Author: ThomasV <thomasv@gitorious>
       Date:   Tue,  2 Sep 2014 07:47:10 +0200
       
       plugin for TrustedCoin
       
       Diffstat:
         A plugins/trustedcoin.py              |     711 +++++++++++++++++++++++++++++++
       
       1 file changed, 711 insertions(+), 0 deletions(-)
       ---
   DIR diff --git a/plugins/trustedcoin.py b/plugins/trustedcoin.py
       t@@ -0,0 +1,711 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - Lightweight Bitcoin Client
       +# Copyright (C) 2015 Thomas Voegtlin
       +#
       +# This program is free software: you can redistribute it and/or modify
       +# it under the terms of the GNU General Public License as published by
       +# the Free Software Foundation, either version 3 of the License, or
       +# (at your option) any later version.
       +#
       +# This program is distributed in the hope that it will be useful,
       +# but WITHOUT ANY WARRANTY; without even the implied warranty of
       +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
       +# GNU General Public License for more details.
       +#
       +# You should have received a copy of the GNU General Public License
       +# along with this program. If not, see <http://www.gnu.org/licenses/>.
       +
       +import threading
       +import socket
       +import os
       +import re
       +import requests
       +import json
       +from hashlib import sha256
       +from urlparse import urljoin
       +from urllib import quote
       +
       +from PyQt4.QtGui import *
       +from PyQt4.QtCore import *
       +
       +import electrum
       +from electrum import bitcoin
       +from electrum.bitcoin import *
       +from electrum.mnemonic import Mnemonic
       +from electrum import version
       +from electrum.wallet import Wallet_2of3
       +from electrum.i18n import _
       +from electrum.plugins import BasePlugin, run_hook, hook
       +
       +from electrum_gui.qt.util import text_dialog, EnterButton, WaitingDialog
       +from electrum_gui.qt.qrcodewidget import QRCodeWidget
       +from electrum_gui.qt import ok_cancel_buttons, ok_cancel_buttons2, close_button
       +from electrum_gui.qt.amountedit import AmountEdit
       +from electrum_gui.qt.main_window import StatusBarButton
       +
       +from decimal import Decimal
       +
       +# signing_xpub is hardcoded so that the wallet can be restored from seed, without TrustedCoin's server
       +signing_xpub = "xpub661MyMwAqRbcGnMkaTx2594P9EDuiEqMq25PM2aeG6UmwzaohgA6uDmNsvSUV8ubqwA3Wpste1hg69XHgjUuCD5HLcEp2QPzyV1HMrPppsL"
       +billing_xpub = "xpub6DTBdtBB8qUmH5c77v8qVGVoYk7WjJNpGvutqjLasNG1mbux6KsojaLrYf2sRhXAVU4NaFuHhbD9SvVPRt1MB1MaMooRuhHcAZH1yhQ1qDU"
       +
       +SEED_PREFIX = version.SEED_PREFIX_2FA
       +
       +
       +class TrustedCoinException(Exception):
       +    def __init__(self, message, status_code=0):
       +        Exception.__init__(self, message)
       +        self.status_code = status_code
       +
       +class TrustedCoinCosignerClient(object):
       +    def __init__(self, user_agent=None, base_url='https://api.trustedcoin.com/2/', debug=False):
       +        self.base_url = base_url
       +        self.debug = debug
       +        self.user_agent = user_agent
       +        
       +    def send_request(self, method, relative_url, data=None):
       +        kwargs = {'headers': {}}
       +        if self.user_agent:
       +            kwargs['headers']['user-agent'] = self.user_agent
       +        if method == 'get' and data:
       +            kwargs['params'] = data
       +        elif method == 'post' and data:
       +            kwargs['data'] = json.dumps(data)
       +            kwargs['headers']['content-type'] = 'application/json'
       +        url = urljoin(self.base_url, relative_url)
       +        if self.debug:
       +            print '%s %s %s' % (method, url, data)
       +        response = requests.request(method, url, **kwargs)
       +        if self.debug:
       +            print response.text
       +            print
       +        if response.status_code != 200:
       +            message = str(response.text)
       +            if response.headers.get('content-type') == 'application/json':
       +                r = response.json()
       +                if 'message' in r:
       +                    message = r['message']
       +            raise TrustedCoinException(message, response.status_code)        
       +        if response.headers.get('content-type') == 'application/json':
       +            return response.json()
       +        else:
       +            return response.text
       +        
       +    def get_terms_of_service(self, billing_plan='electrum-per-tx-otp'):
       +        """
       +        Returns the TOS for the given billing plan as a plain/text unicode string.
       +        :param billing_plan: the plan to return the terms for
       +        """
       +        payload = {'billing_plan': billing_plan}
       +        return self.send_request('get', 'tos', payload)
       +        
       +    def create(self, xpubkey1, xpubkey2, email, billing_plan='electrum-per-tx-otp'):
       +        """
       +        Creates a new cosigner resource.
       +        :param xpubkey1: a bip32 extended public key (customarily the hot key)
       +        :param xpubkey2: a bip32 extended public key (customarily the cold key)
       +        :param email: a contact email 
       +        :param billing_plan: the billing plan for the cosigner
       +        """
       +        payload = {
       +            'email': email,
       +            'xpubkey1': xpubkey1,
       +            'xpubkey2': xpubkey2,
       +            'billing_plan': billing_plan,
       +        }
       +        return self.send_request('post', 'cosigner', payload)
       +        
       +    def auth(self, id, otp):
       +        """
       +        Attempt to authenticate for a particular cosigner.
       +        :param id: the id of the cosigner
       +        :param otp: the one time password
       +        """
       +        payload = {'otp': otp}
       +        return self.send_request('post', 'cosigner/%s/auth' % quote(id), payload)
       +        
       +    def get(self, id):
       +        """
       +        Attempt to authenticate for a particular cosigner.
       +        :param id: the id of the cosigner
       +        :param otp: the one time password
       +        """
       +        return self.send_request('get', 'cosigner/%s' % quote(id))
       +        
       +    def sign(self, id, transaction, otp):
       +        """
       +        Attempt to authenticate for a particular cosigner.
       +        :param id: the id of the cosigner
       +        :param transaction: the hex encoded [partially signed] compact transaction to sign
       +        :param otp: the one time password
       +        """
       +        payload = {
       +            'otp': otp,
       +            'transaction': transaction
       +        }
       +        return self.send_request('post', 'cosigner/%s/sign' % quote(id), payload)
       +
       +    def transfer_credit(self, id, recipient, otp, signature_callback):
       +        """
       +        Tranfer a cosigner's credits to another cosigner.
       +        :param id: the id of the sending cosigner
       +        :param recipient: the id of the recipient cosigner
       +        :param otp: the one time password (of the sender)
       +        :param signature_callback: a callback that signs a text message using xpubkey1/0/0 returning a compact sig
       +        """
       +        payload = {
       +            'otp': otp,
       +            'recipient': recipient,
       +            'timestamp': int(time.time()),
       +            
       +        }
       +        relative_url = 'cosigner/%s/transfer' % quote(id)
       +        full_url = urljoin(self.base_url, relative_url)
       +        headers = {
       +            'x-signature': signature_callback(full_url + '\n' + json.dumps(payload))
       +        }
       +        return self.send_request('post', relative_url, payload, headers)
       +
       +
       +server = TrustedCoinCosignerClient(user_agent="Electrum/" + version.ELECTRUM_VERSION)
       +
       +
       +class Wallet_2fa(Wallet_2of3):
       +
       +    wallet_type = '2fa'
       +
       +    def get_action(self):
       +        xpub1 = self.master_public_keys.get("x1/")
       +        xpub2 = self.master_public_keys.get("x2/")
       +        xpub3 = self.master_public_keys.get("x3/")
       +        if xpub2 is None and not self.storage.get('use_trustedcoin'):
       +            return 'show_disclaimer'
       +        if xpub2 is None:
       +            return 'create_extended_seed'
       +        if xpub3 is None:
       +            return 'create_remote_key'
       +        if not self.accounts:
       +            return 'create_accounts'
       +
       +    def make_seed(self):
       +        return Mnemonic('english').make_seed(num_bits=256, prefix=SEED_PREFIX)
       +
       +    def estimated_fee(self, tx):
       +        fee = Wallet_2of3.estimated_fee(self, tx)
       +        x = run_hook('extra_fee', tx)
       +        if x: fee += x
       +        return fee
       +
       +    def get_tx_fee(self, tx):
       +        fee = Wallet_2of3.get_tx_fee(self, tx)
       +        x = run_hook('extra_fee', tx)
       +        if x: fee += x
       +        return fee
       +
       +
       +
       +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.wallet = wallet
       +        if self.is_enabled():
       +            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))
       +        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
       +
       +