|
@ -25,6 +25,7 @@ |
|
|
|
|
|
|
|
|
import os |
|
|
import os |
|
|
import sys |
|
|
import sys |
|
|
|
|
|
import copy |
|
|
import traceback |
|
|
import traceback |
|
|
from functools import partial |
|
|
from functools import partial |
|
|
from typing import List, TYPE_CHECKING, Tuple, NamedTuple, Any |
|
|
from typing import List, TYPE_CHECKING, Tuple, NamedTuple, Any |
|
@ -65,12 +66,12 @@ class WizardStackItem(NamedTuple): |
|
|
|
|
|
|
|
|
class BaseWizard(object): |
|
|
class BaseWizard(object): |
|
|
|
|
|
|
|
|
def __init__(self, config: SimpleConfig, plugins: Plugins, storage: WalletStorage): |
|
|
def __init__(self, config: SimpleConfig, plugins: Plugins): |
|
|
super(BaseWizard, self).__init__() |
|
|
super(BaseWizard, self).__init__() |
|
|
self.config = config |
|
|
self.config = config |
|
|
self.plugins = plugins |
|
|
self.plugins = plugins |
|
|
self.storage = storage |
|
|
self.data = {} |
|
|
self.wallet = None # type: Abstract_Wallet |
|
|
self.pw_args = None |
|
|
self._stack = [] # type: List[WizardStackItem] |
|
|
self._stack = [] # type: List[WizardStackItem] |
|
|
self.plugin = None |
|
|
self.plugin = None |
|
|
self.keystores = [] |
|
|
self.keystores = [] |
|
@ -83,7 +84,7 @@ class BaseWizard(object): |
|
|
def run(self, *args): |
|
|
def run(self, *args): |
|
|
action = args[0] |
|
|
action = args[0] |
|
|
args = args[1:] |
|
|
args = args[1:] |
|
|
storage_data = self.storage.get_all_data() |
|
|
storage_data = copy.deepcopy(self.data) |
|
|
self._stack.append(WizardStackItem(action, args, storage_data)) |
|
|
self._stack.append(WizardStackItem(action, args, storage_data)) |
|
|
if not action: |
|
|
if not action: |
|
|
return |
|
|
return |
|
@ -110,7 +111,7 @@ class BaseWizard(object): |
|
|
stack_item = self._stack.pop() |
|
|
stack_item = self._stack.pop() |
|
|
# try to undo side effects since we last entered 'previous' frame |
|
|
# try to undo side effects since we last entered 'previous' frame |
|
|
# FIXME only self.storage is properly restored |
|
|
# FIXME only self.storage is properly restored |
|
|
self.storage.overwrite_all_data(stack_item.storage_data) |
|
|
self.data = copy.deepcopy(stack_item.storage_data) |
|
|
# rerun 'previous' frame |
|
|
# rerun 'previous' frame |
|
|
self.run(stack_item.action, *stack_item.args) |
|
|
self.run(stack_item.action, *stack_item.args) |
|
|
|
|
|
|
|
@ -118,8 +119,7 @@ class BaseWizard(object): |
|
|
self._stack = [] |
|
|
self._stack = [] |
|
|
|
|
|
|
|
|
def new(self): |
|
|
def new(self): |
|
|
name = os.path.basename(self.storage.path) |
|
|
title = _("Create new wallet") |
|
|
title = _("Create") + ' ' + name |
|
|
|
|
|
message = '\n'.join([ |
|
|
message = '\n'.join([ |
|
|
_("What kind of wallet do you want to create?") |
|
|
_("What kind of wallet do you want to create?") |
|
|
]) |
|
|
]) |
|
@ -132,36 +132,35 @@ class BaseWizard(object): |
|
|
choices = [pair for pair in wallet_kinds if pair[0] in wallet_types] |
|
|
choices = [pair for pair in wallet_kinds if pair[0] in wallet_types] |
|
|
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type) |
|
|
self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type) |
|
|
|
|
|
|
|
|
def upgrade_storage(self): |
|
|
def upgrade_storage(self, storage): |
|
|
exc = None |
|
|
exc = None |
|
|
def on_finished(): |
|
|
def on_finished(): |
|
|
if exc is None: |
|
|
if exc is None: |
|
|
self.wallet = Wallet(self.storage) |
|
|
|
|
|
self.terminate() |
|
|
self.terminate() |
|
|
else: |
|
|
else: |
|
|
raise exc |
|
|
raise exc |
|
|
def do_upgrade(): |
|
|
def do_upgrade(): |
|
|
nonlocal exc |
|
|
nonlocal exc |
|
|
try: |
|
|
try: |
|
|
self.storage.upgrade() |
|
|
storage.upgrade() |
|
|
except Exception as e: |
|
|
except Exception as e: |
|
|
exc = e |
|
|
exc = e |
|
|
self.waiting_dialog(do_upgrade, _('Upgrading wallet format...'), on_finished=on_finished) |
|
|
self.waiting_dialog(do_upgrade, _('Upgrading wallet format...'), on_finished=on_finished) |
|
|
|
|
|
|
|
|
def load_2fa(self): |
|
|
def load_2fa(self): |
|
|
self.storage.put('wallet_type', '2fa') |
|
|
self.data['wallet_type'] = '2fa' |
|
|
self.storage.put('use_trustedcoin', True) |
|
|
self.data['use_trustedcoin'] = True |
|
|
self.plugin = self.plugins.load_plugin('trustedcoin') |
|
|
self.plugin = self.plugins.load_plugin('trustedcoin') |
|
|
|
|
|
|
|
|
def on_wallet_type(self, choice): |
|
|
def on_wallet_type(self, choice): |
|
|
self.wallet_type = choice |
|
|
self.data['wallet_type'] = self.wallet_type = choice |
|
|
if choice == 'standard': |
|
|
if choice == 'standard': |
|
|
action = 'choose_keystore' |
|
|
action = 'choose_keystore' |
|
|
elif choice == 'multisig': |
|
|
elif choice == 'multisig': |
|
|
action = 'choose_multisig' |
|
|
action = 'choose_multisig' |
|
|
elif choice == '2fa': |
|
|
elif choice == '2fa': |
|
|
self.load_2fa() |
|
|
self.load_2fa() |
|
|
action = self.storage.get_action() |
|
|
action = self.plugin.get_action(self.data) |
|
|
elif choice == 'imported': |
|
|
elif choice == 'imported': |
|
|
action = 'import_addresses_or_keys' |
|
|
action = 'import_addresses_or_keys' |
|
|
self.run(action) |
|
|
self.run(action) |
|
@ -169,7 +168,7 @@ class BaseWizard(object): |
|
|
def choose_multisig(self): |
|
|
def choose_multisig(self): |
|
|
def on_multisig(m, n): |
|
|
def on_multisig(m, n): |
|
|
multisig_type = "%dof%d" % (m, n) |
|
|
multisig_type = "%dof%d" % (m, n) |
|
|
self.storage.put('wallet_type', multisig_type) |
|
|
self.data['wallet_type'] = multisig_type |
|
|
self.n = n |
|
|
self.n = n |
|
|
self.run('choose_keystore') |
|
|
self.run('choose_keystore') |
|
|
self.multisig_dialog(run_next=on_multisig) |
|
|
self.multisig_dialog(run_next=on_multisig) |
|
@ -206,27 +205,24 @@ class BaseWizard(object): |
|
|
is_valid=v, allow_multi=True, show_wif_help=True) |
|
|
is_valid=v, allow_multi=True, show_wif_help=True) |
|
|
|
|
|
|
|
|
def on_import(self, text): |
|
|
def on_import(self, text): |
|
|
# create a temporary wallet and exploit that modifications |
|
|
# text is already sanitized by is_address_list and is_private_keys_list |
|
|
# will be reflected on self.storage |
|
|
|
|
|
if keystore.is_address_list(text): |
|
|
if keystore.is_address_list(text): |
|
|
w = Imported_Wallet(self.storage) |
|
|
self.data['addresses'] = {} |
|
|
addresses = text.split() |
|
|
for addr in text.split(): |
|
|
good_inputs, bad_inputs = w.import_addresses(addresses, write_to_disk=False) |
|
|
assert bitcoin.is_address(addr) |
|
|
|
|
|
self.data['addresses'][addr] = {} |
|
|
elif keystore.is_private_key_list(text): |
|
|
elif keystore.is_private_key_list(text): |
|
|
|
|
|
self.data['addresses'] = {} |
|
|
k = keystore.Imported_KeyStore({}) |
|
|
k = keystore.Imported_KeyStore({}) |
|
|
self.storage.put('keystore', k.dump()) |
|
|
|
|
|
w = Imported_Wallet(self.storage) |
|
|
|
|
|
keys = keystore.get_private_keys(text) |
|
|
keys = keystore.get_private_keys(text) |
|
|
good_inputs, bad_inputs = w.import_private_keys(keys, None, write_to_disk=False) |
|
|
for pk in keys: |
|
|
self.keystores.append(w.keystore) |
|
|
assert bitcoin.is_private_key(pk) |
|
|
|
|
|
txin_type, pubkey = k.import_privkey(pk, None) |
|
|
|
|
|
addr = bitcoin.pubkey_to_address(txin_type, pubkey) |
|
|
|
|
|
self.data['addresses'][addr] = {'type':txin_type, 'pubkey':pubkey, 'redeem_script':None} |
|
|
|
|
|
self.keystores.append(k) |
|
|
else: |
|
|
else: |
|
|
return self.terminate() |
|
|
return self.terminate() |
|
|
if bad_inputs: |
|
|
|
|
|
msg = "\n".join(f"{key[:10]}... ({msg})" for key, msg in bad_inputs[:10]) |
|
|
|
|
|
if len(bad_inputs) > 10: msg += '\n...' |
|
|
|
|
|
self.show_error(_("The following inputs could not be imported") |
|
|
|
|
|
+ f' ({len(bad_inputs)}):\n' + msg) |
|
|
|
|
|
# FIXME what if len(good_inputs) == 0 ? |
|
|
|
|
|
return self.run('create_wallet') |
|
|
return self.run('create_wallet') |
|
|
|
|
|
|
|
|
def restore_from_key(self): |
|
|
def restore_from_key(self): |
|
@ -246,7 +242,7 @@ class BaseWizard(object): |
|
|
k = keystore.from_master_key(text) |
|
|
k = keystore.from_master_key(text) |
|
|
self.on_keystore(k) |
|
|
self.on_keystore(k) |
|
|
|
|
|
|
|
|
def choose_hw_device(self, purpose=HWD_SETUP_NEW_WALLET): |
|
|
def choose_hw_device(self, purpose=HWD_SETUP_NEW_WALLET, storage=None): |
|
|
title = _('Hardware Keystore') |
|
|
title = _('Hardware Keystore') |
|
|
# check available plugins |
|
|
# check available plugins |
|
|
supported_plugins = self.plugins.get_hardware_support() |
|
|
supported_plugins = self.plugins.get_hardware_support() |
|
@ -348,7 +344,7 @@ class BaseWizard(object): |
|
|
xpub = self.plugin.get_xpub(device_info.device.id_, derivation, 'standard', self) |
|
|
xpub = self.plugin.get_xpub(device_info.device.id_, derivation, 'standard', self) |
|
|
password = keystore.Xpub.get_pubkey_from_xpub(xpub, ()) |
|
|
password = keystore.Xpub.get_pubkey_from_xpub(xpub, ()) |
|
|
try: |
|
|
try: |
|
|
self.storage.decrypt(password) |
|
|
storage.decrypt(password) |
|
|
except InvalidPassword: |
|
|
except InvalidPassword: |
|
|
# try to clear session so that user can type another passphrase |
|
|
# try to clear session so that user can type another passphrase |
|
|
devmgr = self.plugins.device_manager |
|
|
devmgr = self.plugins.device_manager |
|
@ -539,32 +535,37 @@ class BaseWizard(object): |
|
|
|
|
|
|
|
|
def on_password(self, password, *, encrypt_storage, |
|
|
def on_password(self, password, *, encrypt_storage, |
|
|
storage_enc_version=STO_EV_USER_PW, encrypt_keystore): |
|
|
storage_enc_version=STO_EV_USER_PW, encrypt_keystore): |
|
|
assert not self.storage.file_exists(), "file was created too soon! plaintext keys might have been written to disk" |
|
|
|
|
|
self.storage.set_keystore_encryption(bool(password) and encrypt_keystore) |
|
|
|
|
|
if encrypt_storage: |
|
|
|
|
|
self.storage.set_password(password, enc_version=storage_enc_version) |
|
|
|
|
|
for k in self.keystores: |
|
|
for k in self.keystores: |
|
|
if k.may_have_password(): |
|
|
if k.may_have_password(): |
|
|
k.update_password(None, password) |
|
|
k.update_password(None, password) |
|
|
if self.wallet_type == 'standard': |
|
|
if self.wallet_type == 'standard': |
|
|
self.storage.put('seed_type', self.seed_type) |
|
|
self.data['seed_type'] = self.seed_type |
|
|
keys = self.keystores[0].dump() |
|
|
keys = self.keystores[0].dump() |
|
|
self.storage.put('keystore', keys) |
|
|
self.data['keystore'] = keys |
|
|
self.wallet = Standard_Wallet(self.storage) |
|
|
|
|
|
self.run('create_addresses') |
|
|
|
|
|
elif self.wallet_type == 'multisig': |
|
|
elif self.wallet_type == 'multisig': |
|
|
for i, k in enumerate(self.keystores): |
|
|
for i, k in enumerate(self.keystores): |
|
|
self.storage.put('x%d/'%(i+1), k.dump()) |
|
|
self.data['x%d/'%(i+1)] = k.dump() |
|
|
self.storage.write() |
|
|
|
|
|
self.wallet = Multisig_Wallet(self.storage) |
|
|
|
|
|
self.run('create_addresses') |
|
|
|
|
|
elif self.wallet_type == 'imported': |
|
|
elif self.wallet_type == 'imported': |
|
|
if len(self.keystores) > 0: |
|
|
if len(self.keystores) > 0: |
|
|
keys = self.keystores[0].dump() |
|
|
keys = self.keystores[0].dump() |
|
|
self.storage.put('keystore', keys) |
|
|
self.data['keystore'] = keys |
|
|
self.wallet = Imported_Wallet(self.storage) |
|
|
else: |
|
|
self.wallet.storage.write() |
|
|
raise BaseException('Unknown wallet type') |
|
|
self.terminate() |
|
|
self.pw_args = password, encrypt_storage, storage_enc_version |
|
|
|
|
|
self.terminate() |
|
|
|
|
|
|
|
|
|
|
|
def create_storage(self, path): |
|
|
|
|
|
if not self.pw_args: |
|
|
|
|
|
return |
|
|
|
|
|
password, encrypt_storage, storage_enc_version = self.pw_args |
|
|
|
|
|
storage = WalletStorage(path) |
|
|
|
|
|
for key, value in self.data.items(): |
|
|
|
|
|
storage.put(key, value) |
|
|
|
|
|
storage.set_keystore_encryption(bool(password))# and encrypt_keystore) |
|
|
|
|
|
if encrypt_storage: |
|
|
|
|
|
storage.set_password(password, enc_version=storage_enc_version) |
|
|
|
|
|
storage.write() |
|
|
|
|
|
return storage |
|
|
|
|
|
|
|
|
def show_xpub_and_add_cosigners(self, xpub): |
|
|
def show_xpub_and_add_cosigners(self, xpub): |
|
|
self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore')) |
|
|
self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore')) |
|
|