URI: 
       twallet recovery: use static methods - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 7f305730270d63d64c2cefb87942aeb42cf60388
   DIR parent 0524e5ddd1e072beb276c6baaec45dff487f4c19
  HTML Author: ThomasV <thomasv@gitorious>
       Date:   Tue, 14 Feb 2012 09:52:03 +0100
       
       wallet recovery: use static methods
       
       Diffstat:
         M client/electrum                     |      15 +++++++++++----
         M client/gui.py                       |      73 ++++++++++---------------------
         M client/gui_qt.py                    |     200 +++++++++++++++++++++++--------
         M client/wallet.py                    |      40 +++++++++++++++++++++++++++++++
       
       4 files changed, 227 insertions(+), 101 deletions(-)
       ---
   DIR diff --git a/client/electrum b/client/electrum
       t@@ -60,16 +60,23 @@ if __name__ == '__main__':
                    import gui
        
                interface.get_servers()
       +        gui = gui.ElectrumGui(wallet)
       +
                try:
                    found = wallet.read()
                    if not found:
       -                gui.restore_create_dialog(wallet)
       +                found = gui.restore_or_create()
                except BaseException, e:
       -            gui.show_message(e.message)
       +            import traceback
       +            traceback.print_exc(file=sys.stdout)
       +            #gui.show_message(e.message)
                    exit(1)
       -            
       -        gui = gui.BitcoinGUI(wallet)
       +
       +        if not found: exit(1)
       +
                interface.start(wallet)
       +        gui.main()
       +        sys.exit(0)
        
                if re.match('^bitcoin:', cmd):
        
   DIR diff --git a/client/gui.py b/client/gui.py
       t@@ -88,8 +88,8 @@ def restore_create_dialog(wallet):
            dialog.show()
            r = dialog.run()
            dialog.destroy()
       -    if r==2:
       -        sys.exit(1)
       +
       +    if r==2: return False
                
            is_recovery = (r==1)
        
       t@@ -108,7 +108,6 @@ def restore_create_dialog(wallet):
                    
                #ask for password
                change_password_dialog(wallet, None, None)
       -
            else:
                # ask for the server.
                run_network_dialog( wallet, parent=None )
       t@@ -132,7 +131,6 @@ def restore_create_dialog(wallet):
                        wallet.update_tx_history()
                        wallet.fill_addressbook()
                        print "recovery successful"
       -                wallet.save()
        
                    gobject.idle_add( dialog.destroy )
        
       t@@ -143,6 +141,9 @@ def restore_create_dialog(wallet):
                if not wallet.is_found:
                    show_message("No transactions found for this seed")
        
       +    wallet.save()
       +    return True
       +
        
        def run_recovery_dialog(wallet):
            message = "Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet."
       t@@ -215,7 +216,7 @@ def run_recovery_dialog(wallet):
        
        def run_settings_dialog(wallet, parent):
        
       -    message = "These are the settings of your wallet. For more explanations, click on the question mark buttons next to each input field."
       +    message = "Here are the settings of your wallet. For more explanations, click on the question mark buttons next to each input field."
                
            dialog = gtk.MessageDialog(
                parent = parent,
       t@@ -461,7 +462,7 @@ gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.W, gtk.gdk.CONTROL_MASK, 'myk
        gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.Q, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+Q')
        
        
       -class BitcoinGUI:
       +class ElectrumWindow:
        
            def show_message(self, msg):
                show_message(msg, self.window)
       t@@ -567,7 +568,7 @@ class BitcoinGUI:
                            r = r.strip()
                            if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
                                try:
       -                            to_address = self.get_alias(r, interactive=False)
       +                            to_address = self.wallet.get_alias(r, interactive=False)
                                except:
                                    continue
                                if to_address:
       t@@ -699,7 +700,7 @@ class BitcoinGUI:
        
                if signature:
                    if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
       -                signing_address = self.get_alias(identity, interactive = True)
       +                signing_address = self.wallet.get_alias(identity, True, self.show_message, self.question)
                    elif self.wallet.is_valid(identity):
                        signing_address = identity
                    else:
       t@@ -717,7 +718,7 @@ class BitcoinGUI:
                #if label and payto:
                #    self.labels[payto] = label
                if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', payto):
       -            payto_address = self.get_alias(payto, interactive=True)
       +            payto_address = self.wallet.get_alias(payto, True, self.show_message, self.question)
                    if payto_address:
                        payto = payto + ' <' + payto_address + '>'
        
       t@@ -757,46 +758,6 @@ class BitcoinGUI:
                dialog.destroy()
                return result == gtk.RESPONSE_OK
        
       -    def get_alias(self, alias, interactive = False):
       -        try:
       -            target, signing_address, auth_name = self.wallet.read_alias(alias)
       -        except BaseException, e:
       -            # raise exception if verify fails (verify the chain)
       -            if interactive:
       -                self.show_message("Alias error: " + e.message)
       -            return
       -
       -        print target, signing_address, auth_name
       -
       -        if auth_name is None:
       -            a = self.wallet.aliases.get(alias)
       -            if not a:
       -                msg = "Warning: the alias '%s' is self-signed.\nThe signing address is %s.\n\nDo you want to add this alias to your list of contacts?"%(alias,signing_address)
       -                if interactive and self.question( msg ):
       -                    self.wallet.aliases[alias] = (signing_address, target)
       -                else:
       -                    target = None
       -            else:
       -                if signing_address != a[0]:
       -                    msg = "Warning: the key of alias '%s' has changed since your last visit! It is possible that someone is trying to do something nasty!!!\nDo you accept to change your trusted key?"%alias
       -                    if interactive and self.question( msg ):
       -                        self.wallet.aliases[alias] = (signing_address, target)
       -                    else:
       -                        target = None
       -        else:
       -            if signing_address not in self.wallet.authorities.keys():
       -                msg = "The alias: '%s' links to %s\n\nWarning: this alias was signed by an unknown key.\nSigning authority: %s\nSigning address: %s\n\nDo you want to add this key to your list of trusted keys?"%(alias,target,auth_name,signing_address)
       -                if interactive and self.question( msg ):
       -                    self.wallet.authorities[signing_address] = auth_name
       -                else:
       -                    target = None
       -
       -        if target:
       -            self.wallet.aliases[alias] = (signing_address, target)
       -            self.update_sending_tab()
       -
       -            
       -        return target
                    
        
        
       t@@ -810,9 +771,12 @@ class BitcoinGUI:
                m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
                
                if m1:
       -            to_address = self.get_alias(r, interactive = True)
       +            to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
                    if not to_address:
                        return
       +            else:
       +                self.update_sending_tab()
       +
                elif m2:
                    to_address = m2.group(5)
                else:
       t@@ -1258,6 +1222,15 @@ class BitcoinGUI:
                        errorDialog.destroy()
        
            
       +
       +class ElectrumGui():
       +
       +    def __init__(self, wallet):
       +        self.wallet = wallet
       +
            def main(self):
       +        ElectrumWindow(self.wallet)
                gtk.main()
        
       +    def restore_or_create(self):
       +        return restore_create_dialog(self.wallet)
   DIR diff --git a/client/gui_qt.py b/client/gui_qt.py
       t@@ -10,8 +10,10 @@ import PyQt4.QtGui as QtGui
        from wallet import format_satoshis
        from decimal import Decimal
        
       -def restore_create_dialog(wallet):
       -    pass
       +
       +
       +
       +
        
        class Sender(QtCore.QThread):
            def run(self):
       t@@ -33,6 +35,18 @@ class StatusBarButton(QPushButton):
                    apply(self.func,())
        
        
       +def ok_cancel_buttons(dialog):
       +    hbox = QHBoxLayout()
       +    hbox.addStretch(1)
       +    b = QPushButton("OK")
       +    hbox.addWidget(b)
       +    b.clicked.connect(dialog.accept)
       +    b = QPushButton("Cancel")
       +    hbox.addWidget(b)
       +    b.clicked.connect(dialog.reject)
       +    return hbox
       +
       +
        class ElectrumWindow(QMainWindow):
        
            def __init__(self, wallet):
       t@@ -58,16 +72,7 @@ class ElectrumWindow(QMainWindow):
                QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
                QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
        
       -    def ok_cancel_buttons(self,d):
       -        hbox = QHBoxLayout()
       -        hbox.addStretch(1)
       -        b = QPushButton("OK")
       -        hbox.addWidget(b)
       -        b.clicked.connect(d.accept)
       -        b = QPushButton("Cancel")
       -        hbox.addWidget(b)
       -        b.clicked.connect(d.reject)
       -        return hbox
       +
        
            def connect_slots(self, sender):
                self.connect(sender, QtCore.SIGNAL('testsignal'), self.update_wallet)
       t@@ -272,7 +277,7 @@ class ElectrumWindow(QMainWindow):
                m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
                
                if m1:
       -            to_address = self.get_alias(r, interactive = True)
       +            to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
                    if not to_address:
                        return
                elif m2:
       t@@ -439,10 +444,10 @@ class ElectrumWindow(QMainWindow):
            def create_status_bar(self):
                sb = QStatusBar()
                sb.setFixedHeight(35)
       -        sb.addPermanentWidget( StatusBarButton( QIcon("icons/lock.svg"), "Password", self.change_password_dialog ) )
       +        sb.addPermanentWidget( StatusBarButton( QIcon("icons/lock.svg"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) )
                sb.addPermanentWidget( StatusBarButton( QIcon("icons/preferences.png"), "Preferences", self.settings_dialog ) )
       -        sb.addPermanentWidget( StatusBarButton( QIcon("icons/seed.png"), "Seed", self.show_seed_dialog ) )
       -        self.status_button = StatusBarButton( QIcon("icons/status_disconnected.png"), "Network", self.network_dialog ) 
       +        sb.addPermanentWidget( StatusBarButton( QIcon("icons/seed.png"), "Seed", lambda: self.show_seed_dialog(self.wallet, self) ) )
       +        self.status_button = StatusBarButton( QIcon("icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) ) 
                sb.addPermanentWidget( self.status_button )
                self.setStatusBar(sb)
        
       t@@ -458,18 +463,19 @@ class ElectrumWindow(QMainWindow):
                    else:
                        QMessageBox.warning(self, 'Error', 'Invalid Address', 'OK')
        
       -    def show_seed_dialog(self):
       +    @staticmethod
       +    def show_seed_dialog(wallet, parent=None):
                import mnemonic
       -        if self.wallet.use_encryption:
       +        if wallet.use_encryption:
                    password = self.password_dialog()
                    if not password: return
                else:
                    password = None
                    
                try:
       -            seed = self.wallet.pw_decode( self.wallet.seed, password)
       +            seed = wallet.pw_decode( wallet.seed, password)
                except:
       -            QMessageBox.warning(self, 'Error', 'Invalid Password', 'OK')
       +            QMessageBox.warning(parent, 'Error', 'Invalid Password', 'OK')
                    return
        
                msg = "Your wallet generation seed is:\n\n" + seed \
       t@@ -477,11 +483,17 @@ class ElectrumWindow(QMainWindow):
                      + "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:\n\n\"" \
                      + ' '.join(mnemonic.mn_encode(seed)) + "\""
        
       -        QMessageBox.information(self, 'Seed', msg, 'OK')
       +        QMessageBox.information(parent, 'Seed', msg, 'OK')
        
       +    def question(self, msg):
       +        return QMessageBox.question(self, 'Message', msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        
       -    def password_dialog(self):
       -        d = QDialog(self)
       +    def show_message(self, msg):
       +        QMessageBox.information(self, 'Message', msg, 'OK')
       +
       +    @staticmethod
       +    def password_dialog( parent=None ):
       +        d = QDialog(parent)
                d.setModal(1)
        
                pw = QLineEdit()
       t@@ -497,14 +509,15 @@ class ElectrumWindow(QMainWindow):
                grid.addWidget(pw, 1, 1)
                vbox.addLayout(grid)
        
       -        vbox.addLayout(self.ok_cancel_buttons(d))
       +        vbox.addLayout(ok_cancel_buttons(d))
                d.setLayout(vbox) 
        
                if not d.exec_(): return
                return str(pw.text())
        
       -    def change_password_dialog(self):
       -        d = QDialog(self)
       +    @staticmethod
       +    def change_password_dialog( wallet, parent=None ):
       +        d = QDialog(parent)
                d.setModal(1)
        
                pw = QLineEdit()
       t@@ -515,13 +528,13 @@ class ElectrumWindow(QMainWindow):
                conf_pw.setEchoMode(2)
        
                vbox = QVBoxLayout()
       -        msg = 'Your wallet is encrypted. Use this dialog to change your password.\nTo disable wallet encryption, enter an empty new password.' if self.wallet.use_encryption else 'Your wallet keys are not encrypted'
       +        msg = 'Your wallet is encrypted. Use this dialog to change your password.\nTo disable wallet encryption, enter an empty new password.' if wallet.use_encryption else 'Your wallet keys are not encrypted'
                vbox.addWidget(QLabel(msg))
        
                grid = QGridLayout()
                grid.setSpacing(8)
        
       -        if self.wallet.use_encryption:
       +        if wallet.use_encryption:
                    grid.addWidget(QLabel('Password'), 1, 0)
                    grid.addWidget(pw, 1, 1)
        
       t@@ -532,26 +545,76 @@ class ElectrumWindow(QMainWindow):
                grid.addWidget(conf_pw, 3, 1)
                vbox.addLayout(grid)
        
       -        vbox.addLayout(self.ok_cancel_buttons(d))
       +        vbox.addLayout(ok_cancel_buttons(d))
                d.setLayout(vbox) 
        
                if not d.exec_(): return
        
       -        password = str(pw.text()) if self.wallet.use_encryption else None
       +        password = str(pw.text()) if wallet.use_encryption else None
                new_password = str(new_pw.text())
                new_password2 = str(conf_pw.text())
        
                try:
       -            seed = self.wallet.pw_decode( self.wallet.seed, password)
       +            seed = wallet.pw_decode( wallet.seed, password)
                except:
       -            QMessageBox.warning(self, 'Error', 'Incorrect Password', 'OK')
       +            QMessageBox.warning(parent, 'Error', 'Incorrect Password', 'OK')
                    return
        
                if new_password != new_password2:
       -            QMessageBox.warning(self, 'Error', 'Passwords do not match', 'OK')
       +            QMessageBox.warning(parent, 'Error', 'Passwords do not match', 'OK')
                    return
        
       -        self.wallet.update_password(seed, new_password)
       +        wallet.update_password(seed, new_password)
       +
       +    @staticmethod
       +    def seed_dialog(wallet, parent=None):
       +        d = QDialog(parent)
       +        d.setModal(1)
       +
       +        vbox = QVBoxLayout()
       +        msg = "Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet."
       +        vbox.addWidget(QLabel(msg))
       +
       +        grid = QGridLayout()
       +        grid.setSpacing(8)
       +
       +        seed_e = QLineEdit()
       +        grid.addWidget(QLabel('Seed or mnemonic'), 1, 0)
       +        grid.addWidget(seed_e, 1, 1)
       +
       +        gap_e = QLineEdit()
       +        gap_e.setText("5")
       +        grid.addWidget(QLabel('Gap limit'), 2, 0)
       +        grid.addWidget(gap_e, 2, 1)
       +
       +        vbox.addLayout(grid)
       +
       +        vbox.addLayout(ok_cancel_buttons(d))
       +        d.setLayout(vbox) 
       +
       +        if not d.exec_(): return
       +
       +        try:
       +            gap = int(str(gap_e.text()))
       +        except:
       +            show_message("error")
       +            sys.exit(1)
       +
       +        try:
       +            seed = str(seed_e.text())
       +            seed.decode('hex')
       +        except:
       +            import mnemonic
       +            print "not hex, trying decode"
       +            seed = mnemonic.mn_decode( seed.split(' ') )
       +        if not seed:
       +            show_message("no seed")
       +            sys.exit(1)
       +        
       +        wallet.seed = seed
       +        wallet.gap_limit = gap
       +        return True
       +
        
            def settings_dialog(self):
                d = QDialog(self)
       t@@ -559,7 +622,7 @@ class ElectrumWindow(QMainWindow):
        
                vbox = QVBoxLayout()
        
       -        msg = 'These are the settings of your wallet'
       +        msg = 'Here are the settings of your wallet'
                vbox.addWidget(QLabel(msg))
        
                grid = QGridLayout()
       t@@ -571,7 +634,7 @@ class ElectrumWindow(QMainWindow):
                grid.addWidget(fee_line, 2, 1)
                vbox.addLayout(grid)
        
       -        vbox.addLayout(self.ok_cancel_buttons(d))
       +        vbox.addLayout(ok_cancel_buttons(d))
                d.setLayout(vbox) 
        
                if not d.exec_(): return
       t@@ -586,8 +649,9 @@ class ElectrumWindow(QMainWindow):
                self.wallet.fee = fee
                self.wallet.save()
        
       -    def network_dialog(self):
       -        wallet = self.wallet
       +    @staticmethod 
       +    def network_dialog(wallet, parent=None):
       +
                if True:
                    if wallet.interface.is_connected:
                        status = "Connected to %s.\n%d blocks\nresponse time: %f"%(wallet.interface.host, wallet.interface.blocks, wallet.interface.rtime)
       t@@ -601,7 +665,7 @@ class ElectrumWindow(QMainWindow):
                    host = random.choice( wallet.interface.servers )
                    port = 50000
        
       -        d = QDialog(self)
       +        d = QDialog(parent)
                d.setModal(1)
        
                vbox = QVBoxLayout()
       t@@ -615,7 +679,7 @@ class ElectrumWindow(QMainWindow):
                grid.addWidget(host_line, 2, 1)
                vbox.addLayout(grid)
        
       -        vbox.addLayout(self.ok_cancel_buttons(d))
       +        vbox.addLayout(ok_cancel_buttons(d))
                d.setLayout(vbox) 
        
                if not d.exec_(): return
       t@@ -636,23 +700,65 @@ class ElectrumWindow(QMainWindow):
                        return
        
                wallet.interface.set_server(host, port) 
       +        return True
        
       -        if parent:
       -            wallet.save()
        
        
        
       -
       -class BitcoinGUI():
       +class ElectrumGui():
        
            def __init__(self, wallet):
                self.wallet = wallet
       +        self.app = QApplication(sys.argv)
       +
       +    def restore_or_create(self):
       +
       +        msg = "Wallet file not found.\nDo you want to create a new wallet,\n or to restore an existing one?"
       +        r = QMessageBox.question(None, 'Message', msg, 'create', 'restore', 'cancel', 0, 2)
       +        if r==2: return False
       +        
       +        is_recovery = (r==1)
       +        wallet = self.wallet
       +        if not is_recovery:
       +            wallet.new_seed(None)
       +            # ask for the server.
       +            ElectrumWindow.network_dialog(wallet)
       +            # generate first key
       +            wallet.synchronize()
       +            # run a dialog indicating the seed, ask the user to remember it
       +            ElectrumWindow.show_seed_dialog(wallet)
       +            #ask for password
       +            ElectrumWindow.change_password_dialog(wallet)
       +        else:
       +            # ask for the server.
       +            r = ElectrumWindow.network_dialog( wallet, parent=None )
       +            if not r: return False
       +            # ask for seed and gap.
       +            r = ElectrumWindow.seed_dialog( wallet )
       +            if not r: return False
       +
       +            wallet.init_mpk( wallet.seed ) # not encrypted at this point
       +            wallet.synchronize()
       +
       +            if wallet.is_found():
       +                # history and addressbook
       +                wallet.update_tx_history()
       +                wallet.fill_addressbook()
       +                print "recovery successful"
       +                wallet.save()
       +            else:
       +                QMessageBox.information(None, 'Message', "No transactions found for this seed", 'OK')
       +
       +        wallet.save()
       +        return True
       +
       +
        
            def main(self):
       +
                s = Sender()
                s.start()
       -        app = QApplication(sys.argv)
                w = ElectrumWindow(self.wallet)
       -        w.app = app
       +        w.app = self.app
                w.connect_slots(s)
       -        app.exec_()
       +        self.app.exec_()
   DIR diff --git a/client/wallet.py b/client/wallet.py
       t@@ -769,3 +769,43 @@ class Wallet:
                    c = self.pw_encode(b, new_password)
                    self.imported_keys[k] = c
                self.save()
       +
       +
       +    def get_alias(self, alias, interactive = False, show_message=None, question = None):
       +        try:
       +            target, signing_address, auth_name = self.read_alias(alias)
       +        except BaseException, e:
       +            # raise exception if verify fails (verify the chain)
       +            if interactive:
       +                show_message("Alias error: " + e.message)
       +            return
       +
       +        print target, signing_address, auth_name
       +
       +        if auth_name is None:
       +            a = self.aliases.get(alias)
       +            if not a:
       +                msg = "Warning: the alias '%s' is self-signed.\nThe signing address is %s.\n\nDo you want to add this alias to your list of contacts?"%(alias,signing_address)
       +                if interactive and question( msg ):
       +                    self.aliases[alias] = (signing_address, target)
       +                else:
       +                    target = None
       +            else:
       +                if signing_address != a[0]:
       +                    msg = "Warning: the key of alias '%s' has changed since your last visit! It is possible that someone is trying to do something nasty!!!\nDo you accept to change your trusted key?"%alias
       +                    if interactive and question( msg ):
       +                        self.aliases[alias] = (signing_address, target)
       +                    else:
       +                        target = None
       +        else:
       +            if signing_address not in self.authorities.keys():
       +                msg = "The alias: '%s' links to %s\n\nWarning: this alias was signed by an unknown key.\nSigning authority: %s\nSigning address: %s\n\nDo you want to add this key to your list of trusted keys?"%(alias,target,auth_name,signing_address)
       +                if interactive and question( msg ):
       +                    self.authorities[signing_address] = auth_name
       +                else:
       +                    target = None
       +
       +        if target:
       +            self.aliases[alias] = (signing_address, target)
       +            
       +        return target