Compare commits

...

10 Commits

Author SHA1 Message Date
ThomasV
82e88cb89d prepare release 3.0.6 2018-02-03 07:38:36 +01:00
SomberNight
d76f36df70 ledger: mention "bitcoin" app when update is needed 2018-02-02 02:41:41 +01:00
SomberNight
cf82b1d9d5 follow-up 70aa1f1db9 2018-01-31 07:05:29 +01:00
SomberNight
29206852fb follow-up prev commit. better handling of p2sh-segwit. added comment to describe the problem for native segwit. 2018-01-31 07:05:13 +01:00
Neil Booth
f0942a2535 Fix PNG file
Avoids libpng warning: iCCP: known incorrect sRGB profile
2018-01-31 07:04:57 +01:00
SomberNight
7b8b75c7d3 fix #3788
# Conflicts:
#	lib/transaction.py
2018-01-31 07:04:16 +01:00
SomberNight
b0966671d7 fix #3790 2018-01-31 07:02:46 +01:00
SomberNight
64fa7dd6c3 fix #3783 2018-01-31 07:02:31 +01:00
Johann Bauer
d0433c1539 Change number of zero when base unit changes 2018-01-29 20:57:23 +01:00
SomberNight
ff38e90405 revert adding handling of 'blockchain.address.subscribe' in network.py 2018-01-29 20:57:11 +01:00
10 changed files with 106 additions and 35 deletions

View File

@ -1,3 +1,8 @@
# Release 3.0.6 :
* Fix transaction parsing bug #3788
# Release 3.0.5 : (Security update)
This is a follow-up to the 3.0.4 release, which did not completely fix

View File

@ -2605,7 +2605,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
@ -2620,13 +2620,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())

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -5,6 +5,7 @@ import sys
from threading import Thread
import time
import csv
import decimal
from decimal import Decimal
from .bitcoin import COIN
@ -389,7 +390,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

View File

@ -604,9 +604,6 @@ class Network(util.DaemonThread):
elif method == 'blockchain.scripthash.subscribe':
response['params'] = [params[0]] # addr
response['result'] = params[1]
elif method == 'blockchain.address.subscribe':
response['params'] = [params[0]]
response['result'] = params[1]
callbacks = self.subscriptions.get(k, [])
# update cache if it's a subscription

View File

@ -224,6 +224,10 @@ class TestTransaction(unittest.TestCase):
tx = transaction.Transaction('010000000001010d350cefa29138de18a2d63a93cffda63721b07a6ecfa80a902f9514104b55ca0000000000fdffffff012a4a824a00000000160014b869999d342a5d42d6dc7af1efc28456da40297a024730440220475bb55814a52ea1036919e4408218c693b8bf93637b9f54c821b5baa3b846e102207276ed7a79493142c11fb01808a4142bbdd525ae7bdccdf8ecb7b8e3c856b4d90121024cdeaca7a53a7e23a1edbe9260794eaa83063534b5f111ee3c67d8b0cb88f0eec8010000')
self.assertEqual('51087ece75c697cc872d2e643d646b0f3e1f2666fa1820b7bff4343d50dd680e', tx.txid())
def test_txid_input_p2wsh_p2sh_not_multisig(self):
tx = transaction.Transaction('0100000000010160f84fdcda039c3ca1b20038adea2d49a53db92f7c467e8def13734232bb610804000000232200202814720f16329ab81cb8867c4d447bd13255931f23e6655944c9ada1797fcf88ffffffff0ba3dcfc04000000001976a91488124a57c548c9e7b1dd687455af803bd5765dea88acc9f44900000000001976a914da55045a0ccd40a56ce861946d13eb861eb5f2d788ac49825e000000000017a914ca34d4b190e36479aa6e0023cfe0a8537c6aa8dd87680c0d00000000001976a914651102524c424b2e7c44787c4f21e4c54dffafc088acf02fa9000000000017a914ee6c596e6f7066466d778d4f9ba633a564a6e95d874d250900000000001976a9146ca7976b48c04fd23867748382ee8401b1d27c2988acf5119600000000001976a914cf47d5dcdba02fd547c600697097252d38c3214a88ace08a12000000000017a914017bef79d92d5ec08c051786bad317e5dd3befcf87e3d76201000000001976a9148ec1b88b66d142bcbdb42797a0fd402c23e0eec288ac718f6900000000001976a914e66344472a224ce6f843f2989accf435ae6a808988ac65e51300000000001976a914cad6717c13a2079066f876933834210ebbe68c3f88ac0347304402201a4907c4706104320313e182ecbb1b265b2d023a79586671386de86bb47461590220472c3db9fc99a728ebb9b555a72e3481d20b181bd059a9c1acadfb853d90c96c01210338a46f2a54112fef8803c8478bc17e5f8fc6a5ec276903a946c1fafb2e3a8b181976a914eda8660085bf607b82bd18560ca8f3a9ec49178588ac00000000')
self.assertEqual('e9933221a150f78f9f224899f8568ff6422ffcc28ca3d53d87936368ff7c4b1d', tx.txid())
class NetworkMock(object):

View File

@ -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,10 +310,23 @@ def parse_scriptSig(d, _bytes):
if match_decoded(decoded, match):
item = decoded[0][1]
if item[0] == 0:
# segwit embedded into p2sh
# witness version 0
# segwit embedded into p2sh
d['address'] = bitcoin.hash160_to_p2sh(bitcoin.hash_160(item))
d['type'] = 'p2wpkh-p2sh' if len(item) == 22 else 'p2wsh-p2sh'
if len(item) == 22:
d['type'] = 'p2wpkh-p2sh'
elif len(item) == 34:
d['type'] = 'p2wsh-p2sh'
else:
print_error("unrecognized txin type", bh2u(item))
elif opcodes.OP_1 <= item[0] <= opcodes.OP_16:
# segwit embedded into p2sh
# witness version 1-16
pass
else:
# payto_pubkey
# assert item[0] == 0x30
# pay-to-pubkey
d['type'] = 'p2pk'
d['address'] = "(pubkey)"
d['signatures'] = [bh2u(item)]
@ -361,7 +382,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)
@ -430,21 +451,40 @@ def parse_witness(vds, txin):
if n == 0xffffffff:
txin['value'] = vds.read_uint64()
n = vds.read_compact_size()
# now 'n' is the number of items in the witness
w = list(bh2u(vds.read_bytes(vds.read_compact_size())) for i in range(n))
add_w = lambda x: var_int(len(x) // 2) + x
txin['witness'] = var_int(n) + ''.join(add_w(i) for i in w)
# FIXME: witness version > 0 will probably fail here.
# For native segwit, we would need the scriptPubKey of the parent txn
# to determine witness program version, and properly parse the witness.
# In case of p2sh-segwit, we can tell based on the scriptSig in this txn.
# The code below assumes witness version 0.
# p2sh-segwit should work in that case; for native segwit we need to tell
# between p2wpkh and p2wsh; we do this based on number of witness items,
# hence (FIXME) p2wsh with n==2 (maybe n==1 ?) will probably fail.
# If v==0 and n==2, we need parent scriptPubKey to distinguish between p2wpkh and p2wsh.
if txin['type'] == 'coinbase':
pass
elif n > 2:
elif txin['type'] == 'p2wsh-p2sh' or n > 2:
try:
m, n, x_pubkeys, pubkeys, witnessScript = parse_redeemScript(bfh(w[-1]))
except NotRecognizedRedeemScript:
raise UnknownTxinType()
txin['signatures'] = parse_sig(w[1:-1])
m, n, x_pubkeys, pubkeys, witnessScript = parse_redeemScript(bfh(w[-1]))
txin['num_sig'] = m
txin['x_pubkeys'] = x_pubkeys
txin['pubkeys'] = pubkeys
txin['witnessScript'] = witnessScript
else:
elif txin['type'] == 'p2wpkh-p2sh' or n == 2:
txin['num_sig'] = 1
txin['x_pubkeys'] = [w[1]]
txin['pubkeys'] = [safe_parse_pubkey(w[1])]
txin['signatures'] = parse_sig([w[0]])
else:
raise UnknownTxinType()
def parse_output(vds, i):
d = {}
@ -474,7 +514,12 @@ def deserialize(raw):
if is_segwit:
for i in range(n_vin):
txin = d['inputs'][i]
parse_witness(vds, txin)
try:
parse_witness(vds, txin)
except UnknownTxinType:
txin['type'] = 'unknown'
# FIXME: GUI might show 'unknown' address (e.g. for a non-multisig p2wsh)
continue
# segwit-native script
if not txin.get('scriptSig'):
if txin['num_sig'] == 1:
@ -674,7 +719,9 @@ class Transaction:
witness_script = multisig_script(pubkeys, txin['num_sig'])
witness = var_int(n) + '00' + ''.join(add_w(x) for x in sig_list) + add_w(witness_script)
else:
raise BaseException('wrong txin type')
witness = txin.get('witness', None)
if not witness:
raise BaseException('wrong txin type:', txin['type'])
if self.is_txin_complete(txin) or estimate_size:
value_field = ''
else:
@ -682,8 +729,13 @@ class Transaction:
return value_field + witness
@classmethod
def is_segwit_input(self, txin):
return txin['type'] in ['p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh']
def is_segwit_input(cls, txin):
has_nonzero_witness = txin.get('witness', '00') != '00'
return cls.is_segwit_inputtype(txin['type']) or has_nonzero_witness
@classmethod
def is_segwit_inputtype(cls, txin_type):
return txin_type in ('p2wpkh', 'p2wpkh-p2sh', 'p2wsh', 'p2wsh-p2sh')
@classmethod
def input_script(self, txin, estimate_size=False):

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.1' # protocol version requested
# The hash of the mnemonic seed must begin with this

View File

@ -361,7 +361,8 @@ 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)
self.verifier.merkle_roots.pop(tx_hash, None)
if self.verifier:
self.verifier.merkle_roots.pop(tx_hash, None)
# tx will be verified only if height > 0
if tx_hash not in self.verified_tx:

View File

@ -25,6 +25,12 @@ try:
except ImportError:
BTCHIP = False
MSG_NEEDS_FW_UPDATE_GENERIC = _('Firmware version too old. Please update at') + \
' https://www.ledgerwallet.com'
MSG_NEEDS_FW_UPDATE_SEGWIT = _('Firmware version (or "Bitcoin" app) too old for Segwit support. Please update at') + \
' https://www.ledgerwallet.com'
class Ledger_Client():
def __init__(self, hidDevice):
self.dongleObject = btchip(hidDevice)
@ -46,7 +52,7 @@ class Ledger_Client():
return ""
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,
@ -72,9 +78,9 @@ class Ledger_Client():
#self.get_client() # prompt for the PIN before displaying the dialog if necessary
#self.handler.show_message("Computing master public key")
if xtype in ['p2wpkh', 'p2wsh'] and not self.supports_native_segwit():
raise Exception("Firmware version too old for Segwit support. Please update at https://www.ledgerwallet.com")
raise Exception(MSG_NEEDS_FW_UPDATE_SEGWIT)
if xtype in ['p2wpkh-p2sh', 'p2wsh-p2sh'] and not self.supports_segwit():
raise Exception("Firmware version too old for Segwit support. Please update at https://www.ledgerwallet.com")
raise Exception(MSG_NEEDS_FW_UPDATE_SEGWIT)
splitPath = bip32_path.split('/')
if splitPath[0] == 'm':
splitPath = splitPath[1:]
@ -134,7 +140,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:
@ -198,14 +204,14 @@ class Ledger_KeyStore(Hardware_KeyStore):
return obj
def get_derivation(self):
return self.derivation
return self.derivation
def get_client(self):
return self.plugin.get_client(self).dongleObject
def get_client_electrum(self):
return self.plugin.get_client(self)
def give_error(self, message, clear_client = False):
print_error(message)
if not self.signing:
@ -300,12 +306,12 @@ class Ledger_KeyStore(Hardware_KeyStore):
if txin['type'] in ['p2wpkh-p2sh', 'p2wsh-p2sh']:
if not self.get_client_electrum().supports_segwit():
self.give_error("Firmware version too old to support segwit. Please update at https://www.ledgerwallet.com")
self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT)
segwitTransaction = True
if txin['type'] in ['p2wpkh', 'p2wsh']:
if not self.get_client_electrum().supports_native_segwit():
self.give_error("Firmware version too old to support native segwit. Please update at https://www.ledgerwallet.com")
self.give_error(MSG_NEEDS_FW_UPDATE_SEGWIT)
segwitTransaction = True
pubkeys, x_pubkeys = tx.get_sorted_pubkeys(txin)
@ -357,10 +363,10 @@ class Ledger_KeyStore(Hardware_KeyStore):
self.handler.show_message(_("Confirm Transaction on your Ledger device..."))
try:
# Get trusted inputs from the original transactions
for utxo in inputs:
for utxo in inputs:
sequence = int_to_hex(utxo[5], 4)
if segwitTransaction:
txtmp = bitcoinTransaction(bfh(utxo[0]))
txtmp = bitcoinTransaction(bfh(utxo[0]))
tmp = bfh(utxo[3])[::-1]
tmp += bfh(int_to_hex(utxo[1], 4))
tmp += txtmp.outputs[utxo[1]].amount
@ -449,7 +455,7 @@ class LedgerPlugin(HW_PluginBase):
libraries_available = BTCHIP
keystore_class = Ledger_KeyStore
client = None
DEVICE_IDS = [
DEVICE_IDS = [
(0x2581, 0x1807), # HW.1 legacy btchip
(0x2581, 0x2b7c), # HW.1 transitional production
(0x2581, 0x3b7c), # HW.1 ledger production
@ -474,12 +480,12 @@ class LedgerPlugin(HW_PluginBase):
def get_btchip_device(self, device):
ledger = False
if (device.product_key[0] == 0x2581 and device.product_key[1] == 0x3b7c) or (device.product_key[0] == 0x2581 and device.product_key[1] == 0x4b7c) or (device.product_key[0] == 0x2c97):
ledger = True
ledger = True
dev = hid.device()
dev.open_path(device.path)
dev.set_nonblocking(True)
return HIDDongleHIDAPI(dev, ledger, BTCHIP_DEBUG)
def create_client(self, device, handler):
self.handler = handler
@ -488,7 +494,7 @@ class LedgerPlugin(HW_PluginBase):
client = Ledger_Client(client)
return client
def setup_device(self, device_info, wizard):
def setup_device(self, device_info, wizard):
devmgr = self.device_manager()
device_id = device_info.device.id_
client = devmgr.client_by_id(device_id)
@ -509,10 +515,10 @@ class LedgerPlugin(HW_PluginBase):
devmgr = self.device_manager()
handler = keystore.handler
with devmgr.hid_lock:
client = devmgr.client_for_keystore(self, handler, keystore, force_pair)
client = devmgr.client_for_keystore(self, handler, keystore, force_pair)
# returns the client for a given keystore. can use xpub
#if client:
# client.used()
if client is not None:
client.checkDevice()
client.checkDevice()
return client