Browse Source

transaction: make get_address_from_output_script safer

closes #4743
3.3.3.1
SomberNight 7 years ago
parent
commit
ce5cc135cd
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 8
      electrum/ecc.py
  2. 16
      electrum/tests/test_transaction.py
  3. 72
      electrum/transaction.py

8
electrum/ecc.py

@ -296,6 +296,14 @@ class ECPubkey(object):
def is_at_infinity(self): def is_at_infinity(self):
return self == point_at_infinity() 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: def msg_magic(message: bytes) -> bytes:
from .bitcoin import var_int from .bitcoin import var_int

16
electrum/tests/test_transaction.py

@ -175,6 +175,8 @@ class TestTransaction(SequentialTestCase):
# the inverse of this test is in test_bitcoin: test_address_to_script # 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_from_script = lambda script: transaction.get_address_from_output_script(bfh(script))
ADDR = transaction.TYPE_ADDRESS ADDR = transaction.TYPE_ADDRESS
PUBKEY = transaction.TYPE_PUBKEY
SCRIPT = transaction.TYPE_SCRIPT
# bech32 native segwit # bech32 native segwit
# test vectors from BIP-0173 # test vectors from BIP-0173
@ -182,14 +184,28 @@ class TestTransaction(SequentialTestCase):
self.assertEqual((ADDR, 'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx'), addr_from_script('5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6')) self.assertEqual((ADDR, 'bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx'), addr_from_script('5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6'))
self.assertEqual((ADDR, 'bc1sw50qa3jx3s'), addr_from_script('6002751e')) self.assertEqual((ADDR, 'bc1sw50qa3jx3s'), addr_from_script('6002751e'))
self.assertEqual((ADDR, 'bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'), addr_from_script('5210751e76e8199196d454941c45d1b3a323')) self.assertEqual((ADDR, 'bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'), addr_from_script('5210751e76e8199196d454941c45d1b3a323'))
# almost but not quite
self.assertEqual((SCRIPT, '0013751e76e8199196d454941c45d1b3a323f1433b'), addr_from_script('0013751e76e8199196d454941c45d1b3a323f1433b'))
# base58 p2pkh # base58 p2pkh
self.assertEqual((ADDR, '14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG'), addr_from_script('76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac')) self.assertEqual((ADDR, '14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG'), addr_from_script('76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac'))
self.assertEqual((ADDR, '1BEqfzh4Y3zzLosfGhw1AsqbEKVW6e1qHv'), addr_from_script('76a914704f4b81cadb7bf7e68c08cd3657220f680f863c88ac')) self.assertEqual((ADDR, '1BEqfzh4Y3zzLosfGhw1AsqbEKVW6e1qHv'), addr_from_script('76a914704f4b81cadb7bf7e68c08cd3657220f680f863c88ac'))
# almost but not quite
self.assertEqual((SCRIPT, '76a9130000000000000000000000000000000000000088ac'), addr_from_script('76a9130000000000000000000000000000000000000088ac'))
# base58 p2sh # base58 p2sh
self.assertEqual((ADDR, '35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT'), addr_from_script('a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487')) self.assertEqual((ADDR, '35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT'), addr_from_script('a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487'))
self.assertEqual((ADDR, '3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji'), addr_from_script('a914f47c8954e421031ad04ecd8e7752c9479206b9d387')) 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'))
##### #####

72
electrum/transaction.py

@ -27,7 +27,8 @@
# Note: The deserialization code originally comes from ABE. # 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 from .util import print_error, profiler
@ -288,15 +289,39 @@ def script_GetOpName(opcode):
return (opcodes.whatis(opcode)).replace("OP_", "") 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): def match_decoded(decoded, to_match):
if decoded is None: if decoded is None:
return False return False
if len(decoded) != len(to_match): if len(decoded) != len(to_match):
return False return False
for i in range(len(decoded)): 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: to_match_item = to_match[i]
continue # Opcodes below OP_PUSHDATA4 all just push data onto stack, and are equivalent. decoded_item = decoded[i]
if to_match[i] != decoded[i][0]: 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 False
return True return True
@ -319,7 +344,7 @@ def parse_scriptSig(d, _bytes):
bh2u(_bytes)) bh2u(_bytes))
return return
match = [ opcodes.OP_PUSHDATA4 ] match = [OPPushDataGeneric]
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:
@ -350,7 +375,7 @@ def parse_scriptSig(d, _bytes):
# p2pkh TxIn transactions push a signature # p2pkh TxIn transactions push a signature
# (71-73 bytes) and then their public key # (71-73 bytes) and then their public key
# (33 or 65 bytes) onto the stack: # (33 or 65 bytes) onto the stack:
match = [ opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4 ] match = [OPPushDataGeneric, OPPushDataGeneric]
if match_decoded(decoded, match): if match_decoded(decoded, match):
sig = bh2u(decoded[0][1]) sig = bh2u(decoded[0][1])
x_pubkey = bh2u(decoded[1][1]) x_pubkey = bh2u(decoded[1][1])
@ -370,7 +395,7 @@ def parse_scriptSig(d, _bytes):
return return
# p2sh transaction, m of n # 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): if match_decoded(decoded, match):
x_sig = [bh2u(x[1]) for x in decoded[1:-1]] 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 redeem_script_unsanitized = decoded[-1][1] # for partial multisig txn, this has x_pubkeys
@ -393,7 +418,7 @@ def parse_scriptSig(d, _bytes):
return return
# custom partial format for imported addresses # 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): if match_decoded(decoded, match):
x_pubkey = bh2u(decoded[2][1]) x_pubkey = bh2u(decoded[2][1])
pubkey, address = xpubkey_to_address(x_pubkey) pubkey, address = xpubkey_to_address(x_pubkey)
@ -421,7 +446,7 @@ def parse_redeemScript_multisig(redeem_script: bytes):
raise NotRecognizedRedeemScript() raise NotRecognizedRedeemScript()
op_m = opcodes.OP_1 + m - 1 op_m = opcodes.OP_1 + m - 1
op_n = opcodes.OP_1 + n - 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): if not match_decoded(dec2, match_multisig):
raise NotRecognizedRedeemScript() raise NotRecognizedRedeemScript()
x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]] x_pubkeys = [bh2u(x[1]) for x in dec2[1:-2]]
@ -433,33 +458,36 @@ def parse_redeemScript_multisig(redeem_script: bytes):
return m, n, x_pubkeys, pubkeys, redeem_script_sanitized 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: try:
decoded = [x for x in script_GetOp(_bytes)] decoded = [x for x in script_GetOp(_bytes)]
except MalformedBitcoinScript: except MalformedBitcoinScript:
decoded = None decoded = None
# The Genesis Block, self-payments, and pay-by-IP-address payments look like: # p2pk
# 65 BYTES:... CHECKSIG match = [OPPushDataPubkey, opcodes.OP_CHECKSIG]
match = [ opcodes.OP_PUSHDATA4, opcodes.OP_CHECKSIG ] if match_decoded(decoded, match) and ecc.ECPubkey.is_pubkey_bytes(decoded[0][1]):
if match_decoded(decoded, match):
return TYPE_PUBKEY, bh2u(decoded[0][1]) return TYPE_PUBKEY, bh2u(decoded[0][1])
# Pay-by-Bitcoin-address TxOuts look like: # p2pkh
# DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG match = [opcodes.OP_DUP, opcodes.OP_HASH160, OPPushDataGeneric(lambda x: x == 20), opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG]
match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ]
if match_decoded(decoded, match): if match_decoded(decoded, match):
return TYPE_ADDRESS, hash160_to_p2pkh(decoded[2][1], net=net) return TYPE_ADDRESS, hash160_to_p2pkh(decoded[2][1], net=net)
# p2sh # 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): if match_decoded(decoded, match):
return TYPE_ADDRESS, hash160_to_p2sh(decoded[1][1], net=net) return TYPE_ADDRESS, hash160_to_p2sh(decoded[1][1], net=net)
# segwit address # segwit address (version 0)
possible_witness_versions = [opcodes.OP_0] + list(range(opcodes.OP_1, opcodes.OP_16 + 1)) match = [opcodes.OP_0, OPPushDataGeneric(lambda x: x in (20, 32))]
for witver, opcode in enumerate(possible_witness_versions): if match_decoded(decoded, match):
match = [ opcode, opcodes.OP_PUSHDATA4 ] 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): if match_decoded(decoded, match):
return TYPE_ADDRESS, hash_to_segwit_addr(decoded[1][1], witver=witver, net=net) return TYPE_ADDRESS, hash_to_segwit_addr(decoded[1][1], witver=witver, net=net)

Loading…
Cancel
Save