integrate electrum-3.0.6 changes
This commit is contained in:
parent
a3aa9f4015
commit
184a9d68d8
|
@ -1,3 +1,7 @@
|
|||
# Release 3.0.6 :
|
||||
|
||||
* Integrate changes from Electrum-3.0.6
|
||||
|
||||
# Release 3.0.5 : (Security update)
|
||||
|
||||
This is a follow-up to the 3.0.4 release, which did not completely fix
|
||||
|
|
|
@ -278,6 +278,7 @@ def run_offline_command(config, config_options):
|
|||
# arguments passed to function
|
||||
args = [config.get(x) for x in cmd.params]
|
||||
# decode json arguments
|
||||
if cmdname not in ('setconfig',):
|
||||
args = list(map(json_decode, args))
|
||||
# options
|
||||
kwargs = {}
|
||||
|
|
|
@ -923,6 +923,10 @@ class ElectrumWindow(App):
|
|||
return
|
||||
if not self.wallet.can_export():
|
||||
return
|
||||
try:
|
||||
key = str(self.wallet.export_private_key(addr, password)[0])
|
||||
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))
|
||||
|
|
|
@ -93,6 +93,6 @@ class AddressDialog(WindowModalDialog):
|
|||
def show_qr(self):
|
||||
text = self.address
|
||||
try:
|
||||
self.parent.show_qrcode(text, 'Address')
|
||||
self.parent.show_qrcode(text, 'Address', parent=self)
|
||||
except Exception as e:
|
||||
self.show_message(str(e))
|
||||
|
|
|
@ -2135,6 +2135,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
self.show_message(_("This is a watching-only wallet"))
|
||||
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.setMinimumSize(850, 300)
|
||||
vbox = QVBoxLayout(d)
|
||||
|
@ -2160,14 +2164,16 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
private_keys = {}
|
||||
addresses = self.wallet.get_addresses()
|
||||
done = False
|
||||
cancelled = False
|
||||
def privkeys_thread():
|
||||
for addr in addresses:
|
||||
time.sleep(0.1)
|
||||
if done:
|
||||
if done or cancelled:
|
||||
break
|
||||
privkey = self.wallet.export_private_key(addr, password)[0]
|
||||
private_keys[addr] = privkey
|
||||
self.computing_privkeys_signal.emit()
|
||||
if not cancelled:
|
||||
self.computing_privkeys_signal.disconnect()
|
||||
self.show_privkeys_signal.emit()
|
||||
|
||||
|
@ -2176,9 +2182,20 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
e.setText(s)
|
||||
b.setEnabled(True)
|
||||
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.show_privkeys_signal.connect(show_privkeys)
|
||||
d.finished.connect(on_dialog_closed)
|
||||
threading.Thread(target=privkeys_thread).start()
|
||||
|
||||
if not d.exec_():
|
||||
|
@ -2549,7 +2566,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
unit_combo = QComboBox()
|
||||
unit_combo.addItems(units)
|
||||
unit_combo.setCurrentIndex(units.index(self.base_unit()))
|
||||
def on_unit(x):
|
||||
def on_unit(x, nz):
|
||||
unit_result = units[unit_combo.currentIndex()]
|
||||
if self.base_unit() == unit_result:
|
||||
return
|
||||
|
@ -2564,13 +2581,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||
else:
|
||||
raise Exception('Unknown base unit')
|
||||
self.config.set_key('decimal_point', self.decimal_point, True)
|
||||
nz.setMaximum(self.decimal_point)
|
||||
self.history_list.update()
|
||||
self.request_list.update()
|
||||
self.address_list.update()
|
||||
for edit, amount in zip(edits, amounts):
|
||||
edit.setAmount(amount)
|
||||
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))
|
||||
|
||||
block_explorers = sorted(util.block_explorer_info().keys())
|
||||
|
|
|
@ -113,8 +113,7 @@ class QRDialog(WindowModalDialog):
|
|||
|
||||
def copy_to_clipboard():
|
||||
p = qscreen.grabWindow(qrw.winId())
|
||||
p.save(filename, 'png')
|
||||
QApplication.clipboard().setImage(QImage(filename))
|
||||
QApplication.clipboard().setPixmap(p)
|
||||
self.show_message(_("QR code copied to clipboard"))
|
||||
|
||||
b = QPushButton(_("Copy"))
|
||||
|
|
16
gui/stdio.py
16
gui/stdio.py
|
@ -85,12 +85,11 @@ class ElectrumGui:
|
|||
delta = (80 - sum(width) - 4)/3
|
||||
format_str = "%"+"%d"%width[0]+"s"+"%"+"%d"%(width[1]+delta)+"s"+"%" \
|
||||
+ "%d"%(width[2]+delta)+"s"+"%"+"%d"%(width[3]+delta)+"s"
|
||||
b = 0
|
||||
messages = []
|
||||
|
||||
for item in self.wallet.get_history():
|
||||
tx_hash, confirmations, value, timestamp, balance = item
|
||||
if confirmations:
|
||||
tx_hash, height, conf, timestamp, delta, balance = item
|
||||
if conf:
|
||||
try:
|
||||
time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
|
||||
except Exception:
|
||||
|
@ -99,7 +98,7 @@ class ElectrumGui:
|
|||
time_str = 'unconfirmed'
|
||||
|
||||
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")))
|
||||
|
||||
|
@ -149,12 +148,13 @@ class ElectrumGui:
|
|||
for i, x in enumerate( self.wallet.network.banner.split('\n') ):
|
||||
print( x )
|
||||
|
||||
def print_list(self, list, firstline):
|
||||
self.maxpos = len(list)
|
||||
def print_list(self, lst, firstline):
|
||||
lst = list(lst)
|
||||
self.maxpos = len(lst)
|
||||
if not self.maxpos: return
|
||||
print(firstline)
|
||||
for i in range(self.maxpos):
|
||||
msg = list[i] if i < len(list) else ""
|
||||
msg = lst[i] if i < len(lst) else ""
|
||||
print(msg)
|
||||
|
||||
|
||||
|
@ -176,7 +176,7 @@ class ElectrumGui:
|
|||
print(_('Invalid Fee'))
|
||||
return
|
||||
|
||||
if self.wallet.use_encryption:
|
||||
if self.wallet.has_password():
|
||||
password = self.password_dialog()
|
||||
if not password:
|
||||
return
|
||||
|
|
BIN
icons/unpaid.png
BIN
icons/unpaid.png
Binary file not shown.
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 23 KiB |
|
@ -34,7 +34,7 @@ from functools import wraps
|
|||
from decimal import Decimal
|
||||
|
||||
from .import util
|
||||
from .util import bfh, bh2u, format_satoshis
|
||||
from .util import bfh, bh2u, format_satoshis, json_decode
|
||||
from .import bitcoin
|
||||
from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
|
||||
from .i18n import _
|
||||
|
@ -151,10 +151,8 @@ class Commands:
|
|||
@command('')
|
||||
def setconfig(self, key, value):
|
||||
"""Set a configuration variable. 'value' may be a string or a Python expression."""
|
||||
try:
|
||||
value = ast.literal_eval(value)
|
||||
except:
|
||||
pass
|
||||
if key not in ('rpcuser', 'rpcpassword'):
|
||||
value = json_decode(value)
|
||||
self.config.set_key(key, value)
|
||||
return True
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import sys
|
|||
from threading import Thread
|
||||
import time
|
||||
import csv
|
||||
import decimal
|
||||
from decimal import Decimal
|
||||
|
||||
from .bitcoin import COIN
|
||||
|
@ -197,7 +198,11 @@ class FxThread(ThreadJob):
|
|||
def ccy_amount_str(self, amount, commas):
|
||||
prec = CCY_PRECISIONS.get(self.ccy, 2)
|
||||
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):
|
||||
# This runs from the plugins thread which catches exceptions
|
||||
|
|
|
@ -171,6 +171,9 @@ class Mnemonic(object):
|
|||
n_custom = int(math.ceil(math.log(custom_entropy, 2)))
|
||||
n = max(16, num_bits - n_custom)
|
||||
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))
|
||||
nonce = 0
|
||||
while True:
|
||||
|
|
|
@ -45,6 +45,14 @@ class SerializationError(Exception):
|
|||
""" Thrown when there's a problem deserializing or serializing """
|
||||
|
||||
|
||||
class UnknownTxinType(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NotRecognizedRedeemScript(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BCDataStream(object):
|
||||
def __init__(self):
|
||||
self.input = None
|
||||
|
@ -302,7 +310,8 @@ def parse_scriptSig(d, _bytes):
|
|||
if match_decoded(decoded, match):
|
||||
item = decoded[0][1]
|
||||
if item[0] != 0:
|
||||
# payto_pubkey
|
||||
# assert item[0] == 0x30
|
||||
# pay-to-pubkey
|
||||
d['type'] = 'p2pk'
|
||||
d['address'] = "(pubkey)"
|
||||
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 ]
|
||||
if not match_decoded(dec2, match_multisig):
|
||||
print_error("cannot find address in input script", bh2u(s))
|
||||
return
|
||||
raise NotRecognizedRedeemScript()
|
||||
x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]]
|
||||
pubkeys = [safe_parse_pubkey(x) for x in x_pubkeys]
|
||||
redeemScript = multisig_script(pubkeys, m)
|
||||
|
|
|
@ -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
|
||||
|
||||
# The hash of the mnemonic seed must begin with this
|
||||
|
|
|
@ -360,6 +360,7 @@ class Abstract_Wallet(PrintError):
|
|||
def add_unverified_tx(self, tx_hash, tx_height):
|
||||
if tx_height == 0 and tx_hash in self.verified_tx:
|
||||
self.verified_tx.pop(tx_hash)
|
||||
if self.verifier:
|
||||
self.verifier.merkle_roots.pop(tx_hash, None)
|
||||
|
||||
# tx will be verified only if height > 0
|
||||
|
|
|
@ -84,7 +84,8 @@ class WsClientThread(util.DaemonThread):
|
|||
l = self.subscriptions.get(addr, [])
|
||||
l.append((ws, amount))
|
||||
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):
|
||||
|
@ -100,10 +101,13 @@ class WsClientThread(util.DaemonThread):
|
|||
result = r.get('result')
|
||||
if result is None:
|
||||
continue
|
||||
if method == 'blockchain.address.subscribe':
|
||||
self.network.send([('blockchain.address.get_balance', params)], self.response_queue.put)
|
||||
elif method == 'blockchain.address.get_balance':
|
||||
addr = params[0]
|
||||
if method == 'blockchain.scripthash.subscribe':
|
||||
self.network.send([('blockchain.scripthash.get_balance', params)], self.response_queue.put)
|
||||
elif method == 'blockchain.scripthash.get_balance':
|
||||
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, [])
|
||||
for ws, amount in l:
|
||||
if not ws.closed:
|
||||
|
|
|
@ -33,6 +33,10 @@ try:
|
|||
except ImportError:
|
||||
BTCHIP = False
|
||||
|
||||
MSG_NEEDS_FW_UPDATE_GENERIC = _('Firmware version too old. Please update at') + \
|
||||
' https://www.ledgerwallet.com'
|
||||
|
||||
|
||||
class Ledger_Client():
|
||||
def __init__(self, hidDevice):
|
||||
self.dongleObject = btchip(hidDevice)
|
||||
|
@ -56,6 +60,21 @@ class Ledger_Client():
|
|||
def i4b(self, 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):
|
||||
self.checkDevice()
|
||||
# bip32_path is of the form 44'/133'/1'
|
||||
|
@ -72,7 +91,7 @@ class Ledger_Client():
|
|||
if len(splitPath) > 1:
|
||||
prevPath = "/".join(splitPath[0:len(splitPath) - 1])
|
||||
nodeData = self.dongleObject.getWalletPublicKey(prevPath)
|
||||
publicKey = compress_public_key(nodeData['publicKey'])#
|
||||
publicKey = compress_public_key(nodeData['publicKey'])
|
||||
h = hashlib.new('ripemd160')
|
||||
h.update(hashlib.sha256(publicKey).digest())
|
||||
fingerprint = unpack(">I", h.digest()[0:4])[0]
|
||||
|
@ -117,7 +136,7 @@ class Ledger_Client():
|
|||
|
||||
if not checkFirmware(firmware):
|
||||
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:
|
||||
self.dongleObject.getOperationMode()
|
||||
except BTChipException as e:
|
||||
|
|
Loading…
Reference in New Issue