electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 344b5e0a232fce4945ffd65c946c01340ca1e283
parent e3a082d58dff5d2cf4ca8b42fc285ff34b995aba
Author: ThomasV <thomasv@electrum.org>
Date:   Thu, 15 Feb 2018 09:31:20 +0100

Merge pull request #3901 from SomberNight/fix_3899

fix #3899; and more aggressively catch exceptions in tx.deserialize()
Diffstat:
Mlib/tests/test_transaction.py | 5+++++
Mlib/transaction.py | 127++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
2 files changed, 79 insertions(+), 53 deletions(-)

diff --git a/lib/tests/test_transaction.py b/lib/tests/test_transaction.py @@ -235,6 +235,11 @@ class TestTransaction(unittest.TestCase): tx = transaction.Transaction('0100000000010160f84fdcda039c3ca1b20038adea2d49a53db92f7c467e8def13734232bb610804000000232200202814720f16329ab81cb8867c4d447bd13255931f23e6655944c9ada1797fcf88ffffffff0ba3dcfc04000000001976a91488124a57c548c9e7b1dd687455af803bd5765dea88acc9f44900000000001976a914da55045a0ccd40a56ce861946d13eb861eb5f2d788ac49825e000000000017a914ca34d4b190e36479aa6e0023cfe0a8537c6aa8dd87680c0d00000000001976a914651102524c424b2e7c44787c4f21e4c54dffafc088acf02fa9000000000017a914ee6c596e6f7066466d778d4f9ba633a564a6e95d874d250900000000001976a9146ca7976b48c04fd23867748382ee8401b1d27c2988acf5119600000000001976a914cf47d5dcdba02fd547c600697097252d38c3214a88ace08a12000000000017a914017bef79d92d5ec08c051786bad317e5dd3befcf87e3d76201000000001976a9148ec1b88b66d142bcbdb42797a0fd402c23e0eec288ac718f6900000000001976a914e66344472a224ce6f843f2989accf435ae6a808988ac65e51300000000001976a914cad6717c13a2079066f876933834210ebbe68c3f88ac0347304402201a4907c4706104320313e182ecbb1b265b2d023a79586671386de86bb47461590220472c3db9fc99a728ebb9b555a72e3481d20b181bd059a9c1acadfb853d90c96c01210338a46f2a54112fef8803c8478bc17e5f8fc6a5ec276903a946c1fafb2e3a8b181976a914eda8660085bf607b82bd18560ca8f3a9ec49178588ac00000000') self.assertEqual('e9933221a150f78f9f224899f8568ff6422ffcc28ca3d53d87936368ff7c4b1d', tx.txid()) + # input: p2sh, not multisig + def test_txid_regression_issue_3899(self): + tx = transaction.Transaction('0100000004328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c010000000b0009630330472d5fae685bffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c020000000b0009630359646d5fae6858ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c030000000b000963034bd4715fae6854ffffffff328685b0352c981d3d451b471ae3bfc78b82565dc2a54049a81af273f0a9fd9c040000000b000963036de8705fae6860ffffffff0130750000000000001976a914b5abca61d20f9062fb1fdbb880d9d93bac36675188ac00000000') + self.assertEqual('f570d5d1e965ee61bcc7005f8fefb1d3abbed9d7ddbe035e2a68fa07e5fc4a0d', tx.txid()) + # these transactions are from Bitcoin Core unit tests ---> # https://github.com/bitcoin/bitcoin/blob/11376b5583a283772c82f6d32d0007cdbf5b8ef0/src/test/data/tx_valid.json diff --git a/lib/transaction.py b/lib/transaction.py @@ -32,6 +32,8 @@ from .util import print_error, profiler from . import bitcoin from .bitcoin import * import struct +import traceback +import sys # # Workalike python implementation of Bitcoin's CDataStream class. @@ -303,7 +305,8 @@ def parse_scriptSig(d, _bytes): decoded = [ x for x in script_GetOp(_bytes) ] except Exception as e: # coinbase transactions raise an exception - print_error("cannot find address in input script", bh2u(_bytes)) + print_error("parse_scriptSig: cannot find address in input script (coinbase?)", + bh2u(_bytes)) return match = [ opcodes.OP_PUSHDATA4 ] @@ -334,9 +337,9 @@ def parse_scriptSig(d, _bytes): d['pubkeys'] = ["(pubkey)"] return - # non-generated TxIn transactions push a signature - # (seventy-something bytes) and then their public key - # (65 bytes) onto the stack: + # p2pkh TxIn transactions push a signature + # (71-73 bytes) and then their public key + # (33 or 65 bytes) onto the stack: match = [ opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4 ] if match_decoded(decoded, match): sig = bh2u(decoded[0][1]) @@ -345,7 +348,8 @@ def parse_scriptSig(d, _bytes): signatures = parse_sig([sig]) pubkey, address = xpubkey_to_address(x_pubkey) except: - print_error("cannot find address in input script", bh2u(_bytes)) + print_error("parse_scriptSig: cannot find address in input script (p2pkh?)", + bh2u(_bytes)) return d['type'] = 'p2pkh' d['signatures'] = signatures @@ -357,30 +361,41 @@ def parse_scriptSig(d, _bytes): # p2sh transaction, m of n match = [ opcodes.OP_0 ] + [ opcodes.OP_PUSHDATA4 ] * (len(decoded) - 1) - if not match_decoded(decoded, match): - print_error("cannot find address in input script", bh2u(_bytes)) + if match_decoded(decoded, match): + x_sig = [bh2u(x[1]) for x in decoded[1:-1]] + try: + m, n, x_pubkeys, pubkeys, redeemScript = parse_redeemScript(decoded[-1][1]) + except NotRecognizedRedeemScript: + print_error("parse_scriptSig: cannot find address in input script (p2sh?)", + bh2u(_bytes)) + # we could still guess: + # d['address'] = hash160_to_p2sh(hash_160(decoded[-1][1])) + return + # write result in d + d['type'] = 'p2sh' + d['num_sig'] = m + d['signatures'] = parse_sig(x_sig) + d['x_pubkeys'] = x_pubkeys + d['pubkeys'] = pubkeys + d['redeemScript'] = redeemScript + d['address'] = hash160_to_p2sh(hash_160(bfh(redeemScript))) return - x_sig = [bh2u(x[1]) for x in decoded[1:-1]] - m, n, x_pubkeys, pubkeys, redeemScript = parse_redeemScript(decoded[-1][1]) - # write result in d - d['type'] = 'p2sh' - d['num_sig'] = m - d['signatures'] = parse_sig(x_sig) - d['x_pubkeys'] = x_pubkeys - d['pubkeys'] = pubkeys - d['redeemScript'] = redeemScript - d['address'] = hash160_to_p2sh(hash_160(bfh(redeemScript))) + + print_error("parse_scriptSig: cannot find address in input script (unknown)", + bh2u(_bytes)) def parse_redeemScript(s): dec2 = [ x for x in script_GetOp(s) ] - m = dec2[0][0] - opcodes.OP_1 + 1 - n = dec2[-2][0] - opcodes.OP_1 + 1 + try: + m = dec2[0][0] - opcodes.OP_1 + 1 + n = dec2[-2][0] - opcodes.OP_1 + 1 + except IndexError: + raise NotRecognizedRedeemScript() op_m = opcodes.OP_1 + m - 1 op_n = opcodes.OP_1 + n - 1 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)) raise NotRecognizedRedeemScript() x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]] pubkeys = [safe_parse_pubkey(x) for x in x_pubkeys] @@ -436,7 +451,11 @@ def parse_input(vds): d['num_sig'] = 0 if scriptSig: d['scriptSig'] = bh2u(scriptSig) - parse_scriptSig(d, scriptSig) + try: + parse_scriptSig(d, scriptSig) + except BaseException: + traceback.print_exc(file=sys.stderr) + print_error('failed to parse scriptSig', bh2u(scriptSig)) else: d['scriptSig'] = '' @@ -465,25 +484,40 @@ def parse_witness(vds, txin): # 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 txin['type'] == 'p2wsh-p2sh' or n > 2: - try: - m, n, x_pubkeys, pubkeys, witnessScript = parse_redeemScript(bfh(w[-1])) - except NotRecognizedRedeemScript: + try: + if txin['type'] == 'coinbase': + pass + 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]) + txin['num_sig'] = m + txin['x_pubkeys'] = x_pubkeys + txin['pubkeys'] = pubkeys + txin['witnessScript'] = witnessScript + if not txin.get('scriptSig'): # native segwit script + txin['type'] = 'p2wsh' + txin['address'] = bitcoin.script_to_p2wsh(txin['witnessScript']) + 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]]) + if not txin.get('scriptSig'): # native segwit script + txin['type'] = 'p2wpkh' + txin['address'] = bitcoin.public_key_to_p2wpkh(bfh(txin['pubkeys'][0])) + else: raise UnknownTxinType() - txin['signatures'] = parse_sig(w[1:-1]) - txin['num_sig'] = m - txin['x_pubkeys'] = x_pubkeys - txin['pubkeys'] = pubkeys - txin['witnessScript'] = witnessScript - 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() + except UnknownTxinType: + txin['type'] = 'unknown' + # FIXME: GUI might show 'unknown' address (e.g. for a non-multisig p2wsh) + except BaseException: + txin['type'] = 'unknown' + traceback.print_exc(file=sys.stderr) + print_error('failed to parse witness', txin.get('witness')) + def parse_output(vds, i): d = {} @@ -513,20 +547,7 @@ def deserialize(raw): if is_segwit: for i in range(n_vin): txin = d['inputs'][i] - 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: - txin['type'] = 'p2wpkh' - txin['address'] = bitcoin.public_key_to_p2wpkh(bfh(txin['pubkeys'][0])) - else: - txin['type'] = 'p2wsh' - txin['address'] = bitcoin.script_to_p2wsh(txin['witnessScript']) + parse_witness(vds, txin) d['lockTime'] = vds.read_uint32() return d