ThomasV
9 years ago
11 changed files with 450 additions and 648 deletions
@ -1,328 +0,0 @@ |
|||
#!/usr/bin/env python |
|||
# |
|||
# Electrum - lightweight Bitcoin client |
|||
# Copyright (C) 2015 thomasv@gitorious, kyuupichan@gmail |
|||
# |
|||
# Permission is hereby granted, free of charge, to any person |
|||
# obtaining a copy of this software and associated documentation files |
|||
# (the "Software"), to deal in the Software without restriction, |
|||
# including without limitation the rights to use, copy, modify, merge, |
|||
# publish, distribute, sublicense, and/or sell copies of the Software, |
|||
# and to permit persons to whom the Software is furnished to do so, |
|||
# subject to the following conditions: |
|||
# |
|||
# The above copyright notice and this permission notice shall be |
|||
# included in all copies or substantial portions of the Software. |
|||
# |
|||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
|||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
|||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
|||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
# SOFTWARE. |
|||
|
|||
from electrum import WalletStorage |
|||
from electrum.plugins import run_hook |
|||
from util import PrintError |
|||
from wallet import Wallet |
|||
from i18n import _ |
|||
|
|||
MSG_GENERATING_WAIT = _("Electrum is generating your addresses, please wait...") |
|||
MSG_ENTER_ANYTHING = _("Please enter a seed phrase, a master key, a list of " |
|||
"Bitcoin addresses, or a list of private keys") |
|||
MSG_ENTER_SEED_OR_MPK = _("Please enter a seed phrase or a master key (xpub or xprv):") |
|||
MSG_VERIFY_SEED = _("Your seed is important!\nTo make sure that you have properly saved your seed, please retype it here.") |
|||
MSG_COSIGNER = _("Please enter the master public key of cosigner #%d:") |
|||
MSG_SHOW_MPK = _("Here is your master public key:") |
|||
MSG_ENTER_PASSWORD = _("Choose a password to encrypt your wallet keys. " |
|||
"Enter nothing if you want to disable encryption.") |
|||
MSG_RESTORE_PASSPHRASE = \ |
|||
_("Please enter the passphrase you used when creating your %s wallet. " |
|||
"Note this is NOT a password. Enter nothing if you did not use " |
|||
"one or are unsure.") |
|||
|
|||
class WizardBase(PrintError): |
|||
'''Base class for gui-specific install wizards.''' |
|||
user_actions = ('create', 'restore') |
|||
wallet_kinds = [ |
|||
('standard', _("Standard wallet")), |
|||
('twofactor', _("Wallet with two-factor authentication")), |
|||
('multisig', _("Multi-signature wallet")), |
|||
('hardware', _("Hardware wallet")), |
|||
] |
|||
|
|||
# Derived classes must set: |
|||
# self.language_for_seed |
|||
# self.plugins |
|||
|
|||
def show_error(self, msg): |
|||
raise NotImplementedError |
|||
|
|||
def show_warning(self, msg): |
|||
raise NotImplementedError |
|||
|
|||
def remove_from_recently_open(self, filename): |
|||
"""Remove filename from the recently used list.""" |
|||
raise NotImplementedError |
|||
|
|||
def query_create_or_restore(self, wallet_kinds): |
|||
"""Ask the user what they want to do, and which wallet kind. |
|||
wallet_kinds is an array of translated wallet descriptions. |
|||
Return a a tuple (action, kind_index). Action is 'create' or |
|||
'restore', and kind the index of the wallet kind chosen.""" |
|||
raise NotImplementedError |
|||
|
|||
def query_multisig(self, action): |
|||
"""Asks the user what kind of multisig wallet they want. Returns a |
|||
string like "2of3". Action is 'create' or 'restore'.""" |
|||
raise NotImplementedError |
|||
|
|||
def query_choice(self, msg, choices): |
|||
"""Asks the user which of several choices they would like. |
|||
Return the index of the choice.""" |
|||
raise NotImplementedError |
|||
|
|||
def query_hw_wallet_choice(self, msg, action, choices): |
|||
"""Asks the user which hardware wallet kind they are using. Action is |
|||
'create' or 'restore' from the initial screen. As this is |
|||
confusing for hardware wallets ask a new question with the |
|||
three possibilities Initialize ('create'), Use ('create') or |
|||
Restore a software-only wallet ('restore'). Return a pair |
|||
(action, choice).""" |
|||
raise NotImplementedError |
|||
|
|||
def show_and_verify_seed(self, seed): |
|||
"""Show the user their seed. Ask them to re-enter it. Return |
|||
True on success.""" |
|||
raise NotImplementedError |
|||
|
|||
def request_passphrase(self, device_text): |
|||
"""When restoring a wallet, request the passphrase that was used for |
|||
the wallet on the given device and confirm it. Should return |
|||
a unicode string.""" |
|||
raise NotImplementedError |
|||
|
|||
def request_password(self, msg=None): |
|||
"""Request the user enter a new password and confirm it. Return |
|||
the password or None for no password.""" |
|||
raise NotImplementedError |
|||
|
|||
def request_seed(self, msg, is_valid=None): |
|||
"""Request the user enter a seed. Returns the seed the user entered. |
|||
is_valid is a function that returns True if a seed is valid, for |
|||
dynamic feedback. If not provided, Wallet.is_any is used.""" |
|||
raise NotImplementedError |
|||
|
|||
def request_many(self, n, xpub_hot=None): |
|||
"""If xpub_hot is provided, a new wallet is being created. Request N |
|||
master public keys for cosigners; xpub_hot is the master xpub |
|||
key for the wallet. |
|||
|
|||
If xpub_hot is None, request N cosigning master xpub keys, |
|||
xprv keys, or seeds in order to perform wallet restore.""" |
|||
raise NotImplementedError |
|||
|
|||
def choose_server(self, network): |
|||
"""Choose a server if one is not set in the config anyway.""" |
|||
raise NotImplementedError |
|||
|
|||
def show_restore(self, wallet, network): |
|||
"""Show restore result""" |
|||
pass |
|||
|
|||
def finished(self): |
|||
"""Called when the wizard is done.""" |
|||
pass |
|||
|
|||
def run(self, network, storage): |
|||
'''The main entry point of the wizard. Open a wallet from the given |
|||
filename. If the file doesn't exist launch the GUI-specific |
|||
install wizard proper, created by calling create_wizard().''' |
|||
need_sync = False |
|||
is_restore = False |
|||
|
|||
if storage.file_exists: |
|||
wallet = Wallet(storage) |
|||
if wallet.imported_keys: |
|||
self.update_wallet_format(wallet) |
|||
action = wallet.get_action() |
|||
if action != 'new': |
|||
self.hide() |
|||
path = storage.path |
|||
msg = _("The file '%s' contains an incompletely created wallet.\n" |
|||
"Do you want to complete its creation now?") % path |
|||
if not self.question(msg): |
|||
if self.question(_("Do you want to delete '%s'?") % path): |
|||
import os |
|||
os.remove(path) |
|||
self.show_warning(_('The file was removed')) |
|||
return |
|||
return |
|||
self.show() |
|||
else: |
|||
cr, wallet = self.create_or_restore(storage) |
|||
if not wallet: |
|||
return |
|||
need_sync = True |
|||
is_restore = (cr == 'restore') |
|||
|
|||
while True: |
|||
action = wallet.get_action() |
|||
if not action: |
|||
break |
|||
need_sync = True |
|||
self.run_wallet_action(wallet, action) |
|||
# Save the wallet after each action |
|||
wallet.storage.write() |
|||
|
|||
if network: |
|||
# Show network dialog if config does not exist |
|||
if self.config.get('auto_connect') is None: |
|||
self.choose_server(network) |
|||
else: |
|||
self.show_warning(_('You are offline')) |
|||
|
|||
if need_sync: |
|||
self.create_addresses(wallet) |
|||
|
|||
# start wallet threads |
|||
if network: |
|||
wallet.start_threads(network) |
|||
|
|||
if is_restore: |
|||
self.show_restore(wallet, network) |
|||
|
|||
self.finished() |
|||
|
|||
return wallet |
|||
|
|||
def run_wallet_action(self, wallet, action): |
|||
self.print_error("action %s on %s" % (action, wallet.basename())) |
|||
# Run the action on the wallet plugin, if any, then the |
|||
# wallet and finally ourselves |
|||
calls = [] |
|||
if hasattr(wallet, 'plugin'): |
|||
calls.append((wallet.plugin, (wallet, self))) |
|||
calls.extend([(wallet, ()), (self, (wallet, ))]) |
|||
calls = [(getattr(actor, action), args) for (actor, args) in calls |
|||
if hasattr(actor, action)] |
|||
if not calls: |
|||
raise RuntimeError("No handler found for %s action" % action) |
|||
for method, args in calls: |
|||
method(*args) |
|||
|
|||
def create_or_restore(self, storage): |
|||
'''After querying the user what they wish to do, create or restore |
|||
a wallet and return it.''' |
|||
self.remove_from_recently_open(storage.path) |
|||
|
|||
# Filter out any unregistered wallet kinds |
|||
registered_kinds = Wallet.categories() |
|||
kinds, descriptions = zip(*[pair for pair in WizardBase.wallet_kinds |
|||
if pair[0] in registered_kinds]) |
|||
action, kind_index = self.query_create_or_restore(descriptions) |
|||
assert action in WizardBase.user_actions |
|||
kind = kinds[kind_index] |
|||
if kind == 'multisig': |
|||
wallet_type = self.query_multisig(action) |
|||
elif kind == 'hardware': |
|||
hw_wallet_types, choices = self.plugins.hardware_wallets(action) |
|||
if choices: |
|||
msg = _('Select the type of hardware wallet: ') |
|||
else: |
|||
msg = ' '.join([ |
|||
_('No hardware wallet support found on your system.'), |
|||
_('Please install the relevant libraries (eg python-trezor for Trezor).'), |
|||
]) |
|||
choice = self.query_hw_wallet_choice(msg, choices) |
|||
wallet_type = hw_wallet_types[choice] |
|||
elif kind == 'twofactor': |
|||
wallet_type = '2fa' |
|||
else: |
|||
wallet_type = 'standard' |
|||
|
|||
if action == 'create': |
|||
wallet = self.create_wallet(storage, wallet_type, kind) |
|||
else: |
|||
wallet = self.restore_wallet(storage, wallet_type, kind) |
|||
|
|||
return action, wallet |
|||
|
|||
def construct_wallet(self, storage, wallet_type): |
|||
storage.put('wallet_type', wallet_type) |
|||
return Wallet(storage) |
|||
|
|||
def create_wallet(self, storage, wallet_type, kind): |
|||
wallet = self.construct_wallet(storage, wallet_type) |
|||
if kind == 'hardware': |
|||
wallet.plugin.on_create_wallet(wallet, self) |
|||
return wallet |
|||
|
|||
def restore_wallet(self, storage, wallet_type, kind): |
|||
if wallet_type == 'standard': |
|||
return self.restore_standard_wallet(storage) |
|||
|
|||
if kind == 'multisig': |
|||
return self.restore_multisig_wallet(storage, wallet_type) |
|||
|
|||
# Plugin (two-factor or hardware) |
|||
wallet = self.construct_wallet(storage, wallet_type) |
|||
return wallet.plugin.on_restore_wallet(wallet, self) |
|||
|
|||
def restore_standard_wallet(self, storage): |
|||
text = self.request_seed(MSG_ENTER_ANYTHING) |
|||
need_password = Wallet.should_encrypt(text) |
|||
password = self.request_password() if need_password else None |
|||
return Wallet.from_text(text, password, storage) |
|||
|
|||
def restore_multisig_wallet(self, storage, wallet_type): |
|||
# FIXME: better handling of duplicate keys |
|||
m, n = Wallet.multisig_type(wallet_type) |
|||
key_list = self.request_many(n - 1) |
|||
need_password = any(Wallet.should_encrypt(text) for text in key_list) |
|||
password = self.request_password() if need_password else None |
|||
return Wallet.from_multisig(key_list, password, storage, wallet_type) |
|||
|
|||
def create_seed(self, wallet): |
|||
'''The create_seed action creates a seed and generates |
|||
master keys.''' |
|||
seed = wallet.make_seed(self.language_for_seed) |
|||
self.show_and_verify_seed(seed) |
|||
password = self.request_password() |
|||
wallet.add_seed(seed, password) |
|||
wallet.create_master_keys(password) |
|||
|
|||
def create_main_account(self, wallet): |
|||
# FIXME: BIP44 restore requires password |
|||
wallet.create_main_account() |
|||
|
|||
def create_addresses(self, wallet): |
|||
wallet.synchronize() |
|||
|
|||
def add_cosigners(self, wallet): |
|||
# FIXME: better handling of duplicate keys |
|||
m, n = Wallet.multisig_type(wallet.wallet_type) |
|||
xpub1 = wallet.master_public_keys.get("x1/") |
|||
xpubs = self.request_many(n - 1, xpub1) |
|||
for i, xpub in enumerate(xpubs): |
|||
wallet.add_master_public_key("x%d/" % (i + 2), xpub) |
|||
|
|||
def update_wallet_format(self, wallet): |
|||
# Backwards compatibility: convert old-format imported keys |
|||
msg = _("Please enter your password in order to update " |
|||
"imported keys") |
|||
if wallet.use_encryption: |
|||
password = self.request_password(msg) |
|||
else: |
|||
password = None |
|||
|
|||
try: |
|||
wallet.convert_imported_keys(password) |
|||
except Exception as e: |
|||
self.show_error(str(e)) |
|||
|
|||
# Call synchronize to regenerate addresses in case we're offline |
|||
if wallet.get_master_public_keys() and not wallet.addresses(): |
|||
wallet.synchronize() |
Loading…
Reference in new issue