diff --git a/.gitignore b/.gitignore index 7918ea80..1a10a8c9 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ locale/ .devlocaltmp/ *_trial_temp packages +env/ diff --git a/electrum b/electrum index 0d5d7635..90536aea 100755 --- a/electrum +++ b/electrum @@ -120,7 +120,7 @@ def arg_parser(): parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http") parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information") parser.add_option("-P", "--portable", action="store_true", dest="portable", default=False, help="portable wallet") - parser.add_option("-L", "--lang", dest="language", default=None, help="defaut language used in GUI") + parser.add_option("-L", "--lang", dest="language", default=None, help="default language used in GUI") parser.add_option("-G", "--gap", dest="gap_limit", default=None, help="gap limit") parser.add_option("-W", "--password", dest="password", default=None, help="set password for usage with commands (currently only implemented for create command, do not use it for longrunning gui session since the password is visible in /proc)") parser.add_option("-1", "--oneserver", action="store_true", dest="oneserver", default=False, help="connect to one server only") diff --git a/electrum-env b/electrum-env index 0e2cc70f..0c9c1d7c 100755 --- a/electrum-env +++ b/electrum-env @@ -17,8 +17,8 @@ else python setup.py install fi -export PYTHONPATH=/usr/local/lib/python2.7/site-packages:$PYTHONPATH +export PYTHONPATH="/usr/local/lib/python2.7/site-packages:$PYTHONPATH" -./electrum +./electrum "$@" deactivate diff --git a/electrum.desktop b/electrum.desktop index 1b0c5ab3..20a0b1ee 100644 --- a/electrum.desktop +++ b/electrum.desktop @@ -13,5 +13,5 @@ Categories=Network; StartupNotify=false Terminal=false Type=Application -MimeType=x-scheme-handler/bitcoin +MimeType=x-scheme-handler/bitcoin; diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 2ea39a15..fef18b5b 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -296,6 +296,8 @@ class ElectrumWindow(QMainWindow): wallet.start_threads(self.network) # load new wallet in gui self.load_wallet(wallet) + # save path + self.config.set_key('default_wallet_path', filename) @@ -577,7 +579,7 @@ class ElectrumWindow(QMainWindow): elif be == 'Insight.is': block_explorer = 'http://live.insight.is/tx/' elif be == "Blocktrail.com": - block_explorer = 'https://www.blocktrail.com/tx/' + block_explorer = 'https://www.blocktrail.com/BTC/tx/' if not item: return tx_hash = str(item.data(0, Qt.UserRole).toString()) @@ -1907,39 +1909,44 @@ class ElectrumWindow(QMainWindow): dialog.setModal(1) dialog.setWindowTitle(_("Master Public Keys")) - main_layout = QGridLayout() mpk_dict = self.wallet.get_master_public_keys() - # filter out the empty keys (PendingAccount) - mpk_dict = {acc:mpk for acc,mpk in mpk_dict.items() if mpk} - + vbox = QVBoxLayout() # only show the combobox in case multiple accounts are available if len(mpk_dict) > 1: - combobox = QComboBox() - for name in mpk_dict: - combobox.addItem(name) - combobox.setCurrentIndex(0) - main_layout.addWidget(combobox, 1, 0) + gb = QGroupBox(_("Master Public Keys")) + vbox.addWidget(gb) + group = QButtonGroup() + first_button = None + for name in sorted(mpk_dict.keys()): + b = QRadioButton(gb) + b.setText(name) + group.addButton(b) + vbox.addWidget(b) + if not first_button: + first_button = b - account = unicode(combobox.currentText()) - mpk_text = ShowQRTextEdit(text=mpk_dict[account]) + mpk_text = ShowQRTextEdit() mpk_text.setMaximumHeight(170) - mpk_text.selectAll() # for easy copying - main_layout.addWidget(mpk_text, 2, 0) + vbox.addWidget(mpk_text) - def show_mpk(account): - mpk = mpk_dict.get(unicode(account), "") + def show_mpk(b): + name = str(b.text()) + mpk = mpk_dict.get(name, "") mpk_text.setText(mpk) + mpk_text.selectAll() # for easy copying - combobox.currentIndexChanged[str].connect(lambda acc: show_mpk(acc)) + group.buttonReleased.connect(show_mpk) + first_button.setChecked(True) + show_mpk(first_button) + + #combobox.currentIndexChanged[str].connect(lambda acc: show_mpk(acc)) elif len(mpk_dict) == 1: mpk = mpk_dict.values()[0] mpk_text = ShowQRTextEdit(text=mpk) mpk_text.setMaximumHeight(170) mpk_text.selectAll() # for easy copying - main_layout.addWidget(mpk_text, 2, 0) + vbox.addWidget(mpk_text) - vbox = QVBoxLayout() - vbox.addLayout(main_layout) vbox.addLayout(close_button(dialog)) dialog.setLayout(vbox) diff --git a/gui/qt/network_dialog.py b/gui/qt/network_dialog.py index a52cb1c2..98f0f3f0 100644 --- a/gui/qt/network_dialog.py +++ b/gui/qt/network_dialog.py @@ -201,9 +201,12 @@ class NetworkDialog(QDialog): def change_server(self, host, protocol): pp = self.servers.get(host, DEFAULT_PORTS) + if protocol and protocol not in protocol_letters: + protocol = None if protocol: port = pp.get(protocol) - if not port: protocol = None + if port is None: + protocol = None if not protocol: if 's' in pp.keys(): @@ -217,15 +220,6 @@ class NetworkDialog(QDialog): self.server_port.setText( port ) self.server_protocol.setCurrentIndex(protocol_letters.index(protocol)) - if not self.servers: return - for p in protocol_letters: - i = protocol_letters.index(p) - j = self.server_protocol.model().index(i,0) - #if p not in pp.keys(): # and self.interface.is_connected: - # self.server_protocol.model().setData(j, QVariant(0), Qt.UserRole-1) - #else: - # self.server_protocol.model().setData(j, QVariant(33), Qt.UserRole-1) - def do_exec(self): diff --git a/lib/network.py b/lib/network.py index de6ac30a..e9624877 100644 --- a/lib/network.py +++ b/lib/network.py @@ -368,7 +368,7 @@ class Network(threading.Thread): out['result'] = f(*params) except BaseException as e: out['error'] = str(e) - traceback.print_exc(file=sys.stout) + traceback.print_exc(file=sys.stdout) print_error("network error", str(e)) self.response_queue.put(out) diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py index 0c963026..df5dfd62 100644 --- a/lib/paymentrequest.py +++ b/lib/paymentrequest.py @@ -43,42 +43,8 @@ import x509 REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'} ACK_HEADERS = {'Content-Type':'application/bitcoin-payment','Accept':'application/bitcoin-paymentack','User-Agent':'Electrum'} - -ca_list = {} ca_path = requests.certs.where() - - - - -def load_certificates(): - try: - ca_f = open(ca_path, 'r') - except Exception: - print "ERROR: Could not open %s"%ca_path - print "ca-bundle.crt file should be placed in ~/.electrum/ca/ca-bundle.crt" - print "Documentation on how to download or create the file here: http://curl.haxx.se/docs/caextract.html" - print "Payment will continue with manual verification." - return False - c = "" - for line in ca_f: - if line == "-----BEGIN CERTIFICATE-----\n": - c = line - else: - c += line - if line == "-----END CERTIFICATE-----\n": - x = x509.X509() - try: - x.parse(c) - except Exception as e: - util.print_error("cannot parse cert:", e) - continue - ca_list[x.getFingerprint()] = x - ca_f.close() - util.print_error("%d certificates"%len(ca_list)) - return True - -load_certificates() - +ca_list = x509.load_certificates(ca_path) class PaymentRequest: @@ -190,8 +156,13 @@ class PaymentRequest: verify = pubkey.hashAndVerify(sig, data) elif algo.getComponentByName('algorithm') == x509.ALGO_RSA_SHA256: hashBytes = bytearray(hashlib.sha256(data).digest()) - prefixBytes = bytearray([0x30,0x31,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x01,0x05,0x00,0x04,0x20]) - verify = pubkey.verify(sig, prefixBytes + hashBytes) + verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA256 + hashBytes) + elif algo.getComponentByName('algorithm') == x509.ALGO_RSA_SHA384: + hashBytes = bytearray(hashlib.sha384(data).digest()) + verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA384 + hashBytes) + elif algo.getComponentByName('algorithm') == x509.ALGO_RSA_SHA512: + hashBytes = bytearray(hashlib.sha512(data).digest()) + verify = pubkey.verify(sig, x509.PREFIX_RSA_SHA512 + hashBytes) else: self.error = "Algorithm not supported" util.print_error(self.error, algo.getComponentByName('algorithm')) @@ -226,8 +197,7 @@ class PaymentRequest: if paymntreq.pki_type == "x509+sha256": hashBytes = bytearray(hashlib.sha256(msgBytes).digest()) - prefixBytes = bytearray([0x30,0x31,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x01,0x05,0x00,0x04,0x20]) - verify = pubkey0.verify(sigBytes, prefixBytes + hashBytes) + verify = pubkey0.verify(sigBytes, x509.PREFIX_RSA_SHA256 + hashBytes) elif paymntreq.pki_type == "x509+sha1": verify = pubkey0.hashAndVerify(sigBytes, msgBytes) else: @@ -321,7 +291,6 @@ class PaymentRequest: if __name__ == "__main__": util.set_verbosity(True) - load_certificates() try: uri = sys.argv[1] diff --git a/lib/plugins.py b/lib/plugins.py index 956caffa..cb860019 100644 --- a/lib/plugins.py +++ b/lib/plugins.py @@ -40,7 +40,7 @@ def hook(func): def run_hook(name, *args): - SPECIAL_HOOKS = ['get_wizard_action'] + SPECIAL_HOOKS = ['get_wizard_action','installwizard_restore'] results = [] f_list = hooks.get(name,[]) for p, f in f_list: @@ -71,6 +71,7 @@ class BasePlugin: def __init__(self, config, name): self.name = name self.config = config + self.wallet = None # add self to hooks for k in dir(self): if k in hook_names: diff --git a/lib/wallet.py b/lib/wallet.py index c9c10aee..3264845f 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -67,7 +67,7 @@ class WalletStorage(object): # path in config file path = config.get('default_wallet_path') - if path: + if path and os.path.exists(path): return path # default path @@ -1494,7 +1494,7 @@ class Wallet_2of2(BIP32_Wallet, Mnemonic): def get_master_public_keys(self): xpub1 = self.master_public_keys.get("x1/") xpub2 = self.master_public_keys.get("x2/") - return {'x1':xpub1, 'x2':xpub2} + return { 'Self':xpub1, 'Cosigner':xpub2 } def get_action(self): xpub1 = self.master_public_keys.get("x1/") diff --git a/lib/x509.py b/lib/x509.py index 41da17a9..5b06533f 100644 --- a/lib/x509.py +++ b/lib/x509.py @@ -23,6 +23,7 @@ import sys import pyasn1 import pyasn1_modules import tlslite +import util # workaround https://github.com/trevp/tlslite/issues/15 tlslite.utils.cryptomath.pycryptoLoaded = False @@ -41,8 +42,18 @@ from pyasn1_modules.rfc2459 import id_at_organizationalUnitName as OU_NAME from pyasn1_modules.rfc2459 import id_ce_basicConstraints, BasicConstraints XMPP_ADDR = ObjectIdentifier('1.3.6.1.5.5.7.8.5') SRV_NAME = ObjectIdentifier('1.3.6.1.5.5.7.8.7') + +# algo OIDs ALGO_RSA_SHA1 = ObjectIdentifier('1.2.840.113549.1.1.5') ALGO_RSA_SHA256 = ObjectIdentifier('1.2.840.113549.1.1.11') +ALGO_RSA_SHA384 = ObjectIdentifier('1.2.840.113549.1.1.12') +ALGO_RSA_SHA512 = ObjectIdentifier('1.2.840.113549.1.1.13') + +# prefixes, see http://stackoverflow.com/questions/3713774/c-sharp-how-to-calculate-asn-1-der-encoding-of-a-particular-hash-algorithm +PREFIX_RSA_SHA256 = bytearray([0x30,0x31,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x01,0x05,0x00,0x04,0x20]) +PREFIX_RSA_SHA384 = bytearray([0x30,0x41,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x02,0x05,0x00,0x04,0x30]) +PREFIX_RSA_SHA512 = bytearray([0x30,0x51,0x30,0x0d,0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x03,0x05,0x00,0x04,0x40]) + class CertificateError(Exception): pass @@ -214,3 +225,21 @@ class X509(tlslite.X509): class X509CertChain(tlslite.X509CertChain): pass + + + + +def load_certificates(ca_path): + ca_list = {} + with open(ca_path, 'r') as f: + s = f.read() + bList = tlslite.utils.pem.dePemList(s, "CERTIFICATE") + for b in bList: + x = X509() + try: + x.parseBinary(b) + except Exception as e: + util.print_error("cannot parse cert:", e) + continue + ca_list[x.getFingerprint()] = x + return ca_list diff --git a/plugins/audio_modem.py b/plugins/audio_modem.py index efe35a28..a6f1f35d 100644 --- a/plugins/audio_modem.py +++ b/plugins/audio_modem.py @@ -15,8 +15,7 @@ import platform try: import amodem.audio - import amodem.recv - import amodem.send + import amodem.main import amodem.config print_error('Audio MODEM is available.') amodem.log.addHandler(amodem.logging.StreamHandler(sys.stderr)) @@ -115,7 +114,7 @@ class Plugin(BasePlugin): with self._audio_interface() as interface: src = BytesIO(blob) dst = interface.player() - amodem.send.main(config=self.modem_config, src=src, dst=dst) + amodem.main.send(config=self.modem_config, src=src, dst=dst) except Exception: traceback.print_exc() @@ -132,7 +131,7 @@ class Plugin(BasePlugin): with self._audio_interface() as interface: src = interface.recorder() dst = BytesIO() - amodem.recv.main(config=self.modem_config, src=src, dst=dst) + amodem.main.recv(config=self.modem_config, src=src, dst=dst) return dst.getvalue() except Exception: traceback.print_exc() diff --git a/plugins/exchange_rate.py b/plugins/exchange_rate.py index 12a4df65..1afdd465 100644 --- a/plugins/exchange_rate.py +++ b/plugins/exchange_rate.py @@ -17,6 +17,7 @@ from electrum_gui.qt.amountedit import AmountEdit EXCHANGES = ["BitcoinAverage", "BitcoinVenezuela", + "BTCParalelo", "Bitcurex", "Bitmarket", "BitPay", @@ -63,6 +64,26 @@ class Exchanger(threading.Thread): except Exception: raise return json_resp + + def get_json_insecure(self, site, get_string): + """ get_json_insecure shouldn't be used in production releases + It doesn't use SSL, and so prices could be manipulated by a middle man + This should be used ONLY when developing plugins when you don't have a + SSL certificate that validates against HTTPSConnection + """ + try: + connection = httplib.HTTPConnection(site) + connection.request("GET", get_string, headers={"User-Agent":"Electrum"}) + except Exception: + raise + resp = connection.getresponse() + if resp.reason == httplib.responses[httplib.NOT_FOUND]: + raise + try: + json_resp = json.loads(resp.read()) + except Exception: + raise + return json_resp def exchange(self, btc_amount, quote_currency): @@ -82,6 +103,7 @@ class Exchanger(threading.Thread): update_rates = { "BitcoinAverage": self.update_ba, "BitcoinVenezuela": self.update_bv, + "BTCParalelo": self.update_bpl, "Bitcurex": self.update_bx, "Bitmarket": self.update_bm, "BitPay": self.update_bp, @@ -110,6 +132,9 @@ class Exchanger(threading.Thread): def update_cd(self): try: resp_currencies = self.get_json('api.coindesk.com', "/v1/bpi/supported-currencies.json") + except SSLError: + print("SSL Error when accesing coindesk") + return except Exception: return @@ -138,6 +163,9 @@ class Exchanger(threading.Thread): try: resp_rate = self.get_json('api.itbit.com', "/v1/markets/XBT" + str(current_cur) + "/ticker") quote_currencies[str(current_cur)] = decimal.Decimal(str(resp_rate["lastPrice"])) + except SSLError: + print("SSL Error when accesing itbit") + return except Exception: return with self.lock: @@ -147,6 +175,9 @@ class Exchanger(threading.Thread): def update_wd(self): try: winkresp = self.get_json('winkdex.com', "/api/v0/price") + except SSLError: + print("SSL Error when accesing winkdex") + return except Exception: return quote_currencies = {"USD": 0.0} @@ -162,6 +193,9 @@ class Exchanger(threading.Thread): def update_cv(self): try: jsonresp = self.get_json('www.cavirtex.com', "/api/CAD/ticker.json") + except SSLError: + print("SSL Error when accesing cavirtex") + return except Exception: return quote_currencies = {"CAD": 0.0} @@ -177,6 +211,9 @@ class Exchanger(threading.Thread): def update_bm(self): try: jsonresp = self.get_json('www.bitmarket.pl', "/json/BTCPLN/ticker.json") + except SSLError: + print("SSL Error when accesing bitmarket") + return except Exception: return quote_currencies = {"PLN": 0.0} @@ -192,6 +229,9 @@ class Exchanger(threading.Thread): def update_bx(self): try: jsonresp = self.get_json('pln.bitcurex.com', "/data/ticker.json") + except SSLError: + print("SSL Error when accesing bitcurex") + return except Exception: return quote_currencies = {"PLN": 0.0} @@ -207,6 +247,9 @@ class Exchanger(threading.Thread): def update_CNY(self): try: jsonresp = self.get_json('data.btcchina.com', "/data/ticker") + except SSLError: + print("SSL Error when accesing btcchina") + return except Exception: return quote_currencies = {"CNY": 0.0} @@ -222,6 +265,9 @@ class Exchanger(threading.Thread): def update_bp(self): try: jsonresp = self.get_json('bitpay.com', "/api/rates") + except SSLError: + print("SSL Error when accesing bitpay") + return except Exception: return quote_currencies = {} @@ -237,6 +283,9 @@ class Exchanger(threading.Thread): def update_cb(self): try: jsonresp = self.get_json('coinbase.com', "/api/v1/currencies/exchange_rates") + except SSLError: + print("SSL Error when accesing coinbase") + return except Exception: return @@ -255,6 +304,9 @@ class Exchanger(threading.Thread): def update_bc(self): try: jsonresp = self.get_json('blockchain.info', "/ticker") + except SSLError: + print("SSL Error when accesing blockchain") + return except Exception: return quote_currencies = {} @@ -270,6 +322,9 @@ class Exchanger(threading.Thread): def update_lb(self): try: jsonresp = self.get_json('localbitcoins.com', "/bitcoinaverage/ticker-all-currencies/") + except SSLError: + print("SSL Error when accesing localbitcoins") + return except Exception: return quote_currencies = {} @@ -285,23 +340,52 @@ class Exchanger(threading.Thread): def update_bv(self): try: - jsonresp = self.get_json('api.bitcoinvenezuela.com', "/") + jsonresp = self.get_json_insecure('api.bitcoinvenezuela.com', "/") + print("**WARNING**: update_bv is using an insecure connection, shouldn't be used on production") + except SSLError: + print("SSL Error when accesing bitcoinvenezuela") + return except Exception: return + quote_currencies = {} try: for r in jsonresp["BTC"]: quote_currencies[r] = Decimal(jsonresp["BTC"][r]) + with self.lock: self.quote_currencies = quote_currencies except KeyError: - pass + print ("KeyError") self.parent.set_currencies(quote_currencies) - + + def update_bpl(self): + try: + jsonresp = self.get_json_insecure('btcparalelo.com', "/api/price") + print("**WARNING**: update_bpl is using an insecure connection, shouldn't be used on production") + except SSLError: + print("SSL Error when accesing btcparalelo") + return + except Exception: + return + + + quote_currencies = {} + try: + quote_currencies = {"VEF": Decimal(jsonresp["price"])} + with self.lock: + self.quote_currencies = quote_currencies + except KeyError: + print ("KeyError") + self.parent.set_currencies(quote_currencies) + def update_ba(self): try: jsonresp = self.get_json('api.bitcoinaverage.com', "/ticker/global/all") + except SSLError: + print("SSL Error when accesing bitcoinaverage") + return except Exception: return quote_currencies = {} @@ -411,7 +495,6 @@ class Plugin(BasePlugin): @hook def load_wallet(self, wallet): - self.wallet = wallet tx_list = {} for item in self.wallet.get_tx_history(self.wallet.storage.get("current_account", None)): tx_hash, conf, is_mine, value, fee, balance, timestamp = item @@ -471,6 +554,8 @@ class Plugin(BasePlugin): return if not self.resp_hist: return + if not self.wallet: + return self.win.is_edit = True self.win.history_list.setColumnCount(6) diff --git a/plugins/trezor.py b/plugins/trezor.py index 1048b6e0..0f47cd05 100644 --- a/plugins/trezor.py +++ b/plugins/trezor.py @@ -96,6 +96,7 @@ class Plugin(BasePlugin): print_error("trezor: clear session") if self.wallet and self.wallet.client: self.wallet.client.clear_session() + self.wallet.client.transport.close() @hook def load_wallet(self, wallet): @@ -131,6 +132,10 @@ class Plugin(BasePlugin): self.wallet.trezor_sign(tx) except Exception as e: tx.error = str(e) + @hook + def receive_menu(self, menu, addrs): + if not self.wallet.is_watching_only() and self.wallet.atleast_version(1, 3) and len(addrs) == 1: + menu.addAction(_("Show on TREZOR"), lambda: self.wallet.show_address(addrs[0])) def settings_widget(self, window): return EnterButton(_('Settings'), self.settings_dialog) @@ -212,15 +217,22 @@ class TrezorWallet(BIP32_HD_Wallet): except: give_error('Could not connect to your Trezor. Please verify the cable is connected and that no other app is using it.') self.client = QtGuiTrezorClient(self.transport) - if (self.client.features.major_version == 1 and self.client.features.minor_version < 2) or (self.client.features.major_version == 1 and self.client.features.minor_version == 2 and self.client.features.patch_version < 1): - give_error('Outdated Trezor firmware. Please update the firmware from https://www.mytrezor.com') self.client.set_tx_api(self) #self.client.clear_session()# TODO Doesn't work with firmware 1.1, returns proto.Failure self.client.bad = False self.device_checked = False self.proper_device = False + if not self.atleast_version(1, 2, 1): + give_error('Outdated Trezor firmware. Please update the firmware from https://www.mytrezor.com') return self.client + def compare_version(self, major, minor=0, patch=0): + features = self.get_client().features + return cmp([features.major_version, features.minor_version, features.patch_version], [major, minor, patch]) + + def atleast_version(self, major, minor=0, patch=0): + return self.compare_version(major, minor, patch) >= 0 + def address_id(self, address): account_id, (change, address_index) = self.get_address_index(address) return "44'/0'/%s'/%d/%d" % (account_id, change, address_index) @@ -277,6 +289,21 @@ class TrezorWallet(BIP32_HD_Wallet): # twd.emit(SIGNAL('trezor_done')) #return str(decrypted_msg) + def show_address(self, address): + if not self.check_proper_device(): + give_error('Wrong device or password') + try: + address_path = self.address_id(address) + address_n = self.get_client().expand_path(address_path) + except Exception, e: + give_error(e) + try: + self.get_client().get_address('Bitcoin', address_n, True) + except Exception, e: + give_error(e) + finally: + twd.emit(SIGNAL('trezor_done')) + def sign_message(self, address, message, password): if not self.check_proper_device(): give_error('Wrong device or password') @@ -426,6 +453,8 @@ class TrezorQtGuiMixin(object): message = "Confirm transaction fee on Trezor device to continue" elif msg.code == 7: message = "Confirm message to sign on Trezor device to continue" + elif msg.code == 10: + message = "Confirm address on Trezor device to continue" else: message = "Check Trezor device to continue" twd.start(message) diff --git a/scripts/bip70 b/scripts/bip70 new file mode 100755 index 00000000..f7563eb9 --- /dev/null +++ b/scripts/bip70 @@ -0,0 +1,59 @@ +# create a BIP70 payment request signed with a certificate + +import tlslite +import time +import hashlib + +from electrum import paymentrequest_pb2 as pb2 +from electrum.transaction import Transaction +from electrum import bitcoin +from electrum import x509 + + +chain_file = 'mychain.pem' +cert_file = 'mycert.pem' +amount = 1000000 +address = "18U5kpCAU4s8weFF8Ps5n8HAfpdUjDVF64" +memo = "blah" +out_file = "payreq" + + +with open(chain_file, 'r') as f: + chain = tlslite.X509CertChain() + chain.parsePemList(f.read()) + +certificates = pb2.X509Certificates() +certificates.certificate.extend(map(lambda x: str(x.bytes), chain.x509List)) + +with open(cert_file, 'r') as f: + rsakey = tlslite.utils.python_rsakey.Python_RSAKey.parsePEM(f.read()) + + +def make_payment_request(amount, script, memo): + """Generates a http PaymentRequest object""" + pd = pb2.PaymentDetails() + pd.outputs.add(amount=amount, script=script) + now = int(time.time()) + pd.time = now + pd.expires = now + 15*60 + pd.memo = memo + pd.payment_url = 'http://payment_ack.url' + pr = pb2.PaymentRequest() + pr.serialized_payment_details = pd.SerializeToString() + pr.pki_type = 'x509+sha256' + pr.pki_data = certificates.SerializeToString() + pr.signature = '' + msgBytes = bytearray(pr.SerializeToString()) + hashBytes = bytearray(hashlib.sha256(msgBytes).digest()) + sig = rsakey.sign(x509.PREFIX_RSA_SHA256 + hashBytes) + pr.signature = bytes(sig) + return pr.SerializeToString() + + +script = Transaction.pay_script('address', address).decode('hex') + +pr_string = make_payment_request(amount, script, memo) +with open(out_file,'wb') as f: + f.write(pr_string) + +print "Payment request was written to file '%s'"%out_file diff --git a/scripts/estimate_fee b/scripts/estimate_fee new file mode 100755 index 00000000..aa8a2f05 --- /dev/null +++ b/scripts/estimate_fee @@ -0,0 +1,9 @@ +#!/usr/bin/env python +import util, json +peers = util.get_peers() +results = util.send_request(peers, {'method':'blockchain.estimatefee','params':[1]}) +print json.dumps(results, indent=4) + + + +