electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 7f305730270d63d64c2cefb87942aeb42cf60388
parent 0524e5ddd1e072beb276c6baaec45dff487f4c19
Author: ThomasV <thomasv@gitorious>
Date:   Tue, 14 Feb 2012 09:52:03 +0100

wallet recovery: use static methods

Diffstat:
Mclient/electrum | 15+++++++++++----
Mclient/gui.py | 73+++++++++++++++++++++++--------------------------------------------------
Mclient/gui_qt.py | 200++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mclient/wallet.py | 40++++++++++++++++++++++++++++++++++++++++
4 files changed, 227 insertions(+), 101 deletions(-)

diff --git a/client/electrum b/client/electrum @@ -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): diff --git a/client/gui.py b/client/gui.py @@ -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) @@ -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 ) @@ -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 ) @@ -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." @@ -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, @@ -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) @@ -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: @@ -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: @@ -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 + '>' @@ -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 @@ -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: @@ -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) diff --git a/client/gui_qt.py b/client/gui_qt.py @@ -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): @@ -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): @@ -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) @@ -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: @@ -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) @@ -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 \ @@ -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() @@ -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() @@ -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) @@ -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) @@ -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() @@ -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 @@ -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) @@ -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() @@ -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 @@ -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_() diff --git a/client/wallet.py b/client/wallet.py @@ -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