From 0a6b1a42a34661e781653c0f68e818988592b2af Mon Sep 17 00:00:00 2001 From: thomasv Date: Mon, 11 Mar 2013 13:35:51 +0100 Subject: [PATCH 1/7] improve plugins tab --- gui/gui_classic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/gui_classic.py b/gui/gui_classic.py index 836e8f13..52ad0e6d 100644 --- a/gui/gui_classic.py +++ b/gui/gui_classic.py @@ -2000,7 +2000,7 @@ class ElectrumWindow(QMainWindow): # plugins if self.plugins: - tab5 = QWidget() + tab5 = QScrollArea() grid_plugins = QGridLayout(tab5) grid_plugins.setColumnStretch(0,1) tabs.addTab(tab5, _('Plugins') ) @@ -2011,7 +2011,7 @@ class ElectrumWindow(QMainWindow): name, description = p.get_info() cb = QCheckBox(name) 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: From 9b10b2f121e6eed703e356f1453c2790885421b6 Mon Sep 17 00:00:00 2001 From: thomasv Date: Mon, 11 Mar 2013 13:45:19 +0100 Subject: [PATCH 2/7] move 'load transaction' to import/export tab --- gui/gui_classic.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/gui/gui_classic.py b/gui/gui_classic.py index 52ad0e6d..41b88b67 100644 --- a/gui/gui_classic.py +++ b/gui/gui_classic.py @@ -1922,11 +1922,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() @@ -1995,7 +1990,13 @@ 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 From 0d143e074a535bf941ff46cf860f251c2d71c0a1 Mon Sep 17 00:00:00 2001 From: thomasv Date: Mon, 11 Mar 2013 13:49:43 +0100 Subject: [PATCH 3/7] update indices and add stretch to ui tab --- gui/gui_classic.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/gui/gui_classic.py b/gui/gui_classic.py index 41b88b67..2292cbd2 100644 --- a/gui/gui_classic.py +++ b/gui/gui_classic.py @@ -1864,18 +1864,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()) @@ -1884,8 +1884,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) @@ -1893,7 +1893,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: @@ -1901,20 +1901,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() From 7bf87bc18916d5d8f1a1622574c47577e10367d1 Mon Sep 17 00:00:00 2001 From: thomasv Date: Mon, 11 Mar 2013 14:30:30 +0100 Subject: [PATCH 4/7] plugins: define is_available() --- gui/gui_classic.py | 1 + plugins/pointofsale.py | 30 ++++++++++++++++-------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/gui/gui_classic.py b/gui/gui_classic.py index 2292cbd2..cdc29e2f 100644 --- a/gui/gui_classic.py +++ b/gui/gui_classic.py @@ -2012,6 +2012,7 @@ class ElectrumWindow(QMainWindow): try: name, description = p.get_info() cb = QCheckBox(name) + cb.setDisabled(not p.is_available()) cb.setChecked(p.is_enabled()) cb.clicked.connect(mk_toggle(cb,p)) grid_plugins.addWidget(cb, i, 0) diff --git a/plugins/pointofsale.py b/plugins/pointofsale.py index 25dc04bf..87d15147 100644 --- 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): From 70144c7f118e2d1330d24e6ef8b86d5028955fa5 Mon Sep 17 00:00:00 2001 From: thomasv Date: Mon, 11 Mar 2013 15:09:38 +0100 Subject: [PATCH 5/7] first parameter of QFileDialog should be the parent --- gui/gui_classic.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gui/gui_classic.py b/gui/gui_classic.py index cdc29e2f..b53077bc 100644 --- a/gui/gui_classic.py +++ b/gui/gui_classic.py @@ -806,7 +806,7 @@ class ElectrumWindow(QMainWindow): else: filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime())) try: - fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename))) + fileName = QFileDialog.getSaveFileName(self, _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename))) 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,7 +1667,7 @@ class ElectrumWindow(QMainWindow): def read_tx_from_file(self): - fileName = QFileDialog.getOpenFileName(QWidget(), _("Select your transaction file"), os.path.expanduser('~')) + fileName = QFileDialog.getOpenFileName(self, _("Select your transaction file"), os.path.expanduser('~')) if not fileName: return try: @@ -1684,7 +1684,7 @@ 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 = QFileDialog.getSaveFileName(self, _("Select where to save your signed transaction"), os.path.expanduser('~/signed_tx_%s' % (tx.hash()[0:8]))) if fileName: with open(fileName, "w+") as f: f.write(json.dumps(tx.as_dict(),indent=4) + '\n') @@ -1773,7 +1773,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 = QFileDialog.getSaveFileName(self, select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv") if fileName: with open(fileName, "w+") as csvfile: transaction = csv.writer(csvfile) @@ -1795,7 +1795,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 = QFileDialog.getOpenFileName(self, _("Open text file"), util.user_dir(), self.tr("Text Files (labels.dat)")) if not labelsFile: return try: f = open(labelsFile, 'r') From a7da96114c9db57d68ea4be492afc584667b009b Mon Sep 17 00:00:00 2001 From: thomasv Date: Mon, 11 Mar 2013 15:57:37 +0100 Subject: [PATCH 6/7] custom wrappers that store the path selected by the user --- gui/gui_classic.py | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/gui/gui_classic.py b/gui/gui_classic.py index b53077bc..4bd0cac4 100644 --- a/gui/gui_classic.py +++ b/gui/gui_classic.py @@ -362,6 +362,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,)) @@ -804,9 +822,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(self, _("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,7 +1685,7 @@ class ElectrumWindow(QMainWindow): def read_tx_from_file(self): - fileName = QFileDialog.getOpenFileName(self, _("Select your transaction file"), os.path.expanduser('~')) + fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn") if not fileName: return try: @@ -1684,7 +1702,7 @@ class ElectrumWindow(QMainWindow): try: self.wallet.signrawtransaction(tx, input_info, [], password) - fileName = QFileDialog.getSaveFileName(self, _("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') @@ -1773,7 +1791,7 @@ class ElectrumWindow(QMainWindow): try: select_export = _('Select file to export your private keys to') - fileName = QFileDialog.getSaveFileName(self, 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) @@ -1795,7 +1813,7 @@ class ElectrumWindow(QMainWindow): def do_import_labels(self): - labelsFile = QFileDialog.getOpenFileName(self, _("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') @@ -1807,16 +1825,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)) From c9302bcd56541c797b744b4d38ac1a28219b8dbc Mon Sep 17 00:00:00 2001 From: thomasv Date: Mon, 11 Mar 2013 19:04:46 +0100 Subject: [PATCH 7/7] add 2 of 3 sequences --- lib/bitcoin.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 1da39ae9..a3403fef 100644 --- 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