electrum

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

commit 884371eeddffe8dc270f7af33d88687ad5a38a92
parent e4ec690ffd70956331ea2e04aee1f963c3d19f68
Author: Maran <maran.hidskes@gmail.com>
Date:   Tue, 12 Mar 2013 16:52:15 +0100

Merge branch 'master' into feature/label_sync

Diffstat:
Melectrum | 8+++++---
Mgui/gui_classic.py | 93++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mgui/i18n.py | 2++
Mlib/bitcoin.py | 38+++++++++++++++++++++-----------------
Mlib/version.py | 2+-
Mmake_packages | 7+++++--
Mplugins/pointofsale.py | 30++++++++++++++++--------------
7 files changed, 107 insertions(+), 73 deletions(-)

diff --git a/electrum b/electrum @@ -34,8 +34,10 @@ except ImportError: sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'") +is_android = 'ANDROID_DATA' in os.environ + # load local module as electrum -if os.path.exists("lib"): +if os.path.exists("lib") or is_android: import imp fp, pathname, description = imp.find_module('lib') imp.load_module('electrum', fp, pathname, description) @@ -89,8 +91,8 @@ if __name__ == '__main__': set_verbosity(options.verbose) # config is an object passed to the various constructors (wallet, interface, gui) - if 'ANDROID_DATA' in os.environ: - config_options = {'wallet_path':"/sdcard/electrum.dat", 'portable':True, 'verbose':True, 'gui':'android'} + if is_android: + config_options = {'wallet_path':"/sdcard/electrum.dat", 'portable':True, 'verbose':True, 'gui':'android', 'auto_cycle':True} else: config_options = eval(str(options)) for k, v in config_options.items(): diff --git a/gui/gui_classic.py b/gui/gui_classic.py @@ -363,6 +363,24 @@ class ElectrumWindow(QMainWindow): apply(cb, args) + # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user + def getOpenFileName(self, title, filter = None): + directory = self.config.get('io_dir', os.path.expanduser('~')) + fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) ) + if fileName and directory != os.path.dirname(fileName): + self.config.set_key('io_dir', os.path.dirname(fileName), True) + return fileName + + def getSaveFileName(self, title, filename, filter = None): + directory = self.config.get('io_dir', os.path.expanduser('~')) + path = os.path.join( directory, filename ) + fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) ) + if fileName and directory != os.path.dirname(fileName): + self.config.set_key('io_dir', os.path.dirname(fileName), True) + return fileName + + + def close(self): QMainWindow.close(self) self.run_hook('close_main_window', (self,)) @@ -808,9 +826,9 @@ class ElectrumWindow(QMainWindow): else: QMessageBox.warning(self, _('Error'), msg, _('OK')) else: - filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime())) + filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime())) try: - fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename))) + fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn") with open(fileName,'w') as f: f.write(json.dumps(tx.as_dict(),indent=4) + '\n') QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK')) @@ -1667,13 +1685,13 @@ class ElectrumWindow(QMainWindow): if not tx_dict["complete"]: assert "input_info" in tx_dict.keys() except: - QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction:")) + QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction")) return None return tx_dict def read_tx_from_file(self): - fileName = QFileDialog.getOpenFileName(QWidget(), _("Select your transaction file"), os.path.expanduser('~')) + fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn") if not fileName: return try: @@ -1690,11 +1708,11 @@ class ElectrumWindow(QMainWindow): try: self.wallet.signrawtransaction(tx, input_info, [], password) - fileName = QFileDialog.getSaveFileName(QWidget(), _("Select where to save your signed transaction"), os.path.expanduser('~/signed_tx_%s' % (tx.hash()[0:8]))) + fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn") if fileName: with open(fileName, "w+") as f: f.write(json.dumps(tx.as_dict(),indent=4) + '\n') - self.show_message(_("Transaction saved succesfully")) + self.show_message(_("Transaction saved successfully")) if dialog: dialog.done(0) except BaseException, e: @@ -1704,7 +1722,7 @@ class ElectrumWindow(QMainWindow): def send_raw_transaction(self, raw_tx, dialog = ""): result, result_message = self.wallet.sendtx( raw_tx ) if result: - self.show_message("Transaction succesfully sent: %s" % (result_message)) + self.show_message("Transaction successfully sent: %s" % (result_message)) if dialog: dialog.done(0) else: @@ -1747,7 +1765,7 @@ class ElectrumWindow(QMainWindow): l = QGridLayout() dialog.setLayout(l) - l.addWidget(QLabel(_("Transaction status: ")), 3,0) + l.addWidget(QLabel(_("Transaction status:")), 3,0) l.addWidget(QLabel(_("Actions")), 4,0) if tx_dict["complete"] == False: @@ -1779,7 +1797,7 @@ class ElectrumWindow(QMainWindow): try: select_export = _('Select file to export your private keys to') - fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv") + fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv") if fileName: with open(fileName, "w+") as csvfile: transaction = csv.writer(csvfile) @@ -1801,7 +1819,7 @@ class ElectrumWindow(QMainWindow): def do_import_labels(self): - labelsFile = QFileDialog.getOpenFileName(QWidget(), _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)")) + labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat") if not labelsFile: return try: f = open(labelsFile, 'r') @@ -1813,16 +1831,16 @@ class ElectrumWindow(QMainWindow): QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile)) except (IOError, os.error), reason: QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason)) - + def do_export_labels(self): labels = self.wallet.labels try: - labelsFile = util.user_dir() + '/labels.dat' - f = open(labelsFile, 'w+') - json.dump(labels, f) - f.close() - QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(labelsFile)) + fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat") + if fileName: + with open(fileName, 'w+') as f: + json.dump(labels, f) + QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName)) except (IOError, os.error), reason: QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason)) @@ -1871,18 +1889,18 @@ class ElectrumWindow(QMainWindow): tabs.addTab(tab1, _('Display') ) nz_label = QLabel(_('Display zeros')) - grid_ui.addWidget(nz_label, 3, 0) + grid_ui.addWidget(nz_label, 0, 0) nz_e = QLineEdit() nz_e.setText("%d"% self.wallet.num_zeros) - grid_ui.addWidget(nz_e, 3, 1) + grid_ui.addWidget(nz_e, 0, 1) msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"') - grid_ui.addWidget(HelpButton(msg), 3, 2) + grid_ui.addWidget(HelpButton(msg), 0, 2) nz_e.textChanged.connect(lambda: numbify(nz_e,True)) if not self.config.is_modifiable('num_zeros'): for w in [nz_e, nz_label]: w.setEnabled(False) lang_label=QLabel(_('Language') + ':') - grid_ui.addWidget(lang_label , 8, 0) + grid_ui.addWidget(lang_label, 1, 0) lang_combo = QComboBox() from i18n import languages lang_combo.addItems(languages.values()) @@ -1891,8 +1909,8 @@ class ElectrumWindow(QMainWindow): except: index = 0 lang_combo.setCurrentIndex(index) - grid_ui.addWidget(lang_combo, 8, 1) - grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 8, 2) + grid_ui.addWidget(lang_combo, 1, 1) + grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2) if not self.config.is_modifiable('language'): for w in [lang_combo, lang_label]: w.setEnabled(False) @@ -1900,7 +1918,7 @@ class ElectrumWindow(QMainWindow): currencies.insert(0, "None") cur_label=QLabel(_('Currency') + ':') - grid_ui.addWidget(cur_label , 9, 0) + grid_ui.addWidget(cur_label , 2, 0) cur_combo = QComboBox() cur_combo.addItems(currencies) try: @@ -1908,20 +1926,21 @@ class ElectrumWindow(QMainWindow): except: index = 0 cur_combo.setCurrentIndex(index) - grid_ui.addWidget(cur_combo, 9, 1) - grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 9, 2) + grid_ui.addWidget(cur_combo, 2, 1) + grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2) view_label=QLabel(_('Receive Tab') + ':') - grid_ui.addWidget(view_label , 10, 0) + grid_ui.addWidget(view_label , 3, 0) view_combo = QComboBox() view_combo.addItems([_('Simple'), _('Advanced')]) view_combo.setCurrentIndex(self.expert_mode) - grid_ui.addWidget(view_combo, 10, 1) + grid_ui.addWidget(view_combo, 3, 1) hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \ + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \ + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' - grid_ui.addWidget(HelpButton(hh), 10, 2) + grid_ui.addWidget(HelpButton(hh), 3, 2) + grid_ui.setRowStretch(4,1) # wallet tab tab2 = QWidget() @@ -1929,11 +1948,6 @@ class ElectrumWindow(QMainWindow): grid_wallet.setColumnStretch(0,1) tabs.addTab(tab2, _('Wallet') ) - grid_wallet.addWidget(QLabel(_("Load raw transaction")), 3, 0) - grid_wallet.addWidget(EnterButton(_("From file"), self.do_process_from_file),3,1) - grid_wallet.addWidget(EnterButton(_("From text"), self.do_process_from_text),3,2) - grid_wallet.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")),3,3) - fee_label = QLabel(_('Transaction fee')) grid_wallet.addWidget(fee_label, 0, 0) fee_e = QLineEdit() @@ -2002,12 +2016,18 @@ class ElectrumWindow(QMainWindow): + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \ + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3) - grid_io.setRowStretch(4,1) + + grid_io.addWidget(QLabel(_("Load transaction")), 5, 0) + grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1) + grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2) + grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3) + + grid_io.setRowStretch(5,1) # plugins if self.plugins: - tab5 = QWidget() + tab5 = QScrollArea() grid_plugins = QGridLayout(tab5) grid_plugins.setColumnStretch(0,1) tabs.addTab(tab5, _('Plugins') ) @@ -2017,8 +2037,9 @@ class ElectrumWindow(QMainWindow): try: name, description = p.get_info() cb = QCheckBox(name) + cb.setDisabled(not p.is_available()) cb.setChecked(p.is_enabled()) - cb.stateChanged.connect(mk_toggle(cb,p)) + cb.clicked.connect(mk_toggle(cb,p)) grid_plugins.addWidget(cb, i, 0) grid_plugins.addWidget(HelpButton(description), i, 2) except: diff --git a/gui/i18n.py b/gui/i18n.py @@ -44,10 +44,12 @@ languages = { 'es':_('Spanish'), 'fr':_('French'), 'it':_('Italian'), + 'ja':_('Japanese'), 'lv':_('Latvian'), 'nl':_('Dutch'), 'ru':_('Russian'), 'sl':_('Slovenian'), + 'ta':_('Tamil'), 'vi':_('Vietnamese'), 'zh':_('Chinese') } diff --git a/lib/bitcoin.py b/lib/bitcoin.py @@ -415,13 +415,10 @@ def CKD_prime(K, c, n): class ElectrumSequence: """ Privatekey(type,n) = Master_private_key + H(n|S|type) """ - def __init__(self, master_public_key, mpk2 = None): + def __init__(self, master_public_key, mpk2 = None, mpk3 = None): self.master_public_key = master_public_key - if mpk2: - self.mpk2 = mpk2 - self.is_p2sh = True - else: - self.is_p2sh = False + self.mpk2 = mpk2 + self.mpk3 = mpk3 @classmethod def mpk_from_seed(klass, seed): @@ -443,18 +440,23 @@ class ElectrumSequence: return string_to_number( Hash( "%d:%d:"%(n,for_change) + mpk.decode('hex') ) ) def get_address(self, sequence): - if not self.is_p2sh: + if not self.mpk2: pubkey = self.get_pubkey(sequence) address = public_key_to_bc_address( pubkey.decode('hex') ) - else: + elif not self.mpk3: pubkey1 = self.get_pubkey(sequence) pubkey2 = self.get_pubkey(sequence, use_mpk2=True) address = Transaction.multisig_script([pubkey1, pubkey2], 2)["address"] + else: + pubkey1 = self.get_pubkey(sequence) + pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2) + pubkey3 = self.get_pubkey(sequence, mpk = self.mpk3) + address = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)["address"] return address - def get_pubkey(self, sequence, use_mpk2=False): + def get_pubkey(self, sequence, mpk=None): curve = SECP256k1 - mpk = self.mpk2 if use_mpk2 else self.master_public_key + if mpk is None: mpk = self.master_public_key z = self.get_sequence(sequence, mpk) master_public_key = ecdsa.VerifyingKey.from_string( mpk.decode('hex'), curve = SECP256k1 ) pubkey_point = master_public_key.pubkey.point + z*curve.generator @@ -484,21 +486,23 @@ class ElectrumSequence: if master_public_key != self.master_public_key: print_error('invalid password (mpk)') raise BaseException('Invalid password') - return True - def get_input_info(self, sequence): - - if not self.is_p2sh: + if not self.mpk2: pk_addr = self.get_address(sequence) redeemScript = None - else: + elif not self.mpk3: pubkey1 = self.get_pubkey(sequence) - pubkey2 = self.get_pubkey(sequence,use_mpk2=True) + pubkey2 = self.get_pubkey(sequence,mpk=self.mpk2) pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key redeemScript = Transaction.multisig_script([pubkey1, pubkey2], 2)['redeemScript'] - + else: + pubkey1 = self.get_pubkey(sequence) + pubkey2 = self.get_pubkey(sequence,mpk=self.mpk2) + pubkey3 = self.get_pubkey(sequence,mpk=self.mpk3) + pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key + redeemScript = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)['redeemScript'] return pk_addr, redeemScript diff --git a/lib/version.py b/lib/version.py @@ -1,4 +1,4 @@ ELECTRUM_VERSION = "1.7" # version of the client package PROTOCOL_VERSION = '0.6' # protocol version requested SEED_VERSION = 4 # bump this every time the seed generation is modified -TRANSLATION_ID = 3958 # version of the wiki page +TRANSLATION_ID = 3992 # version of the wiki page diff --git a/make_packages b/make_packages @@ -20,10 +20,13 @@ if __name__ == '__main__': # android os.system('rm -rf dist/e4a-%s'%version) os.mkdir('dist/e4a-%s'%version) - shutil.copyfile("electrum",'dist/e4a-%s/electrum.py'%version) + shutil.copyfile("electrum",'dist/e4a-%s/e4a.py'%version) shutil.copytree("ecdsa",'dist/e4a-%s/ecdsa'%version) shutil.copytree("aes",'dist/e4a-%s/aes'%version) - shutil.copytree("lib",'dist/e4a-%s/electrum'%version) + shutil.copytree("lib",'dist/e4a-%s/lib'%version) + os.mkdir('dist/e4a-%s/gui'%version) + shutil.copy("gui/gui_android.py",'dist/e4a-%s/gui'%version) + shutil.copy("gui/__init__.py",'dist/e4a-%s/gui'%version) os.chdir("dist") # create the zip file diff --git a/plugins/pointofsale.py b/plugins/pointofsale.py @@ -92,38 +92,36 @@ class QR_Window(QWidget): - +config = {} def get_info(): return 'Point of Sale', _('Show QR code window and amounts requested for each address. Add menu item to request amount.') def init(gui): - gui.requested_amounts = gui.config.get('requested_amounts',{}) - gui.merchant_name = gui.config.get('merchant_name', 'Invoice') + global config + config = gui.config + gui.requested_amounts = config.get('requested_amounts',{}) + gui.merchant_name = config.get('merchant_name', 'Invoice') gui.qr_window = None - - -enabled = False - def is_enabled(): - return False + return config.get('pointofsale') is True + +def is_available(): + return True def toggle(gui): - global enabled - enabled = not enabled - toggle_QR_window(gui, enabled) - if enabled: + if not is_enabled(): gui.expert_mode = True gui.receive_list.setHeaderLabels([ _('Address'), _('Label'), _('Balance'), _('Request')]) - gui.set_hook('item_changed', item_changed) gui.set_hook('current_item_changed', recv_changed) gui.set_hook('receive_menu', receive_menu) gui.set_hook('update_receive_item', update_receive_item) gui.set_hook('timer_actions', timer_actions) gui.set_hook('close_main_window', close_main_window) + enabled = True else: gui.receive_list.setHeaderLabels([ _('Address'), _('Label'), _('Balance'), _('Tx')]) gui.unset_hook('item_changed', item_changed) @@ -132,9 +130,13 @@ def toggle(gui): gui.unset_hook('update_receive_item', update_receive_item) gui.unset_hook('timer_actions', timer_actions) gui.unset_hook('close_main_window', close_main_window) - + enabled = False + config.set_key('pointofsale', enabled, True) + toggle_QR_window(gui, enabled) return enabled + + def toggle_QR_window(self, show):