URI: 
       tMajor refactoring - separation between Wallet and key management (Keystore) - simplification of wallet classes - remove support for multiple accounts in the same wallet - add support for OP_RETURN to Trezor plugin - split multi-accounts wallets for backward compatibility - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 1159f85e053d14cf931f5b32444bda071860619c
   DIR parent 6373a76a4a80038936a2c4201b021e1b33c8e8bd
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Sat,  2 Jul 2016 08:58:56 +0200
       
       Major refactoring
        - separation between Wallet and key management (Keystore)
        - simplification of wallet classes
        - remove support for multiple accounts in the same wallet
        - add support for OP_RETURN to Trezor plugin
        - split multi-accounts wallets for backward compatibility
       
       Diffstat:
         M gui/kivy/main_window.py             |      14 +++++++-------
         M gui/kivy/uix/dialogs/installwizard… |       4 ++--
         M gui/kivy/uix/screens.py             |       2 +-
         M gui/qt/__init__.py                  |       4 ++--
         M gui/qt/address_list.py              |      46 +++++++------------------------
         M gui/qt/history_list.py              |       2 +-
         M gui/qt/installwizard.py             |      40 +++++++++++++++++++++++++------
         M gui/qt/main_window.py               |     150 ++++++--------------------------
         M gui/qt/password_dialog.py           |       2 +-
         M gui/qt/request_list.py              |      19 +++++++------------
         M gui/qt/seed_dialog.py               |       8 +-------
         D lib/account.py                      |     381 -------------------------------
         M lib/base_wizard.py                  |     217 ++++++++++++++-----------------
         M lib/commands.py                     |      12 ++++--------
         M lib/daemon.py                       |       8 ++++----
         A lib/keystore.py                     |     701 +++++++++++++++++++++++++++++++
         M lib/plugins.py                      |      74 +++++++++++++++++--------------
         A lib/storage.py                      |     253 +++++++++++++++++++++++++++++++
         M lib/synchronizer.py                 |       2 +-
         M lib/transaction.py                  |      17 -----------------
         M lib/wallet.py                       |    1321 ++++++++-----------------------
         M plugins/cosigner_pool/qt.py         |       3 ++-
         M plugins/hw_wallet/__init__.py       |       1 -
         D plugins/hw_wallet/hw_wallet.py      |      95 ------------------------------
         M plugins/hw_wallet/plugin.py         |      33 +++----------------------------
         M plugins/keepkey/__init__.py         |       4 ++--
         M plugins/keepkey/keepkey.py          |       6 +++---
         M plugins/ledger/__init__.py          |       4 ++--
         M plugins/ledger/ledger.py            |       8 ++++----
         M plugins/trezor/__init__.py          |       4 ++--
         M plugins/trezor/clientbase.py        |      23 +++++++++++++++++------
         M plugins/trezor/plugin.py            |     116 +++++++++++++++----------------
         M plugins/trezor/qt_generic.py        |      35 ++++++++++++++++++-------------
         M plugins/trezor/trezor.py            |       7 +++----
         M plugins/trustedcoin/trustedcoin.py  |     173 +++++++++++++++++--------------
       
       35 files changed, 1737 insertions(+), 2052 deletions(-)
       ---
   DIR diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py
       t@@ -425,7 +425,7 @@ class ElectrumWindow(App):
                    Logger.debug('Electrum: Wallet not found. Launching install wizard')
                    wizard = Factory.InstallWizard(self.electrum_config, self.network, path)
                    wizard.bind(on_wizard_complete=self.on_wizard_complete)
       -            action = wizard.get_action()
       +            action = wizard.storage.get_action()
                    wizard.run(action)
        
            def on_stop(self):
       t@@ -562,7 +562,7 @@ class ElectrumWindow(App):
                    elif server_lag > 1:
                        status = _("Server lagging (%d blocks)"%server_lag)
                    else:
       -                c, u, x = self.wallet.get_account_balance(self.current_account)
       +                c, u, x = self.wallet.get_balance(self.current_account)
                        text = self.format_amount(c+x+u)
                        status = str(text.strip() + ' ' + self.base_unit)
                else:
       t@@ -749,7 +749,7 @@ class ElectrumWindow(App):
                popup.open()
        
            def protected(self, msg, f, args):
       -        if self.wallet.use_encryption:
       +        if self.wallet.has_password():
                    self.password_dialog(msg, f, args)
                else:
                    apply(f, args + (None,))
       t@@ -769,7 +769,7 @@ class ElectrumWindow(App):
                wallet_path = self.get_wallet_path()
                dirname = os.path.dirname(wallet_path)
                basename = os.path.basename(wallet_path)
       -        if self.wallet.use_encryption:
       +        if self.wallet.has_password():
                    try:
                        self.wallet.check_password(pw)
                    except:
       t@@ -787,7 +787,7 @@ class ElectrumWindow(App):
                self.protected(_("Enter your PIN code in order to decrypt your seed"), self._show_seed, (label,))
        
            def _show_seed(self, label, password):
       -        if self.wallet.use_encryption and password is None:
       +        if self.wallet.has_password() and password is None:
                    return
                try:
                    seed = self.wallet.get_seed(password)
       t@@ -797,13 +797,13 @@ class ElectrumWindow(App):
                label.text = _('Seed') + ':\n' + seed
        
            def change_password(self, cb):
       -        if self.wallet.use_encryption:
       +        if self.wallet.has_password():
                    self.protected(_("Changing PIN code.") + '\n' + _("Enter your current PIN:"), self._change_password, (cb,))
                else:
                    self._change_password(cb, None)
        
            def _change_password(self, cb, old_password):
       -        if self.wallet.use_encryption:
       +        if self.wallet.has_password():
                    if old_password is None:
                        return
                    try:
   DIR diff --git a/gui/kivy/uix/dialogs/installwizard.py b/gui/kivy/uix/dialogs/installwizard.py
       t@@ -742,7 +742,7 @@ class InstallWizard(BaseWizard, Widget):
            def request_password(self, run_next):
                def callback(pin):
                    if pin:
       -                self.run('confirm_password', (pin, run_next))
       +                self.run('confirm_password', pin, run_next)
                    else:
                        run_next(None)
                self.password_dialog('Choose a PIN code', callback)
       t@@ -753,7 +753,7 @@ class InstallWizard(BaseWizard, Widget):
                        run_next(pin)
                    else:
                        self.show_error(_('PIN mismatch'))
       -                self.run('request_password', (run_next,))
       +                self.run('request_password', run_next)
                self.password_dialog('Confirm your PIN code', callback)
        
            def action_dialog(self, action, run_next):
   DIR diff --git a/gui/kivy/uix/screens.py b/gui/kivy/uix/screens.py
       t@@ -331,7 +331,7 @@ class ReceiveScreen(CScreen):
            def get_new_address(self):
                if not self.app.wallet:
                    return False
       -        addr = self.app.wallet.get_unused_address(None)
       +        addr = self.app.wallet.get_unused_address()
                if addr is None:
                    return False
                self.clear()
   DIR diff --git a/gui/qt/__init__.py b/gui/qt/__init__.py
       t@@ -163,8 +163,8 @@ class ElectrumGui:
                        wallet = wizard.run_and_get_wallet()
                        if not wallet:
                            return
       -                if wallet.get_action():
       -                    return
       +                #if wallet.get_action():
       +                #    return
                        self.daemon.add_wallet(wallet)
                    w = self.create_window_for_wallet(wallet)
                if uri:
   DIR diff --git a/gui/qt/address_list.py b/gui/qt/address_list.py
       t@@ -41,26 +41,14 @@ class AddressList(MyTreeWidget):
        
            def on_update(self):
                self.wallet = self.parent.wallet
       -        self.accounts_expanded = self.wallet.storage.get('accounts_expanded', {})
                item = self.currentItem()
                current_address = item.data(0, Qt.UserRole).toString() if item else None
                self.clear()
       -        accounts = self.wallet.get_accounts()
       -        if self.parent.current_account is None:
       -            account_items = sorted(accounts.items())
       -        else:
       -            account_items = [(self.parent.current_account, accounts.get(self.parent.current_account))]
       -        for k, account in account_items:
       -            if len(accounts) > 1:
       -                name = self.wallet.get_account_name(k)
       -                c, u, x = self.wallet.get_account_balance(k)
       -                account_item = QTreeWidgetItem([ name, '', self.parent.format_amount(c + u + x), ''])
       -                account_item.setData(0, Qt.UserRole, k)
       -                self.addTopLevelItem(account_item)
       -                account_item.setExpanded(self.accounts_expanded.get(k, True))
       -            else:
       -                account_item = self
       -            sequences = [0,1] if account.has_change() else [0]
       +        receiving_addresses = self.wallet.get_receiving_addresses()
       +        change_addresses = self.wallet.get_change_addresses()
       +        if True:
       +            account_item = self
       +            sequences = [0,1] if change_addresses else [0]
                    for is_change in sequences:
                        if len(sequences) > 1:
                            name = _("Receiving") if not is_change else _("Change")
       t@@ -72,7 +60,7 @@ class AddressList(MyTreeWidget):
                            seq_item = account_item
                        used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
                        used_flag = False
       -                addr_list = account.get_addresses(is_change)
       +                addr_list = change_addresses if is_change else receiving_addresses
                        for address in addr_list:
                            num = len(self.wallet.history.get(address,[]))
                            is_used = self.wallet.is_used(address)
       t@@ -85,7 +73,7 @@ class AddressList(MyTreeWidget):
                            address_item.setData(0, Qt.UserRole+1, True) # label can be edited
                            if self.wallet.is_frozen(address):
                                address_item.setBackgroundColor(0, QColor('lightblue'))
       -                    if self.wallet.is_beyond_limit(address, account, is_change):
       +                    if self.wallet.is_beyond_limit(address, is_change):
                                address_item.setBackgroundColor(0, QColor('red'))
                            if is_used:
                                if not used_flag:
       t@@ -107,8 +95,9 @@ class AddressList(MyTreeWidget):
                                address_item.addChild(utxo_item)
        
            def create_menu(self, position):
       -        from electrum.wallet import Multisig_Wallet
       +        from electrum.wallet import Multisig_Wallet, Imported_Wallet
                is_multisig = isinstance(self.wallet, Multisig_Wallet)
       +        is_imported = isinstance(self.wallet, Imported_Wallet)
                selected = self.selectedItems()
                multi_select = len(selected) > 1
                addrs = [unicode(item.text(0)) for item in selected]
       t@@ -142,7 +131,7 @@ class AddressList(MyTreeWidget):
                    if not is_multisig and not self.wallet.is_watching_only():
                        menu.addAction(_("Sign/verify message"), lambda: self.parent.sign_verify_message(addr))
                        menu.addAction(_("Encrypt/decrypt message"), lambda: self.parent.encrypt_message(addr))
       -            if self.wallet.is_imported(addr):
       +            if is_imported:
                        menu.addAction(_("Remove from wallet"), lambda: self.parent.delete_imported_key(addr))
                    addr_URL = block_explorer_URL(self.config, 'addr', addr)
                    if addr_URL:
       t@@ -161,18 +150,3 @@ class AddressList(MyTreeWidget):
                run_hook('receive_menu', menu, addrs, self.wallet)
                menu.exec_(self.viewport().mapToGlobal(position))
        
       -    def create_account_menu(self, position, k, item):
       -        menu = QMenu()
       -        exp = item.isExpanded()
       -        menu.addAction(_("Minimize") if exp else _("Maximize"), lambda: self.set_account_expanded(item, k, not exp))
       -        menu.addAction(_("Rename"), lambda: self.parent.edit_account_label(k))
       -        if self.wallet.seed_version > 4:
       -            menu.addAction(_("View details"), lambda: self.parent.show_account_details(k))
       -        menu.exec_(self.viewport().mapToGlobal(position))
       -
       -    def set_account_expanded(self, item, k, b):
       -        item.setExpanded(b)
       -        self.accounts_expanded[k] = b
       -
       -    def on_close(self):
       -        self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
   DIR diff --git a/gui/qt/history_list.py b/gui/qt/history_list.py
       t@@ -61,7 +61,7 @@ class HistoryList(MyTreeWidget):
        
            def get_domain(self):
                '''Replaced in address_dialog.py'''
       -        return self.wallet.get_account_addresses(self.parent.current_account)
       +        return self.wallet.get_addresses()
        
            def on_update(self):
                self.wallet = self.parent.wallet
   DIR diff --git a/gui/qt/installwizard.py b/gui/qt/installwizard.py
       t@@ -1,4 +1,5 @@
        import sys
       +import os
        
        from PyQt4.QtGui import *
        from PyQt4.QtCore import *
       t@@ -156,22 +157,47 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
                    if self.config.get('auto_connect') is None:
                        self.choose_server(self.network)
        
       -        action = self.get_action()
       -        if action != 'new':
       +        path = self.storage.path
       +        if self.storage.requires_split():
       +            self.hide()
       +            msg = _("The wallet '%s' contains multiple accounts, which are no longer supported in Electrum 2.7.\n\n"
       +                    "Do you want to split your wallet into multiple files?"%path)
       +            if not self.question(msg):
       +                return
       +            file_list = '\n'.join(self.storage.split_accounts())
       +            msg = _('Your accounts have been moved to:\n %s.\n\nDo you want to delete the old file:\n%s' % (file_list, path))
       +            if self.question(msg):
       +                os.remove(path)
       +                self.show_warning(_('The file was removed'))
       +            return
       +
       +        if self.storage.requires_upgrade():
       +            self.hide()
       +            msg = _("The format of your wallet '%s' must be upgraded for Electrum. This change will not be backward compatible"%path)
       +            if not self.question(msg):
       +                return
       +            self.storage.upgrade()
       +            self.show_warning(_('Your wallet was upgraded successfully'))
       +            self.wallet = Wallet(self.storage)
       +            self.terminate()
       +            return self.wallet
       +
       +        action = self.storage.get_action()
       +        if action and action != 'new':
                    self.hide()
       -            path = self.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()
       -        self.run(action)
       -        return self.wallet
       +        if action:
       +            # self.wallet is set in run
       +            self.run(action)
       +            return self.wallet
       +
        
            def finished(self):
                '''Ensure the dialog is closed.'''
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -51,7 +51,7 @@ from electrum.util import (block_explorer, block_explorer_info, format_time,
        from electrum import Transaction, mnemonic
        from electrum import util, bitcoin, commands, coinchooser
        from electrum import SimpleConfig, paymentrequest
       -from electrum.wallet import Wallet, BIP32_RD_Wallet, Multisig_Wallet
       +from electrum.wallet import Wallet, Multisig_Wallet
        
        from amountedit import BTCAmountEdit, MyLineEdit, BTCkBEdit
        from network_dialog import NetworkDialog
       t@@ -248,21 +248,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    t.setDaemon(True)
                    t.start()
        
       -    def update_account_selector(self):
       -        # account selector
       -        accounts = self.wallet.get_account_names()
       -        self.account_selector.clear()
       -        if len(accounts) > 1:
       -            self.account_selector.addItems([_("All accounts")] + accounts.values())
       -            self.account_selector.setCurrentIndex(0)
       -            self.account_selector.show()
       -        else:
       -            self.account_selector.hide()
       -
            def close_wallet(self):
                if self.wallet:
                    self.print_error('close_wallet', self.wallet.storage.path)
       -            self.address_list.on_close()
                run_hook('close_wallet', self.wallet)
        
            def load_wallet(self, wallet):
       t@@ -270,13 +258,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                self.wallet = wallet
                self.update_recently_visited(wallet.storage.path)
                # address used to create a dummy transaction and estimate transaction fee
       -        self.current_account = self.wallet.storage.get("current_account", None)
                self.history_list.update()
                self.need_update.set()
                # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
                self.notify_transactions()
                # update menus
       -        self.update_new_account_menu()
                self.seed_menu.setEnabled(self.wallet.has_seed())
                self.mpk_menu.setEnabled(self.wallet.is_deterministic())
                self.update_lock_icon()
       t@@ -391,8 +377,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
        
                wallet_menu = menubar.addMenu(_("&Wallet"))
                wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
       -        self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
       -
                wallet_menu.addSeparator()
        
                self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
       t@@ -569,7 +553,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                        text = _("Server is lagging (%d blocks)"%server_lag)
                        icon = QIcon(":icons/status_lagging.png")
                    else:
       -                c, u, x = self.wallet.get_account_balance(self.current_account)
       +                c, u, x = self.wallet.get_balance()
                        text =  _("Balance" ) + ": %s "%(self.format_amount_and_units(c))
                        if u:
                            text +=  " [%s unconfirmed]"%(self.format_amount(u, True).strip())
       t@@ -593,8 +577,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                self.update_status()
                if self.wallet.up_to_date or not self.network or not self.network.is_connected():
                    self.update_tabs()
       -        if self.wallet.up_to_date:
       -            self.check_next_account()
        
            def update_tabs(self):
                self.history_list.update()
       t@@ -788,7 +770,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    self.saved = True
        
            def new_payment_request(self):
       -        addr = self.wallet.get_unused_address(self.current_account)
       +        addr = self.wallet.get_unused_address(None)
                if addr is None:
                    from electrum.wallet import Imported_Wallet
                    if isinstance(self.wallet, Imported_Wallet):
       t@@ -796,7 +778,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                        return
                    if not self.question(_("Warning: The next address will not be recovered automatically if you restore your wallet from seed; you may need to add it manually.\n\nThis occurs because you have too many unused addresses in your wallet. To avoid this situation, use the existing addresses first.\n\nCreate anyway?")):
                        return
       -            addr = self.wallet.create_new_address(self.current_account, False)
       +            addr = self.wallet.create_new_address(None, False)
                self.set_receive_address(addr)
                self.expires_label.hide()
                self.expires_combo.show()
       t@@ -809,7 +791,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                self.receive_amount_e.setAmount(None)
        
            def clear_receive_tab(self):
       -        addr = self.wallet.get_unused_address(self.current_account)
       +        addr = self.wallet.get_unused_address()
                self.receive_address_e.setText(addr if addr else '')
                self.receive_message_e.setText('')
                self.receive_amount_e.setAmount(None)
       t@@ -1102,7 +1084,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                def request_password(self, *args, **kwargs):
                    parent = self.top_level_window()
                    password = None
       -            while self.wallet.use_encryption:
       +            while self.wallet.has_password():
                        password = self.password_dialog(parent=parent)
                        try:
                            if password:
       t@@ -1208,7 +1190,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                if tx.get_fee() >= self.config.get('confirm_fee', 100000):
                    msg.append(_('Warning')+ ': ' + _("The fee for this transaction seems unusually high."))
        
       -        if self.wallet.use_encryption:
       +        if self.wallet.has_password():
                    msg.append("")
                    msg.append(_("Enter your password to proceed"))
                    password = self.password_dialog('\n'.join(msg))
       t@@ -1237,7 +1219,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                '''Sign the transaction in a separate thread.  When done, calls
                the callback with a success code of True or False.
                '''
       -        if self.wallet.use_encryption and not password:
       +        if self.wallet.has_password() and not password:
                    callback(False) # User cancelled password input
                    return
        
       t@@ -1438,7 +1420,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                if self.pay_from:
                    return self.pay_from
                else:
       -            domain = self.wallet.get_account_addresses(self.current_account)
       +            domain = self.wallet.get_addresses()
                    return self.wallet.get_spendable_coins(domain)
        
        
       t@@ -1561,18 +1543,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                console.updateNamespace(methods)
        
        
       -    def change_account(self,s):
       -        if s == _("All accounts"):
       -            self.current_account = None
       -        else:
       -            accounts = self.wallet.get_account_names()
       -            for k, v in accounts.items():
       -                if v == s:
       -                    self.current_account = k
       -        self.history_list.update()
       -        self.update_status()
       -        self.address_list.update()
       -        self.request_list.update()
        
            def create_status_bar(self):
        
       t@@ -1583,11 +1553,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                self.balance_label = QLabel("")
                sb.addWidget(self.balance_label)
        
       -        self.account_selector = QComboBox()
       -        self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
       -        self.connect(self.account_selector, SIGNAL("activated(QString)"), self.change_account)
       -        sb.addPermanentWidget(self.account_selector)
       -
                self.search_box = QLineEdit()
                self.search_box.textChanged.connect(self.do_search)
                self.search_box.hide()
       t@@ -1606,7 +1571,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                self.setStatusBar(sb)
        
            def update_lock_icon(self):
       -        icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
       +        icon = QIcon(":icons/lock.png") if self.wallet.has_password() else QIcon(":icons/unlock.png")
                self.password_button.setIcon(icon)
        
            def update_buttons_on_seed(self):
       t@@ -1619,7 +1584,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
        
                msg = (_('Your wallet is encrypted. Use this dialog to change your '
                         'password. To disable wallet encryption, enter an empty new '
       -                 'password.') if self.wallet.use_encryption
       +                 'password.') if self.wallet.has_password()
                       else _('Your wallet keys are not encrypted'))
                d = PasswordDialog(self, self.wallet, msg, PW_CHANGE)
                ok, password, new_password = d.run()
       t@@ -1684,48 +1649,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                if self.set_contact(unicode(line2.text()), str(line1.text())):
                    self.tabs.setCurrentIndex(4)
        
       -    def update_new_account_menu(self):
       -        self.new_account_menu.setVisible(self.wallet.can_create_accounts())
       -        self.new_account_menu.setEnabled(self.wallet.permit_account_naming())
       -        self.update_account_selector()
       -
       -    def new_account_dialog(self):
       -        dialog = WindowModalDialog(self, _("New Account Name"))
       -        vbox = QVBoxLayout()
       -        msg = _("Enter a name to give the account.  You will not be "
       -                "permitted to create further accounts until the new account "
       -                "receives at least one transaction.") + "\n"
       -        label = QLabel(msg)
       -        label.setWordWrap(True)
       -        vbox.addWidget(label)
       -        e = QLineEdit()
       -        vbox.addWidget(e)
       -        vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog)))
       -        dialog.setLayout(vbox)
       -        if dialog.exec_():
       -            self.wallet.set_label(self.wallet.last_account_id(), str(e.text()))
       -            self.address_list.update()
       -            self.tabs.setCurrentIndex(3)
       -            self.update_new_account_menu()
       -
       -    def check_next_account(self):
       -        if self.wallet.needs_next_account() and not self.checking_accounts:
       -            self.checking_accounts = True
       -            msg = _("All the accounts in your wallet have received "
       -                    "transactions.  Electrum must check whether more "
       -                    "accounts exist; one will only be shown if "
       -                    "it has been used or you give it a name.")
       -            self.show_message(msg, title=_("Check Accounts"))
       -            self.create_next_account()
       -
       -    @protected
       -    def create_next_account(self, password):
       -        def on_done():
       -            self.checking_accounts = False
       -            self.update_new_account_menu()
       -        task = partial(self.wallet.create_next_account, password)
       -        self.wallet.thread.add(task, on_done=on_done)
       -
            def show_master_public_keys(self):
                dialog = WindowModalDialog(self, "Master Public Keys")
                mpk_dict = self.wallet.get_master_public_keys()
       t@@ -1741,7 +1664,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                if len(mpk_dict) > 1:
                    def label(key):
                        if isinstance(self.wallet, Multisig_Wallet):
       -                    is_mine = self.wallet.master_private_keys.has_key(key)
       +                    is_mine = False#self.wallet.master_private_keys.has_key(key)
                            mine_text = [_("cosigner"), _("self")]
                            return "%s (%s)" % (key, mine_text[is_mine])
                        return key
       t@@ -1759,19 +1682,19 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
        
            @protected
            def show_seed_dialog(self, password):
       -        if self.wallet.use_encryption and password is None:
       -            return    # User cancelled password input
       +        if self.wallet.has_password() and password is None:
       +            # User cancelled password input
       +            return
                if not self.wallet.has_seed():
                    self.show_message(_('This wallet has no seed'))
                    return
       -
                try:
                    mnemonic = self.wallet.get_mnemonic(password)
                except BaseException as e:
                    self.show_error(str(e))
                    return
                from seed_dialog import SeedDialog
       -        d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
       +        d = SeedDialog(self, mnemonic)
                d.exec_()
        
        
       t@@ -1795,9 +1718,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                d.setMinimumSize(600, 200)
                vbox = QVBoxLayout()
                vbox.addWidget( QLabel(_("Address") + ': ' + address))
       -        if isinstance(self.wallet, BIP32_RD_Wallet):
       -            derivation = self.wallet.address_id(address)
       -            vbox.addWidget(QLabel(_("Derivation") + ': ' + derivation))
       +        #if isinstance(self.wallet, BIP32_RD_Wallet):
       +        #    derivation = self.wallet.address_id(address)
       +        #    vbox.addWidget(QLabel(_("Derivation") + ': ' + derivation))
                vbox.addWidget(QLabel(_("Public key") + ':'))
                keys_e = ShowQRTextEdit(text='\n'.join(pubkey_list))
                keys_e.addCopyButton(self.app)
       t@@ -2045,7 +1968,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                if self.wallet.is_watching_only():
                    self.show_message(_("This is a watching-only wallet"))
                    return
       -
                try:
                    self.wallet.check_password(password)
                except Exception as e:
       t@@ -2235,7 +2157,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                keys_e.setTabChangesFocus(True)
                vbox.addWidget(keys_e)
        
       -        addresses = self.wallet.get_unused_addresses(self.current_account)
       +        addresses = self.wallet.get_unused_addresses(None)
                h, address_e = address_field(addresses)
                vbox.addLayout(h)
        
       t@@ -2271,19 +2193,16 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
        
            @protected
            def do_import_privkey(self, password):
       -        if not self.wallet.has_imported_keys():
       -            if not self.question('<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
       -                                 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
       -                                 + _('Are you sure you understand what you are doing?'), title=_('Warning')):
       -                return
       -
       +        if not self.wallet.keystore.can_import():
       +            return
                text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
       -        if not text: return
       -
       +        if not text:
       +            return
                text = str(text).split()
                badkeys = []
                addrlist = []
                for key in text:
       +            addr = self.wallet.import_key(key, password)
                    try:
                        addr = self.wallet.import_key(key, password)
                    except Exception as e:
       t@@ -2673,25 +2592,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                vbox.addLayout(Buttons(CloseButton(d)))
                d.exec_()
        
       -    def show_account_details(self, k):
       -        account = self.wallet.accounts[k]
       -        d = WindowModalDialog(self, _('Account Details'))
       -        vbox = QVBoxLayout(d)
       -        name = self.wallet.get_account_name(k)
       -        label = QLabel('Name: ' + name)
       -        vbox.addWidget(label)
       -        vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
       -        vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
       -        vbox.addWidget(QLabel(_('Master Public Key:')))
       -        text = QTextEdit()
       -        text.setReadOnly(True)
       -        text.setMaximumHeight(170)
       -        vbox.addWidget(text)
       -        mpk_text = '\n'.join(account.get_master_pubkeys())
       -        text.setText(mpk_text)
       -        vbox.addLayout(Buttons(CloseButton(d)))
       -        d.exec_()
       -
            def bump_fee_dialog(self, tx):
                is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
                d = WindowModalDialog(self, _('Bump Fee'))
   DIR diff --git a/gui/qt/password_dialog.py b/gui/qt/password_dialog.py
       t@@ -94,7 +94,7 @@ class PasswordLayout(object):
        
                    m1 = _('New Password:') if kind == PW_NEW else _('Password:')
                    msgs = [m1, _('Confirm Password:')]
       -            if wallet and wallet.use_encryption:
       +            if wallet and wallet.has_password():
                        grid.addWidget(QLabel(_('Current Password:')), 0, 0)
                        grid.addWidget(self.pw, 0, 1)
                        lockfile = ":icons/lock.png"
   DIR diff --git a/gui/qt/request_list.py b/gui/qt/request_list.py
       t@@ -36,20 +36,19 @@ from util import MyTreeWidget, pr_tooltips, pr_icons
        class RequestList(MyTreeWidget):
        
            def __init__(self, parent):
       -        MyTreeWidget.__init__(self, parent, self.create_menu, [_('Date'), _('Account'), _('Address'), '', _('Description'), _('Amount'), _('Status')], 4)
       +        MyTreeWidget.__init__(self, parent, self.create_menu, [_('Date'), _('Address'), '', _('Description'), _('Amount'), _('Status')], 3)
                self.currentItemChanged.connect(self.item_changed)
                self.itemClicked.connect(self.item_changed)
                self.setSortingEnabled(True)
                self.setColumnWidth(0, 180)
                self.hideColumn(1)
       -        self.hideColumn(2)
        
            def item_changed(self, item):
                if item is None:
                    return
                if not self.isItemSelected(item):
                    return
       -        addr = str(item.text(2))
       +        addr = str(item.text(1))
                req = self.wallet.receive_requests[addr]
                expires = age(req['time'] + req['exp']) if req.get('exp') else _('Never')
                amount = req['amount']
       t@@ -72,13 +71,10 @@ class RequestList(MyTreeWidget):
                    self.parent.expires_label.hide()
                    self.parent.expires_combo.show()
        
       -        # check if it is necessary to show the account
       -        self.setColumnHidden(1, len(self.wallet.get_accounts()) == 1)
       -
                # update the receive address if necessary
                current_address = self.parent.receive_address_e.text()
       -        domain = self.wallet.get_account_addresses(self.parent.current_account, include_change=False)
       -        addr = self.wallet.get_unused_address(self.parent.current_account)
       +        domain = self.wallet.get_receiving_addresses()
       +        addr = self.wallet.get_unused_address()
                if not current_address in domain and addr:
                    self.parent.set_receive_address(addr)
                self.parent.new_request_button.setEnabled(addr != current_address)
       t@@ -98,11 +94,10 @@ class RequestList(MyTreeWidget):
                    signature = req.get('sig')
                    requestor = req.get('name', '')
                    amount_str = self.parent.format_amount(amount) if amount else ""
       -            account = ''
       -            item = QTreeWidgetItem([date, account, address, '', message, amount_str, pr_tooltips.get(status,'')])
       +            item = QTreeWidgetItem([date, address, '', message, amount_str, pr_tooltips.get(status,'')])
                    if signature is not None:
       -                item.setIcon(3, QIcon(":icons/seal.png"))
       -                item.setToolTip(3, 'signed by '+ requestor)
       +                item.setIcon(2, QIcon(":icons/seal.png"))
       +                item.setToolTip(2, 'signed by '+ requestor)
                    if status is not PR_UNKNOWN:
                        item.setIcon(6, QIcon(pr_icons.get(status)))
                    self.addTopLevelItem(item)
   DIR diff --git a/gui/qt/seed_dialog.py b/gui/qt/seed_dialog.py
       t@@ -39,19 +39,13 @@ def icon_filename(sid):
                return ":icons/seed.png"
        
        class SeedDialog(WindowModalDialog):
       -    def __init__(self, parent, seed, imported_keys):
       +    def __init__(self, parent, seed):
                WindowModalDialog.__init__(self, parent, ('Electrum - ' + _('Seed')))
                self.setMinimumWidth(400)
                vbox = QVBoxLayout(self)
                vbox.addLayout(SeedWarningLayout(seed).layout())
       -        if imported_keys:
       -            warning = ("<b>" + _("WARNING") + ":</b> " +
       -                       _("Your wallet contains imported keys. These keys "
       -                         "cannot be recovered from your seed.") + "</b><p>")
       -            vbox.addWidget(WWLabel(warning))
                vbox.addLayout(Buttons(CloseButton(self)))
        
       -
        class SeedLayoutBase(object):
            def _seed_layout(self, seed=None, title=None, sid=None):
                logo = QLabel()
   DIR diff --git a/lib/account.py b/lib/account.py
       t@@ -1,381 +0,0 @@
       -#!/usr/bin/env python
       -#
       -# Electrum - lightweight Bitcoin client
       -# Copyright (C) 2013 thomasv@gitorious
       -#
       -# 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.
       -
       -import bitcoin
       -from bitcoin import *
       -from i18n import _
       -from transaction import Transaction, is_extended_pubkey
       -from util import InvalidPassword
       -
       -
       -class Account(object):
       -    def __init__(self, v):
       -        self.receiving_pubkeys   = v.get('receiving', [])
       -        self.change_pubkeys      = v.get('change', [])
       -        # addresses will not be stored on disk
       -        self.receiving_addresses = map(self.pubkeys_to_address, self.receiving_pubkeys)
       -        self.change_addresses    = map(self.pubkeys_to_address, self.change_pubkeys)
       -
       -    def dump(self):
       -        return {'receiving':self.receiving_pubkeys, 'change':self.change_pubkeys}
       -
       -    def get_pubkey(self, for_change, n):
       -        pubkeys_list = self.change_pubkeys if for_change else self.receiving_pubkeys
       -        return pubkeys_list[n]
       -
       -    def get_address(self, for_change, n):
       -        addr_list = self.change_addresses if for_change else self.receiving_addresses
       -        return addr_list[n]
       -
       -    def get_pubkeys(self, for_change, n):
       -        return [ self.get_pubkey(for_change, n)]
       -
       -    def get_addresses(self, for_change):
       -        addr_list = self.change_addresses if for_change else self.receiving_addresses
       -        return addr_list[:]
       -
       -    def derive_pubkeys(self, for_change, n):
       -        pass
       -
       -    def create_new_address(self, for_change):
       -        pubkeys_list = self.change_pubkeys if for_change else self.receiving_pubkeys
       -        addr_list = self.change_addresses if for_change else self.receiving_addresses
       -        n = len(pubkeys_list)
       -        pubkeys = self.derive_pubkeys(for_change, n)
       -        address = self.pubkeys_to_address(pubkeys)
       -        pubkeys_list.append(pubkeys)
       -        addr_list.append(address)
       -        return address
       -
       -    def pubkeys_to_address(self, pubkey):
       -        return public_key_to_bc_address(pubkey.decode('hex'))
       -
       -    def has_change(self):
       -        return True
       -
       -    def get_name(self, k):
       -        return _('Main account')
       -
       -    def redeem_script(self, for_change, n):
       -        return None
       -
       -    def is_used(self, wallet):
       -        addresses = self.get_addresses(False)
       -        return any(wallet.address_is_old(a, -1) for a in addresses)
       -
       -    def synchronize_sequence(self, wallet, for_change):
       -        limit = wallet.gap_limit_for_change if for_change else wallet.gap_limit
       -        while True:
       -            addresses = self.get_addresses(for_change)
       -            if len(addresses) < limit:
       -                address = self.create_new_address(for_change)
       -                wallet.add_address(address)
       -                continue
       -            if map( lambda a: wallet.address_is_old(a), addresses[-limit:] ) == limit*[False]:
       -                break
       -            else:
       -                address = self.create_new_address(for_change)
       -                wallet.add_address(address)
       -
       -    def synchronize(self, wallet):
       -        self.synchronize_sequence(wallet, False)
       -        self.synchronize_sequence(wallet, True)
       -
       -
       -class ImportedAccount(Account):
       -    def __init__(self, d):
       -        self.keypairs = d['imported']
       -
       -    def synchronize(self, wallet):
       -        return
       -
       -    def get_addresses(self, for_change):
       -        return [] if for_change else sorted(self.keypairs.keys())
       -
       -    def get_pubkey(self, *sequence):
       -        for_change, i = sequence
       -        assert for_change == 0
       -        addr = self.get_addresses(0)[i]
       -        return self.keypairs[addr][0]
       -
       -    def get_xpubkeys(self, for_change, n):
       -        return self.get_pubkeys(for_change, n)
       -
       -    def get_private_key(self, sequence, wallet, password):
       -        from wallet import pw_decode
       -        for_change, i = sequence
       -        assert for_change == 0
       -        address = self.get_addresses(0)[i]
       -        pk = pw_decode(self.keypairs[address][1], password)
       -        # this checks the password
       -        if address != address_from_private_key(pk):
       -            raise InvalidPassword()
       -        return [pk]
       -
       -    def has_change(self):
       -        return False
       -
       -    def add(self, address, pubkey, privkey, password):
       -        from wallet import pw_encode
       -        self.keypairs[address] = [pubkey, pw_encode(privkey, password)]
       -
       -    def remove(self, address):
       -        self.keypairs.pop(address)
       -
       -    def dump(self):
       -        return {'imported':self.keypairs}
       -
       -    def get_name(self, k):
       -        return _('Imported keys')
       -
       -    def update_password(self, old_password, new_password):
       -        for k, v in self.keypairs.items():
       -            pubkey, a = v
       -            b = pw_decode(a, old_password)
       -            c = pw_encode(b, new_password)
       -            self.keypairs[k] = (pubkey, c)
       -
       -
       -class OldAccount(Account):
       -    """  Privatekey(type,n) = Master_private_key + H(n|S|type)  """
       -
       -    def __init__(self, v):
       -        Account.__init__(self, v)
       -        self.mpk = v['mpk'].decode('hex')
       -
       -    @classmethod
       -    def mpk_from_seed(klass, seed):
       -        secexp = klass.stretch_key(seed)
       -        master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
       -        master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
       -        return master_public_key
       -
       -    @classmethod
       -    def stretch_key(self,seed):
       -        oldseed = seed
       -        for i in range(100000):
       -            seed = hashlib.sha256(seed + oldseed).digest()
       -        return string_to_number( seed )
       -
       -    @classmethod
       -    def get_sequence(self, mpk, for_change, n):
       -        return string_to_number( Hash( "%d:%d:"%(n,for_change) + mpk ) )
       -
       -    def get_address(self, for_change, n):
       -        pubkey = self.get_pubkey(for_change, n)
       -        address = public_key_to_bc_address( pubkey.decode('hex') )
       -        return address
       -
       -    @classmethod
       -    def get_pubkey_from_mpk(self, mpk, for_change, n):
       -        z = self.get_sequence(mpk, for_change, n)
       -        master_public_key = ecdsa.VerifyingKey.from_string(mpk, curve = SECP256k1)
       -        pubkey_point = master_public_key.pubkey.point + z*SECP256k1.generator
       -        public_key2 = ecdsa.VerifyingKey.from_public_point(pubkey_point, curve = SECP256k1)
       -        return '04' + public_key2.to_string().encode('hex')
       -
       -    def derive_pubkeys(self, for_change, n):
       -        return self.get_pubkey_from_mpk(self.mpk, for_change, n)
       -
       -    def get_private_key_from_stretched_exponent(self, for_change, n, secexp):
       -        order = generator_secp256k1.order()
       -        secexp = ( secexp + self.get_sequence(self.mpk, for_change, n) ) % order
       -        pk = number_to_string( secexp, generator_secp256k1.order() )
       -        compressed = False
       -        return SecretToASecret( pk, compressed )
       -
       -
       -    def get_private_key(self, sequence, wallet, password):
       -        seed = wallet.get_seed(password)
       -        self.check_seed(seed)
       -        for_change, n = sequence
       -        secexp = self.stretch_key(seed)
       -        pk = self.get_private_key_from_stretched_exponent(for_change, n, secexp)
       -        return [pk]
       -
       -
       -    def check_seed(self, seed):
       -        secexp = self.stretch_key(seed)
       -        master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
       -        master_public_key = master_private_key.get_verifying_key().to_string()
       -        if master_public_key != self.mpk:
       -            print_error('invalid password (mpk)', self.mpk.encode('hex'), master_public_key.encode('hex'))
       -            raise InvalidPassword()
       -        return True
       -
       -    def get_master_pubkeys(self):
       -        return [self.mpk.encode('hex')]
       -
       -    def get_type(self):
       -        return _('Old Electrum format')
       -
       -    def get_xpubkeys(self, for_change, n):
       -        s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (for_change, n)))
       -        mpk = self.mpk.encode('hex')
       -        x_pubkey = 'fe' + mpk + s
       -        return [ x_pubkey ]
       -
       -    @classmethod
       -    def parse_xpubkey(self, x_pubkey):
       -        assert is_extended_pubkey(x_pubkey)
       -        pk = x_pubkey[2:]
       -        mpk = pk[0:128]
       -        dd = pk[128:]
       -        s = []
       -        while dd:
       -            n = int(bitcoin.rev_hex(dd[0:4]), 16)
       -            dd = dd[4:]
       -            s.append(n)
       -        assert len(s) == 2
       -        return mpk, s
       -
       -
       -class BIP32_Account(Account):
       -
       -    def __init__(self, v):
       -        Account.__init__(self, v)
       -        self.xpub = v['xpub']
       -        self.xpub_receive = None
       -        self.xpub_change = None
       -
       -    def dump(self):
       -        d = Account.dump(self)
       -        d['xpub'] = self.xpub
       -        return d
       -
       -    def first_address(self):
       -        pubkeys = self.derive_pubkeys(0, 0)
       -        addr = self.pubkeys_to_address(pubkeys)
       -        return addr, pubkeys
       -
       -    def get_master_pubkeys(self):
       -        return [self.xpub]
       -
       -    @classmethod
       -    def derive_pubkey_from_xpub(self, xpub, for_change, n):
       -        _, _, _, c, cK = deserialize_xkey(xpub)
       -        for i in [for_change, n]:
       -            cK, c = CKD_pub(cK, c, i)
       -        return cK.encode('hex')
       -
       -    def get_pubkey_from_xpub(self, xpub, for_change, n):
       -        xpubs = self.get_master_pubkeys()
       -        i = xpubs.index(xpub)
       -        pubkeys = self.get_pubkeys(for_change, n)
       -        return pubkeys[i]
       -
       -    def derive_pubkeys(self, for_change, n):
       -        xpub = self.xpub_change if for_change else self.xpub_receive
       -        if xpub is None:
       -            xpub = bip32_public_derivation(self.xpub, "", "/%d"%for_change)
       -            if for_change:
       -                self.xpub_change = xpub
       -            else:
       -                self.xpub_receive = xpub
       -        _, _, _, c, cK = deserialize_xkey(xpub)
       -        cK, c = CKD_pub(cK, c, n)
       -        result = cK.encode('hex')
       -        return result
       -
       -
       -    def get_private_key(self, sequence, wallet, password):
       -        out = []
       -        xpubs = self.get_master_pubkeys()
       -        roots = [k for k, v in wallet.master_public_keys.iteritems() if v in xpubs]
       -        for root in roots:
       -            xpriv = wallet.get_master_private_key(root, password)
       -            if not xpriv:
       -                continue
       -            _, _, _, c, k = deserialize_xkey(xpriv)
       -            pk = bip32_private_key( sequence, k, c )
       -            out.append(pk)
       -        return out
       -
       -    def get_type(self):
       -        return _('Standard 1 of 1')
       -
       -    def get_xpubkeys(self, for_change, n):
       -        # unsorted
       -        s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (for_change,n)))
       -        xpubs = self.get_master_pubkeys()
       -        return map(lambda xpub: 'ff' + bitcoin.DecodeBase58Check(xpub).encode('hex') + s, xpubs)
       -
       -    @classmethod
       -    def parse_xpubkey(self, pubkey):
       -        assert is_extended_pubkey(pubkey)
       -        pk = pubkey.decode('hex')
       -        pk = pk[1:]
       -        xkey = bitcoin.EncodeBase58Check(pk[0:78])
       -        dd = pk[78:]
       -        s = []
       -        while dd:
       -            n = int( bitcoin.rev_hex(dd[0:2].encode('hex')), 16)
       -            dd = dd[2:]
       -            s.append(n)
       -        assert len(s) == 2
       -        return xkey, s
       -
       -    def get_name(self, k):
       -        return "Main account" if k == '0' else "Account " + k
       -
       -
       -
       -
       -class Multisig_Account(BIP32_Account):
       -
       -    def __init__(self, v):
       -        self.m = v.get('m', 2)
       -        Account.__init__(self, v)
       -        self.xpub_list = v['xpubs']
       -
       -    def dump(self):
       -        d = Account.dump(self)
       -        d['xpubs'] = self.xpub_list
       -        d['m'] = self.m
       -        return d
       -
       -    def get_pubkeys(self, for_change, n):
       -        return self.get_pubkey(for_change, n)
       -
       -    def derive_pubkeys(self, for_change, n):
       -        return map(lambda x: self.derive_pubkey_from_xpub(x, for_change, n), self.get_master_pubkeys())
       -
       -    def redeem_script(self, for_change, n):
       -        pubkeys = self.get_pubkeys(for_change, n)
       -        return Transaction.multisig_script(sorted(pubkeys), self.m)
       -
       -    def pubkeys_to_address(self, pubkeys):
       -        redeem_script = Transaction.multisig_script(sorted(pubkeys), self.m)
       -        address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5)
       -        return address
       -
       -    def get_address(self, for_change, n):
       -        return self.pubkeys_to_address(self.get_pubkeys(for_change, n))
       -
       -    def get_master_pubkeys(self):
       -        return self.xpub_list
       -
       -    def get_type(self):
       -        return _('Multisig %d of %d'%(self.m, len(self.xpub_list)))
   DIR diff --git a/lib/base_wizard.py b/lib/base_wizard.py
       t@@ -24,24 +24,21 @@
        # SOFTWARE.
        
        import os
       -from electrum.wallet import Wallet, Multisig_Wallet, WalletStorage
       +import keystore
       +from wallet import Wallet, Imported_Wallet, Standard_Wallet, Multisig_Wallet, WalletStorage
        from i18n import _
       -
       -
       -is_any_key = lambda x: Wallet.is_old_mpk(x) or Wallet.is_xprv(x) or Wallet.is_xpub(x) or Wallet.is_address(x) or Wallet.is_private_key(x)
       -is_private_key = lambda x: Wallet.is_xprv(x) or Wallet.is_private_key(x)
       -is_bip32_key = lambda x: Wallet.is_xprv(x) or Wallet.is_xpub(x)
       -
       +from plugins import run_hook
        
        class BaseWizard(object):
        
            def __init__(self, config, network, path):
                super(BaseWizard, self).__init__()
       -        self.config  = config
       +        self.config = config
                self.network = network
                self.storage = WalletStorage(path)
                self.wallet = None
                self.stack = []
       +        self.plugin = None
        
            def run(self, *args):
                action = args[0]
       t@@ -49,27 +46,17 @@ class BaseWizard(object):
                self.stack.append((action, args))
                if not action:
                    return
       -        if hasattr(self.wallet, 'plugin') and hasattr(self.wallet.plugin, action):
       -            f = getattr(self.wallet.plugin, action)
       -            apply(f, (self.wallet, self) + args)
       +        if type(action) is tuple:
       +            self.plugin, action = action
       +        if self.plugin and hasattr(self.plugin, action):
       +            f = getattr(self.plugin, action)
       +            apply(f, (self,) + args)
                elif hasattr(self, action):
                    f = getattr(self, action)
                    apply(f, args)
                else:
                    raise BaseException("unknown action", action)
        
       -    def get_action(self):
       -        if self.storage.file_exists:
       -            self.wallet = Wallet(self.storage)
       -            action = self.wallet.get_action()
       -        else:
       -            action = 'new'
       -        return action
       -
       -    def get_wallet(self):
       -        if self.wallet and self.wallet.get_action() is None:
       -            return self.wallet
       -
            def can_go_back(self):
                return len(self.stack)>1
        
       t@@ -91,11 +78,10 @@ class BaseWizard(object):
                    ('standard',  _("Standard wallet")),
                    ('twofactor', _("Wallet with two-factor authentication")),
                    ('multisig',  _("Multi-signature wallet")),
       -            ('hardware',  _("Hardware wallet")),
                ]
                registered_kinds = Wallet.categories()
       -        choices = [pair for pair in wallet_kinds if pair[0] in registered_kinds]
       -        self.choice_dialog(title = title, message=message, choices=choices, run_next=self.on_wallet_type)
       +        choices = wallet_kinds#[pair for pair in wallet_kinds if pair[0] in registered_kinds]
       +        self.choice_dialog(title=title, message=message, choices=choices, run_next=self.on_wallet_type)
        
            def on_wallet_type(self, choice):
                self.wallet_type = choice
       t@@ -103,66 +89,58 @@ class BaseWizard(object):
                    action = 'choose_seed'
                elif choice == 'multisig':
                    action = 'choose_multisig'
       -        elif choice == 'hardware':
       -            action = 'choose_hw'
                elif choice == 'twofactor':
       -            action = 'choose_seed'
       +            self.storage.put('wallet_type', '2fa')
       +            self.storage.put('use_trustedcoin', True)
       +            self.plugin = self.plugins.load_plugin('trustedcoin')
       +            action = self.storage.get_action()
       +
                self.run(action)
        
            def choose_multisig(self):
                def on_multisig(m, n):
                    self.multisig_type = "%dof%d"%(m, n)
       +            self.n = n
                    self.run('choose_seed')
                self.multisig_dialog(run_next=on_multisig)
        
            def choose_seed(self):
       -        title = _('Choose Seed')
       -        message = _("Do you want to create a new seed, or to restore a wallet using an existing seed?")
       -        if self.wallet_type == 'standard':
       -            choices = [
       -                ('create_seed', _('Create a new seed')),
       -                ('restore_seed', _('I already have a seed')),
       -                ('restore_from_key', _('Import keys')),
       -            ]
       -        elif self.wallet_type == 'twofactor':
       -            choices = [
       -                ('create_2fa', _('Create a new seed')),
       -                ('restore_2fa', _('I already have a seed')),
       -            ]
       -        elif self.wallet_type == 'multisig':
       +        title = _('Seed and Private Keys')
       +        message = _('Do you want to create a new seed, or to restore a wallet using an existing seed?')
       +        if self.wallet_type in ['standard', 'multisig']:
                    choices = [
                        ('create_seed', _('Create a new seed')),
                        ('restore_seed', _('I already have a seed')),
       -                ('restore_from_key', _('I have a master key')),
       -                #('choose_hw', _('Cosign with hardware wallet')),
       +                ('restore_from_key', _('Import keys or addresses')),
       +                ('choose_hw',  _('Use hardware wallet')),
                    ]
       -        self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
       -
       -    def create_2fa(self):
       -        self.storage.put('wallet_type', '2fa')
       -        self.wallet = Wallet(self.storage)
       -        self.run('show_disclaimer')
       +            self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
        
            def restore_seed(self):
                # TODO: return derivation password too
       -        self.restore_seed_dialog(run_next=self.add_password, is_valid=Wallet.is_seed)
       +        self.restore_seed_dialog(run_next=self.add_password, is_valid=keystore.is_seed)
        
            def on_restore(self, text):
       -        if is_private_key(text):
       +        if keystore.is_address_list(text):
       +            self.wallet = Imported_Wallet(self.storage)
       +            for x in text.split():
       +                self.wallet.add_address(x)
       +            self.terminate()
       +        elif keystore.is_private(text):
                    self.add_password(text)
                else:
       -            self.create_wallet(text, None)
       +            self.create_keystore(text, None)
        
            def restore_from_key(self):
                if self.wallet_type == 'standard':
       -            v = is_any_key
       +            v = keystore.is_any_key
                    title = _("Import keys")
                    message = ' '.join([
                        _("To create a watching-only wallet, please enter your master public key (xpub), or a list of Bitcoin addresses."),
                        _("To create a spending wallet, please enter a master private key (xprv), or a list of Bitcoin private keys.")
                    ])
                else:
       -            v = is_bip32_key
       +            v = keystore.is_bip32_key
                    title = _("Master public or private key")
                    message = ' '.join([
                        _("To create a watching-only wallet, please enter your master public key (xpub)."),
       t@@ -170,12 +148,8 @@ class BaseWizard(object):
                    ])
                self.restore_keys_dialog(title=title, message=message, run_next=self.on_restore, is_valid=v)
        
       -    def restore_2fa(self):
       -        self.storage.put('wallet_type', '2fa')
       -        self.wallet = Wallet(self.storage)
       -        self.wallet.plugin.on_restore_wallet(self.wallet, self)
       -
            def choose_hw(self):
       +        self.storage.put('key_type', 'hardware')
                hw_wallet_types, choices = self.plugins.hardware_wallets('create')
                choices = zip(hw_wallet_types, choices)
                title = _('Hardware wallet')
       t@@ -189,84 +163,87 @@ class BaseWizard(object):
                self.choice_dialog(title=title, message=msg, choices=choices, run_next=self.on_hardware)
        
            def on_hardware(self, hw_type):
       -        self.hw_type = hw_type
       -        if self.wallet_type == 'multisig':
       -            self.create_hardware_multisig()
       -        else:
       -            title = _('Hardware wallet') + ' [%s]' % hw_type
       -            message = _('Do you have a device, or do you want to restore a wallet using an existing seed?')
       -            choices = [
       -                ('create_hardware_wallet', _('I have a device')),
       -                ('restore_hardware_wallet', _('Use hardware wallet seed')),
       -            ]
       -            self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
       -
       -    def create_hardware_multisig(self):
       -        self.storage.put('wallet_type', self.multisig_type)
       -        self.wallet = Multisig_Wallet(self.storage)
       -        # todo: get the xpub from the plugin
       -        self.run('create_wallet', xpub, None)
       -
       -    def create_hardware_wallet(self):
       -        self.storage.put('wallet_type', self.hw_type)
       -        self.wallet = Wallet(self.storage)
       -        self.wallet.plugin.on_create_wallet(self.wallet, self)
       -        self.terminate()
       -
       -    def restore_hardware_wallet(self):
       -        self.storage.put('wallet_type', self.wallet_type)
       -        self.wallet = Wallet(self.storage)
       -        self.wallet.plugin.on_restore_wallet(self.wallet, self)
       -        self.terminate()
       +        self.storage.put('hardware_type', hw_type)
       +        title = _('Hardware wallet') + ' [%s]' % hw_type
       +        message = _('Do you have a device, or do you want to restore a wallet using an existing seed?')
       +        choices = [
       +            ('on_hardware_device', _('I have a %s device')%hw_type),
       +            ('on_hardware_seed', _('I have a %s seed')%hw_type),
       +        ]
       +        self.choice_dialog(title=title, message=message, choices=choices, run_next=self.run)
        
       -    def create_wallet(self, text, password):
       +    def on_hardware_device(self):
       +        from keystore import load_keystore
       +        keystore = load_keystore(self.storage, None)
       +        keystore.plugin.on_create_wallet(keystore, self)
       +        self.create_wallet(keystore, None)
       +
       +    def on_hardware_seed(self):
       +        from keystore import load_keystore
       +        self.storage.put('key_type', 'hw_seed')
       +        keystore = load_keystore(self.storage, None)
       +        self.plugin = keystore #fixme .plugin
       +        keystore.on_restore_wallet(self)
       +        self.wallet = Standard_Wallet(self.storage)
       +        self.run('create_addresses')
       +
       +    def create_wallet(self, k, password):
                if self.wallet_type == 'standard':
       -            self.wallet = Wallet.from_text(text, password, self.storage)
       +            k.save(self.storage, 'x/')
       +            self.wallet = Standard_Wallet(self.storage)
                    self.run('create_addresses')
                elif self.wallet_type == 'multisig':
                    self.storage.put('wallet_type', self.multisig_type)
       -            self.wallet = Multisig_Wallet(self.storage)
       -            self.wallet.add_cosigner('x1/', text, password)
       +            self.add_cosigner(k, 0)
       +            xpub = k.get_master_public_key()
                    self.stack = []
       -            self.run('show_xpub_and_add_cosigners', (password,))
       +            self.run('show_xpub_and_add_cosigners', password, xpub)
       +
       +    def show_xpub_and_add_cosigners(self, password, xpub):
       +        self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('add_cosigners', password, 1))
        
       -    def show_xpub_and_add_cosigners(self, password):
       -        xpub = self.wallet.master_public_keys.get('x1/')
       -        self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('add_cosigners', password))
       +    def add_cosigner(self, keystore, i):
       +        d = self.storage.get('master_public_keys', {})
       +        if keystore.xpub in d.values():
       +            raise BaseException('duplicate key')
       +        keystore.save(self.storage, 'x%d/'%(i+1))
        
       -    def add_cosigners(self, password):
       -        i = self.wallet.get_missing_cosigner()
       -        self.add_cosigner_dialog(run_next=lambda x: self.on_cosigner(x, password), index=(i-1), is_valid=Wallet.is_xpub)
       +    def add_cosigners(self, password, i):
       +        self.add_cosigner_dialog(run_next=lambda x: self.on_cosigner(x, password, i), index=i, is_valid=keystore.is_xpub)
        
       -    def on_cosigner(self, text, password):
       -        i = self.wallet.get_missing_cosigner()
       +    def on_cosigner(self, text, password, i):
       +        k = keystore.from_text(text, password)
                try:
       -            self.wallet.add_cosigner('x%d/'%i, text, password)
       +            self.add_cosigner(k, i)
                except BaseException as e:
       -            print "error:" + str(e)
       -        i = self.wallet.get_missing_cosigner()
       -        if i:
       -            self.run('add_cosigners', password)
       +            self.show_message("error:" + str(e))
       +            return
       +        if i < self.n - 1:
       +            self.run('add_cosigners', password, i+1)
                else:
       +            self.wallet = Multisig_Wallet(self.storage)
                    self.create_addresses()
        
       -    def create_addresses(self):
       -        def task():
       -            self.wallet.create_main_account()
       -            self.wallet.synchronize()
       -            self.wallet.storage.write()
       -            self.terminate()
       -        msg = _("Electrum is generating your addresses, please wait.")
       -        self.waiting_dialog(task, msg)
       -
            def create_seed(self):
       -        from electrum.wallet import BIP32_Wallet
       -        seed = BIP32_Wallet.make_seed()
       +        from electrum.mnemonic import Mnemonic
       +        seed = Mnemonic('en').make_seed()
                self.show_seed_dialog(run_next=self.confirm_seed, seed_text=seed)
        
            def confirm_seed(self, seed):
                self.confirm_seed_dialog(run_next=self.add_password, is_valid=lambda x: x==seed)
        
            def add_password(self, text):
       -        f = lambda pw: self.run('create_wallet', text, pw)
       +        f = lambda pw: self.run('create_keystore', text, pw)
                self.request_password(run_next=f)
       +
       +    def create_keystore(self, text, password):
       +        k = keystore.from_text(text, password)
       +        self.create_wallet(k, password)
       +
       +    def create_addresses(self):
       +        def task():
       +            self.wallet.synchronize()
       +            self.wallet.storage.write()
       +            self.terminate()
       +        msg = _("Electrum is generating your addresses, please wait.")
       +        self.waiting_dialog(task, msg)
   DIR diff --git a/lib/commands.py b/lib/commands.py
       t@@ -300,12 +300,9 @@ class Commands:
                return self.wallet.get_public_keys(address)
        
            @command('w')
       -    def getbalance(self, account=None):
       +    def getbalance(self):
                """Return the balance of your wallet. """
       -        if account is None:
       -            c, u, x = self.wallet.get_balance()
       -        else:
       -            c, u, x = self.wallet.get_account_balance(account)
       +        c, u, x = self.wallet.get_balance()
                out = {"confirmed": str(Decimal(c)/COIN)}
                if u:
                    out["unconfirmed"] = str(Decimal(u)/COIN)
       t@@ -357,7 +354,7 @@ class Commands:
            @command('wp')
            def getmasterprivate(self):
                """Get master private key. Return your wallet\'s master private key"""
       -        return str(self.wallet.get_master_private_key(self.wallet.root_name, self._password))
       +        return str(self.wallet.keystore.get_master_private_key(self._password))
        
            @command('wp')
            def getseed(self):
       t@@ -499,7 +496,7 @@ class Commands:
            def listaddresses(self, receiving=False, change=False, show_labels=False, frozen=False, unused=False, funded=False, show_balance=False):
                """List wallet addresses. Returns the list of all addresses in your wallet. Use optional arguments to filter the results."""
                out = []
       -        for addr in self.wallet.addresses(True):
       +        for addr in self.wallet.get_addresses():
                    if frozen and not self.wallet.is_frozen(addr):
                        continue
                    if receiving and self.wallet.is_change(addr):
       t@@ -681,7 +678,6 @@ command_options = {
            'unsigned':    ("-u", "--unsigned",    "Do not sign transaction"),
            'rbf':         (None, "--rbf",         "Replace-by-fee transaction"),
            'domain':      ("-D", "--domain",      "List of addresses"),
       -    'account':     (None, "--account",     "Account"),
            'memo':        ("-m", "--memo",        "Description of the request"),
            'expiration':  (None, "--expiration",  "Time in seconds"),
            'timeout':     (None, "--timeout",     "Timeout in seconds"),
   DIR diff --git a/lib/daemon.py b/lib/daemon.py
       t@@ -37,7 +37,7 @@ from util import print_msg, print_error, print_stderr
        from wallet import WalletStorage, Wallet
        from commands import known_commands, Commands
        from simple_config import SimpleConfig
       -
       +from plugins import run_hook
        
        def get_lockfile(config):
            return os.path.join(config.path, 'daemon')
       t@@ -171,16 +171,16 @@ class Daemon(DaemonThread):
                return response
        
            def load_wallet(self, path):
       +        # wizard will be launched if we return
                if path in self.wallets:
                    wallet = self.wallets[path]
                    return wallet
                storage = WalletStorage(path)
                if not storage.file_exists:
                    return
       -        wallet = Wallet(storage)
       -        action = wallet.get_action()
       -        if action:
       +        if storage.requires_split() or storage.requires_upgrade() or storage.get_action():
                    return
       +        wallet = Wallet(storage)
                wallet.start_threads(self.network)
                self.wallets[path] = wallet
                return wallet
   DIR diff --git a/lib/keystore.py b/lib/keystore.py
       t@@ -0,0 +1,701 @@
       +#!/usr/bin/env python2
       +# -*- mode: python -*-
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2016  The Electrum developers
       +#
       +# 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 unicodedata import normalize
       +
       +from version import *
       +import bitcoin
       +from bitcoin import pw_encode, pw_decode, bip32_root, bip32_private_derivation, bip32_public_derivation, bip32_private_key, deserialize_xkey
       +from bitcoin import public_key_from_private_key, public_key_to_bc_address
       +from bitcoin import *
       +
       +from bitcoin import is_old_seed, is_new_seed
       +from util import PrintError, InvalidPassword
       +from mnemonic import Mnemonic
       +
       +
       +class KeyStore(PrintError):
       +
       +    def has_seed(self):
       +        return False
       +
       +    def has_password(self):
       +        return False
       +
       +    def is_watching_only(self):
       +        return False
       +
       +    def can_import(self):
       +        return False
       +
       +
       +class Software_KeyStore(KeyStore):
       +
       +    def __init__(self):
       +        KeyStore.__init__(self)
       +        self.use_encryption = False
       +
       +    def has_password(self):
       +        return self.use_encryption
       +
       +
       +class Imported_KeyStore(Software_KeyStore):
       +    # keystore for imported private keys
       +
       +    def __init__(self):
       +        Software_KeyStore.__init__(self)
       +        self.keypairs = {}
       +
       +    def is_deterministic(self):
       +        return False
       +
       +    def can_change_password(self):
       +        return True
       +
       +    def get_master_public_key(self):
       +        return None
       +
       +    def load(self, storage, name):
       +        self.keypairs = storage.get('keypairs', {})
       +        self.use_encryption = storage.get('use_encryption', False)
       +        self.receiving_pubkeys = self.keypairs.keys()
       +        self.change_pubkeys = []
       +
       +    def save(self, storage, root_name):
       +        storage.put('key_type', 'imported')
       +        storage.put('keypairs', self.keypairs)
       +        storage.put('use_encryption', self.use_encryption)
       +
       +    def can_import(self):
       +        return True
       +
       +    def check_password(self, password):
       +        self.get_private_key((0,0), password)
       +
       +    def import_key(self, sec, password):
       +        if not self.can_import():
       +            raise BaseException('This wallet cannot import private keys')
       +        try:
       +            pubkey = public_key_from_private_key(sec)
       +        except Exception:
       +            raise Exception('Invalid private key')
       +        self.keypairs[pubkey] = sec
       +        return pubkey
       +
       +    def delete_imported_key(self, key):
       +        self.keypairs.pop(key)
       +
       +    def get_private_key(self, sequence, password):
       +        for_change, i = sequence
       +        assert for_change == 0
       +        pubkey = (self.change_pubkeys if for_change else self.receiving_pubkeys)[i]
       +        pk = pw_decode(self.keypairs[pubkey], password)
       +        # this checks the password
       +        if pubkey != public_key_from_private_key(pk):
       +            raise InvalidPassword()
       +        return pk
       +
       +    def update_password(self, old_password, new_password):
       +        if old_password is not None:
       +            self.check_password(old_password)
       +        if new_password == '':
       +            new_password = None
       +        for k, v in self.keypairs.items():
       +            b = pw_decode(v, old_password)
       +            c = pw_encode(b, new_password)
       +            self.keypairs[k] = b
       +        self.use_encryption = (new_password is not None)
       +
       +
       +class Deterministic_KeyStore(Software_KeyStore):
       +
       +    def __init__(self):
       +        Software_KeyStore.__init__(self)
       +        self.seed = ''
       +
       +    def is_deterministic(self):
       +        return True
       +
       +    def load(self, storage, name):
       +        self.seed = storage.get('seed', '')
       +        self.use_encryption = storage.get('use_encryption', False)
       +
       +    def save(self, storage, name):
       +        storage.put('seed', self.seed)
       +        storage.put('use_encryption', self.use_encryption)
       +
       +    def has_seed(self):
       +        return self.seed != ''
       +
       +    def can_change_password(self):
       +        return not self.is_watching_only()
       +
       +    def add_seed(self, seed, password):
       +        if self.seed:
       +            raise Exception("a seed exists")
       +        self.seed_version, self.seed = self.format_seed(seed)
       +        if password:
       +            self.seed = pw_encode(self.seed, password)
       +        self.use_encryption = (password is not None)
       +
       +    def get_seed(self, password):
       +        return pw_decode(self.seed, password).encode('utf8')
       +
       +
       +class Xpub:
       +
       +    def __init__(self):
       +        self.xpub = None
       +        self.xpub_receive = None
       +        self.xpub_change = None
       +
       +    def add_master_public_key(self, xpub):
       +        self.xpub = xpub
       +
       +    def get_master_public_key(self):
       +        return self.xpub
       +
       +    def derive_pubkey(self, for_change, n):
       +        xpub = self.xpub_change if for_change else self.xpub_receive
       +        if xpub is None:
       +            xpub = bip32_public_derivation(self.xpub, "", "/%d"%for_change)
       +            if for_change:
       +                self.xpub_change = xpub
       +            else:
       +                self.xpub_receive = xpub
       +        _, _, _, c, cK = deserialize_xkey(xpub)
       +        cK, c = CKD_pub(cK, c, n)
       +        result = cK.encode('hex')
       +        return result
       +
       +    def get_xpubkey(self, c, i):
       +        s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (c, i)))
       +        return 'ff' + bitcoin.DecodeBase58Check(self.xpub).encode('hex') + s
       +
       +
       +class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
       +    root_derivation = "m/"
       +
       +    def __init__(self):
       +        Xpub.__init__(self)
       +        Deterministic_KeyStore.__init__(self)
       +        self.xprv = None
       +
       +    def format_seed(self, seed):
       +        return NEW_SEED_VERSION, ' '.join(seed.split())
       +
       +    def load(self, storage, name):
       +        Deterministic_KeyStore.load(self, storage, name)
       +        self.xpub = storage.get('master_public_keys', {}).get(name)
       +        self.xprv = storage.get('master_private_keys', {}).get(name)
       +
       +    def save(self, storage, name):
       +        Deterministic_KeyStore.save(self, storage, name)
       +        d = storage.get('master_public_keys', {})
       +        d[name] = self.xpub
       +        storage.put('master_public_keys', d)
       +        d = storage.get('master_private_keys', {})
       +        d[name] = self.xprv
       +        storage.put('master_private_keys', d)
       +
       +    def add_master_private_key(self, xprv, password):
       +        self.xprv = pw_encode(xprv, password)
       +
       +    def get_master_private_key(self, password):
       +        return pw_decode(self.xprv, password)
       +
       +    def check_password(self, password):
       +        xprv = pw_decode(self.xprv, password)
       +        if deserialize_xkey(xprv)[3] != deserialize_xkey(self.xpub)[3]:
       +            raise InvalidPassword()
       +
       +    def update_password(self, old_password, new_password):
       +        if old_password is not None:
       +            self.check_password(old_password)
       +        if new_password == '':
       +            new_password = None
       +        if self.has_seed():
       +            decoded = self.get_seed(old_password)
       +            self.seed = pw_encode( decoded, new_password)
       +        if self.xprv is not None:
       +            b = pw_decode(self.xprv, old_password)
       +            self.xprv = pw_encode(b, new_password)
       +        self.use_encryption = (new_password is not None)
       +
       +    def is_watching_only(self):
       +        return self.xprv is None
       +
       +    def get_keypairs_for_sig(self, tx, password):
       +        keypairs = {}
       +        for txin in tx.inputs():
       +            num_sig = txin.get('num_sig')
       +            if num_sig is None:
       +                continue
       +            x_signatures = txin['signatures']
       +            signatures = filter(None, x_signatures)
       +            if len(signatures) == num_sig:
       +                # input is complete
       +                continue
       +            for k, x_pubkey in enumerate(txin['x_pubkeys']):
       +                if x_signatures[k] is not None:
       +                    # this pubkey already signed
       +                    continue
       +                derivation = txin['derivation']
       +                sec = self.get_private_key(derivation, password)
       +                if sec:
       +                    keypairs[x_pubkey] = sec
       +
       +        return keypairs
       +
       +    def sign_transaction(self, tx, password):
       +        # Raise if password is not correct.
       +        self.check_password(password)
       +        # Add private keys
       +        keypairs = self.get_keypairs_for_sig(tx, password)
       +        # Sign
       +        if keypairs:
       +            tx.sign(keypairs)
       +
       +    def derive_xkeys(self, root, derivation, password):
       +        x = self.master_private_keys[root]
       +        root_xprv = pw_decode(x, password)
       +        xprv, xpub = bip32_private_derivation(root_xprv, root, derivation)
       +        return xpub, xprv
       +
       +    def get_mnemonic(self, password):
       +        return self.get_seed(password)
       +
       +    def mnemonic_to_seed(self, seed, password):
       +        return Mnemonic.mnemonic_to_seed(seed, password)
       +
       +    @classmethod
       +    def make_seed(self, lang=None):
       +        return Mnemonic(lang).make_seed()
       +
       +    @classmethod
       +    def address_derivation(self, account_id, change, address_index):
       +        account_derivation = self.account_derivation(account_id)
       +        return "%s/%d/%d" % (account_derivation, change, address_index)
       +
       +    def address_id(self, address):
       +        acc_id, (change, address_index) = self.get_address_index(address)
       +        return self.address_derivation(acc_id, change, address_index)
       +
       +    def add_seed_and_xprv(self, seed, password, passphrase=''):
       +        xprv, xpub = bip32_root(self.mnemonic_to_seed(seed, passphrase))
       +        xprv, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation)
       +        self.add_seed(seed, password)
       +        self.add_master_private_key(xprv, password)
       +        self.add_master_public_key(xpub)
       +
       +    def add_xprv(self, xprv, password):
       +        xpub = bitcoin.xpub_from_xprv(xprv)
       +        self.add_master_private_key(xprv, password)
       +        self.add_master_public_key(xpub)
       +
       +    def can_sign(self, xpub):
       +        return xpub == self.xpub and self.xprv is not None
       +
       +    def get_private_key(self, sequence, password):
       +        xprv = self.get_master_private_key(password)
       +        _, _, _, c, k = deserialize_xkey(xprv)
       +        pk = bip32_private_key(sequence, k, c)
       +        return pk
       +
       +
       +class Old_KeyStore(Deterministic_KeyStore):
       +
       +    def __init__(self):
       +        Deterministic_KeyStore.__init__(self)
       +        self.mpk = None
       +
       +    def load(self, storage, name):
       +        Deterministic_KeyStore.load(self, storage, name)
       +        self.mpk = storage.get('master_public_key').decode('hex')
       +
       +    def save(self, storage, name):
       +        Deterministic_KeyStore.save(self, storage, name)
       +        storage.put('wallet_type', 'old')
       +        storage.put('master_public_key', self.mpk.encode('hex'))
       +
       +    def add_seed(self, seed, password):
       +        Deterministic_KeyStore.add_seed(self, seed, password)
       +        self.mpk = self.mpk_from_seed(self.get_seed(password))
       +
       +    def add_master_public_key(self, mpk):
       +        self.mpk = mpk.decode('hex')
       +
       +    def format_seed(self, seed):
       +        import old_mnemonic
       +        # see if seed was entered as hex
       +        seed = seed.strip()
       +        if seed:
       +            try:
       +                seed.decode('hex')
       +                return OLD_SEED_VERSION, str(seed)
       +            except Exception:
       +                pass
       +        words = seed.split()
       +        seed = old_mnemonic.mn_decode(words)
       +        if not seed:
       +            raise Exception("Invalid seed")
       +        return OLD_SEED_VERSION, seed
       +
       +    def get_mnemonic(self, password):
       +        import old_mnemonic
       +        s = self.get_seed(password)
       +        return ' '.join(old_mnemonic.mn_encode(s))
       +
       +    @classmethod
       +    def mpk_from_seed(klass, seed):
       +        secexp = klass.stretch_key(seed)
       +        master_private_key = ecdsa.SigningKey.from_secret_exponent(secexp, curve = SECP256k1)
       +        master_public_key = master_private_key.get_verifying_key().to_string()
       +        return master_public_key
       +
       +    @classmethod
       +    def stretch_key(self, seed):
       +        x = seed
       +        for i in range(100000):
       +            x = hashlib.sha256(x + seed).digest()
       +        return string_to_number(x)
       +
       +    @classmethod
       +    def get_sequence(self, mpk, for_change, n):
       +        return string_to_number(Hash("%d:%d:"%(n, for_change) + mpk))
       +
       +    def get_address(self, for_change, n):
       +        pubkey = self.get_pubkey(for_change, n)
       +        address = public_key_to_bc_address(pubkey.decode('hex'))
       +        return address
       +
       +    @classmethod
       +    def get_pubkey_from_mpk(self, mpk, for_change, n):
       +        z = self.get_sequence(mpk, for_change, n)
       +        master_public_key = ecdsa.VerifyingKey.from_string(mpk, curve = SECP256k1)
       +        pubkey_point = master_public_key.pubkey.point + z*SECP256k1.generator
       +        public_key2 = ecdsa.VerifyingKey.from_public_point(pubkey_point, curve = SECP256k1)
       +        return '04' + public_key2.to_string().encode('hex')
       +
       +    def derive_pubkey(self, for_change, n):
       +        return self.get_pubkey_from_mpk(self.mpk, for_change, n)
       +
       +    def get_private_key_from_stretched_exponent(self, for_change, n, secexp):
       +        order = generator_secp256k1.order()
       +        secexp = (secexp + self.get_sequence(self.mpk, for_change, n)) % order
       +        pk = number_to_string(secexp, generator_secp256k1.order())
       +        compressed = False
       +        return SecretToASecret(pk, compressed)
       +
       +    def get_private_key(self, sequence, password):
       +        seed = self.get_seed(password)
       +        self.check_seed(seed)
       +        for_change, n = sequence
       +        secexp = self.stretch_key(seed)
       +        pk = self.get_private_key_from_stretched_exponent(for_change, n, secexp)
       +        return pk
       +
       +    def check_seed(self, seed):
       +        secexp = self.stretch_key(seed)
       +        master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
       +        master_public_key = master_private_key.get_verifying_key().to_string()
       +        if master_public_key != self.mpk:
       +            print_error('invalid password (mpk)', self.mpk.encode('hex'), master_public_key.encode('hex'))
       +            raise InvalidPassword()
       +
       +    def check_password(self, password):
       +        seed = self.get_seed(password)
       +        self.check_seed(seed)
       +
       +    def get_master_public_key(self):
       +        return self.mpk.encode('hex')
       +
       +    def get_xpubkeys(self, for_change, n):
       +        s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (for_change, n)))
       +        mpk = self.mpk.encode('hex')
       +        x_pubkey = 'fe' + mpk + s
       +        return [ x_pubkey ]
       +
       +    @classmethod
       +    def parse_xpubkey(self, x_pubkey):
       +        assert is_extended_pubkey(x_pubkey)
       +        pk = x_pubkey[2:]
       +        mpk = pk[0:128]
       +        dd = pk[128:]
       +        s = []
       +        while dd:
       +            n = int(bitcoin.rev_hex(dd[0:4]), 16)
       +            dd = dd[4:]
       +            s.append(n)
       +        assert len(s) == 2
       +        return mpk, s
       +
       +    def update_password(self, old_password, new_password):
       +        if old_password is not None:
       +            self.check_password(old_password)
       +        if new_password == '':
       +            new_password = None
       +        if self.has_seed():
       +            decoded = self.get_seed(old_password)
       +            self.seed = pw_encode(decoded, new_password)
       +        self.use_encryption = (new_password is not None)
       +
       +
       +class Hardware_KeyStore(KeyStore, Xpub):
       +    # Derived classes must set:
       +    #   - device
       +    #   - DEVICE_IDS
       +    #   - wallet_type
       +
       +    #restore_wallet_class = BIP32_RD_Wallet
       +    max_change_outputs = 1
       +
       +    def __init__(self):
       +        Xpub.__init__(self)
       +        KeyStore.__init__(self)
       +        # Errors and other user interaction is done through the wallet's
       +        # handler.  The handler is per-window and preserved across
       +        # device reconnects
       +        self.handler = None
       +
       +    def is_deterministic(self):
       +        return True
       +
       +    def load(self, storage, name):
       +        self.xpub = storage.get('master_public_keys', {}).get(name)
       +
       +    def save(self, storage, name):
       +        d = storage.get('master_public_keys', {})
       +        d[name] = self.xpub
       +        storage.put('master_public_keys', d)
       +
       +    def unpaired(self):
       +        '''A device paired with the wallet was diconnected.  This can be
       +        called in any thread context.'''
       +        self.print_error("unpaired")
       +
       +    def paired(self):
       +        '''A device paired with the wallet was (re-)connected.  This can be
       +        called in any thread context.'''
       +        self.print_error("paired")
       +
       +    def can_export(self):
       +        return False
       +
       +    def is_watching_only(self):
       +        '''The wallet is not watching-only; the user will be prompted for
       +        pin and passphrase as appropriate when needed.'''
       +        assert not self.has_seed()
       +        return False
       +
       +    def can_change_password(self):
       +        return False
       +
       +    def derive_xkeys(self, root, derivation, password):
       +        if self.master_public_keys.get(self.root_name):
       +            return BIP44_wallet.derive_xkeys(self, root, derivation, password)
       +        # When creating a wallet we need to ask the device for the
       +        # master public key
       +        xpub = self.get_public_key(derivation)
       +        return xpub, None
       +
       +
       +class BIP44_KeyStore(BIP32_KeyStore):
       +    root_derivation = "m/44'/0'/0'"
       +
       +    def normalize_passphrase(self, passphrase):
       +        return normalize('NFKD', unicode(passphrase or ''))
       +
       +    def is_valid_seed(self, seed):
       +        return True
       +
       +    def mnemonic_to_seed(self, mnemonic, passphrase):
       +        # See BIP39
       +        import pbkdf2, hashlib, hmac
       +        PBKDF2_ROUNDS = 2048
       +        mnemonic = normalize('NFKD', ' '.join(mnemonic.split()))
       +        passphrase = self.normalize_passphrase(passphrase)
       +        return pbkdf2.PBKDF2(mnemonic, 'mnemonic' + passphrase,
       +                             iterations = PBKDF2_ROUNDS, macmodule = hmac,
       +                             digestmodule = hashlib.sha512).read(64)
       +
       +    def on_restore_wallet(self, wizard):
       +        #assert isinstance(keystore, self.keystore_class)
       +        #msg = _("Enter the seed for your %s wallet:" % self.device)
       +        #title=_('Restore hardware wallet'),
       +        f = lambda seed: wizard.run('on_restore_seed', seed)
       +        wizard.restore_seed_dialog(run_next=f, is_valid=self.is_valid_seed)
       +
       +    def on_restore_seed(self, wizard, seed):
       +        f = lambda passphrase: wizard.run('on_restore_passphrase', seed, passphrase)
       +        self.device = ''
       +        wizard.request_passphrase(self.device, run_next=f)
       +
       +    def on_restore_passphrase(self, wizard, seed, passphrase):
       +        f = lambda pw: wizard.run('on_restore_password', seed, passphrase, pw)
       +        wizard.request_password(run_next=f)
       +
       +    def on_restore_password(self, wizard, seed, passphrase, password):
       +        self.add_seed_and_xprv(seed, password, passphrase)
       +        self.save(wizard.storage, 'x/')
       +
       +
       +
       +keystores = []
       +
       +def load_keystore(storage, name):
       +    w = storage.get('wallet_type')
       +    t = storage.get('key_type', 'seed')
       +    seed_version = storage.get_seed_version()
       +    if seed_version == OLD_SEED_VERSION or w == 'old':
       +        k = Old_KeyStore()
       +    elif t == 'imported':
       +        k = Imported_KeyStore()
       +    elif name and name not in [ 'x/', 'x1/' ]:
       +        k = BIP32_KeyStore()
       +    elif t == 'seed':
       +        k = BIP32_KeyStore()
       +    elif t == 'hardware':
       +        hw_type = storage.get('hardware_type')
       +        for cat, _type, constructor in keystores:
       +            if cat == 'hardware' and _type == hw_type:
       +                k = constructor()
       +                break
       +        else:
       +            raise BaseException('unknown hardware type')
       +    elif t == 'hw_seed':
       +        k = BIP44_KeyStore()
       +    else:
       +        raise BaseException('unknown wallet type', t)
       +    k.load(storage, name)
       +    return k
       +
       +
       +def register_keystore(category, type, constructor):
       +    keystores.append((category, type, constructor))
       +
       +
       +def is_old_mpk(mpk):
       +    try:
       +        int(mpk, 16)
       +    except:
       +        return False
       +    return len(mpk) == 128
       +
       +def is_xpub(text):
       +    if text[0:4] != 'xpub':
       +        return False
       +    try:
       +        deserialize_xkey(text)
       +        return True
       +    except:
       +        return False
       +
       +def is_xprv(text):
       +    if text[0:4] != 'xprv':
       +        return False
       +    try:
       +        deserialize_xkey(text)
       +        return True
       +    except:
       +        return False
       +
       +def is_address_list(text):
       +    parts = text.split()
       +    return bool(parts) and all(bitcoin.is_address(x) for x in parts)
       +
       +def is_private_key_list(text):
       +    parts = text.split()
       +    return bool(parts) and all(bitcoin.is_private_key(x) for x in parts)
       +
       +is_seed = lambda x: is_old_seed(x) or is_new_seed(x)
       +is_mpk = lambda x: is_old_mpk(x) or is_xpub(x)
       +is_private = lambda x: is_seed(x) or is_xprv(x) or is_private_key_list(x)
       +is_any_key = lambda x: is_old_mpk(x) or is_xprv(x) or is_xpub(x) or is_address_list(x) or is_private_key_list(x)
       +is_private_key = lambda x: is_xprv(x) or is_private_key_list(x)
       +is_bip32_key = lambda x: is_xprv(x) or is_xpub(x)
       +
       +
       +def from_seed(seed, password):
       +    if is_old_seed(seed):
       +        keystore = Old_KeyStore()
       +        keystore.add_seed(seed, password)
       +    elif is_new_seed(seed):
       +        keystore = BIP32_KeyStore()
       +        keystore.add_seed_and_xprv(seed, password)
       +    return keystore
       +
       +def from_private_key_list(text, password):
       +    keystore = Imported_KeyStore()
       +    for x in text.split():
       +        keystore.import_key(x, None)
       +    keystore.update_password(None, password)
       +    return keystore
       +
       +def from_old_mpk(mpk):
       +    keystore = Old_KeyStore()
       +    keystore.add_master_public_key(mpk)
       +    return keystore
       +
       +def from_xpub(xpub):
       +    keystore = BIP32_KeyStore()
       +    keystore.add_master_public_key(xpub)
       +    return keystore
       +
       +def from_xprv(xprv, password):
       +    xpub = bitcoin.xpub_from_xprv(xprv)
       +    keystore = BIP32_KeyStore()
       +    keystore.add_master_private_key(xprv, password)
       +    keystore.add_master_public_key(xpub)
       +    return keystore
       +
       +def xprv_from_seed(seed, password):
       +    # do not store the seed, only the master xprv
       +    xprv, xpub = bip32_root(Mnemonic.mnemonic_to_seed(seed, ''))
       +    #xprv, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation)
       +    return from_xprv(xprv, password)
       +
       +def xpub_from_seed(seed):
       +    # store only master xpub
       +    xprv, xpub = bip32_root(Mnemonic.mnemonic_to_seed(seed,''))
       +    #xprv, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation)
       +    return from_xpub(xpub)
       +
       +def from_text(text, password):
       +    if is_xprv(text):
       +        k = from_xprv(text, password)
       +    elif is_old_mpk(text):
       +        k = from_old_mpk(text)
       +    elif is_xpub(text):
       +        k = from_xpub(text)
       +    elif is_private_key_list(text):
       +        k = from_private_key_list(text, password)
       +    elif is_seed(text):
       +        k = from_seed(text, password)
       +    else:
       +        raise BaseException('Invalid seedphrase or key')
       +    return k
   DIR diff --git a/lib/plugins.py b/lib/plugins.py
       t@@ -35,6 +35,10 @@ from util import *
        from i18n import _
        from util import profiler, PrintError, DaemonThread, UserCancelled
        
       +plugin_loaders = {}
       +hook_names = set()
       +hooks = {}
       +
        
        class Plugins(DaemonThread):
        
       t@@ -66,15 +70,17 @@ class Plugins(DaemonThread):
                        continue
                    details = d.get('registers_wallet_type')
                    if details:
       -                self.register_plugin_wallet(name, gui_good, details)
       +                self.register_wallet_type(name, gui_good, details)
       +            details = d.get('registers_keystore')
       +            if details:
       +                self.register_keystore(name, gui_good, details)
                    self.descriptions[name] = d
                    if not d.get('requires_wallet_type') and self.config.get('use_' + name):
                        try:
                            self.load_plugin(name)
                        except BaseException as e:
                            traceback.print_exc(file=sys.stdout)
       -                    self.print_error("cannot initialize plugin %s:" % name,
       -                                     str(e))
       +                    self.print_error("cannot initialize plugin %s:" % name, str(e))
        
            def get(self, name):
                return self.plugins.get(name)
       t@@ -83,6 +89,8 @@ class Plugins(DaemonThread):
                return len(self.plugins)
        
            def load_plugin(self, name):
       +        if name in self.plugins:
       +            return
                full_name = 'electrum_plugins.' + name + '.' + self.gui_name
                loader = pkgutil.find_loader(full_name)
                if not loader:
       t@@ -145,17 +153,23 @@ class Plugins(DaemonThread):
                            self.print_error("cannot load plugin for:", name)
                return wallet_types, descs
        
       -    def register_plugin_wallet(self, name, gui_good, details):
       +    def register_wallet_type(self, name, gui_good, details):
                from wallet import Wallet
       -
       -        def dynamic_constructor(storage):
       -            return self.wallet_plugin_loader(name).wallet_class(storage)
       -
       +        global plugin_loaders
       +        def loader():
       +            plugin = self.wallet_plugin_loader(name)
       +            Wallet.register_constructor(details[0], details[1], plugin.wallet_class)
       +        self.print_error("registering wallet type %s: %s" %(name, details))
       +        plugin_loaders[details[1]] = loader
       +
       +    def register_keystore(self, name, gui_good, details):
       +        from keystore import register_keystore
       +        def dynamic_constructor():
       +            return self.wallet_plugin_loader(name).keystore_class()
                if details[0] == 'hardware':
                    self.hw_wallets[name] = (gui_good, details)
       -        self.print_error("registering wallet %s: %s" %(name, details))
       -        Wallet.register_plugin_wallet(details[0], details[1],
       -                                      dynamic_constructor)
       +            self.print_error("registering keystore %s: %s" %(name, details))
       +        register_keystore(details[0], details[1], dynamic_constructor)
        
            def wallet_plugin_loader(self, name):
                if not name in self.plugins:
       t@@ -169,9 +183,6 @@ class Plugins(DaemonThread):
                self.on_stop()
        
        
       -hook_names = set()
       -hooks = {}
       -
        def hook(func):
            hook_names.add(func.func_name)
            return func
       t@@ -375,48 +386,45 @@ class DeviceMgr(ThreadJob, PrintError):
                self.scan_devices(handler)
                return self.client_lookup(id_)
        
       -    def client_for_wallet(self, plugin, wallet, force_pair):
       -        assert wallet.handler
       -
       -        devices = self.scan_devices(wallet.handler)
       -        wallet_id = self.wallet_id(wallet)
       -
       +    def client_for_keystore(self, plugin, keystore, force_pair):
       +        assert keystore.handler
       +        devices = self.scan_devices(keystore.handler)
       +        wallet_id = self.wallet_id(keystore)
                client = self.client_lookup(wallet_id)
                if client:
                    # An unpaired client might have another wallet's handler
                    # from a prior scan.  Replace to fix dialog parenting.
       -            client.handler = wallet.handler
       +            client.handler = keystore.handler
                    return client
        
                for device in devices:
                    if device.id_ == wallet_id:
       -                return self.create_client(device, wallet.handler, plugin)
       +                return self.create_client(device, keystore.handler, plugin)
        
                if force_pair:
       -            return self.force_pair_wallet(plugin, wallet, devices)
       +            return self.force_pair_wallet(plugin, keystore, devices)
        
                return None
        
       -    def force_pair_wallet(self, plugin, wallet, devices):
       -        first_address, derivation = wallet.first_address()
       -        assert first_address
       +    def force_pair_wallet(self, plugin, keystore, devices):
       +        xpub = keystore.get_master_public_key()
       +        derivation = keystore.get_derivation()
        
                # The wallet has not been previously paired, so let the user
                # choose an unpaired device and compare its first address.
       -        info = self.select_device(wallet, plugin, devices)
       -
       +        info = self.select_device(keystore, plugin, devices)
                client = self.client_lookup(info.device.id_)
                if client and client.is_pairable():
                    # See comment above for same code
       -            client.handler = wallet.handler
       +            client.handler = keystore.handler
                    # This will trigger a PIN/passphrase entry request
                    try:
       -                client_first_address = client.first_address(derivation)
       +                client_xpub = client.get_xpub(derivation)
                    except (UserCancelled, RuntimeError):
                         # Bad / cancelled PIN / passphrase
       -                client_first_address = None
       -            if client_first_address == first_address:
       -                self.pair_wallet(wallet, info.device.id_)
       +                client_xpub = None
       +            if client_xpub == xpub:
       +                self.pair_wallet(keystore, info.device.id_)
                        return client
        
                # The user input has wrong PIN or passphrase, or cancelled input,
   DIR diff --git a/lib/storage.py b/lib/storage.py
       t@@ -0,0 +1,253 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2015 Thomas Voegtlin
       +#
       +# 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.
       +
       +import os
       +import ast
       +import threading
       +import random
       +import time
       +import json
       +import copy
       +import re
       +import stat
       +
       +from i18n import _
       +from util import NotEnoughFunds, PrintError, profiler
       +from plugins import run_hook, plugin_loaders
       +
       +class WalletStorage(PrintError):
       +
       +    def __init__(self, path):
       +        self.lock = threading.RLock()
       +        self.data = {}
       +        self.path = path
       +        self.file_exists = False
       +        self.modified = False
       +        self.print_error("wallet path", self.path)
       +        if self.path:
       +            self.read(self.path)
       +
       +        # check here if I need to load a plugin
       +        t = self.get('wallet_type')
       +        l = plugin_loaders.get(t)
       +        if l: l()
       +
       +
       +    def read(self, path):
       +        """Read the contents of the wallet file."""
       +        try:
       +            with open(self.path, "r") as f:
       +                data = f.read()
       +        except IOError:
       +            return
       +        if not data:
       +            return
       +        try:
       +            self.data = json.loads(data)
       +        except:
       +            try:
       +                d = ast.literal_eval(data)  #parse raw data from reading wallet file
       +                labels = d.get('labels', {})
       +            except Exception as e:
       +                raise IOError("Cannot read wallet file '%s'" % self.path)
       +            self.data = {}
       +            # In old versions of Electrum labels were latin1 encoded, this fixes breakage.
       +            for i, label in labels.items():
       +                try:
       +                    unicode(label)
       +                except UnicodeDecodeError:
       +                    d['labels'][i] = unicode(label.decode('latin1'))
       +            for key, value in d.items():
       +                try:
       +                    json.dumps(key)
       +                    json.dumps(value)
       +                except:
       +                    self.print_error('Failed to convert label to json format', key)
       +                    continue
       +                self.data[key] = value
       +        self.file_exists = True
       +
       +    def get(self, key, default=None):
       +        with self.lock:
       +            v = self.data.get(key)
       +            if v is None:
       +                v = default
       +            else:
       +                v = copy.deepcopy(v)
       +        return v
       +
       +    def put(self, key, value):
       +        try:
       +            json.dumps(key)
       +            json.dumps(value)
       +        except:
       +            self.print_error("json error: cannot save", key)
       +            return
       +        with self.lock:
       +            if value is not None:
       +                if self.data.get(key) != value:
       +                    self.modified = True
       +                    self.data[key] = copy.deepcopy(value)
       +            elif key in self.data:
       +                self.modified = True
       +                self.data.pop(key)
       +
       +    def write(self):
       +        with self.lock:
       +            self._write()
       +        self.file_exists = True
       +
       +    def _write(self):
       +        if threading.currentThread().isDaemon():
       +            self.print_error('warning: daemon thread cannot write wallet')
       +            return
       +        if not self.modified:
       +            return
       +        s = json.dumps(self.data, indent=4, sort_keys=True)
       +        temp_path = "%s.tmp.%s" % (self.path, os.getpid())
       +        with open(temp_path, "w") as f:
       +            f.write(s)
       +            f.flush()
       +            os.fsync(f.fileno())
       +
       +        mode = os.stat(self.path).st_mode if os.path.exists(self.path) else stat.S_IREAD | stat.S_IWRITE
       +        # perform atomic write on POSIX systems
       +        try:
       +            os.rename(temp_path, self.path)
       +        except:
       +            os.remove(self.path)
       +            os.rename(temp_path, self.path)
       +        os.chmod(self.path, mode)
       +        self.print_error("saved", self.path)
       +        self.modified = False
       +
       +    def requires_split(self):
       +        d = self.get('accounts', {})
       +        return len(d) > 1
       +
       +    def split_accounts(storage):
       +        result = []
       +        # backward compatibility with old wallets
       +        d = storage.get('accounts', {})
       +        if len(d) < 2:
       +            return
       +        wallet_type = storage.get('wallet_type')
       +        if wallet_type == 'old':
       +            assert len(d) == 2
       +            storage1 = WalletStorage(storage.path + '.deterministic')
       +            storage1.data = copy.deepcopy(storage.data)
       +            storage1.put('accounts', {'0': d['0']})
       +            storage1.write()
       +            storage2 = WalletStorage(storage.path + '.imported')
       +            storage2.data = copy.deepcopy(storage.data)
       +            storage2.put('accounts', {'/x': d['/x']})
       +            storage2.put('seed', None)
       +            storage2.put('seed_version', None)
       +            storage2.put('master_public_key', None)
       +            storage2.put('wallet_type', 'imported')
       +            storage2.write()
       +            storage2.upgrade()
       +            result = [storage1.path, storage2.path]
       +        elif wallet_type in ['bip44', 'trezor']:
       +            mpk = storage.get('master_public_keys')
       +            for k in d.keys():
       +                i = int(k)
       +                x = d[k]
       +                if x.get("pending"):
       +                    continue
       +                xpub = mpk["x/%d'"%i]
       +                new_path = storage.path + '.' + k
       +                storage2 = WalletStorage(new_path)
       +                storage2.data = copy.deepcopy(storage.data)
       +                storage2.put('wallet_type', 'standard')
       +                if wallet_type in ['trezor', 'keepkey']:
       +                    storage2.put('key_type', 'hardware')
       +                    storage2.put('hardware_type', wallet_type)
       +                storage2.put('accounts', {'0': x})
       +                # need to save derivation and xpub too
       +                storage2.put('master_public_keys', {'x/': xpub})
       +                storage2.put('account_id', k)
       +                storage2.write()
       +                result.append(new_path)
       +        else:
       +            raise BaseException("This wallet has multiple accounts and must be split")
       +        return result
       +
       +    def requires_upgrade(storage):
       +        # '/x' is the internal ID for imported accounts
       +        return bool(storage.get('accounts', {}).get('/x', {}).get('imported',{}))
       +
       +    def upgrade(storage):
       +        d = storage.get('accounts', {}).get('/x', {}).get('imported',{})
       +        addresses = []
       +        keypairs = {}
       +        for addr, v in d.items():
       +            pubkey, privkey = v
       +            if privkey:
       +                keypairs[pubkey] = privkey
       +            else:
       +                addresses.append(addr)
       +        if addresses and keypairs:
       +            raise BaseException('mixed addresses and privkeys')
       +        elif addresses:
       +            storage.put('addresses', addresses)
       +            storage.put('accounts', None)
       +        elif keypairs:
       +            storage.put('wallet_type', 'standard')
       +            storage.put('key_type', 'imported')
       +            storage.put('keypairs', keypairs)
       +            storage.put('accounts', None)
       +        else:
       +            raise BaseException('no addresses or privkeys')
       +        storage.write()
       +
       +    def get_action(self):
       +        action = run_hook('get_action', self)
       +        if action:
       +            return action
       +        if not self.file_exists:
       +            return 'new'
       +
       +    def get_seed_version(self):
       +        from version import OLD_SEED_VERSION, NEW_SEED_VERSION
       +        seed_version = self.get('seed_version')
       +        if not seed_version:
       +            seed_version = OLD_SEED_VERSION if len(self.get('master_public_key','')) == 128 else NEW_SEED_VERSION
       +        if seed_version not in [OLD_SEED_VERSION, NEW_SEED_VERSION]:
       +            msg = "Your wallet has an unsupported seed version."
       +            msg += '\n\nWallet file: %s' % os.path.abspath(self.path)
       +            if seed_version in [5, 7, 8, 9, 10]:
       +                msg += "\n\nTo open this wallet, try 'git checkout seed_v%d'"%seed_version
       +            if seed_version == 6:
       +                # version 1.9.8 created v6 wallets when an incorrect seed was entered in the restore dialog
       +                msg += '\n\nThis file was created because of a bug in version 1.9.8.'
       +                if self.get('master_public_keys') is None and self.get('master_private_keys') is None and self.get('imported_keys') is None:
       +                    # pbkdf2 was not included with the binaries, and wallet creation aborted.
       +                    msg += "\nIt does not contain any keys, and can safely be removed."
       +                else:
       +                    # creation was complete if electrum was run from source
       +                    msg += "\nPlease open this file with Electrum 1.9.8, and move your coins to a new wallet."
       +            raise BaseException(msg)
       +        return seed_version
   DIR diff --git a/lib/synchronizer.py b/lib/synchronizer.py
       t@@ -180,7 +180,7 @@ class Synchronizer(ThreadJob):
        
                if self.requested_tx:
                    self.print_error("missing tx", self.requested_tx)
       -        self.subscribe_to_addresses(set(self.wallet.addresses(True)))
       +        self.subscribe_to_addresses(set(self.wallet.get_addresses()))
        
            def run(self):
                '''Called from the network proxy thread main loop.'''
   DIR diff --git a/lib/transaction.py b/lib/transaction.py
       t@@ -761,23 +761,6 @@ class Transaction:
                        out.add(i)
                return out
        
       -    def inputs_to_sign(self):
       -        out = set()
       -        for txin in self.inputs():
       -            num_sig = txin.get('num_sig')
       -            if num_sig is None:
       -                continue
       -            x_signatures = txin['signatures']
       -            signatures = filter(None, x_signatures)
       -            if len(signatures) == num_sig:
       -                # input is complete
       -                continue
       -            for k, x_pubkey in enumerate(txin['x_pubkeys']):
       -                if x_signatures[k] is not None:
       -                    # this pubkey already signed
       -                    continue
       -                out.add(x_pubkey)
       -        return out
        
            def sign(self, keypairs):
                for i, txin in enumerate(self.inputs()):
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -23,6 +23,14 @@
        # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        # SOFTWARE.
        
       +"""
       +Wallet classes:
       +  - Imported_Wallet: imported address, no keystore
       +  - Standard_Wallet: one keystore, P2PKH
       +  - Multisig_Wallet: several keystores, P2SH
       +
       +"""
       +
        import os
        import hashlib
        import ast
       t@@ -34,15 +42,14 @@ import copy
        import re
        import stat
        from functools import partial
       -from unicodedata import normalize
        from collections import namedtuple, defaultdict
        
        from i18n import _
        from util import NotEnoughFunds, PrintError, profiler
        
        from bitcoin import *
       -from account import *
        from version import *
       +from keystore import load_keystore
        
        from transaction import Transaction
        from plugins import run_hook
       t@@ -54,9 +61,7 @@ from mnemonic import Mnemonic
        
        import paymentrequest
        
       -# internal ID for imported account
       -IMPORTED_ACCOUNT = '/x'
       -
       +from storage import WalletStorage
        
        TX_STATUS = [
            _('Replaceable'),
       t@@ -67,104 +72,6 @@ TX_STATUS = [
        ]
        
        
       -class WalletStorage(PrintError):
       -
       -    def __init__(self, path):
       -        self.lock = threading.RLock()
       -        self.data = {}
       -        self.path = path
       -        self.file_exists = False
       -        self.modified = False
       -        self.print_error("wallet path", self.path)
       -        if self.path:
       -            self.read(self.path)
       -
       -    def read(self, path):
       -        """Read the contents of the wallet file."""
       -        try:
       -            with open(self.path, "r") as f:
       -                data = f.read()
       -        except IOError:
       -            return
       -        if not data:
       -            return
       -        try:
       -            self.data = json.loads(data)
       -        except:
       -            try:
       -                d = ast.literal_eval(data)  #parse raw data from reading wallet file
       -                labels = d.get('labels', {})
       -            except Exception as e:
       -                raise IOError("Cannot read wallet file '%s'" % self.path)
       -            self.data = {}
       -            # In old versions of Electrum labels were latin1 encoded, this fixes breakage.
       -            for i, label in labels.items():
       -                try:
       -                    unicode(label)
       -                except UnicodeDecodeError:
       -                    d['labels'][i] = unicode(label.decode('latin1'))
       -            for key, value in d.items():
       -                try:
       -                    json.dumps(key)
       -                    json.dumps(value)
       -                except:
       -                    self.print_error('Failed to convert label to json format', key)
       -                    continue
       -                self.data[key] = value
       -        self.file_exists = True
       -
       -    def get(self, key, default=None):
       -        with self.lock:
       -            v = self.data.get(key)
       -            if v is None:
       -                v = default
       -            else:
       -                v = copy.deepcopy(v)
       -        return v
       -
       -    def put(self, key, value):
       -        try:
       -            json.dumps(key)
       -            json.dumps(value)
       -        except:
       -            self.print_error("json error: cannot save", key)
       -            return
       -        with self.lock:
       -            if value is not None:
       -                if self.data.get(key) != value:
       -                    self.modified = True
       -                    self.data[key] = copy.deepcopy(value)
       -            elif key in self.data:
       -                self.modified = True
       -                self.data.pop(key)
       -
       -    def write(self):
       -        with self.lock: self._write()
       -
       -    def _write(self):
       -        if threading.currentThread().isDaemon():
       -            self.print_error('warning: daemon thread cannot write wallet')
       -            return
       -        if not self.modified:
       -            return
       -        s = json.dumps(self.data, indent=4, sort_keys=True)
       -        temp_path = "%s.tmp.%s" % (self.path, os.getpid())
       -        with open(temp_path, "w") as f:
       -            f.write(s)
       -            f.flush()
       -            os.fsync(f.fileno())
       -
       -        mode = os.stat(self.path).st_mode if os.path.exists(self.path) else stat.S_IREAD | stat.S_IWRITE
       -        # perform atomic write on POSIX systems
       -        try:
       -            os.rename(temp_path, self.path)
       -        except:
       -            os.remove(self.path)
       -            os.rename(temp_path, self.path)
       -        os.chmod(self.path, mode)
       -        self.print_error("saved", self.path)
       -        self.modified = False
       -
        
        class Abstract_Wallet(PrintError):
            """
       t@@ -184,20 +91,15 @@ class Abstract_Wallet(PrintError):
        
                self.gap_limit_for_change = 6 # constant
                # saved fields
       -        self.seed_version          = storage.get('seed_version', NEW_SEED_VERSION)
                self.use_change            = storage.get('use_change', True)
                self.multiple_change       = storage.get('multiple_change', False)
       -        self.use_encryption        = storage.get('use_encryption', False)
       -        self.seed                  = storage.get('seed', '')               # encrypted
                self.labels                = storage.get('labels', {})
                self.frozen_addresses      = set(storage.get('frozen_addresses',[]))
                self.stored_height         = storage.get('stored_height', 0)       # last known height (for offline mode)
                self.history               = storage.get('addr_history',{})        # address -> list(txid, height)
        
       -        # imported_keys is deprecated. The GUI should call convert_imported_keys
       -        self.imported_keys = self.storage.get('imported_keys',{})
       -
       -        self.load_accounts()
       +        self.load_keystore()
       +        self.load_addresses()
                self.load_transactions()
                self.build_reverse_history()
        
       t@@ -209,7 +111,7 @@ class Abstract_Wallet(PrintError):
                self.unverified_tx = defaultdict(int)
        
                # Verified transactions.  Each value is a (height, timestamp, block_pos) tuple.  Access with self.lock.
       -        self.verified_tx   = storage.get('verified_tx3',{})
       +        self.verified_tx = storage.get('verified_tx3', {})
        
                # there is a difference between wallet.up_to_date and interface.is_up_to_date()
                # interface.is_up_to_date() returns true when all requests have been answered and processed
       t@@ -230,12 +132,8 @@ class Abstract_Wallet(PrintError):
            def __str__(self):
                return self.basename()
        
       -    def set_use_encryption(self, use_encryption):
       -        self.use_encryption = use_encryption
       -        self.storage.put('use_encryption', use_encryption)
       -
            def get_master_public_key(self):
       -        pass
       +        raise NotImplementedError
        
            @profiler
            def load_transactions(self):
       t@@ -306,58 +204,23 @@ class Abstract_Wallet(PrintError):
                if save:
                    self.save_transactions()
        
       -    # wizard action
       -    def get_action(self):
       -        pass
       -
            def basename(self):
                return os.path.basename(self.storage.path)
        
       -    def convert_imported_keys(self, password):
       -        for k, v in self.imported_keys.items():
       -            sec = pw_decode(v, password)
       -            pubkey = public_key_from_private_key(sec)
       -            address = public_key_to_bc_address(pubkey.decode('hex'))
       -            if address != k:
       -                raise InvalidPassword()
       -            self.import_key(sec, password)
       -            self.imported_keys.pop(k)
       -        self.storage.put('imported_keys', self.imported_keys)
       -
       -    def load_accounts(self):
       -        self.accounts = {}
       -        d = self.storage.get('accounts', {})
       -        removed = False
       -        for k, v in d.items():
       -            if self.wallet_type == 'old' and k in [0, '0']:
       -                v['mpk'] = self.storage.get('master_public_key')
       -                self.accounts['0'] = OldAccount(v)
       -            elif v.get('imported'):
       -                self.accounts[k] = ImportedAccount(v)
       -            elif v.get('xpub'):
       -                self.accounts[k] = BIP32_Account(v)
       -            elif v.get('pending'):
       -                removed = True
       -            else:
       -                self.print_error("cannot load account", v)
       -        if removed:
       -            self.save_accounts()
       +    def save_pubkeys(self):
       +        # this name is inherited from old multi-account wallets
       +        self.storage.put('accounts', {'0': {'receiving':self.receiving_pubkeys, 'change':self.change_pubkeys}})
        
       -    def create_main_account(self):
       -        pass
       +    def load_addresses(self):
       +        d = self.storage.get('accounts', {}).get('0', {})
       +        self.receiving_pubkeys = d.get('receiving', [])
       +        self.change_pubkeys = d.get('change', [])
       +        self.receiving_addresses = map(self.pubkeys_to_address, self.receiving_pubkeys)
       +        self.change_addresses = map(self.pubkeys_to_address, self.change_pubkeys)
        
            def synchronize(self):
                pass
        
       -    def can_create_accounts(self):
       -        return False
       -
       -    def needs_next_account(self):
       -        return self.can_create_accounts() and self.accounts_all_used()
       -
       -    def permit_account_naming(self):
       -        return self.can_create_accounts()
       -
            def set_up_to_date(self, up_to_date):
                with self.lock:
                    self.up_to_date = up_to_date
       t@@ -367,49 +230,6 @@ class Abstract_Wallet(PrintError):
            def is_up_to_date(self):
                with self.lock: return self.up_to_date
        
       -    def is_imported(self, addr):
       -        account = self.accounts.get(IMPORTED_ACCOUNT)
       -        if account:
       -            return addr in account.get_addresses(0)
       -        else:
       -            return False
       -
       -    def has_imported_keys(self):
       -        account = self.accounts.get(IMPORTED_ACCOUNT)
       -        return account is not None
       -
       -    def import_key(self, sec, password):
       -        if not self.can_import():
       -            raise BaseException('This wallet cannot import private keys')
       -        try:
       -            pubkey = public_key_from_private_key(sec)
       -            address = public_key_to_bc_address(pubkey.decode('hex'))
       -        except Exception:
       -            raise Exception('Invalid private key')
       -
       -        if self.is_mine(address):
       -            raise Exception('Address already in wallet')
       -
       -        if self.accounts.get(IMPORTED_ACCOUNT) is None:
       -            self.accounts[IMPORTED_ACCOUNT] = ImportedAccount({'imported':{}})
       -        self.accounts[IMPORTED_ACCOUNT].add(address, pubkey, sec, password)
       -        self.save_accounts()
       -
       -        # force resynchronization, because we need to re-run add_transaction
       -        if address in self.history:
       -            self.history.pop(address)
       -
       -        if self.synchronizer:
       -            self.synchronizer.add(address)
       -        return address
       -
       -    def delete_imported_key(self, addr):
       -        account = self.accounts[IMPORTED_ACCOUNT]
       -        account.remove(addr)
       -        if not account.get_addresses(0):
       -            self.accounts.pop(IMPORTED_ACCOUNT)
       -        self.save_accounts()
       -
            def set_label(self, name, text = None):
                changed = False
                old_text = self.labels.get(name)
       t@@ -428,35 +248,33 @@ class Abstract_Wallet(PrintError):
        
                return changed
        
       -    def addresses(self, include_change = True):
       -        return list(addr for acc in self.accounts for addr in self.get_account_addresses(acc, include_change))
       -
            def is_mine(self, address):
       -        return address in self.addresses(True)
       +        return address in self.get_addresses()
        
            def is_change(self, address):
       -        if not self.is_mine(address): return False
       -        acct, s = self.get_address_index(address)
       -        if s is None: return False
       +        if not self.is_mine(address):
       +            return False
       +        s = self.get_address_index(address)
       +        if s is None:
       +            return False
                return s[0] == 1
        
            def get_address_index(self, address):
       -        for acc_id in self.accounts:
       -            for for_change in [0,1]:
       -                addresses = self.accounts[acc_id].get_addresses(for_change)
       -                if address in addresses:
       -                    return acc_id, (for_change, addresses.index(address))
       +        if address in self.receiving_addresses:
       +            return False, self.receiving_addresses.index(address)
       +        if address in self.change_addresses:
       +            return True, self.change_addresses.index(address)
                raise Exception("Address not found", address)
        
            def get_private_key(self, address, password):
                if self.is_watching_only():
                    return []
       -        account_id, sequence = self.get_address_index(address)
       -        return self.accounts[account_id].get_private_key(sequence, self, password)
       +        sequence = self.get_address_index(address)
       +        return [ self.keystore.get_private_key(sequence, password) ]
        
            def get_public_keys(self, address):
       -        account_id, sequence = self.get_address_index(address)
       -        return self.accounts[account_id].get_pubkeys(*sequence)
       +        sequence = self.get_address_index(address)
       +        return self.get_pubkeys(*sequence)
        
            def sign_message(self, address, message, password):
                keys = self.get_private_key(address, password)
       t@@ -556,7 +374,7 @@ class Abstract_Wallet(PrintError):
        
            def get_wallet_delta(self, tx):
                """ effect of tx on wallet """
       -        addresses = self.addresses(True)
       +        addresses = self.get_addresses()
                is_relevant = False
                is_mine = False
                is_pruned = False
       t@@ -711,11 +529,10 @@ class Abstract_Wallet(PrintError):
                            u -= v
                return c, u, x
        
       -
            def get_spendable_coins(self, domain = None, exclude_frozen = True):
                coins = []
                if domain is None:
       -            domain = self.addresses(True)
       +            domain = self.get_addresses()
                if exclude_frozen:
                    domain = set(domain) - self.frozen_addresses
                for addr in domain:
       t@@ -728,7 +545,7 @@ class Abstract_Wallet(PrintError):
                return coins
        
            def dummy_address(self):
       -        return self.addresses(False)[0]
       +        return self.get_receiving_addresses()[0]
        
            def get_max_amount(self, config, inputs, recipient, fee):
                sendable = sum(map(lambda x:x['value'], inputs))
       t@@ -742,34 +559,18 @@ class Abstract_Wallet(PrintError):
                amount = max(0, sendable - fee)
                return amount, fee
        
       -    def get_account_addresses(self, acc_id, include_change=True):
       -        '''acc_id of None means all user-visible accounts'''
       -        addr_list = []
       -        acc_ids = self.accounts_to_show() if acc_id is None else [acc_id]
       -        for acc_id in acc_ids:
       -            if acc_id in self.accounts:
       -                acc = self.accounts[acc_id]
       -                addr_list += acc.get_addresses(0)
       -                if include_change:
       -                    addr_list += acc.get_addresses(1)
       -        return addr_list
       -
       -    def get_account_from_address(self, addr):
       -        "Returns the account that contains this address, or None"
       -        for acc_id in self.accounts:    # similar to get_address_index but simpler
       -            if addr in self.get_account_addresses(acc_id):
       -                return acc_id
       -        return None
       -
       -    def get_account_balance(self, account):
       -        return self.get_balance(self.get_account_addresses(account))
       +    def get_addresses(self):
       +        out = []
       +        out += self.get_receiving_addresses()
       +        out += self.get_change_addresses()
       +        return out
        
            def get_frozen_balance(self):
                return self.get_balance(self.frozen_addresses)
        
            def get_balance(self, domain=None):
                if domain is None:
       -            domain = self.addresses(True)
       +            domain = self.get_addresses()
                cc = uu = xx = 0
                for addr in domain:
                    c, u, x = self.get_addr_balance(addr)
       t@@ -904,8 +705,7 @@ class Abstract_Wallet(PrintError):
            def get_history(self, domain=None):
                # get domain
                if domain is None:
       -            domain = self.get_account_addresses(None)
       -
       +            domain = self.get_addresses()
                # 1. Get the history of each address in the domain, maintain the
                #    delta of a tx as the sum of its deltas on domain addresses
                tx_deltas = defaultdict(int)
       t@@ -1027,14 +827,11 @@ class Abstract_Wallet(PrintError):
                if change_addr:
                    change_addrs = [change_addr]
                else:
       -            # send change to one of the accounts involved in the tx
       -            address = coins[0].get('address')
       -            account, _ = self.get_address_index(address)
       -            if self.use_change and self.accounts[account].has_change():
       +            addrs = self.get_change_addresses()[-self.gap_limit_for_change:]
       +            if self.use_change and addrs:
                        # New change addresses are created only after a few
                        # confirmations.  Select the unused addresses within the
                        # gap limit; if none take one at random
       -                addrs = self.accounts[account].get_addresses(1)[-self.gap_limit_for_change:]
                        change_addrs = [addr for addr in addrs if
                                        self.get_num_tx(addr) == 0]
                        if not change_addrs:
       t@@ -1073,71 +870,6 @@ class Abstract_Wallet(PrintError):
                self.sign_transaction(tx, password)
                return tx
        
       -    def add_input_info(self, txin):
       -        address = txin['address']
       -        account_id, sequence = self.get_address_index(address)
       -        account = self.accounts[account_id]
       -        redeemScript = account.redeem_script(*sequence)
       -        pubkeys = account.get_pubkeys(*sequence)
       -        x_pubkeys = account.get_xpubkeys(*sequence)
       -        # sort pubkeys and x_pubkeys, using the order of pubkeys
       -        pubkeys, x_pubkeys = zip( *sorted(zip(pubkeys, x_pubkeys)))
       -        txin['pubkeys'] = list(pubkeys)
       -        txin['x_pubkeys'] = list(x_pubkeys)
       -        txin['signatures'] = [None] * len(pubkeys)
       -        if redeemScript:
       -            txin['redeemScript'] = redeemScript
       -            txin['num_sig'] = account.m
       -        else:
       -            txin['redeemPubkey'] = account.get_pubkey(*sequence)
       -            txin['num_sig'] = 1
       -
       -    def sign_transaction(self, tx, password):
       -        if self.is_watching_only():
       -            return
       -        # Raise if password is not correct.
       -        self.check_password(password)
       -        # Add derivation for utxo in wallets
       -        for i, addr in self.utxo_can_sign(tx):
       -            txin = tx.inputs()[i]
       -            txin['address'] = addr
       -            self.add_input_info(txin)
       -        # Add private keys
       -        keypairs = {}
       -        for x in self.xkeys_can_sign(tx):
       -            sec = self.get_private_key_from_xpubkey(x, password)
       -            if sec:
       -                keypairs[x] = sec
       -        # Sign
       -        if keypairs:
       -            tx.sign(keypairs)
       -
       -    def update_password(self, old_password, new_password):
       -        if old_password is not None:
       -            self.check_password(old_password)
       -
       -        if new_password == '':
       -            new_password = None
       -
       -        if self.has_seed():
       -            decoded = self.get_seed(old_password)
       -            self.seed = pw_encode( decoded, new_password)
       -            self.storage.put('seed', self.seed)
       -
       -        imported_account = self.accounts.get(IMPORTED_ACCOUNT)
       -        if imported_account:
       -            imported_account.update_password(old_password, new_password)
       -            self.save_accounts()
       -
       -        if hasattr(self, 'master_private_keys'):
       -            for k, v in self.master_private_keys.items():
       -                b = pw_decode(v, old_password)
       -                c = pw_encode(b, new_password)
       -                self.master_private_keys[k] = c
       -            self.storage.put('master_private_keys', self.master_private_keys)
       -
       -        self.set_use_encryption(new_password is not None)
       -
            def is_frozen(self, addr):
                return addr in self.frozen_addresses
        
       t@@ -1214,33 +946,6 @@ class Abstract_Wallet(PrintError):
                else:
                    self.synchronize()
        
       -    def accounts_to_show(self):
       -        return self.accounts.keys()
       -
       -    def get_accounts(self):
       -        return {a_id: a for a_id, a in self.accounts.items()
       -                if a_id in self.accounts_to_show()}
       -
       -    def get_account_name(self, k):
       -        return self.labels.get(k, self.accounts[k].get_name(k))
       -
       -    def get_account_names(self):
       -        ids = self.accounts_to_show()
       -        return dict(zip(ids, map(self.get_account_name, ids)))
       -
       -    def add_account(self, account_id, account):
       -        self.accounts[account_id] = account
       -        self.save_accounts()
       -
       -    def save_accounts(self):
       -        d = {}
       -        for k, v in self.accounts.items():
       -            d[k] = v.dump()
       -        self.storage.put('accounts', d)
       -
       -    def can_import(self):
       -        return not self.is_watching_only()
       -
            def can_export(self):
                return not self.is_watching_only()
        
       t@@ -1282,96 +987,71 @@ class Abstract_Wallet(PrintError):
                new_tx = Transaction.from_io(inputs, outputs)
                return new_tx
        
       -    def can_sign(self, tx):
       -        if self.is_watching_only():
       -            return False
       -        if tx.is_complete():
       -            return False
       -        if self.xkeys_can_sign(tx):
       -            return True
       -        if self.utxo_can_sign(tx):
       -            return True
       -        return False
       -
       -    def utxo_can_sign(self, tx):
       -        out = set()
       +    def add_input_info(self, txin):
       +        # Add address for utxo that are in wallet
                coins = self.get_spendable_coins()
       -        for i in tx.inputs_without_script():
       -            txin = tx.inputs()[i]
       +        if txin.get('scriptSig') == '':
                    for item in coins:
                        if txin.get('prevout_hash') == item.get('prevout_hash') and txin.get('prevout_n') == item.get('prevout_n'):
       -                    out.add((i, item.get('address')))
       -        return out
       -
       -    def xkeys_can_sign(self, tx):
       -        out = set()
       -        for x in tx.inputs_to_sign():
       -            if self.can_sign_xpubkey(x):
       -                out.add(x)
       -        return out
       -
       -    def get_private_key_from_xpubkey(self, x_pubkey, password):
       -        if x_pubkey[0:2] in ['02','03','04']:
       -            addr = bitcoin.public_key_to_bc_address(x_pubkey.decode('hex'))
       -            if self.is_mine(addr):
       -                return self.get_private_key(addr, password)[0]
       -        elif x_pubkey[0:2] == 'ff':
       -            xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey)
       -            for k, v in self.master_public_keys.items():
       -                if v == xpub:
       -                    xprv = self.get_master_private_key(k, password)
       -                    if xprv:
       -                        _, _, _, c, k = deserialize_xkey(xprv)
       -                        return bip32_private_key(sequence, k, c)
       -        elif x_pubkey[0:2] == 'fe':
       -            xpub, sequence = OldAccount.parse_xpubkey(x_pubkey)
       -            for k, account in self.accounts.items():
       -                if xpub in account.get_master_pubkeys():
       -                    pk = account.get_private_key(sequence, self, password)
       -                    return pk[0]
       -        elif x_pubkey[0:2] == 'fd':
       -            addrtype = ord(x_pubkey[2:4].decode('hex'))
       -            addr = hash_160_to_bc_address(x_pubkey[4:].decode('hex'), addrtype)
       -            if self.is_mine(addr):
       -                return self.get_private_key(addr, password)[0]
       -        else:
       -            raise BaseException("z")
       -
       -
       -    def can_sign_xpubkey(self, x_pubkey):
       -        if x_pubkey[0:2] in ['02','03','04']:
       -            addr = bitcoin.public_key_to_bc_address(x_pubkey.decode('hex'))
       -            return self.is_mine(addr)
       -        elif x_pubkey[0:2] == 'ff':
       -            if not isinstance(self, BIP32_Wallet): return False
       -            xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey)
       -            return xpub in [ self.master_public_keys[k] for k in self.master_private_keys.keys() ]
       -        elif x_pubkey[0:2] == 'fe':
       -            if not isinstance(self, OldWallet): return False
       -            xpub, sequence = OldAccount.parse_xpubkey(x_pubkey)
       -            return xpub == self.get_master_public_key()
       -        elif x_pubkey[0:2] == 'fd':
       -            addrtype = ord(x_pubkey[2:4].decode('hex'))
       -            addr = hash_160_to_bc_address(x_pubkey[4:].decode('hex'), addrtype)
       -            return self.is_mine(addr)
       +                    txin['address'] = item.get('address')
       +        address = txin['address']
       +        if self.is_mine(address):
       +            self.add_input_sig_info(txin, address)
                else:
       -            raise BaseException("z")
       +            txin['can_sign'] = False
        
       +    def can_sign(self, tx):
       +        if self.is_watching_only():
       +            return False
       +        if tx.is_complete():
       +            return False
       +        # add input info. (should be done already)
       +        for txin in tx.inputs():
       +            self.add_input_info(txin)
       +        can_sign = any([txin['can_sign'] for txin in tx.inputs()])
       +        return can_sign
       +
       +    def get_input_tx(self, tx_hash):
       +        # First look up an input transaction in the wallet where it
       +        # will likely be.  If co-signing a transaction it may not have
       +        # all the input txs, in which case we ask the network.
       +        tx = self.transactions.get(tx_hash)
       +        if not tx:
       +            request = ('blockchain.transaction.get', [tx_hash])
       +            # FIXME: what if offline?
       +            tx = Transaction(self.network.synchronous_get(request))
       +        return tx
        
       -    def is_watching_only(self):
       -        False
       -
       -    def can_change_password(self):
       -        return not self.is_watching_only()
       +    def sign_transaction(self, tx, password):
       +        if self.is_watching_only():
       +            return
        
       -    def get_unused_addresses(self, account):
       +        # add previous tx for hw wallets
       +        for txin in tx.inputs():
       +            tx_hash = txin['prevout_hash']
       +            txin['prev_tx'] = self.get_input_tx(tx_hash)
       +            # I should add the address index if it's an address of mine
       +
       +        # add output info for hw wallets
       +        tx.output_info = []
       +        for i, txout in enumerate(tx.outputs()):
       +            _type, addr, amount = txout
       +            change, address_index = self.get_address_index(addr) if self.is_change(addr) else None, None
       +            tx.output_info.append((change, address_index))
       +
       +        # sign
       +        for keystore in self.get_keystores():
       +            if not keystore.is_watching_only():
       +                keystore.sign_transaction(tx, password)
       +
       +    def get_unused_addresses(self):
                # fixme: use slots from expired requests
       -        domain = self.get_account_addresses(account, include_change=False)
       +        domain = self.get_receiving_addresses()
                return [addr for addr in domain if not self.history.get(addr)
                        and addr not in self.receive_requests.keys()]
        
       -    def get_unused_address(self, account):
       -        addrs = self.get_unused_addresses(account)
       +    def get_unused_address(self):
       +        addrs = self.get_unused_addresses()
                if addrs:
                    return addrs[0]
        
       t@@ -1489,19 +1169,32 @@ class Abstract_Wallet(PrintError):
                raise NotImplementedError()
        
        
       +
        class Imported_Wallet(Abstract_Wallet):
       +    # wallet made of imported addresses
       +
            wallet_type = 'imported'
        
            def __init__(self, storage):
                Abstract_Wallet.__init__(self, storage)
       -        a = self.accounts.get(IMPORTED_ACCOUNT)
       -        if not a:
       -            self.accounts[IMPORTED_ACCOUNT] = ImportedAccount({'imported':{}})
       +
       +    def load_keystore(self):
       +        pass
       +
       +    def load_addresses(self):
       +        self.addresses = self.storage.get('addresses', [])
       +
       +    def has_password(self):
       +        return False
       +
       +    def can_change_password(self):
       +        return False
       +
       +    def can_import(self):
       +        return True
        
            def is_watching_only(self):
       -        acc = self.accounts[IMPORTED_ACCOUNT]
       -        n = acc.keypairs.values()
       -        return len(n) > 0 and n == [[None, None]] * len(n)
       +        return True
        
            def has_seed(self):
                return False
       t@@ -1509,51 +1202,93 @@ class Imported_Wallet(Abstract_Wallet):
            def is_deterministic(self):
                return False
        
       -    def check_password(self, password):
       -        self.accounts[IMPORTED_ACCOUNT].get_private_key((0,0), self, password)
       -
            def is_used(self, address):
                return False
        
            def get_master_public_keys(self):
                return {}
        
       -    def is_beyond_limit(self, address, account, is_change):
       +    def is_beyond_limit(self, address, is_change):
                return False
        
            def get_fingerprint(self):
                return ''
        
       +    def get_addresses(self, include_change=False):
       +        return self.addresses
       +
       +    def add_address(self, address):
       +        if address in self.addresses:
       +            return
       +        self.addresses.append(address)
       +        self.storage.put('addresses', self.addresses)
       +        self.storage.write()
       +
       +        # force resynchronization, because we need to re-run add_transaction
       +        if address in self.history:
       +            self.history.pop(address)
       +        if self.synchronizer:
       +            self.synchronizer.add(address)
       +        return address
       +
       +    def get_receiving_addresses(self):
       +        return self.addresses[:]
       +
       +    def get_change_addresses(self):
       +        return []
       +
       +
       +
       +class P2PK_Wallet(Abstract_Wallet):
       +
       +    def pubkeys_to_address(self, pubkey):
       +        return public_key_to_bc_address(pubkey.decode('hex'))
       +
       +    def load_keystore(self):
       +        self.keystore = load_keystore(self.storage, self.root_name)
       +
       +    def get_pubkey(self, c, i):
       +        pubkey_list = self.change_pubkeys if c else self.receiving_pubkeys
       +        return pubkey_list[i]
       +
       +    def add_input_sig_info(self, txin, address):
       +        txin['derivation'] = derivation = self.get_address_index(address)
       +        x_pubkey = self.keystore.get_xpubkey(*derivation)
       +        pubkey = self.get_pubkey(*derivation)
       +        txin['x_pubkeys'] = [x_pubkey]
       +        txin['pubkeys'] = [pubkey]
       +        txin['signatures'] = [None]
       +        txin['redeemPubkey'] = pubkey
       +        txin['num_sig'] = 1
       +        txin['can_sign'] = any([x is None for x in txin['signatures']])
       +
       +
        class Deterministic_Wallet(Abstract_Wallet):
        
            def __init__(self, storage):
                Abstract_Wallet.__init__(self, storage)
       +        self.gap_limit = storage.get('gap_limit', 20)
        
            def has_seed(self):
       -        return self.seed != ''
       +        return self.keystore.has_seed()
        
            def is_deterministic(self):
       -        return True
       -
       -    def is_watching_only(self):
       -        return not self.has_seed()
       +        return self.keystore.is_deterministic()
        
       -    def add_seed(self, seed, password):
       -        if self.seed:
       -            raise Exception("a seed exists")
       +    def get_receiving_addresses(self):
       +        return self.receiving_addresses
        
       -        self.seed_version, self.seed = self.format_seed(seed)
       -        if password:
       -            self.seed = pw_encode(self.seed, password)
       -        self.storage.put('seed', self.seed)
       -        self.storage.put('seed_version', self.seed_version)
       -        self.set_use_encryption(password is not None)
       +    def get_change_addresses(self):
       +        return self.change_addresses
        
            def get_seed(self, password):
       -        return pw_decode(self.seed, password)
       +        return self.keystore.get_seed(password)
       +
       +    def add_seed(self, seed, pw):
       +        self.keystore.add_seed(seed, pw)
        
            def get_mnemonic(self, password):
       -        return self.get_seed(password)
       +        return self.keystore.get_mnemonic(password)
        
            def change_gap_limit(self, value):
                '''This method is not called in the code, it is kept for console use'''
       t@@ -1561,17 +1296,15 @@ class Deterministic_Wallet(Abstract_Wallet):
                    self.gap_limit = value
                    self.storage.put('gap_limit', self.gap_limit)
                    return True
       -
                elif value >= self.min_acceptable_gap():
       -            for key, account in self.accounts.items():
       -                addresses = account.get_addresses(False)
       -                k = self.num_unused_trailing_addresses(addresses)
       -                n = len(addresses) - k + value
       -                account.receiving_pubkeys = account.receiving_pubkeys[0:n]
       -                account.receiving_addresses = account.receiving_addresses[0:n]
       +            addresses = self.get_receiving_addresses()
       +            k = self.num_unused_trailing_addresses(addresses)
       +            n = len(addresses) - k + value
       +            self.receiving_pubkeys = self.receiving_pubkeys[0:n]
       +            self.receiving_addresses = self.receiving_addresses[0:n]
                    self.gap_limit = value
                    self.storage.put('gap_limit', self.gap_limit)
       -            self.save_accounts()
       +            self.save_pubkeys()
                    return True
                else:
                    return False
       t@@ -1587,44 +1320,61 @@ class Deterministic_Wallet(Abstract_Wallet):
                # fixme: this assumes wallet is synchronized
                n = 0
                nmax = 0
       -
       -        for account in self.accounts.values():
       -            addresses = account.get_addresses(0)
       -            k = self.num_unused_trailing_addresses(addresses)
       -            for a in addresses[0:-k]:
       -                if self.history.get(a):
       -                    n = 0
       -                else:
       -                    n += 1
       -                    if n > nmax: nmax = n
       +        addresses = self.account.get_receiving_addresses()
       +        k = self.num_unused_trailing_addresses(addresses)
       +        for a in addresses[0:-k]:
       +            if self.history.get(a):
       +                n = 0
       +            else:
       +                n += 1
       +                if n > nmax: nmax = n
                return nmax + 1
        
       -    def default_account(self):
       -        return self.accounts['0']
       -
       -    def create_new_address(self, account=None, for_change=0):
       -        if account is None:
       -            account = self.default_account()
       -        address = account.create_new_address(for_change)
       -        self.add_address(address)
       -        return address
       -
            def add_address(self, address):
                if address not in self.history:
                    self.history[address] = []
                if self.synchronizer:
                    self.synchronizer.add(address)
       -        self.save_accounts()
       +
       +    def create_new_address(self, for_change):
       +        pubkey_list = self.change_pubkeys if for_change else self.receiving_pubkeys
       +        n = len(pubkey_list)
       +        x = self.new_pubkeys(for_change, n)
       +        pubkey_list.append(x)
       +        self.save_pubkeys()
       +        address = self.pubkeys_to_address(x)
       +        addr_list = self.change_addresses if for_change else self.receiving_addresses
       +        addr_list.append(address)
       +        self.add_address(address)
       +        return address
       +
       +    def synchronize_sequence(self, for_change):
       +        limit = self.gap_limit_for_change if for_change else self.gap_limit
       +        while True:
       +            addresses = self.get_change_addresses() if for_change else self.get_receiving_addresses()
       +            if len(addresses) < limit:
       +                self.create_new_address(for_change)
       +                continue
       +            if map(lambda a: self.address_is_old(a), addresses[-limit:] ) == limit*[False]:
       +                break
       +            else:
       +                self.create_new_address(for_change)
        
            def synchronize(self):
                with self.lock:
       -            for account in self.accounts.values():
       -                account.synchronize(self)
       -
       -    def is_beyond_limit(self, address, account, is_change):
       -        if type(account) == ImportedAccount:
       -            return False
       -        addr_list = account.get_addresses(is_change)
       +            if self.is_deterministic():
       +                self.synchronize_sequence(False)
       +                self.synchronize_sequence(True)
       +            else:
       +                if len(self.receiving_pubkeys) != len(self.keystore.keypairs):
       +                    self.receiving_pubkeys = self.keystore.keypairs.keys()
       +                    self.save_pubkeys()
       +                    self.receiving_addresses = map(self.pubkeys_to_address, self.receiving_pubkeys)
       +                    for addr in self.receiving_addresses:
       +                        self.add_address(addr)
       +
       +    def is_beyond_limit(self, address, is_change):
       +        addr_list = self.get_change_addresses() if is_change else self.get_receiving_addresses()
                i = addr_list.index(address)
                prev_addresses = addr_list[:max(0, i)]
                limit = self.gap_limit_for_change if is_change else self.gap_limit
       t@@ -1636,393 +1386,146 @@ class Deterministic_Wallet(Abstract_Wallet):
                        return False
                return True
        
       -    def get_action(self):
       -        if not self.get_master_public_key():
       -            return 'create_seed'
       -        if not self.accounts:
       -            return 'create_main_account'
       -
            def get_master_public_keys(self):
       -        out = {}
       -        for k, account in self.accounts.items():
       -            if type(account) == ImportedAccount:
       -                continue
       -            name = self.get_account_name(k)
       -            mpk_text = '\n\n'.join(account.get_master_pubkeys())
       -            out[name] = mpk_text
       -        return out
       +        return {'x':self.get_master_public_key()}
        
            def get_fingerprint(self):
                return self.get_master_public_key()
        
        
       -class BIP32_Wallet(Deterministic_Wallet):
       -    # abstract class, bip32 logic
       +
       +
       +class Standard_Wallet(Deterministic_Wallet, P2PK_Wallet):
            root_name = 'x/'
       +    wallet_type = 'standard'
        
            def __init__(self, storage):
                Deterministic_Wallet.__init__(self, storage)
       -        self.master_public_keys  = storage.get('master_public_keys', {})
       -        self.master_private_keys = storage.get('master_private_keys', {})
       -        self.gap_limit = storage.get('gap_limit', 20)
        
       -    def is_watching_only(self):
       -        return not bool(self.master_private_keys)
       +    def get_master_public_key(self):
       +        return self.keystore.get_master_public_key()
        
       -    def can_import(self):
       -        return False
       +    def new_pubkeys(self, c, i):
       +        return self.keystore.derive_pubkey(c, i)
        
       -    def get_master_public_key(self):
       -        return self.master_public_keys.get(self.root_name)
       -
       -    def get_master_private_key(self, account, password):
       -        k = self.master_private_keys.get(account)
       -        if not k: return
       -        xprv = pw_decode(k, password)
       -        try:
       -            deserialize_xkey(xprv)
       -        except:
       -            raise InvalidPassword()
       -        return xprv
       +    def get_keystore(self):
       +        return self.keystore
        
       -    def check_password(self, password):
       -        xpriv = self.get_master_private_key(self.root_name, password)
       -        xpub = self.master_public_keys[self.root_name]
       -        if deserialize_xkey(xpriv)[3] != deserialize_xkey(xpub)[3]:
       -            raise InvalidPassword()
       -
       -    def add_master_public_key(self, name, xpub):
       -        if xpub in self.master_public_keys.values():
       -            raise BaseException('Duplicate master public key')
       -        self.master_public_keys[name] = xpub
       -        self.storage.put('master_public_keys', self.master_public_keys)
       -
       -    def add_master_private_key(self, name, xpriv, password):
       -        self.master_private_keys[name] = pw_encode(xpriv, password)
       -        self.storage.put('master_private_keys', self.master_private_keys)
       -
       -    def derive_xkeys(self, root, derivation, password):
       -        x = self.master_private_keys[root]
       -        root_xprv = pw_decode(x, password)
       -        xprv, xpub = bip32_private_derivation(root_xprv, root, derivation)
       -        return xpub, xprv
       -
       -    def mnemonic_to_seed(self, seed, password):
       -        return Mnemonic.mnemonic_to_seed(seed, password)
       -
       -    @classmethod
       -    def make_seed(self, lang=None):
       -        return Mnemonic(lang).make_seed()
       -
       -    def format_seed(self, seed):
       -        return NEW_SEED_VERSION, ' '.join(seed.split())
       -
       -
       -class BIP32_Simple_Wallet(BIP32_Wallet):
       -    # Wallet with a single BIP32 account, no seed
       -    # gap limit 20
       -    wallet_type = 'xpub'
       -
       -    def create_xprv_wallet(self, xprv, password):
       -        xpub = bitcoin.xpub_from_xprv(xprv)
       -        account = BIP32_Account({'xpub':xpub})
       -        self.storage.put('seed_version', self.seed_version)
       -        self.add_master_private_key(self.root_name, xprv, password)
       -        self.add_master_public_key(self.root_name, xpub)
       -        self.add_account('0', account)
       -        self.set_use_encryption(password is not None)
       -
       -    def create_xpub_wallet(self, xpub):
       -        account = BIP32_Account({'xpub':xpub})
       -        self.storage.put('seed_version', self.seed_version)
       -        self.add_master_public_key(self.root_name, xpub)
       -        self.add_account('0', account)
       -
       -class BIP32_RD_Wallet(BIP32_Wallet):
       -    # Abstract base class for a BIP32 wallet with a self.root_derivation
       -
       -    @classmethod
       -    def account_derivation(self, account_id):
       -        return self.root_derivation + account_id
       -
       -    @classmethod
       -    def address_derivation(self, account_id, change, address_index):
       -        account_derivation = self.account_derivation(account_id)
       -        return "%s/%d/%d" % (account_derivation, change, address_index)
       -
       -    def address_id(self, address):
       -        acc_id, (change, address_index) = self.get_address_index(address)
       -        return self.address_derivation(acc_id, change, address_index)
       -
       -    def add_xprv_from_seed(self, seed, name, password, passphrase=''):
       -        # we don't store the seed, only the master xpriv
       -        xprv, xpub = bip32_root(self.mnemonic_to_seed(seed, passphrase))
       -        xprv, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation)
       -        self.add_master_public_key(name, xpub)
       -        self.add_master_private_key(name, xprv, password)
       -
       -    def add_xpub_from_seed(self, seed, name):
       -        # store only master xpub
       -        xprv, xpub = bip32_root(self.mnemonic_to_seed(seed,''))
       -        xprv, xpub = bip32_private_derivation(xprv, "m/", self.root_derivation)
       -        self.add_master_public_key(name, xpub)
       -
       -    def create_master_keys(self, password):
       -        seed = self.get_seed(password)
       -        self.add_xprv_from_seed(seed, self.root_name, password)
       -
       -
       -class BIP32_HD_Wallet(BIP32_RD_Wallet):
       -    # Abstract base class for a BIP32 wallet that admits account creation
       +    def get_keystores(self):
       +        return [self.keystore]
        
       -    def __init__(self, storage):
       -        BIP32_Wallet.__init__(self, storage)
       -        # Backwards-compatibility.  Remove legacy "next_account2" and
       -        # drop unused master public key to avoid duplicate errors
       -        acc2 = storage.get('next_account2', None)
       -        if acc2:
       -            self.master_public_keys.pop(self.root_name + acc2[0] + "'", None)
       -            storage.put('next_account2', None)
       -            storage.put('master_public_keys', self.master_public_keys)
       -
       -    def next_account_number(self):
       -        assert (set(self.accounts.keys()) ==
       -                set(['%d' % n for n in range(len(self.accounts))]))
       -        return len(self.accounts)
       -
       -    def show_account(self, account_id):
       -        return self.account_is_used(account_id) or account_id in self.labels
       -
       -    def last_account_id(self):
       -        return '%d' % (self.next_account_number() - 1)
       -
       -    def accounts_to_show(self):
       -        # The last account is shown only if named or used
       -        result = list(self.accounts.keys())
       -        last_id = self.last_account_id()
       -        if not self.show_account(last_id):
       -            result.remove(last_id)
       -        return result
       -
       -    def can_create_accounts(self):
       -        return self.root_name in self.master_private_keys.keys()
       -
       -    def permit_account_naming(self):
       -        return (self.can_create_accounts() and
       -                not self.show_account(self.last_account_id()))
       -
       -    def create_hd_account(self, password):
       -        # First check the password is valid (this raises if it isn't).
       -        if self.can_change_password():
       -            self.check_password(password)
       -        assert self.next_account_number() == 0
       -        self.create_next_account(password, _('Main account'))
       -        self.create_next_account(password)
       -
       -    def create_next_account(self, password, label=None):
       -        account_id = '%d' % self.next_account_number()
       -        derivation = self.account_derivation(account_id)
       -        root_name = self.root_derivation.split('/')[0]  # NOT self.root_name!
       -        xpub, xprv = self.derive_xkeys(root_name, derivation, password)
       -        wallet_key = self.root_name + account_id + "'"
       -        self.add_master_public_key(wallet_key, xpub)
       -        if xprv:
       -            self.add_master_private_key(wallet_key, xprv, password)
       -        account = BIP32_Account({'xpub':xpub})
       -        self.add_account(account_id, account)
       -        if label:
       -            self.set_label(account_id, label)
       -        self.save_accounts()
       -
       -    def account_is_used(self, account_id):
       -        return self.accounts[account_id].is_used(self)
       -
       -    def accounts_all_used(self):
       -        return all(self.account_is_used(acc_id) for acc_id in self.accounts)
       -
       -
       -class BIP44_Wallet(BIP32_HD_Wallet):
       -    root_derivation = "m/44'/0'/"
       -    wallet_type = 'bip44'
       -
       -    @classmethod
       -    def account_derivation(self, account_id):
       -        return self.root_derivation + account_id + "'"
       -
       -    def can_sign_xpubkey(self, x_pubkey):
       -        xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey)
       -        return xpub in self.master_public_keys.values()
       -
       -    def can_create_accounts(self):
       -        return not self.is_watching_only()
       +    def is_watching_only(self):
       +        return self.keystore.is_watching_only()
        
       -    @staticmethod
       -    def normalize_passphrase(passphrase):
       -        return normalize('NFKD', unicode(passphrase or ''))
       +    def can_change_password(self):
       +        return self.keystore.can_change_password()
        
       -    @staticmethod
       -    def mnemonic_to_seed(mnemonic, passphrase):
       -        # See BIP39
       -        import pbkdf2, hashlib, hmac
       -        PBKDF2_ROUNDS = 2048
       -        mnemonic = normalize('NFKD', ' '.join(mnemonic.split()))
       -        passphrase = BIP44_Wallet.normalize_passphrase(passphrase)
       -        return pbkdf2.PBKDF2(mnemonic, 'mnemonic' + passphrase,
       -                             iterations = PBKDF2_ROUNDS, macmodule = hmac,
       -                             digestmodule = hashlib.sha512).read(64)
       -
       -    def derive_xkeys(self, root, derivation, password):
       -        root = self.root_name
       -        derivation = derivation.replace(self.root_derivation, root)
       -        x = self.master_private_keys.get(root)
       -        if x:
       -            root_xprv = pw_decode(x, password)
       -            xprv, xpub = bip32_private_derivation(root_xprv, root, derivation)
       -            return xpub, xprv
       -        else:
       -            root_xpub = self.master_public_keys.get(root)
       -            xpub = bip32_public_derivation(root_xpub, root, derivation)
       -            return xpub, None
       +    def has_password(self):
       +        return self.keystore.has_password()
        
       +    def check_password(self, password):
       +        self.keystore.check_password(password)
        
       -class NewWallet(BIP32_RD_Wallet, Mnemonic):
       -    # Standard wallet
       -    root_derivation = "m/"
       -    wallet_type = 'standard'
       +    def update_password(self, old_pw, new_pw):
       +        self.keystore.update_password(old_pw, new_pw)
       +        self.keystore.save(self.storage, self.root_name)
       +
       +    def can_import(self):
       +        return self.keystore.can_import()
        
       -    def create_main_account(self):
       -        xpub = self.master_public_keys.get("x/")
       -        account = BIP32_Account({'xpub':xpub})
       -        self.add_account('0', account)
       +    def import_key(self, pk, pw):
       +        pubkey = self.keystore.import_key(pk, pw)
       +        self.receiving_pubkeys.append(pubkey)
       +        self.save_pubkeys()
       +        addr = self.pubkeys_to_address(pubkey)
       +        self.receiving_addresses.append(addr)
       +        self.add_address(addr)
       +        return addr
        
        
       -class Multisig_Wallet(BIP32_RD_Wallet, Mnemonic):
       +class Multisig_Wallet(Deterministic_Wallet):
            # generic m of n
            root_name = "x1/"
       -    root_derivation = "m/"
       +    gap_limit = 20
        
            def __init__(self, storage):
       -        BIP32_Wallet.__init__(self, storage)
                self.wallet_type = storage.get('wallet_type')
                self.m, self.n = Wallet.multisig_type(self.wallet_type)
       +        Deterministic_Wallet.__init__(self, storage)
        
       -    def load_accounts(self):
       -        self.accounts = {}
       -        d = self.storage.get('accounts', {})
       -        v = d.get('0')
       -        if v:
       -            if v.get('xpub3'):
       -                v['xpubs'] = [v['xpub'], v['xpub2'], v['xpub3']]
       -            elif v.get('xpub2'):
       -                v['xpubs'] = [v['xpub'], v['xpub2']]
       -            self.accounts = {'0': Multisig_Account(v)}
       -
       -    def create_main_account(self):
       -        account = Multisig_Account({'xpubs': self.master_public_keys.values(), 'm': self.m})
       -        self.add_account('0', account)
       +    def get_pubkeys(self, c, i):
       +        pubkey_list = self.change_pubkeys if c else self.receiving_pubkeys
       +        return pubkey_list[i]
        
       -    def get_master_public_keys(self):
       -        return self.master_public_keys
       +    def redeem_script(self, c, i):
       +        pubkeys = self.get_pubkeys(c, i)
       +        return Transaction.multisig_script(sorted(pubkeys), self.m)
       +
       +    def pubkeys_to_address(self, pubkeys):
       +        redeem_script = Transaction.multisig_script(sorted(pubkeys), self.m)
       +        address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5)
       +        return address
        
       -    def get_missing_cosigner(self):
       +    def new_pubkeys(self, c, i):
       +        return [k.derive_pubkey(c, i) for k in self.keystores.values()]
       +
       +    def load_keystore(self):
       +        self.keystores = {}
                for i in range(self.n):
       -            if self.master_public_keys.get("x%d/"%(i+1)) is None:
       -                return i+1
       -
       -    def add_cosigner(self, name, text, password):
       -        if Wallet.is_xprv(text):
       -            xpub = bitcoin.xpub_from_xprv(text)
       -            self.add_master_public_key(name, xpub)
       -            self.add_master_private_key(name, text, password)
       -        elif Wallet.is_xpub(text):
       -            self.add_master_public_key(name, text)
       -        if Wallet.is_seed(text):
       -            if name == 'x1/':
       -                self.add_seed(text, password)
       -                self.create_master_keys(password)
       -            else:
       -                self.add_xprv_from_seed(text, name, password)
       +            name = 'x%d/'%(i+1)
       +            self.keystores[name] = load_keystore(self.storage, name)
       +        self.keystore = self.keystores[self.root_name]
        
       -    def get_action(self):
       -        i = self.get_missing_cosigner()
       -        if i is not None:
       -            return 'create_seed' if i == 1 else 'show_xpub_and_add_cosigners'
       -        if not self.accounts:
       -            return 'create_main_account'
       +    def get_keystore(self):
       +        return self.keystores.get(self.root_name)
        
       -    def get_fingerprint(self):
       -        return ''.join(sorted(self.get_master_public_keys().values()))
       +    def get_keystores(self):
       +        return self.keystores.values()
       +
       +    def update_password(self, old_pw, new_pw):
       +        for name, keystore in self.keystores.items():
       +            keystore.update_password(old_pw, new_pw)
       +            keystore.save(self.storage, name)
        
       +    def has_seed(self):
       +        return self.keystore.has_seed()
        
       -class OldWallet(Deterministic_Wallet):
       -    wallet_type = 'old'
       +    def can_change_password(self):
       +        return self.keystore.can_change_password()
        
       -    def __init__(self, storage):
       -        Deterministic_Wallet.__init__(self, storage)
       -        self.gap_limit = storage.get('gap_limit', 5)
       -
       -    def make_seed(self):
       -        import old_mnemonic
       -        seed = random_seed(128)
       -        return ' '.join(old_mnemonic.mn_encode(seed))
       -
       -    def format_seed(self, seed):
       -        import old_mnemonic
       -        # see if seed was entered as hex
       -        seed = seed.strip()
       -        if seed:
       -            try:
       -                seed.decode('hex')
       -                return OLD_SEED_VERSION, str(seed)
       -            except Exception:
       -                pass
       -        words = seed.split()
       -        seed = old_mnemonic.mn_decode(words)
       -        if not seed:
       -            raise Exception("Invalid seed")
       -        return OLD_SEED_VERSION, seed
       -
       -    def create_master_keys(self, password):
       -        seed = self.get_seed(password)
       -        mpk = OldAccount.mpk_from_seed(seed)
       -        self.storage.put('master_public_key', mpk)
       +    def has_password(self):
       +        return self.keystore.has_password()
       +
       +    def is_watching_only(self):
       +        return not any([not k.is_watching_only() for k in self.get_keystores()])
        
            def get_master_public_key(self):
       -        return self.storage.get("master_public_key")
       +        return self.keystore.get_master_public_key()
        
            def get_master_public_keys(self):
       -        return {'Main Account':self.get_master_public_key()}
       +        return dict(map(lambda x: (x[0], x[1].get_master_public_key()), self.keystores.items()))
        
       -    def create_main_account(self):
       -        mpk = self.storage.get("master_public_key")
       -        self.create_account(mpk)
       -
       -    def create_account(self, mpk):
       -        self.accounts['0'] = OldAccount({'mpk':mpk, 0:[], 1:[]})
       -        self.save_accounts()
       +    def get_fingerprint(self):
       +        return ''.join(sorted(self.get_master_public_keys()))
        
       -    def create_watching_only_wallet(self, mpk):
       -        self.seed_version = OLD_SEED_VERSION
       -        self.storage.put('seed_version', self.seed_version)
       -        self.storage.put('master_public_key', mpk)
       -        self.create_account(mpk)
       +    def add_input_sig_info(self, txin, address):
       +        txin['derivation'] = derivation = self.get_address_index(address)
       +        pubkeys = self.get_pubkeys(*derivation)
       +        x_pubkeys = self.get_xpubkeys(*derivation)
       +        # sort pubkeys and x_pubkeys, using the order of pubkeys
       +        pubkeys, x_pubkeys = zip( *sorted(zip(pubkeys, x_pubkeys)))
       +        txin['pubkeys'] = list(pubkeys)
       +        txin['x_pubkeys'] = list(x_pubkeys)
       +        txin['signatures'] = [None] * len(pubkeys)
       +        txin['redeemScript'] = self.redeem_script(*derivation)
       +        txin['num_sig'] = self.m
        
       -    def get_seed(self, password):
       -        seed = pw_decode(self.seed, password).encode('utf8')
       -        return seed
        
       -    def check_password(self, password):
       -        seed = self.get_seed(password)
       -        self.accounts['0'].check_seed(seed)
       -
       -    def get_mnemonic(self, password):
       -        import old_mnemonic
       -        s = self.get_seed(password)
       -        return ' '.join(old_mnemonic.mn_encode(s))
        
        
        WalletType = namedtuple("WalletType", "category type constructor")
        
       +
        # former WalletFactory
        class Wallet(object):
            """The main wallet "entry point".
       t@@ -2030,40 +1533,18 @@ class Wallet(object):
            type when passed a WalletStorage instance."""
        
            wallets = [   # category    type        constructor
       -        WalletType('standard', 'old',       OldWallet),
       -        WalletType('standard', 'xpub',      BIP32_Simple_Wallet),
       -        WalletType('standard', 'standard',  NewWallet),
       +        WalletType('standard', 'old',       Standard_Wallet),
       +        WalletType('standard', 'xpub',      Standard_Wallet),
       +        WalletType('standard', 'standard',  Standard_Wallet),
                WalletType('standard', 'imported',  Imported_Wallet),
                WalletType('multisig', '2of2',      Multisig_Wallet),
                WalletType('multisig', '2of3',      Multisig_Wallet),
       -        WalletType('bip44',    'bip44',     BIP44_Wallet),
            ]
        
            def __new__(self, storage):
       -        seed_version = storage.get('seed_version')
       -        if not seed_version:
       -            seed_version = OLD_SEED_VERSION if len(storage.get('master_public_key','')) == 128 else NEW_SEED_VERSION
       -
       -        if seed_version not in [OLD_SEED_VERSION, NEW_SEED_VERSION]:
       -            msg = "Your wallet has an unsupported seed version."
       -            msg += '\n\nWallet file: %s' % os.path.abspath(storage.path)
       -            if seed_version in [5, 7, 8, 9, 10]:
       -                msg += "\n\nTo open this wallet, try 'git checkout seed_v%d'"%seed_version
       -            if seed_version == 6:
       -                # version 1.9.8 created v6 wallets when an incorrect seed was entered in the restore dialog
       -                msg += '\n\nThis file was created because of a bug in version 1.9.8.'
       -                if storage.get('master_public_keys') is None and storage.get('master_private_keys') is None and storage.get('imported_keys') is None:
       -                    # pbkdf2 was not included with the binaries, and wallet creation aborted.
       -                    msg += "\nIt does not contain any keys, and can safely be removed."
       -                else:
       -                    # creation was complete if electrum was run from source
       -                    msg += "\nPlease open this file with Electrum 1.9.8, and move your coins to a new wallet."
       -            raise BaseException(msg)
       -
                wallet_type = storage.get('wallet_type')
       -        WalletClass = Wallet.wallet_class(wallet_type, seed_version)
       +        WalletClass = Wallet.wallet_class(wallet_type)
                wallet = WalletClass(storage)
       -
                # Convert hardware wallets restored with older versions of
                # Electrum to BIP44 wallets.  A hardware wallet does not have
                # a seed and plugins do not need to handle having one.
       t@@ -2072,7 +1553,6 @@ class Wallet(object):
                    storage.print_error("converting wallet type to " + rwc.wallet_type)
                    storage.put('wallet_type', rwc.wallet_type)
                    wallet = rwc(storage)
       -
                return wallet
        
            @staticmethod
       t@@ -2080,79 +1560,17 @@ class Wallet(object):
                return [wallet.category for wallet in Wallet.wallets]
        
            @staticmethod
       -    def register_plugin_wallet(category, type, constructor):
       +    def register_constructor(category, type, constructor):
                Wallet.wallets.append(WalletType(category, type, constructor))
        
            @staticmethod
       -    def wallet_class(wallet_type, seed_version):
       -        if wallet_type:
       -            if Wallet.multisig_type(wallet_type):
       -                return Multisig_Wallet
       -
       -            for wallet in Wallet.wallets:
       -                if wallet.type == wallet_type:
       -                    return wallet.constructor
       -
       -            raise RuntimeError("Unknown wallet type: " + wallet_type)
       -
       -        return OldWallet if seed_version == OLD_SEED_VERSION else NewWallet
       -
       -    @staticmethod
       -    def is_seed(seed):
       -        return is_old_seed(seed) or is_new_seed(seed)
       -
       -    @staticmethod
       -    def is_mpk(text):
       -        return Wallet.is_old_mpk(text) or Wallet.is_xpub(text)
       -
       -    @staticmethod
       -    def is_old_mpk(mpk):
       -        try:
       -            int(mpk, 16)
       -        except:
       -            return False
       -        return len(mpk) == 128
       -
       -    @staticmethod
       -    def is_xpub(text):
       -        if text[0:4] != 'xpub':
       -            return False
       -        try:
       -            deserialize_xkey(text)
       -            return True
       -        except:
       -            return False
       -
       -    @staticmethod
       -    def is_xprv(text):
       -        if text[0:4] != 'xprv':
       -            return False
       -        try:
       -            deserialize_xkey(text)
       -            return True
       -        except:
       -            return False
       -
       -    @staticmethod
       -    def is_address(text):
       -        parts = text.split()
       -        return bool(parts) and all(bitcoin.is_address(x) for x in parts)
       -
       -    @staticmethod
       -    def is_private_key(text):
       -        parts = text.split()
       -        return bool(parts) and all(bitcoin.is_private_key(x) for x in parts)
       -
       -    @staticmethod
       -    def is_any(text):
       -        return (Wallet.is_seed(text) or Wallet.is_old_mpk(text)
       -                or Wallet.is_xprv(text) or Wallet.is_xpub(text)
       -                or Wallet.is_address(text) or Wallet.is_private_key(text))
       -
       -    @staticmethod
       -    def should_encrypt(text):
       -        return (Wallet.is_seed(text) or Wallet.is_xprv(text)
       -                or Wallet.is_private_key(text))
       +    def wallet_class(wallet_type):
       +        if Wallet.multisig_type(wallet_type):
       +            return Multisig_Wallet
       +        for wallet in Wallet.wallets:
       +            if wallet.type == wallet_type:
       +                return wallet.constructor
       +        raise RuntimeError("Unknown wallet type: " + wallet_type)
        
            @staticmethod
            def multisig_type(wallet_type):
       t@@ -2163,66 +1581,3 @@ class Wallet(object):
                    match = [int(x) for x in match.group(1, 2)]
                return match
        
       -    @staticmethod
       -    def from_seed(seed, password, storage):
       -        if is_old_seed(seed):
       -            klass = OldWallet
       -        elif is_new_seed(seed):
       -            klass = NewWallet
       -        w = klass(storage)
       -        w.add_seed(seed, password)
       -        w.create_master_keys(password)
       -        return w
       -
       -    @staticmethod
       -    def from_address(text, storage):
       -        w = Imported_Wallet(storage)
       -        for x in text.split():
       -            w.accounts[IMPORTED_ACCOUNT].add(x, None, None, None)
       -        w.save_accounts()
       -        return w
       -
       -    @staticmethod
       -    def from_private_key(text, password, storage):
       -        w = Imported_Wallet(storage)
       -        w.update_password(None, password)
       -        for x in text.split():
       -            w.import_key(x, password)
       -        return w
       -
       -    @staticmethod
       -    def from_old_mpk(mpk, storage):
       -        w = OldWallet(storage)
       -        w.seed = ''
       -        w.create_watching_only_wallet(mpk)
       -        return w
       -
       -    @staticmethod
       -    def from_xpub(xpub, storage):
       -        w = BIP32_Simple_Wallet(storage)
       -        w.create_xpub_wallet(xpub)
       -        return w
       -
       -    @staticmethod
       -    def from_xprv(xprv, password, storage):
       -        w = BIP32_Simple_Wallet(storage)
       -        w.create_xprv_wallet(xprv, password)
       -        return w
       -
       -    @staticmethod
       -    def from_text(text, password, storage):
       -        if Wallet.is_xprv(text):
       -            wallet = Wallet.from_xprv(text, password, storage)
       -        elif Wallet.is_old_mpk(text):
       -            wallet = Wallet.from_old_mpk(text, storage)
       -        elif Wallet.is_xpub(text):
       -            wallet = Wallet.from_xpub(text, storage)
       -        elif Wallet.is_address(text):
       -            wallet = Wallet.from_address(text, storage)
       -        elif Wallet.is_private_key(text):
       -            wallet = Wallet.from_private_key(text, password, storage)
       -        elif Wallet.is_seed(text):
       -            wallet = Wallet.from_seed(text, password, storage)
       -        else:
       -            raise BaseException('Invalid seedphrase or key')
       -        return wallet
   DIR diff --git a/plugins/cosigner_pool/qt.py b/plugins/cosigner_pool/qt.py
       t@@ -126,7 +126,8 @@ class Plugin(BasePlugin):
                    self.listener = None
                self.keys = []
                self.cosigner_list = []
       -        for key, xpub in wallet.master_public_keys.items():
       +        for key, keystore in wallet.keystores.items():
       +            xpub = keystore.get_master_public_key()
                    K = bitcoin.deserialize_xkey(xpub)[-1].encode('hex')
                    _hash = bitcoin.Hash(K).encode('hex')
                    if wallet.master_private_keys.get(key):
   DIR diff --git a/plugins/hw_wallet/__init__.py b/plugins/hw_wallet/__init__.py
       t@@ -1,2 +1 @@
       -from hw_wallet import BIP44_HW_Wallet
        from plugin import HW_PluginBase
   DIR diff --git a/plugins/hw_wallet/hw_wallet.py b/plugins/hw_wallet/hw_wallet.py
       t@@ -1,95 +0,0 @@
       -#!/usr/bin/env python2
       -# -*- mode: python -*-
       -#
       -# Electrum - lightweight Bitcoin client
       -# Copyright (C) 2016  The Electrum developers
       -#
       -# 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 struct import pack
       -
       -from electrum.wallet import BIP44_Wallet
       -
       -class BIP44_HW_Wallet(BIP44_Wallet):
       -    '''A BIP44 hardware wallet base class.'''
       -    # Derived classes must set:
       -    #   - device
       -    #   - DEVICE_IDS
       -    #   - wallet_type
       -
       -    restore_wallet_class = BIP44_Wallet
       -    max_change_outputs = 1
       -
       -    def __init__(self, storage):
       -        BIP44_Wallet.__init__(self, storage)
       -        # Errors and other user interaction is done through the wallet's
       -        # handler.  The handler is per-window and preserved across
       -        # device reconnects
       -        self.handler = None
       -
       -    def unpaired(self):
       -        '''A device paired with the wallet was diconnected.  This can be
       -        called in any thread context.'''
       -        self.print_error("unpaired")
       -
       -    def paired(self):
       -        '''A device paired with the wallet was (re-)connected.  This can be
       -        called in any thread context.'''
       -        self.print_error("paired")
       -
       -    def get_action(self):
       -        pass
       -
       -    def can_create_accounts(self):
       -        return True
       -
       -    def can_export(self):
       -        return False
       -
       -    def is_watching_only(self):
       -        '''The wallet is not watching-only; the user will be prompted for
       -        pin and passphrase as appropriate when needed.'''
       -        assert not self.has_seed()
       -        return False
       -
       -    def can_change_password(self):
       -        return False
       -
       -    def get_client(self, force_pair=True):
       -        return self.plugin.get_client(self, force_pair)
       -
       -    def first_address(self):
       -        '''Used to check a hardware wallet matches a software wallet'''
       -        account = self.accounts.get('0')
       -        derivation = self.address_derivation('0', 0, 0)
       -        return (account.first_address()[0] if account else None, derivation)
       -
       -    def derive_xkeys(self, root, derivation, password):
       -        if self.master_public_keys.get(self.root_name):
       -            return BIP44_wallet.derive_xkeys(self, root, derivation, password)
       -
       -        # When creating a wallet we need to ask the device for the
       -        # master public key
       -        xpub = self.get_public_key(derivation)
       -        return xpub, None
       -
       -    def i4b(self, x):
       -        return pack('>I', x)
   DIR diff --git a/plugins/hw_wallet/plugin.py b/plugins/hw_wallet/plugin.py
       t@@ -37,8 +37,8 @@ class HW_PluginBase(BasePlugin):
        
            def __init__(self, parent, config, name):
                BasePlugin.__init__(self, parent, config, name)
       -        self.device = self.wallet_class.device
       -        self.wallet_class.plugin = self
       +        self.device = self.keystore_class.device
       +        self.keystore_class.plugin = self
        
            def is_enabled(self):
                return self.libraries_available
       t@@ -48,33 +48,6 @@ class HW_PluginBase(BasePlugin):
        
            @hook
            def close_wallet(self, wallet):
       -        if isinstance(wallet, self.wallet_class):
       +        if isinstance(wallet.get_keystore(), self.keystore_class):
                    self.device_manager().unpair_wallet(wallet)
        
       -    def on_restore_wallet(self, wallet, wizard):
       -        assert isinstance(wallet, self.wallet_class)
       -        msg = _("Enter the seed for your %s wallet:" % self.device)
       -        f = lambda x: wizard.run('on_restore_seed', x)
       -        wizard.enter_seed_dialog(run_next=f, title=_('Restore hardware wallet'), message=msg, is_valid=self.is_valid_seed)
       -
       -    def on_restore_seed(self, wallet, wizard, seed):
       -        f = lambda x: wizard.run('on_restore_passphrase', seed, x)
       -        wizard.request_passphrase(self.device, run_next=f)
       -
       -    def on_restore_passphrase(self, wallet, wizard, seed, passphrase):
       -        f = lambda x: wizard.run('on_restore_password', seed, passphrase, x)
       -        wizard.request_password(run_next=f)
       -
       -    def on_restore_password(self, wallet, wizard, seed, passphrase, password):
       -        # Restored wallets are not hardware wallets
       -        wallet_class = self.wallet_class.restore_wallet_class
       -        wallet.storage.put('wallet_type', wallet_class.wallet_type)
       -        wallet = wallet_class(wallet.storage)
       -        wallet.add_seed(seed, password)
       -        wallet.add_xprv_from_seed(seed, 'x/', password, passphrase)
       -        wallet.create_hd_account(password)
       -        wizard.create_addresses()
       -
       -    @staticmethod
       -    def is_valid_seed(seed):
       -        return True
   DIR diff --git a/plugins/keepkey/__init__.py b/plugins/keepkey/__init__.py
       t@@ -3,6 +3,6 @@ from electrum.i18n import _
        fullname = 'KeepKey'
        description = _('Provides support for KeepKey hardware wallet')
        requires = [('keepkeylib','github.com/keepkey/python-keepkey')]
       -requires_wallet_type = ['keepkey']
       -registers_wallet_type = ('hardware', 'keepkey', _("KeepKey wallet"))
       +#requires_wallet_type = ['keepkey']
       +registers_keystore = ('hardware', 'keepkey', _("KeepKey wallet"))
        available_for = ['qt', 'cmdline']
   DIR diff --git a/plugins/keepkey/keepkey.py b/plugins/keepkey/keepkey.py
       t@@ -1,7 +1,7 @@
       -from ..trezor.plugin import TrezorCompatiblePlugin, TrezorCompatibleWallet
       +from ..trezor.plugin import TrezorCompatiblePlugin, TrezorCompatibleKeyStore
        
        
       -class KeepKeyWallet(TrezorCompatibleWallet):
       +class KeepKey_KeyStore(TrezorCompatibleKeyStore):
            wallet_type = 'keepkey'
            device = 'KeepKey'
        
       t@@ -10,7 +10,7 @@ class KeepKeyPlugin(TrezorCompatiblePlugin):
            firmware_URL = 'https://www.keepkey.com'
            libraries_URL = 'https://github.com/keepkey/python-keepkey'
            minimum_firmware = (1, 0, 0)
       -    wallet_class = KeepKeyWallet
       +    keystore_class = KeepKey_KeyStore
            try:
                from .client import KeepKeyClient as client_class
                import keepkeylib.ckd_public as ckd_public
   DIR diff --git a/plugins/ledger/__init__.py b/plugins/ledger/__init__.py
       t@@ -3,6 +3,6 @@ from electrum.i18n import _
        fullname = 'Ledger Wallet'
        description = 'Provides support for Ledger hardware wallet'
        requires = [('btchip', 'github.com/ledgerhq/btchip-python')]
       -requires_wallet_type = ['btchip']
       -registers_wallet_type = ('hardware', 'btchip', _("Ledger wallet"))
       +#requires_wallet_type = ['btchip']
       +registers_keystore = ('hardware', 'btchip', _("Ledger wallet"))
        available_for = ['qt', 'cmdline']
   DIR diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py
       t@@ -7,7 +7,7 @@ import electrum
        from electrum.bitcoin import EncodeBase58Check, DecodeBase58Check, TYPE_ADDRESS
        from electrum.i18n import _
        from electrum.plugins import BasePlugin, hook
       -from ..hw_wallet import BIP44_HW_Wallet
       +from ..hw_wallet import BIP32_HW_Wallet
        from ..hw_wallet import HW_PluginBase
        from electrum.util import format_satoshis_plain, print_error
        
       t@@ -26,12 +26,12 @@ except ImportError:
            BTCHIP = False
        
        
       -class BTChipWallet(BIP44_HW_Wallet):
       +class BTChipWallet(BIP32_HW_Wallet):
            wallet_type = 'btchip'
            device = 'Ledger'
        
            def __init__(self, storage):
       -        BIP44_HW_Wallet.__init__(self, storage)
       +        BIP32_HW_Wallet.__init__(self, storage)
                # Errors and other user interaction is done through the wallet's
                # handler.  The handler is per-window and preserved across
                # device reconnects
       t@@ -53,7 +53,7 @@ class BTChipWallet(BIP44_HW_Wallet):
        
            def address_id(self, address):
                # Strip the leading "m/"
       -        return BIP44_HW_Wallet.address_id(self, address)[2:]
       +        return BIP32_HW_Wallet.address_id(self, address)[2:]
        
            def get_public_key(self, bip32_path):
                # bip32_path is of the form 44'/0'/1'
   DIR diff --git a/plugins/trezor/__init__.py b/plugins/trezor/__init__.py
       t@@ -3,7 +3,7 @@ from electrum.i18n import _
        fullname = 'TREZOR Wallet'
        description = _('Provides support for TREZOR hardware wallet')
        requires = [('trezorlib','github.com/trezor/python-trezor')]
       -requires_wallet_type = ['trezor']
       -registers_wallet_type = ('hardware', 'trezor', _("TREZOR wallet"))
       +#requires_wallet_type = ['trezor']
       +registers_keystore = ('hardware', 'trezor', _("TREZOR wallet"))
        available_for = ['qt', 'cmdline']
        
   DIR diff --git a/plugins/trezor/clientbase.py b/plugins/trezor/clientbase.py
       t@@ -1,8 +1,10 @@
        import time
       +from struct import pack
        
        from electrum.i18n import _
        from electrum.util import PrintError, UserCancelled
       -from electrum.wallet import BIP44_Wallet
       +from electrum.keystore import BIP44_KeyStore
       +from electrum.bitcoin import EncodeBase58Check
        
        
        class GuiMixin(object):
       t@@ -63,7 +65,7 @@ class GuiMixin(object):
                passphrase = self.handler.get_passphrase(msg, self.creating_wallet)
                if passphrase is None:
                    return self.proto.Cancel()
       -        passphrase = BIP44_Wallet.normalize_passphrase(passphrase)
       +        passphrase = BIP44_KeyStore.normalize_passphrase(passphrase)
                return self.proto.PassphraseAck(passphrase=passphrase)
        
            def callback_WordRequest(self, msg):
       t@@ -142,11 +144,20 @@ class TrezorClientBase(GuiMixin, PrintError):
                '''Provided here as in keepkeylib but not trezorlib.'''
                self.transport.write(self.proto.Cancel())
        
       -    def first_address(self, derivation):
       -        return self.address_from_derivation(derivation)
       +    def i4b(self, x):
       +        return pack('>I', x)
        
       -    def address_from_derivation(self, derivation):
       -        return self.get_address('Bitcoin', self.expand_path(derivation))
       +    def get_xpub(self, bip32_path):
       +        address_n = self.expand_path(bip32_path)
       +        creating = False #self.next_account_number() == 0
       +        node = self.get_public_node(address_n, creating).node
       +        xpub = ("0488B21E".decode('hex') + chr(node.depth)
       +                + self.i4b(node.fingerprint) + self.i4b(node.child_num)
       +                + node.chain_code + node.public_key)
       +        return EncodeBase58Check(xpub)
       +
       +    #def address_from_derivation(self, derivation):
       +    #    return self.get_address('Bitcoin', self.expand_path(derivation))
        
            def toggle_passphrase(self):
                if self.features.passphrase_protection:
   DIR diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py
       t@@ -8,28 +8,32 @@ from functools import partial
        from electrum.account import BIP32_Account
        from electrum.bitcoin import (bc_address_to_hash_160, xpub_from_pubkey,
                                      public_key_to_bc_address, EncodeBase58Check,
       -                              TYPE_ADDRESS)
       +                              TYPE_ADDRESS, TYPE_SCRIPT)
        from electrum.i18n import _
        from electrum.plugins import BasePlugin, hook
        from electrum.transaction import (deserialize, is_extended_pubkey,
                                          Transaction, x_to_xpub)
       -from ..hw_wallet import BIP44_HW_Wallet, HW_PluginBase
       +from electrum.keystore import Hardware_KeyStore
       +
       +from ..hw_wallet import HW_PluginBase
        
        
        # TREZOR initialization methods
        TIM_NEW, TIM_RECOVER, TIM_MNEMONIC, TIM_PRIVKEY = range(0, 4)
        
       -class TrezorCompatibleWallet(BIP44_HW_Wallet):
       +class TrezorCompatibleKeyStore(Hardware_KeyStore):
       +    root = "m/44'/0'"
       +    account_id = 0
       +
       +    def get_derivation(self):
       +        return self.root + "/%d'"%self.account_id
        
       -    def get_public_key(self, bip32_path):
       +    def get_client(self, force_pair=True):
       +        return self.plugin.get_client(self, force_pair)
       +
       +    def init_xpub(self):
                client = self.get_client()
       -        address_n = client.expand_path(bip32_path)
       -        creating = self.next_account_number() == 0
       -        node = client.get_public_node(address_n, creating).node
       -        xpub = ("0488B21E".decode('hex') + chr(node.depth)
       -                + self.i4b(node.fingerprint) + self.i4b(node.child_num)
       -                + node.chain_code + node.public_key)
       -        return EncodeBase58Check(xpub)
       +        self.xpub = client.get_xpub(self.get_derivation())
        
            def decrypt_message(self, pubkey, message, password):
                raise RuntimeError(_('Electrum and %s encryption and decryption are currently incompatible') % self.device)
       t@@ -49,17 +53,6 @@ class TrezorCompatibleWallet(BIP44_HW_Wallet):
                msg_sig = client.sign_message('Bitcoin', address_n, message)
                return msg_sig.signature
        
       -    def get_input_tx(self, tx_hash):
       -        # First look up an input transaction in the wallet where it
       -        # will likely be.  If co-signing a transaction it may not have
       -        # all the input txs, in which case we ask the network.
       -        tx = self.transactions.get(tx_hash)
       -        if not tx:
       -            request = ('blockchain.transaction.get', [tx_hash])
       -            # FIXME: what if offline?
       -            tx = Transaction(self.network.synchronous_get(request))
       -        return tx
       -
            def sign_transaction(self, tx, password):
                if tx.is_complete():
                    return
       t@@ -69,15 +62,13 @@ class TrezorCompatibleWallet(BIP44_HW_Wallet):
                xpub_path = {}
                for txin in tx.inputs():
                    tx_hash = txin['prevout_hash']
       -            prev_tx[tx_hash] = self.get_input_tx(tx_hash)
       +            prev_tx[tx_hash] = txin['prev_tx'] 
                    for x_pubkey in txin['x_pubkeys']:
                        if not is_extended_pubkey(x_pubkey):
                            continue
                        xpub = x_to_xpub(x_pubkey)
       -                for k, v in self.master_public_keys.items():
       -                    if v == xpub:
       -                        acc_id = re.match("x/(\d+)'", k).group(1)
       -                        xpub_path[xpub] = self.account_derivation(acc_id)
       +                if xpub == self.get_master_public_key():
       +                    xpub_path[xpub] = self.get_derivation()
        
                self.plugin.sign_transaction(self, tx, prev_tx, xpub_path)
        
       t@@ -149,18 +140,16 @@ class TrezorCompatiblePlugin(HW_PluginBase):
        
                return client
        
       -    def get_client(self, wallet, force_pair=True):
       +    def get_client(self, keystore, force_pair=True):
                # All client interaction should not be in the main GUI thread
                assert self.main_thread != threading.current_thread()
       -
                devmgr = self.device_manager()
       -        client = devmgr.client_for_wallet(self, wallet, force_pair)
       +        client = devmgr.client_for_keystore(self, keystore, force_pair)
                if client:
                    client.used()
       -
                return client
        
       -    def initialize_device(self, wallet):
       +    def initialize_device(self, keystore):
                # Initialization method
                msg = _("Choose how you want to initialize your %s.\n\n"
                        "The first two methods are secure as no secret information "
       t@@ -179,13 +168,13 @@ class TrezorCompatiblePlugin(HW_PluginBase):
                    _("Upload a master private key")
                ]
        
       -        method = wallet.handler.query_choice(msg, methods)
       +        method = keystore.handler.query_choice(msg, methods)
                (item, label, pin_protection, passphrase_protection) \
                    = wallet.handler.request_trezor_init_settings(method, self.device)
        
                if method == TIM_RECOVER and self.device == 'TREZOR':
                    # Warn user about firmware lameness
       -            wallet.handler.show_error(_(
       +            keystore.handler.show_error(_(
                        "You will be asked to enter 24 words regardless of your "
                        "seed's actual length.  If you enter a word incorrectly or "
                        "misspell it, you cannot change it or go back - you will need "
       t@@ -195,7 +184,7 @@ class TrezorCompatiblePlugin(HW_PluginBase):
                language = 'english'
        
                def initialize_method():
       -            client = self.get_client(wallet)
       +            client = self.get_client(keystore)
        
                    if method == TIM_NEW:
                        strength = 64 * (item + 2)  # 128, 192 or 256
       t@@ -216,35 +205,36 @@ class TrezorCompatiblePlugin(HW_PluginBase):
                        client.load_device_by_xprv(item, pin, passphrase_protection,
                                                   label, language)
                    # After successful initialization create accounts
       -            wallet.create_hd_account(None)
       +            keystore.init_xpub()
       +            #wallet.create_main_account()
        
                return initialize_method
        
       -    def setup_device(self, wallet, on_done, on_error):
       +    def setup_device(self, keystore, on_done, on_error):
                '''Called when creating a new wallet.  Select the device to use.  If
                the device is uninitialized, go through the intialization
                process.  Then create the wallet accounts.'''
                devmgr = self.device_manager()
       -        device_info = devmgr.select_device(wallet, self)
       -        devmgr.pair_wallet(wallet, device_info.device.id_)
       +        device_info = devmgr.select_device(keystore, self)
       +        devmgr.pair_wallet(keystore, device_info.device.id_)
                if device_info.initialized:
       -            task = partial(wallet.create_hd_account, None)
       +            task = keystore.init_xpub
                else:
       -            task = self.initialize_device(wallet)
       -        wallet.thread.add(task, on_done=on_done, on_error=on_error)
       +            task = self.initialize_device(keystore)
       +        keystore.thread.add(task, on_done=on_done, on_error=on_error)
        
       -    def sign_transaction(self, wallet, tx, prev_tx, xpub_path):
       +    def sign_transaction(self, keystore, tx, prev_tx, xpub_path):
                self.prev_tx = prev_tx
                self.xpub_path = xpub_path
       -        client = self.get_client(wallet)
       +        client = self.get_client(keystore)
                inputs = self.tx_inputs(tx, True)
       -        outputs = self.tx_outputs(wallet, tx)
       +        outputs = self.tx_outputs(keystore.get_derivation(), tx)
                signed_tx = client.sign_tx('Bitcoin', inputs, outputs)[1]
                raw = signed_tx.encode('hex')
                tx.update_signatures(raw)
        
            def show_address(self, wallet, address):
       -        client = self.get_client(wallet)
       +        client = self.get_client(wallet.keystore)
                if not client.atleast_version(1, 3):
                    wallet.handler.show_error(_("Your device firmware is too old"))
                    return
       t@@ -313,23 +303,29 @@ class TrezorCompatiblePlugin(HW_PluginBase):
        
                return inputs
        
       -    def tx_outputs(self, wallet, tx):
       +    def tx_outputs(self, derivation, tx):
                outputs = []
       -        for type, address, amount in tx.outputs():
       -            assert type == TYPE_ADDRESS
       +        for i, (_type, address, amount) in enumerate(tx.outputs()):
                    txoutputtype = self.types.TxOutputType()
       -            if wallet.is_change(address):
       -                address_path = wallet.address_id(address)
       -                address_n = self.client_class.expand_path(address_path)
       -                txoutputtype.address_n.extend(address_n)
       -            else:
       -                txoutputtype.address = address
                    txoutputtype.amount = amount
       -            addrtype, hash_160 = bc_address_to_hash_160(address)
       -            if addrtype == 0:
       -                txoutputtype.script_type = self.types.PAYTOADDRESS
       -            elif addrtype == 5:
       -                txoutputtype.script_type = self.types.PAYTOSCRIPTHASH
       +            change, index = tx.output_info[i]
       +            if _type == TYPE_SCRIPT:
       +                txoutputtype.script_type = self.types.PAYTOOPRETURN
       +                txoutputtype.op_return_data = address[2:]
       +            elif _type == TYPE_ADDRESS:
       +                if change is not None:
       +                    address_path = "%s/%d/%d/"%(derivation, change, index)
       +                    address_n = self.client_class.expand_path(address_path)
       +                    txoutputtype.address_n.extend(address_n)
       +                else:
       +                    txoutputtype.address = address
       +                addrtype, hash_160 = bc_address_to_hash_160(address)
       +                if addrtype == 0:
       +                    txoutputtype.script_type = self.types.PAYTOADDRESS
       +                elif addrtype == 5:
       +                    txoutputtype.script_type = self.types.PAYTOSCRIPTHASH
       +                else:
       +                    raise BaseException('addrtype')
                    else:
                        raise BaseException('addrtype')
                    outputs.append(txoutputtype)
   DIR diff --git a/plugins/trezor/qt_generic.py b/plugins/trezor/qt_generic.py
       t@@ -12,7 +12,7 @@ from ..hw_wallet.qt import QtHandlerBase
        from electrum.i18n import _
        from electrum.plugins import hook, DeviceMgr
        from electrum.util import PrintError, UserCancelled
       -from electrum.wallet import Wallet, BIP44_Wallet
       +from electrum.wallet import Wallet
        
        PASSPHRASE_HELP_SHORT =_(
            "Passphrases allow you to access new wallets, each "
       t@@ -273,23 +273,25 @@ def qt_plugin_class(base_plugin_class):
        
            @hook
            def load_wallet(self, wallet, window):
       -        if type(wallet) != self.wallet_class:
       +        keystore = wallet.get_keystore()
       +        if type(keystore) != self.keystore_class:
                    return
                window.tzb = StatusBarButton(QIcon(self.icon_file), self.device,
                                             partial(self.settings_dialog, window))
                window.statusBar().addPermanentWidget(window.tzb)
       -        wallet.handler = self.create_handler(window)
       +        keystore.handler = self.create_handler(window)
       +        keystore.thread = TaskThread(window, window.on_error)
                # Trigger a pairing
       -        wallet.thread.add(partial(self.get_client, wallet))
       +        keystore.thread.add(partial(self.get_client, keystore))
        
       -    def on_create_wallet(self, wallet, wizard):
       -        assert type(wallet) == self.wallet_class
       -        wallet.handler = self.create_handler(wizard)
       -        wallet.thread = TaskThread(wizard, wizard.on_error)
       +    def on_create_wallet(self, keystore, wizard):
       +        #assert type(keystore) == self.keystore_class
       +        keystore.handler = self.create_handler(wizard)
       +        keystore.thread = TaskThread(wizard, wizard.on_error)
                # Setup device and create accounts in separate thread; wait until done
                loop = QEventLoop()
                exc_info = []
       -        self.setup_device(wallet, on_done=loop.quit,
       +        self.setup_device(keystore, on_done=loop.quit,
                                  on_error=lambda info: exc_info.extend(info))
                loop.exec_()
                # If an exception was thrown, show to user and exit install wizard
       t@@ -299,9 +301,10 @@ def qt_plugin_class(base_plugin_class):
        
            @hook
            def receive_menu(self, menu, addrs, wallet):
       -        if type(wallet) == self.wallet_class and len(addrs) == 1:
       +        keystore = wallet.get_keystore()
       +        if type(keystore) == self.keystore_class and len(addrs) == 1:
                    def show_address():
       -                wallet.thread.add(partial(self.show_address, wallet, addrs[0]))
       +                keystore.thread.add(partial(self.show_address, wallet, addrs[0]))
                    menu.addAction(_("Show on %s") % self.device, show_address)
        
            def settings_dialog(self, window):
       t@@ -312,9 +315,10 @@ def qt_plugin_class(base_plugin_class):
            def choose_device(self, window):
                '''This dialog box should be usable even if the user has
                forgotten their PIN or it is in bootloader mode.'''
       -        device_id = self.device_manager().wallet_id(window.wallet)
       +        keystore = window.wallet.get_keystore()
       +        device_id = self.device_manager().wallet_id(keystore)
                if not device_id:
       -            info = self.device_manager().select_device(window.wallet, self)
       +            info = self.device_manager().select_device(keystore, self)
                    device_id = info.device.id_
                return device_id
        
       t@@ -345,8 +349,9 @@ class SettingsDialog(WindowModalDialog):
        
                devmgr = plugin.device_manager()
                config = devmgr.config
       -        handler = window.wallet.handler
       -        thread = window.wallet.thread
       +        keystore = window.wallet.get_keystore()
       +        handler = keystore.handler
       +        thread = keystore.thread
                # wallet can be None, needn't be window.wallet
                wallet = devmgr.wallet_by_id(device_id)
                hs_rows, hs_cols = (64, 128)
   DIR diff --git a/plugins/trezor/trezor.py b/plugins/trezor/trezor.py
       t@@ -1,16 +1,15 @@
       -from .plugin import TrezorCompatiblePlugin, TrezorCompatibleWallet
       +from .plugin import TrezorCompatiblePlugin, TrezorCompatibleKeyStore
        
        
       -class TrezorWallet(TrezorCompatibleWallet):
       +class TrezorKeyStore(TrezorCompatibleKeyStore):
            wallet_type = 'trezor'
            device = 'TREZOR'
        
       -
        class TrezorPlugin(TrezorCompatiblePlugin):
            firmware_URL = 'https://www.mytrezor.com'
            libraries_URL = 'https://github.com/trezor/python-trezor'
            minimum_firmware = (1, 3, 3)
       -    wallet_class = TrezorWallet
       +    keystore_class = TrezorKeyStore
            try:
                from .client import TrezorClient as client_class
                import trezorlib.ckd_public as ckd_public
   DIR diff --git a/plugins/trustedcoin/trustedcoin.py b/plugins/trustedcoin/trustedcoin.py
       t@@ -34,10 +34,11 @@ from urllib import quote
        
        import electrum
        from electrum import bitcoin
       +from electrum import keystore
        from electrum.bitcoin import *
        from electrum.mnemonic import Mnemonic
        from electrum import version
       -from electrum.wallet import Multisig_Wallet, BIP32_Wallet
       +from electrum.wallet import Multisig_Wallet, Deterministic_Wallet, Wallet
        from electrum.i18n import _
        from electrum.plugins import BasePlugin, run_hook, hook
        from electrum.util import NotEnoughFunds
       t@@ -187,29 +188,16 @@ server = TrustedCoinCosignerClient(user_agent="Electrum/" + version.ELECTRUM_VER
        class Wallet_2fa(Multisig_Wallet):
        
            def __init__(self, storage):
       -        BIP32_Wallet.__init__(self, storage)
       -        self.wallet_type = '2fa'
       -        self.m = 2
       -        self.n = 3
       +        self.m, self.n = 2, 3
       +        Deterministic_Wallet.__init__(self, storage)
                self.is_billing = False
                self.billing_info = None
        
       -    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'
       -
       -    def make_seed(self):
       -        return Mnemonic('english').make_seed(num_bits=256, prefix=SEED_PREFIX)
       -
            def can_sign_without_server(self):
       -        return self.master_private_keys.get('x2/') is not None
       +        return not self.keystores.get('x2/').is_watching_only()
       +
       +    def get_user_id(self):
       +        return get_user_id(self.storage)
        
            def get_max_amount(self, config, inputs, recipient, fee):
                from electrum.transaction import Transaction
       t@@ -244,7 +232,7 @@ class Wallet_2fa(Multisig_Wallet):
        
            def make_unsigned_transaction(self, coins, outputs, config,
                                          fixed_fee=None, change_addr=None):
       -        mk_tx = lambda o: BIP32_Wallet.make_unsigned_transaction(
       +        mk_tx = lambda o: Multisig_Wallet.make_unsigned_transaction(
                    self, coins, o, config, fixed_fee, change_addr)
                fee = self.extra_fee()
                if fee:
       t@@ -264,7 +252,7 @@ class Wallet_2fa(Multisig_Wallet):
                return tx
        
            def sign_transaction(self, tx, password):
       -        BIP32_Wallet.sign_transaction(self, tx, password)
       +        Multisig_Wallet.sign_transaction(self, tx, password)
                if tx.is_complete():
                    return
                if not self.auth_code:
       t@@ -279,27 +267,25 @@ class Wallet_2fa(Multisig_Wallet):
                    tx.update(raw_tx)
                self.print_error("twofactor: is complete", tx.is_complete())
        
       -    def get_user_id(self):
       -        def make_long_id(xpub_hot, xpub_cold):
       -            return bitcoin.sha256(''.join(sorted([xpub_hot, xpub_cold])))
       -        xpub_hot = self.master_public_keys["x1/"]
       -        xpub_cold = self.master_public_keys["x2/"]
       -        long_id = make_long_id(xpub_hot, xpub_cold)
       -        short_id = hashlib.sha256(long_id).hexdigest()
       -        return long_id, short_id
        
        # Utility functions
        
       +def get_user_id(storage):
       +    def make_long_id(xpub_hot, xpub_cold):
       +        return bitcoin.sha256(''.join(sorted([xpub_hot, xpub_cold])))
       +    mpk = storage.get('master_public_keys')
       +    xpub1 = mpk["x1/"]
       +    xpub2 = mpk["x2/"]
       +    long_id = make_long_id(xpub1, xpub2)
       +    short_id = hashlib.sha256(long_id).hexdigest()
       +    return long_id, short_id
       +
        def make_xpub(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 restore_third_key(wallet):
       -    long_user_id, short_id = wallet.get_user_id()
       -    xpub3 = make_xpub(signing_xpub, long_user_id)
       -    wallet.add_master_public_key('x3/', xpub3)
        
        def make_billing_address(wallet, num):
            long_id, short_id = wallet.get_user_id()
       t@@ -324,9 +310,6 @@ class TrustedCoinPlugin(BasePlugin):
            def is_available(self):
                return True
        
       -    def set_enabled(self, wallet, enabled):
       -        wallet.storage.put('use_' + self.name, enabled)
       -
            def is_enabled(self):
                return True
        
       t@@ -345,28 +328,42 @@ class TrustedCoinPlugin(BasePlugin):
                wallet.price_per_tx = dict(billing_info['price_per_tx'])
                return True
        
       -    def create_extended_seed(self, wallet, wizard):
       -        self.wallet = wallet
       -        self.wizard = wizard
       -        seed = wallet.make_seed()
       -        self.wizard.show_seed_dialog(run_next=wizard.confirm_seed, seed_text=seed)
       +    def make_seed(self):
       +        return Mnemonic('english').make_seed(num_bits=256, prefix=SEED_PREFIX)
       +
       +    @hook
       +    def do_clear(self, window):
       +        window.wallet.is_billing = False
        
       -    def show_disclaimer(self, wallet, wizard):
       -        self.set_enabled(wallet, True)
       +    def show_disclaimer(self, wizard):
                wizard.set_icon(':icons/trustedcoin.png')
                wizard.stack = []
       -        wizard.confirm_dialog('\n\n'.join(DISCLAIMER), run_next = lambda x: wizard.run('create_extended_seed'))
       +        wizard.confirm_dialog('\n\n'.join(DISCLAIMER), run_next = lambda x: wizard.run('choose_seed'))
       +
       +    def choose_seed(self, wizard):
       +        title = _('Create or restore')
       +        message = _('Do you want to create a new seed, or to restore a wallet using an existing seed?')
       +        choices = [
       +            ('create_seed', _('Create a new seed')),
       +            ('restore_wallet', _('I already have a seed')),
       +        ]
       +        wizard.choice_dialog(title=title, message=message, choices=choices, run_next=wizard.run)
       +
       +    def create_seed(self, wizard):
       +        seed = self.make_seed()
       +        wizard.show_seed_dialog(run_next=wizard.confirm_seed, seed_text=seed)
        
       -    def create_wallet(self, wallet, wizard, seed, password):
       -        wallet.storage.put('seed_version', wallet.seed_version)
       -        wallet.storage.put('use_encryption', password is not None)
       +    def create_keystore(self, wizard, seed, password):
       +        # this overloads the wizard's method
                words = seed.split()
                n = len(words)/2
       -        wallet.add_xprv_from_seed(' '.join(words[0:n]), 'x1/', password)
       -        wallet.add_xpub_from_seed(' '.join(words[n:]), 'x2/')
       -        wallet.storage.write()
       +        keystore1 = keystore.xprv_from_seed(' '.join(words[0:n]), password)
       +        keystore2 = keystore.xpub_from_seed(' '.join(words[n:]))
       +        keystore1.save(wizard.storage, 'x1/')
       +        keystore2.save(wizard.storage, 'x2/')
       +        wizard.storage.write()
                msg = [
       -            _("Your wallet file is: %s.")%os.path.abspath(wallet.storage.path),
       +            _("Your wallet file is: %s.")%os.path.abspath(wizard.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 '
       t@@ -378,41 +375,45 @@ class TrustedCoinPlugin(BasePlugin):
                wizard.stack = []
                wizard.confirm_dialog(msg, run_next = lambda x: wizard.run('create_remote_key'))
        
       -    @hook
       -    def do_clear(self, window):
       -        window.wallet.is_billing = False
       -
       -    def on_restore_wallet(self, wallet, wizard):
       -        assert isinstance(wallet, self.wallet_class)
       +    def restore_wallet(self, wizard):
                title = _("Restore two-factor Wallet")
                f = lambda x: wizard.run('on_restore_seed', x)
       -        wizard.enter_seed_dialog(run_next=f, title=title, message=RESTORE_MSG, is_valid=self.is_valid_seed)
       +        wizard.restore_seed_dialog(run_next=f, is_valid=self.is_valid_seed)
        
       -    def on_restore_seed(self, wallet, wizard, seed):
       -        f = lambda x: wizard.run('on_restore_pw', seed, x)
       +    def on_restore_seed(self, wizard, seed):
       +        f = lambda pw: wizard.run('on_restore_pw', seed, pw)
                wizard.request_password(run_next=f)
        
       -    def on_restore_pw(self, wallet, wizard, seed, password):
       -        wallet.add_seed(seed, password)
       +    def on_restore_pw(self, wizard, seed, password):
       +        # FIXME
       +        # wallet.add_seed(seed, password)
       +        storage = wizard.storage
                words = seed.split()
                n = len(words)/2
       -        wallet.add_xprv_from_seed(' '.join(words[0:n]), 'x1/', password)
       -        wallet.add_xprv_from_seed(' '.join(words[n:]), 'x2/', password)
       -        restore_third_key(wallet)
       +        keystore1 = keystore.xprv_from_seed(' '.join(words[0:n]), password)
       +        keystore2 = keystore.xprv_from_seed(' '.join(words[n:]), password)
       +        keystore1.save(storage, 'x1/')
       +        keystore2.save(storage, 'x2/')
       +        long_user_id, short_id = get_user_id(storage)
       +        xpub3 = make_xpub(signing_xpub, long_user_id)
       +        keystore3 = keystore.from_xpub(xpub3)
       +        keystore3.save(storage, 'x3/')
       +        wizard.wallet = Wallet(storage)
                wizard.create_addresses()
        
       -    def create_remote_key(self, wallet, window):
       -        email = self.accept_terms_of_use(window)
       -        xpub_hot = wallet.master_public_keys["x1/"]
       -        xpub_cold = wallet.master_public_keys["x2/"]
       +    def create_remote_key(self, wizard):
       +        email = self.accept_terms_of_use(wizard)
       +        mpk = wizard.storage.get('master_public_keys')
       +        xpub1 = mpk["x1/"]
       +        xpub2 = mpk["x2/"]
                # Generate third key deterministically.
       -        long_user_id, short_id = wallet.get_user_id()
       +        long_user_id, short_id = get_user_id(wizard.storage)
                xpub3 = make_xpub(signing_xpub, long_user_id)
                # secret must be sent by the server
                try:
       -            r = server.create(xpub_hot, xpub_cold, email)
       +            r = server.create(xpub1, xpub2, email)
                except socket.error:
       -            window.show_message('Server not reachable, aborting')
       +            wizard.show_message('Server not reachable, aborting')
                    return
                except TrustedCoinException as e:
                    if e.status_code == 409:
       t@@ -424,7 +425,7 @@ class TrustedCoinPlugin(BasePlugin):
                else:
                    otp_secret = r.get('otp_secret')
                    if not otp_secret:
       -                window.show_message(_('Error'))
       +                wizard.show_message(_('Error'))
                        return
                    _xpub3 = r['xpubkey_cosigner']
                    _id = r['id']
       t@@ -432,10 +433,24 @@ class TrustedCoinPlugin(BasePlugin):
                        assert _id == short_id, ("user id error", _id, short_id)
                        assert xpub3 == _xpub3, ("xpub3 error", xpub3, _xpub3)
                    except Exception as e:
       -                window.show_message(str(e))
       +                wizard.show_message(str(e))
                        return
       -        if not self.setup_google_auth(window, short_id, otp_secret):
       -            window.show_message("otp error")
       +        if not self.setup_google_auth(wizard, short_id, otp_secret):
       +            wizard.show_message("otp error")
                    return
       -        wallet.add_master_public_key('x3/', xpub3)
       -        window.run('create_addresses')
       +        keystore3 = keystore.from_xpub(xpub3)
       +        keystore3.save(wizard.storage, 'x3/')
       +        wizard.storage.put('use_trustedcoin', True)
       +        wizard.storage.write()
       +        wizard.wallet = Wallet(wizard.storage)
       +        wizard.run('create_addresses')
       +
       +    @hook
       +    def get_action(self, storage):
       +        mpk = storage.get('master_public_keys', {})
       +        if not mpk.get('x1/'):
       +            return self, 'show_disclaimer'
       +        if not mpk.get('x2/'):
       +            return self, 'show_disclaimer'
       +        if not mpk.get('x3/'):
       +            return self, 'create_remote_key'