parent
53fd6a2df5
commit
ce5cc135cd
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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'))
|
||||
|
||||
|
||||
#####
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
@ -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])
|
||||
@ -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
|
||||
@ -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)
|
||||
@ -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]]
|
||||
@ -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)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user