1 changed files with 711 additions and 0 deletions
@ -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 |
|||
|
|||
|
Loading…
Reference in new issue