URI: 
       ttransaction: make get_address_from_output_script safer - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit ce5cc135cd432552006c713d1d83d9fb51e1e7e2
   DIR parent 53fd6a2df590e2acd1117a05e87e66d65843005d
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Sat, 29 Sep 2018 19:47:55 +0200
       
       ttransaction: make get_address_from_output_script safer
       
       closes #4743
       
       Diffstat:
         M electrum/ecc.py                     |       8 ++++++++
         M electrum/tests/test_transaction.py  |      16 ++++++++++++++++
         M electrum/transaction.py             |      72 +++++++++++++++++++++----------
       
       3 files changed, 74 insertions(+), 22 deletions(-)
       ---
   DIR diff --git a/electrum/ecc.py b/electrum/ecc.py
       t@@ -296,6 +296,14 @@ class ECPubkey(object):
            def is_at_infinity(self):
                return self == point_at_infinity()
        
       +    @classmethod
       +    def is_pubkey_bytes(cls, b: bytes):
       +        try:
       +            ECPubkey(b)
       +            return True
       +        except:
       +            return False
       +
        
        def msg_magic(message: bytes) -> bytes:
            from .bitcoin import var_int
   DIR diff --git a/electrum/tests/test_transaction.py b/electrum/tests/test_transaction.py
       t@@ -175,6 +175,8 @@ class TestTransaction(SequentialTestCase):
                # the inverse of this test is in test_bitcoin: test_address_to_script
                addr_from_script = lambda script: transaction.get_address_from_output_script(bfh(script))
                ADDR = transaction.TYPE_ADDRESS
       +        PUBKEY = transaction.TYPE_PUBKEY
       +        SCRIPT = transaction.TYPE_SCRIPT
        
                # bech32 native segwit
                # test vectors from BIP-0173
       t@@ -182,14 +184,28 @@ class TestTransaction(SequentialTestCase):
                self.assertEqual((ADDR, 'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx'), addr_from_script('5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6'))
                self.assertEqual((ADDR, 'bc1sw50qa3jx3s'), addr_from_script('6002751e'))
                self.assertEqual((ADDR, 'bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'), addr_from_script('5210751e76e8199196d454941c45d1b3a323'))
       +        # almost but not quite
       +        self.assertEqual((SCRIPT, '0013751e76e8199196d454941c45d1b3a323f1433b'), addr_from_script('0013751e76e8199196d454941c45d1b3a323f1433b'))
        
                # base58 p2pkh
                self.assertEqual((ADDR, '14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG'), addr_from_script('76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac'))
                self.assertEqual((ADDR, '1BEqfzh4Y3zzLosfGhw1AsqbEKVW6e1qHv'), addr_from_script('76a914704f4b81cadb7bf7e68c08cd3657220f680f863c88ac'))
       +        # almost but not quite
       +        self.assertEqual((SCRIPT, '76a9130000000000000000000000000000000000000088ac'), addr_from_script('76a9130000000000000000000000000000000000000088ac'))
        
                # base58 p2sh
                self.assertEqual((ADDR, '35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT'), addr_from_script('a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487'))
                self.assertEqual((ADDR, '3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji'), addr_from_script('a914f47c8954e421031ad04ecd8e7752c9479206b9d387'))
       +        # almost but not quite
       +        self.assertEqual((SCRIPT, 'a912f47c8954e421031ad04ecd8e7752c947920687'), addr_from_script('a912f47c8954e421031ad04ecd8e7752c947920687'))
       +
       +        # p2pk
       +        self.assertEqual((PUBKEY, '0289e14468d94537493c62e2168318b568912dec0fb95609afd56f2527c2751c8b'), addr_from_script('210289e14468d94537493c62e2168318b568912dec0fb95609afd56f2527c2751c8bac'))
       +        self.assertEqual((PUBKEY, '045485b0b076848af1209e788c893522a90f3df77c1abac2ca545846a725e6c3da1f7743f55a1bc3b5f0c7e0ee4459954ec0307022742d60032b13432953eb7120'), addr_from_script('41045485b0b076848af1209e788c893522a90f3df77c1abac2ca545846a725e6c3da1f7743f55a1bc3b5f0c7e0ee4459954ec0307022742d60032b13432953eb7120ac'))
       +        # almost but not quite
       +        self.assertEqual((SCRIPT, '200289e14468d94537493c62e2168318b568912dec0fb95609afd56f2527c2751cac'), addr_from_script('200289e14468d94537493c62e2168318b568912dec0fb95609afd56f2527c2751cac'))
       +        self.assertEqual((SCRIPT, '210589e14468d94537493c62e2168318b568912dec0fb95609afd56f2527c2751c8bac'), addr_from_script('210589e14468d94537493c62e2168318b568912dec0fb95609afd56f2527c2751c8bac'))
       +
        
        #####
        
   DIR diff --git a/electrum/transaction.py b/electrum/transaction.py
       t@@ -27,7 +27,8 @@
        
        # Note: The deserialization code originally comes from ABE.
        
       -from typing import Sequence, Union, NamedTuple, Tuple, Optional, Iterable
       +from typing import (Sequence, Union, NamedTuple, Tuple, Optional, Iterable,
       +                    Callable)
        
        from .util import print_error, profiler
        
       t@@ -288,15 +289,39 @@ def script_GetOpName(opcode):
            return (opcodes.whatis(opcode)).replace("OP_", "")
        
        
       +class OPPushDataGeneric:
       +    def __init__(self, pushlen: Callable=None):
       +        if pushlen is not None:
       +            self.check_data_len = pushlen
       +
       +    @classmethod
       +    def check_data_len(cls, datalen: int) -> bool:
       +        # Opcodes below OP_PUSHDATA4 all just push data onto stack, and are equivalent.
       +        return opcodes.OP_PUSHDATA4 >= datalen >= 0
       +
       +    @classmethod
       +    def is_instance(cls, item):
       +        # accept objects that are instances of this class
       +        # or other classes that are subclasses
       +        return isinstance(item, cls) \
       +               or (isinstance(item, type) and issubclass(item, cls))
       +
       +
       +OPPushDataPubkey = OPPushDataGeneric(lambda x: x in (33, 65))
       +# note that this does not include x_pubkeys !
       +
       +
        def match_decoded(decoded, to_match):
            if decoded is None:
                return False
            if len(decoded) != len(to_match):
                return False
            for i in range(len(decoded)):
       -        if to_match[i] == opcodes.OP_PUSHDATA4 and decoded[i][0] <= opcodes.OP_PUSHDATA4 and decoded[i][0]>0:
       -            continue  # Opcodes below OP_PUSHDATA4 all just push data onto stack, and are equivalent.
       -        if to_match[i] != decoded[i][0]:
       +        to_match_item = to_match[i]
       +        decoded_item = decoded[i]
       +        if OPPushDataGeneric.is_instance(to_match_item) and to_match_item.check_data_len(decoded_item[0]):
       +            continue
       +        if to_match_item != decoded_item[0]:
                    return False
            return True
        
       t@@ -319,7 +344,7 @@ def parse_scriptSig(d, _bytes):
                            bh2u(_bytes))
                return
        
       -    match = [ opcodes.OP_PUSHDATA4 ]
       +    match = [OPPushDataGeneric]
            if match_decoded(decoded, match):
                item = decoded[0][1]
                if item[0] == 0:
       t@@ -350,7 +375,7 @@ def parse_scriptSig(d, _bytes):
            # 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 ]
       +    match = [OPPushDataGeneric, OPPushDataGeneric]
            if match_decoded(decoded, match):
                sig = bh2u(decoded[0][1])
                x_pubkey = bh2u(decoded[1][1])
       t@@ -370,7 +395,7 @@ def parse_scriptSig(d, _bytes):
                return
        
            # p2sh transaction, m of n
       -    match = [ opcodes.OP_0 ] + [ opcodes.OP_PUSHDATA4 ] * (len(decoded) - 1)
       +    match = [opcodes.OP_0] + [OPPushDataGeneric] * (len(decoded) - 1)
            if match_decoded(decoded, match):
                x_sig = [bh2u(x[1]) for x in decoded[1:-1]]
                redeem_script_unsanitized = decoded[-1][1]  # for partial multisig txn, this has x_pubkeys
       t@@ -393,7 +418,7 @@ def parse_scriptSig(d, _bytes):
                return
        
            # custom partial format for imported addresses
       -    match = [ opcodes.OP_INVALIDOPCODE, opcodes.OP_0, opcodes.OP_PUSHDATA4 ]
       +    match = [opcodes.OP_INVALIDOPCODE, opcodes.OP_0, OPPushDataGeneric]
            if match_decoded(decoded, match):
                x_pubkey = bh2u(decoded[2][1])
                pubkey, address = xpubkey_to_address(x_pubkey)
       t@@ -421,7 +446,7 @@ def parse_redeemScript_multisig(redeem_script: bytes):
                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 ]
       +    match_multisig = [op_m] + [OPPushDataGeneric] * n + [op_n, opcodes.OP_CHECKMULTISIG]
            if not match_decoded(dec2, match_multisig):
                raise NotRecognizedRedeemScript()
            x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]]
       t@@ -433,33 +458,36 @@ def parse_redeemScript_multisig(redeem_script: bytes):
            return m, n, x_pubkeys, pubkeys, redeem_script_sanitized
        
        
       -def get_address_from_output_script(_bytes, *, net=None):
       +def get_address_from_output_script(_bytes: bytes, *, net=None) -> Tuple[int, str]:
            try:
                decoded = [x for x in script_GetOp(_bytes)]
            except MalformedBitcoinScript:
                decoded = None
        
       -    # The Genesis Block, self-payments, and pay-by-IP-address payments look like:
       -    # 65 BYTES:... CHECKSIG
       -    match = [ opcodes.OP_PUSHDATA4, opcodes.OP_CHECKSIG ]
       -    if match_decoded(decoded, match):
       +    # p2pk
       +    match = [OPPushDataPubkey, opcodes.OP_CHECKSIG]
       +    if match_decoded(decoded, match) and ecc.ECPubkey.is_pubkey_bytes(decoded[0][1]):
                return TYPE_PUBKEY, bh2u(decoded[0][1])
        
       -    # Pay-by-Bitcoin-address TxOuts look like:
       -    # DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG
       -    match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ]
       +    # p2pkh
       +    match = [opcodes.OP_DUP, opcodes.OP_HASH160, OPPushDataGeneric(lambda x: x == 20), opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG]
            if match_decoded(decoded, match):
                return TYPE_ADDRESS, hash160_to_p2pkh(decoded[2][1], net=net)
        
            # p2sh
       -    match = [ opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUAL ]
       +    match = [opcodes.OP_HASH160, OPPushDataGeneric(lambda x: x == 20), opcodes.OP_EQUAL]
            if match_decoded(decoded, match):
                return TYPE_ADDRESS, hash160_to_p2sh(decoded[1][1], net=net)
        
       -    # segwit address
       -    possible_witness_versions = [opcodes.OP_0] + list(range(opcodes.OP_1, opcodes.OP_16 + 1))
       -    for witver, opcode in enumerate(possible_witness_versions):
       -        match = [ opcode, opcodes.OP_PUSHDATA4 ]
       +    # segwit address (version 0)
       +    match = [opcodes.OP_0, OPPushDataGeneric(lambda x: x in (20, 32))]
       +    if match_decoded(decoded, match):
       +        return TYPE_ADDRESS, hash_to_segwit_addr(decoded[1][1], witver=0, net=net)
       +
       +    # segwit address (version 1-16)
       +    future_witness_versions = list(range(opcodes.OP_1, opcodes.OP_16 + 1))
       +    for witver, opcode in enumerate(future_witness_versions, start=1):
       +        match = [opcode, OPPushDataGeneric(lambda x: 2 <= x <= 40)]
                if match_decoded(decoded, match):
                    return TYPE_ADDRESS, hash_to_segwit_addr(decoded[1][1], witver=witver, net=net)