integrate electrum-3.0.6 changes

This commit is contained in:
zebra-lucky 2018-04-06 02:42:04 +03:00
parent a3aa9f4015
commit 184a9d68d8
16 changed files with 113 additions and 48 deletions

View File

@ -1,3 +1,7 @@
# Release 3.0.6 :
* Integrate changes from Electrum-3.0.6
# Release 3.0.5 : (Security update) # Release 3.0.5 : (Security update)
This is a follow-up to the 3.0.4 release, which did not completely fix This is a follow-up to the 3.0.4 release, which did not completely fix

View File

@ -278,6 +278,7 @@ def run_offline_command(config, config_options):
# arguments passed to function # arguments passed to function
args = [config.get(x) for x in cmd.params] args = [config.get(x) for x in cmd.params]
# decode json arguments # decode json arguments
if cmdname not in ('setconfig',):
args = list(map(json_decode, args)) args = list(map(json_decode, args))
# options # options
kwargs = {} kwargs = {}

View File

@ -923,6 +923,10 @@ class ElectrumWindow(App):
return return
if not self.wallet.can_export(): if not self.wallet.can_export():
return return
try:
key = str(self.wallet.export_private_key(addr, password)[0]) key = str(self.wallet.export_private_key(addr, password)[0])
pk_label.data = key pk_label.data = key
except InvalidPassword:
self.show_error("Invalid PIN")
return
self.protected(_("Enter your PIN code in order to decrypt your private key"), show_private_key, (addr, pk_label)) self.protected(_("Enter your PIN code in order to decrypt your private key"), show_private_key, (addr, pk_label))

View File

@ -93,6 +93,6 @@ class AddressDialog(WindowModalDialog):
def show_qr(self): def show_qr(self):
text = self.address text = self.address
try: try:
self.parent.show_qrcode(text, 'Address') self.parent.show_qrcode(text, 'Address', parent=self)
except Exception as e: except Exception as e:
self.show_message(str(e)) self.show_message(str(e))

View File

@ -2135,6 +2135,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.show_message(_("This is a watching-only wallet")) self.show_message(_("This is a watching-only wallet"))
return return
if isinstance(self.wallet, Multisig_Wallet):
self.show_message(_('WARNING: This is a multi-signature wallet.') + '\n' +
_('It can not be "backed up" by simply exporting these private keys.'))
d = WindowModalDialog(self, _('Private keys')) d = WindowModalDialog(self, _('Private keys'))
d.setMinimumSize(850, 300) d.setMinimumSize(850, 300)
vbox = QVBoxLayout(d) vbox = QVBoxLayout(d)
@ -2160,14 +2164,16 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
private_keys = {} private_keys = {}
addresses = self.wallet.get_addresses() addresses = self.wallet.get_addresses()
done = False done = False
cancelled = False
def privkeys_thread(): def privkeys_thread():
for addr in addresses: for addr in addresses:
time.sleep(0.1) time.sleep(0.1)
if done: if done or cancelled:
break break
privkey = self.wallet.export_private_key(addr, password)[0] privkey = self.wallet.export_private_key(addr, password)[0]
private_keys[addr] = privkey private_keys[addr] = privkey
self.computing_privkeys_signal.emit() self.computing_privkeys_signal.emit()
if not cancelled:
self.computing_privkeys_signal.disconnect() self.computing_privkeys_signal.disconnect()
self.show_privkeys_signal.emit() self.show_privkeys_signal.emit()
@ -2176,9 +2182,20 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
e.setText(s) e.setText(s)
b.setEnabled(True) b.setEnabled(True)
self.show_privkeys_signal.disconnect() self.show_privkeys_signal.disconnect()
nonlocal done
done = True
def on_dialog_closed(*args):
nonlocal done
nonlocal cancelled
if not done:
cancelled = True
self.computing_privkeys_signal.disconnect()
self.show_privkeys_signal.disconnect()
self.computing_privkeys_signal.connect(lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses)))) self.computing_privkeys_signal.connect(lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
self.show_privkeys_signal.connect(show_privkeys) self.show_privkeys_signal.connect(show_privkeys)
d.finished.connect(on_dialog_closed)
threading.Thread(target=privkeys_thread).start() threading.Thread(target=privkeys_thread).start()
if not d.exec_(): if not d.exec_():
@ -2549,7 +2566,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
unit_combo = QComboBox() unit_combo = QComboBox()
unit_combo.addItems(units) unit_combo.addItems(units)
unit_combo.setCurrentIndex(units.index(self.base_unit())) unit_combo.setCurrentIndex(units.index(self.base_unit()))
def on_unit(x): def on_unit(x, nz):
unit_result = units[unit_combo.currentIndex()] unit_result = units[unit_combo.currentIndex()]
if self.base_unit() == unit_result: if self.base_unit() == unit_result:
return return
@ -2564,13 +2581,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
else: else:
raise Exception('Unknown base unit') raise Exception('Unknown base unit')
self.config.set_key('decimal_point', self.decimal_point, True) self.config.set_key('decimal_point', self.decimal_point, True)
nz.setMaximum(self.decimal_point)
self.history_list.update() self.history_list.update()
self.request_list.update() self.request_list.update()
self.address_list.update() self.address_list.update()
for edit, amount in zip(edits, amounts): for edit, amount in zip(edits, amounts):
edit.setAmount(amount) edit.setAmount(amount)
self.update_status() self.update_status()
unit_combo.currentIndexChanged.connect(on_unit) unit_combo.currentIndexChanged.connect(lambda x: on_unit(x, nz))
gui_widgets.append((unit_label, unit_combo)) gui_widgets.append((unit_label, unit_combo))
block_explorers = sorted(util.block_explorer_info().keys()) block_explorers = sorted(util.block_explorer_info().keys())

View File

@ -113,8 +113,7 @@ class QRDialog(WindowModalDialog):
def copy_to_clipboard(): def copy_to_clipboard():
p = qscreen.grabWindow(qrw.winId()) p = qscreen.grabWindow(qrw.winId())
p.save(filename, 'png') QApplication.clipboard().setPixmap(p)
QApplication.clipboard().setImage(QImage(filename))
self.show_message(_("QR code copied to clipboard")) self.show_message(_("QR code copied to clipboard"))
b = QPushButton(_("Copy")) b = QPushButton(_("Copy"))

View File

@ -85,12 +85,11 @@ class ElectrumGui:
delta = (80 - sum(width) - 4)/3 delta = (80 - sum(width) - 4)/3
format_str = "%"+"%d"%width[0]+"s"+"%"+"%d"%(width[1]+delta)+"s"+"%" \ format_str = "%"+"%d"%width[0]+"s"+"%"+"%d"%(width[1]+delta)+"s"+"%" \
+ "%d"%(width[2]+delta)+"s"+"%"+"%d"%(width[3]+delta)+"s" + "%d"%(width[2]+delta)+"s"+"%"+"%d"%(width[3]+delta)+"s"
b = 0
messages = [] messages = []
for item in self.wallet.get_history(): for item in self.wallet.get_history():
tx_hash, confirmations, value, timestamp, balance = item tx_hash, height, conf, timestamp, delta, balance = item
if confirmations: if conf:
try: try:
time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3] time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
except Exception: except Exception:
@ -99,7 +98,7 @@ class ElectrumGui:
time_str = 'unconfirmed' time_str = 'unconfirmed'
label = self.wallet.get_label(tx_hash) label = self.wallet.get_label(tx_hash)
messages.append( format_str%( time_str, label, format_satoshis(value, whitespaces=True), format_satoshis(balance, whitespaces=True) ) ) messages.append( format_str%( time_str, label, format_satoshis(delta, whitespaces=True), format_satoshis(balance, whitespaces=True) ) )
self.print_list(messages[::-1], format_str%( _("Date"), _("Description"), _("Amount"), _("Balance"))) self.print_list(messages[::-1], format_str%( _("Date"), _("Description"), _("Amount"), _("Balance")))
@ -149,12 +148,13 @@ class ElectrumGui:
for i, x in enumerate( self.wallet.network.banner.split('\n') ): for i, x in enumerate( self.wallet.network.banner.split('\n') ):
print( x ) print( x )
def print_list(self, list, firstline): def print_list(self, lst, firstline):
self.maxpos = len(list) lst = list(lst)
self.maxpos = len(lst)
if not self.maxpos: return if not self.maxpos: return
print(firstline) print(firstline)
for i in range(self.maxpos): for i in range(self.maxpos):
msg = list[i] if i < len(list) else "" msg = lst[i] if i < len(lst) else ""
print(msg) print(msg)
@ -176,7 +176,7 @@ class ElectrumGui:
print(_('Invalid Fee')) print(_('Invalid Fee'))
return return
if self.wallet.use_encryption: if self.wallet.has_password():
password = self.password_dialog() password = self.password_dialog()
if not password: if not password:
return return

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -34,7 +34,7 @@ from functools import wraps
from decimal import Decimal from decimal import Decimal
from .import util from .import util
from .util import bfh, bh2u, format_satoshis from .util import bfh, bh2u, format_satoshis, json_decode
from .import bitcoin from .import bitcoin
from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
from .i18n import _ from .i18n import _
@ -151,10 +151,8 @@ class Commands:
@command('') @command('')
def setconfig(self, key, value): def setconfig(self, key, value):
"""Set a configuration variable. 'value' may be a string or a Python expression.""" """Set a configuration variable. 'value' may be a string or a Python expression."""
try: if key not in ('rpcuser', 'rpcpassword'):
value = ast.literal_eval(value) value = json_decode(value)
except:
pass
self.config.set_key(key, value) self.config.set_key(key, value)
return True return True

View File

@ -5,6 +5,7 @@ import sys
from threading import Thread from threading import Thread
import time import time
import csv import csv
import decimal
from decimal import Decimal from decimal import Decimal
from .bitcoin import COIN from .bitcoin import COIN
@ -197,7 +198,11 @@ class FxThread(ThreadJob):
def ccy_amount_str(self, amount, commas): def ccy_amount_str(self, amount, commas):
prec = CCY_PRECISIONS.get(self.ccy, 2) prec = CCY_PRECISIONS.get(self.ccy, 2)
fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec)) fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec))
return fmt_str.format(round(amount, prec)) try:
rounded_amount = round(amount, prec)
except decimal.InvalidOperation:
rounded_amount = amount
return fmt_str.format(rounded_amount)
def run(self): def run(self):
# This runs from the plugins thread which catches exceptions # This runs from the plugins thread which catches exceptions

View File

@ -171,6 +171,9 @@ class Mnemonic(object):
n_custom = int(math.ceil(math.log(custom_entropy, 2))) n_custom = int(math.ceil(math.log(custom_entropy, 2)))
n = max(16, num_bits - n_custom) n = max(16, num_bits - n_custom)
print_error("make_seed", prefix, "adding %d bits"%n) print_error("make_seed", prefix, "adding %d bits"%n)
my_entropy = 1
while my_entropy < pow(2, n - bpw):
# try again if seed would not contain enough words
my_entropy = ecdsa.util.randrange(pow(2, n)) my_entropy = ecdsa.util.randrange(pow(2, n))
nonce = 0 nonce = 0
while True: while True:

View File

@ -45,6 +45,14 @@ class SerializationError(Exception):
""" Thrown when there's a problem deserializing or serializing """ """ Thrown when there's a problem deserializing or serializing """
class UnknownTxinType(Exception):
pass
class NotRecognizedRedeemScript(Exception):
pass
class BCDataStream(object): class BCDataStream(object):
def __init__(self): def __init__(self):
self.input = None self.input = None
@ -302,7 +310,8 @@ def parse_scriptSig(d, _bytes):
if match_decoded(decoded, match): if match_decoded(decoded, match):
item = decoded[0][1] item = decoded[0][1]
if item[0] != 0: if item[0] != 0:
# payto_pubkey # assert item[0] == 0x30
# pay-to-pubkey
d['type'] = 'p2pk' d['type'] = 'p2pk'
d['address'] = "(pubkey)" d['address'] = "(pubkey)"
d['signatures'] = [bh2u(item)] d['signatures'] = [bh2u(item)]
@ -358,7 +367,7 @@ def parse_redeemScript(s):
match_multisig = [ op_m ] + [opcodes.OP_PUSHDATA4]*n + [ op_n, opcodes.OP_CHECKMULTISIG ] match_multisig = [ op_m ] + [opcodes.OP_PUSHDATA4]*n + [ op_n, opcodes.OP_CHECKMULTISIG ]
if not match_decoded(dec2, match_multisig): if not match_decoded(dec2, match_multisig):
print_error("cannot find address in input script", bh2u(s)) print_error("cannot find address in input script", bh2u(s))
return raise NotRecognizedRedeemScript()
x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]] x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]]
pubkeys = [safe_parse_pubkey(x) for x in x_pubkeys] pubkeys = [safe_parse_pubkey(x) for x in x_pubkeys]
redeemScript = multisig_script(pubkeys, m) redeemScript = multisig_script(pubkeys, m)

View File

@ -1,4 +1,4 @@
ELECTRUM_VERSION = '3.0.5' # version of the client package ELECTRUM_VERSION = '3.0.6' # version of the client package
PROTOCOL_VERSION = '1.2' # protocol version requested PROTOCOL_VERSION = '1.2' # protocol version requested
# The hash of the mnemonic seed must begin with this # The hash of the mnemonic seed must begin with this

View File

@ -360,6 +360,7 @@ class Abstract_Wallet(PrintError):
def add_unverified_tx(self, tx_hash, tx_height): def add_unverified_tx(self, tx_hash, tx_height):
if tx_height == 0 and tx_hash in self.verified_tx: if tx_height == 0 and tx_hash in self.verified_tx:
self.verified_tx.pop(tx_hash) self.verified_tx.pop(tx_hash)
if self.verifier:
self.verifier.merkle_roots.pop(tx_hash, None) self.verifier.merkle_roots.pop(tx_hash, None)
# tx will be verified only if height > 0 # tx will be verified only if height > 0

View File

@ -84,7 +84,8 @@ class WsClientThread(util.DaemonThread):
l = self.subscriptions.get(addr, []) l = self.subscriptions.get(addr, [])
l.append((ws, amount)) l.append((ws, amount))
self.subscriptions[addr] = l self.subscriptions[addr] = l
self.network.send([('blockchain.address.subscribe', [addr])], self.response_queue.put) h = self.network.addr_to_scripthash(addr)
self.network.send([('blockchain.scripthash.subscribe', [h])], self.response_queue.put)
def run(self): def run(self):
@ -100,10 +101,13 @@ class WsClientThread(util.DaemonThread):
result = r.get('result') result = r.get('result')
if result is None: if result is None:
continue continue
if method == 'blockchain.address.subscribe': if method == 'blockchain.scripthash.subscribe':
self.network.send([('blockchain.address.get_balance', params)], self.response_queue.put) self.network.send([('blockchain.scripthash.get_balance', params)], self.response_queue.put)
elif method == 'blockchain.address.get_balance': elif method == 'blockchain.scripthash.get_balance':
addr = params[0] h = params[0]
addr = self.network.h2addr.get(h, None)
if addr is None:
util.print_error("can't find address for scripthash: %s" % h)
l = self.subscriptions.get(addr, []) l = self.subscriptions.get(addr, [])
for ws, amount in l: for ws, amount in l:
if not ws.closed: if not ws.closed:

View File

@ -33,6 +33,10 @@ try:
except ImportError: except ImportError:
BTCHIP = False BTCHIP = False
MSG_NEEDS_FW_UPDATE_GENERIC = _('Firmware version too old. Please update at') + \
' https://www.ledgerwallet.com'
class Ledger_Client(): class Ledger_Client():
def __init__(self, hidDevice): def __init__(self, hidDevice):
self.dongleObject = btchip(hidDevice) self.dongleObject = btchip(hidDevice)
@ -56,6 +60,21 @@ class Ledger_Client():
def i4b(self, x): def i4b(self, x):
return pack('>I', x) return pack('>I', x)
def test_pin_unlocked(func):
"""Function decorator to test the Ledger for being unlocked, and if not,
raise a human-readable exception.
"""
def catch_exception(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except BTChipException as e:
if e.sw == 0x6982:
raise Exception(_('Your Ledger is locked. Please unlock it.'))
else:
raise
return catch_exception
@test_pin_unlocked
def get_xpub(self, bip32_path, xtype): def get_xpub(self, bip32_path, xtype):
self.checkDevice() self.checkDevice()
# bip32_path is of the form 44'/133'/1' # bip32_path is of the form 44'/133'/1'
@ -72,7 +91,7 @@ class Ledger_Client():
if len(splitPath) > 1: if len(splitPath) > 1:
prevPath = "/".join(splitPath[0:len(splitPath) - 1]) prevPath = "/".join(splitPath[0:len(splitPath) - 1])
nodeData = self.dongleObject.getWalletPublicKey(prevPath) nodeData = self.dongleObject.getWalletPublicKey(prevPath)
publicKey = compress_public_key(nodeData['publicKey'])# publicKey = compress_public_key(nodeData['publicKey'])
h = hashlib.new('ripemd160') h = hashlib.new('ripemd160')
h.update(hashlib.sha256(publicKey).digest()) h.update(hashlib.sha256(publicKey).digest())
fingerprint = unpack(">I", h.digest()[0:4])[0] fingerprint = unpack(">I", h.digest()[0:4])[0]
@ -117,7 +136,7 @@ class Ledger_Client():
if not checkFirmware(firmware): if not checkFirmware(firmware):
self.dongleObject.dongle.close() self.dongleObject.dongle.close()
raise Exception("HW1 firmware version too old. Please update at https://www.ledgerwallet.com") raise Exception(MSG_NEEDS_FW_UPDATE_GENERIC)
try: try:
self.dongleObject.getOperationMode() self.dongleObject.getOperationMode()
except BTChipException as e: except BTChipException as e: