electrum

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

commit 796c9927024d96cda2d28c17cfa9828d2d71160b
parent ca31a9e37297242b1f5b7aa0c8b47a88be4f585c
Author: thomasv <thomasv@gitorious>
Date:   Mon, 14 May 2012 13:35:47 +0200

Merge branch 'master' of gitorious.org:electrum/electrum

Diffstat:
Melectrum | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mlib/gui.py | 53+++++++++++++++++++++++++++++++++--------------------
Mlib/gui_qt.py | 114++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mlib/interface.py | 4++--
Mlib/version.py | 2+-
Mlib/wallet.py | 5+++--
6 files changed, 175 insertions(+), 78 deletions(-)

diff --git a/electrum b/electrum @@ -24,8 +24,8 @@ from decimal import Decimal from electrum import Wallet, SecretToASecret, WalletSynchronizer, format_satoshis -known_commands = ['help', 'validateaddress', 'balance', 'contacts', 'create', 'restore', 'payto', 'sendtx', 'password', 'addresses', 'history', 'label', 'mktx','seed','import','signmessage','verifymessage','eval'] -offline_commands = ['password', 'mktx', 'history', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed'] +known_commands = ['help', 'validateaddress', 'balance', 'contacts', 'create', 'restore', 'payto', 'sendtx', 'password', 'addresses', 'history', 'label', 'mktx','seed','import','signmessage','verifymessage','eval','deseed','reseed'] +offline_commands = ['password', 'mktx', 'history', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval', 'create', 'addresses', 'import', 'seed','deseed','reseed'] protected_commands = ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ] if __name__ == '__main__': @@ -34,6 +34,7 @@ if __name__ == '__main__': parser = OptionParser(usage=usage) parser.add_option("-g", "--gui", dest="gui", default="qt", help="gui") parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)") + parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline") parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses") parser.add_option("-b", "--balance", action="store_true", dest="show_balance", default=False, help="show the balance at listed addresses") parser.add_option("-k", "--keys",action="store_true", dest="show_keys",default=False, help="show the private keys of listed addresses") @@ -135,18 +136,20 @@ if __name__ == '__main__': sys.exit(1) wallet.seed = str(seed) - WalletSynchronizer(wallet).start() - print "recovering wallet..." wallet.init_mpk( wallet.seed ) - wallet.up_to_date_event.clear() - wallet.up_to_date = False - wallet.update() - if wallet.is_found(): - wallet.fill_addressbook() - wallet.save() - print "recovery successful" - else: - print "found no history for this wallet" + if not options.offline: + WalletSynchronizer(wallet).start() + print "recovering wallet..." + wallet.up_to_date_event.clear() + wallet.up_to_date = False + wallet.update() + if wallet.is_found(): + print "recovery successful" + else: + print "found no history for this wallet" + wallet.fill_addressbook() + wallet.save() + print "Wallet saved in '%s'"%options.wallet_path else: wallet.new_seed(None) wallet.init_mpk( wallet.seed ) @@ -156,6 +159,7 @@ if __name__ == '__main__': print "Please keep it in a safe place; if you lose it, you will not be able to restore your wallet." print "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:" print "\""+' '.join(mnemonic.mn_encode(wallet.seed))+"\"" + print "Wallet saved in '%s'"%options.wallet_path # check syntax if cmd in ['payto', 'mktx']: @@ -171,7 +175,7 @@ if __name__ == '__main__': cmd = 'help' # open session - if cmd not in offline_commands: + if cmd not in offline_commands and not options.offline: WalletSynchronizer(wallet).start() wallet.update() wallet.save() @@ -237,6 +241,8 @@ if __name__ == '__main__': print "options: --fee, --fromaddr, --changeaddr" elif cmd2 == 'seed': print "show generation seed of your wallet. password protected." + elif cmd2 == 'deseed': + print "remove the seed of your wallet." elif cmd2 == 'eval': print "Run python eval() on an object\nSyntax: eval <expression>\nExample: eval \"wallet.aliases\"" @@ -245,6 +251,47 @@ if __name__ == '__main__': seed = wallet.pw_decode( wallet.seed, password) print seed, '"'+' '.join(mnemonic.mn_encode(seed))+'"' + elif cmd == 'deseed': + if not wallet.seed: + print "Eooro: This wallet has no seed" + elif wallet.use_encryption: + print "Error: This wallet is encrypted" + else: + ns = options.wallet_path+'.seed' + print "Warning: you are going to extract the seed from '%s'\nThe seed will be saved in '%s'"%(options.wallet_path,ns) + if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']: + f = open(ns,'w') + f.write(wallet.seed) + f.close() + wallet.seed = '' + wallet.save() + print "Done." + else: + print "Action canceled." + + elif cmd == 'reseed': + if wallet.seed: + print "This wallet already has a seed" + else: + ns = options.wallet_path+'.seed' + try: + f = open(ns,'r') + seed = f.read() + f.close() + except: + print "seed file not found" + sys.exit() + + mpk = wallet.master_public_key + wallet.seed = seed + wallet.use_encryption = False + wallet.init_mpk(seed) + if mpk == wallet.master_public_key: + wallet.save() + print "done" + else: + print "error: master public key does not match" + elif cmd == 'validateaddress': addr = args[1] print wallet.is_valid(addr) diff --git a/lib/gui.py b/lib/gui.py @@ -60,6 +60,9 @@ def numbify(entry, is_int = False): def show_seed_dialog(wallet, password, parent): from electrum import mnemonic + if not wallet.seed: + show_message("No seed") + return try: seed = wallet.pw_decode( wallet.seed, password) except: @@ -483,6 +486,10 @@ def password_dialog(parent): if result != gtk.RESPONSE_CANCEL: return pw def change_password_dialog(wallet, parent, icon): + if not wallet.seed: + show_message("No seed") + return + if parent: msg = 'Your wallet is encrypted. Use this dialog to change the password. To disable wallet encryption, enter an empty new password.' if wallet.use_encryption else 'Your wallet keys are not encrypted' else: @@ -556,7 +563,9 @@ class ElectrumWindow: self.funds_error = False # True if not enough funds self.window = MyWindow(gtk.WINDOW_TOPLEVEL) - self.window.set_title(APP_NAME + " " + self.wallet.electrum_version) + title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.path + if not self.wallet.seed: title += ' [seedless]' + self.window.set_title(title) self.window.connect("destroy", gtk.main_quit) self.window.set_border_width(0) self.window.connect('mykeypress', gtk.main_quit) @@ -566,7 +575,8 @@ class ElectrumWindow: self.notebook = gtk.Notebook() self.create_history_tab() - self.create_send_tab() + if self.wallet.seed: + self.create_send_tab() self.create_recv_tab() self.create_book_tab() self.create_about_tab() @@ -588,17 +598,18 @@ class ElectrumWindow: self.network_button.show() self.status_bar.pack_end(self.network_button, False, False) - def seedb(w, wallet): - if wallet.use_encryption: - password = password_dialog(self.window) - if not password: return - else: password = None - show_seed_dialog(wallet, password, self.window) - button = gtk.Button('S') - button.connect("clicked", seedb, wallet ) - button.set_relief(gtk.RELIEF_NONE) - button.show() - self.status_bar.pack_end(button,False, False) + if self.wallet.seed: + def seedb(w, wallet): + if wallet.use_encryption: + password = password_dialog(self.window) + if not password: return + else: password = None + show_seed_dialog(wallet, password, self.window) + button = gtk.Button('S') + button.connect("clicked", seedb, wallet ) + button.set_relief(gtk.RELIEF_NONE) + button.show() + self.status_bar.pack_end(button,False, False) settings_icon = gtk.Image() settings_icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) @@ -620,12 +631,13 @@ class ElectrumWindow: pw_icon.set_size_request(16,16 ) pw_icon.show() - password_button = gtk.Button() - password_button.connect("clicked", lambda x: change_password_dialog(self.wallet, self.window, pw_icon)) - password_button.add(pw_icon) - password_button.set_relief(gtk.RELIEF_NONE) - password_button.show() - self.status_bar.pack_end(password_button,False,False) + if self.wallet.seed: + password_button = gtk.Button() + password_button.connect("clicked", lambda x: change_password_dialog(self.wallet, self.window, pw_icon)) + password_button.add(pw_icon) + password_button.set_relief(gtk.RELIEF_NONE) + password_button.show() + self.status_bar.pack_end(password_button,False,False) self.window.add(vbox) self.window.show_all() @@ -661,7 +673,8 @@ class ElectrumWindow: thread.start_new_thread(update_status_bar_thread, ()) - thread.start_new_thread(check_recipient_thread, ()) + if self.wallet.seed: + thread.start_new_thread(check_recipient_thread, ()) self.notebook.set_current_page(0) diff --git a/lib/gui_qt.py b/lib/gui_qt.py @@ -157,7 +157,8 @@ class ElectrumWindow(QMainWindow): self.tabs = tabs = QTabWidget(self) tabs.addTab(self.create_history_tab(), 'History') - tabs.addTab(self.create_send_tab(), 'Send') + if self.wallet.seed: + tabs.addTab(self.create_send_tab(), 'Send') tabs.addTab(self.create_receive_tab(), 'Receive') tabs.addTab(self.create_contacts_tab(),'Contacts') tabs.addTab(self.create_wall_tab(), 'Wall') @@ -166,7 +167,9 @@ class ElectrumWindow(QMainWindow): self.setCentralWidget(tabs) self.create_status_bar() self.setGeometry(100,100,840,400) - self.setWindowTitle( 'Electrum ' + self.wallet.electrum_version ) + title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.path + if not self.wallet.seed: title += ' [seedless]' + self.setWindowTitle( title ) self.show() QShortcut(QKeySequence("Ctrl+W"), self, self.close) @@ -178,8 +181,9 @@ class ElectrumWindow(QMainWindow): def connect_slots(self, sender): - self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient) - self.previous_payto_e='' + if self.wallet.seed: + self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient) + self.previous_payto_e='' def check_recipient(self): if self.payto_e.hasFocus(): @@ -512,42 +516,62 @@ class ElectrumWindow(QMainWindow): entry.setPalette(palette) + def get_current_addr(self, is_recv): + if is_recv: + l = self.receive_list + else: + l = self.contacts_list + i = l.currentItem() + if i: + return unicode( i.text(0) ) + else: + return '' - def clear_buttons(self, hbox): - while hbox.count(): hbox.removeItem(hbox.itemAt(0)) + def add_receive_buttons(self): - def add_buttons(self, l, hbox, is_recv): - self.clear_buttons(hbox) + l = self.receive_list + hbox = self.receive_buttons_hbox - i = l.currentItem() - if not i: return - addr = unicode( i.text(0) ) + hbox.addWidget(EnterButton("QR",lambda: self.show_address_qrcode(self.get_current_addr(True)))) + hbox.addWidget(EnterButton("Copy to Clipboard", lambda: self.app.clipboard().setText(self.get_current_addr(True)))) - hbox.addWidget(EnterButton("QR",lambda: self.show_address_qrcode(addr))) - hbox.addWidget(EnterButton("Copy to Clipboard", lambda: self.app.clipboard().setText(addr))) - if is_recv: - def toggle_freeze(addr): - if addr in self.wallet.frozen_addresses: - self.wallet.frozen_addresses.remove(addr) - else: - self.wallet.frozen_addresses.append(addr) - self.wallet.save() - self.update_receive_tab() + def toggle_freeze(): + addr = self.get_current_addr(True) + if not addr: return + if addr in self.wallet.frozen_addresses: + self.wallet.frozen_addresses.remove(addr) + else: + self.wallet.frozen_addresses.append(addr) + self.wallet.save() + self.update_receive_tab() - t = "Unfreeze" if addr in self.wallet.frozen_addresses else "Freeze" - hbox.addWidget(EnterButton(t, lambda: toggle_freeze(addr))) + self.freezeButton = b = EnterButton("Freeze", toggle_freeze) + hbox.addWidget(b) + hbox.addStretch(1) - else: - def payto(addr): - if not addr:return - self.tabs.setCurrentIndex(1) - self.payto_e.setText(addr) - self.amount_e.setFocus() - hbox.addWidget(EnterButton('Pay to', lambda: payto(addr))) - hbox.addWidget(EnterButton("New", self.newaddress_dialog)) + + def add_contacts_buttons(self): + l = self.contacts_list + hbox = self.contacts_buttons_hbox + + hbox.addWidget(EnterButton("QR",lambda: self.show_address_qrcode(self.get_current_addr(False)))) + hbox.addWidget(EnterButton("Copy to Clipboard", lambda: self.app.clipboard().setText(self.get_current_addr(False)))) + def payto(): + addr = self.get_current_addr(False) + if not addr:return + self.tabs.setCurrentIndex(1) + self.payto_e.setText(addr) + self.amount_e.setFocus() + hbox.addWidget(EnterButton('Pay to', lambda: payto())) + hbox.addWidget(EnterButton("New", self.newaddress_dialog)) hbox.addStretch(1) + def update_receive_buttons(self): + addr = self.get_current_addr(True) + t = "Unfreeze" if addr in self.wallet.frozen_addresses else "Freeze" + self.freezeButton.setText(t) + def create_receive_tab(self): l = QTreeWidget(self) @@ -573,11 +597,15 @@ class ElectrumWindow(QMainWindow): hbox.setSpacing(0) buttons.setLayout(hbox) + self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l)) self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l)) - self.connect(l, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda: self.add_buttons(l, hbox, True)) + l.selectionModel().currentChanged.connect(self.update_receive_buttons) + self.receive_list = l self.receive_buttons_hbox = hbox + self.add_receive_buttons() + return w def create_contacts_tab(self): @@ -606,16 +634,13 @@ class ElectrumWindow(QMainWindow): self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l)) self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l)) self.connect(l, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), self.show_contact_details) - self.connect(l, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda: self.add_buttons(l, hbox, False)) - self.contacts_list = l self.contacts_buttons_hbox = hbox + self.add_contacts_buttons() return w def update_receive_tab(self): self.receive_list.clear() - self.clear_buttons(self.receive_buttons_hbox) - for address in self.wallet.all_addresses(): if self.wallet.is_change(address):continue label = self.wallet.labels.get(address,'') @@ -647,7 +672,6 @@ class ElectrumWindow(QMainWindow): def update_contacts_tab(self): self.contacts_list.clear() - self.clear_buttons(self.contacts_buttons_hbox) for alias, v in self.wallet.aliases.items(): s, target = v @@ -674,9 +698,11 @@ class ElectrumWindow(QMainWindow): def create_status_bar(self): sb = QStatusBar() sb.setFixedHeight(35) - sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "Password", lambda: self.change_password_dialog(self.wallet, self) ) ) + if self.wallet.seed: + sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), "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", lambda: self.show_seed_dialog(self.wallet, self) ) ) + if self.wallet.seed: + 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) @@ -695,6 +721,11 @@ class ElectrumWindow(QMainWindow): @staticmethod def show_seed_dialog(wallet, parent=None): from electrum import mnemonic + + if not wallet.seed: + QMessageBox.information(parent, 'Message', 'No seed', 'OK') + return + if wallet.use_encryption: password = parent.password_dialog() if not password: return @@ -852,6 +883,11 @@ class ElectrumWindow(QMainWindow): @staticmethod def change_password_dialog( wallet, parent=None ): + + if not wallet.seed: + QMessageBox.information(parent, 'Message', 'No seed', 'OK') + return + d = QDialog(parent) d.setModal(1) diff --git a/lib/interface.py b/lib/interface.py @@ -218,9 +218,10 @@ class TcpStratumInterface(Interface): self.s.connect(( self.host, self.port)) self.is_connected = True self.send([('server.version', [ELECTRUM_VERSION])]) + print "Connected to %s:%d"%(self.host,self.port) except: self.is_connected = False - print "not connected" + print "Not connected" def run(self): try: @@ -380,7 +381,6 @@ class WalletSynchronizer(threading.Thread): response = self.interface.responses.get() self.handle_response(response) - print "disconnected, gui callback" self.wallet.gui_callback() if self.loop: time.sleep(5) diff --git a/lib/version.py b/lib/version.py @@ -1,2 +1,2 @@ -ELECTRUM_VERSION = "0.48a" +ELECTRUM_VERSION = "0.49" SEED_VERSION = 4 # bump this everytime the seed generation is modified diff --git a/lib/wallet.py b/lib/wallet.py @@ -23,13 +23,13 @@ try: import ecdsa from ecdsa.util import string_to_number, number_to_string except: - print "python-ecdsa does not seem to be installed. Try 'sudo easy_install ecdsa'" + print "python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'" sys.exit(1) try: import aes except: - print "AES does not seem to be installed. Try 'sudo easy_install slowaes'" + print "AES does not seem to be installed. Try 'sudo pip install slowaes'" sys.exit(1) @@ -308,6 +308,7 @@ class Wallet: self.server = server self.save() self.interface.is_connected = False # this exits the polling loop + self.interface.poke() def set_path(self, wallet_path):