299 lines
9.6 KiB
Python
299 lines
9.6 KiB
Python
|
import typing
|
||
|
from scribe.blockchain.transaction import NameClaim, ClaimUpdate, ClaimSupport
|
||
|
from scribe.blockchain.transaction import unpack_le_uint16_from, unpack_le_uint32_from, pack_le_uint16, pack_le_uint32
|
||
|
|
||
|
|
||
|
class _OpCodes(typing.NamedTuple):
|
||
|
def whatis(self, value: int):
|
||
|
try:
|
||
|
return self._fields[self.index(value)]
|
||
|
except (ValueError, IndexError):
|
||
|
return -1
|
||
|
|
||
|
OP_PUSHDATA1: int = 0x4c
|
||
|
OP_PUSHDATA2: int = 0x4d
|
||
|
OP_PUSHDATA4: int = 0x4e
|
||
|
OP_1NEGATE: int = 0x4f
|
||
|
OP_RESERVED: int = 0x50
|
||
|
OP_1: int = 0x51
|
||
|
OP_2: int = 0x52
|
||
|
OP_3: int = 0x53
|
||
|
OP_4: int = 0x54
|
||
|
OP_5: int = 0x55
|
||
|
OP_6: int = 0x56
|
||
|
OP_7: int = 0x57
|
||
|
OP_8: int = 0x58
|
||
|
OP_9: int = 0x59
|
||
|
OP_10: int = 0x5a
|
||
|
OP_11: int = 0x5b
|
||
|
OP_12: int = 0x5c
|
||
|
OP_13: int = 0x5d
|
||
|
OP_14: int = 0x5e
|
||
|
OP_15: int = 0x5f
|
||
|
OP_16: int = 0x60
|
||
|
OP_NOP: int = 0x61
|
||
|
OP_VER: int = 0x62
|
||
|
OP_IF: int = 0x63
|
||
|
OP_NOTIF: int = 0x64
|
||
|
OP_VERIF: int = 0x65
|
||
|
OP_VERNOTIF: int = 0x66
|
||
|
OP_ELSE: int = 0x67
|
||
|
OP_ENDIF: int = 0x68
|
||
|
OP_VERIFY: int = 0x69
|
||
|
OP_RETURN: int = 0x6a
|
||
|
OP_TOALTSTACK: int = 0x6b
|
||
|
OP_FROMALTSTACK: int = 0x6c
|
||
|
OP_2DROP: int = 0x6d
|
||
|
OP_2DUP: int = 0x6e
|
||
|
OP_3DUP: int = 0x6f
|
||
|
OP_2OVER: int = 0x70
|
||
|
OP_2ROT: int = 0x71
|
||
|
OP_2SWAP: int = 0x72
|
||
|
OP_IFDUP: int = 0x73
|
||
|
OP_DEPTH: int = 0x74
|
||
|
OP_DROP: int = 0x75
|
||
|
OP_DUP: int = 0x76
|
||
|
OP_NIP: int = 0x77
|
||
|
OP_OVER: int = 0x78
|
||
|
OP_PICK: int = 0x79
|
||
|
OP_ROLL: int = 0x7a
|
||
|
OP_ROT: int = 0x7b
|
||
|
OP_SWAP: int = 0x7c
|
||
|
OP_TUCK: int = 0x7d
|
||
|
OP_CAT: int = 0x7e
|
||
|
OP_SUBSTR: int = 0x7f
|
||
|
OP_LEFT: int = 0x80
|
||
|
OP_RIGHT: int = 0x81
|
||
|
OP_SIZE: int = 0x82
|
||
|
OP_INVERT: int = 0x83
|
||
|
OP_AND: int = 0x84
|
||
|
OP_OR: int = 0x85
|
||
|
OP_XOR: int = 0x86
|
||
|
OP_EQUAL: int = 0x87
|
||
|
OP_EQUALVERIFY: int = 0x88
|
||
|
OP_RESERVED1: int = 0x89
|
||
|
OP_RESERVED2: int = 0x8a
|
||
|
OP_1ADD: int = 0x8b
|
||
|
OP_1SUB: int = 0x8c
|
||
|
OP_2MUL: int = 0x8d
|
||
|
OP_2DIV: int = 0x8e
|
||
|
OP_NEGATE: int = 0x8f
|
||
|
OP_ABS: int = 0x90
|
||
|
OP_NOT: int = 0x91
|
||
|
OP_0NOTEQUAL: int = 0x92
|
||
|
OP_ADD: int = 0x93
|
||
|
OP_SUB: int = 0x94
|
||
|
OP_MUL: int = 0x95
|
||
|
OP_DIV: int = 0x96
|
||
|
OP_MOD: int = 0x97
|
||
|
OP_LSHIFT: int = 0x98
|
||
|
OP_RSHIFT: int = 0x99
|
||
|
OP_BOOLAND: int = 0x9a
|
||
|
OP_BOOLOR: int = 0x9b
|
||
|
OP_NUMEQUAL: int = 0x9c
|
||
|
OP_NUMEQUALVERIFY: int = 0x9d
|
||
|
OP_NUMNOTEQUAL: int = 0x9e
|
||
|
OP_LESSTHAN: int = 0x9f
|
||
|
OP_GREATERTHAN: int = 0xa0
|
||
|
OP_LESSTHANOREQUAL: int = 0xa1
|
||
|
OP_GREATERTHANOREQUAL: int = 0xa2
|
||
|
OP_MIN: int = 0xa3
|
||
|
OP_MAX: int = 0xa4
|
||
|
OP_WITHIN: int = 0xa5
|
||
|
OP_RIPEMD160: int = 0xa6
|
||
|
OP_SHA1: int = 0xa7
|
||
|
OP_SHA256: int = 0xa8
|
||
|
OP_HASH160: int = 0xa9
|
||
|
OP_HASH256: int = 0xaa
|
||
|
OP_CODESEPARATOR: int = 0xab
|
||
|
OP_CHECKSIG: int = 0xac
|
||
|
OP_CHECKSIGVERIFY: int = 0xad
|
||
|
OP_CHECKMULTISIG: int = 0xae
|
||
|
OP_CHECKMULTISIGVERIFY: int = 0xaf
|
||
|
OP_NOP1: int = 0xb0
|
||
|
OP_CHECKLOCKTIMEVERIFY: int = 0xb1
|
||
|
OP_CHECKSEQUENCEVERIFY: int = 0xb2
|
||
|
OP_NOP4: int = 0xb3
|
||
|
OP_NOP5: int = 0xb4
|
||
|
OP_CLAIM_NAME: int = 0xb5
|
||
|
OP_SUPPORT_CLAIM: int = 0xb6
|
||
|
OP_UPDATE_CLAIM: int = 0xb7
|
||
|
OP_NOP9: int = 0xb8
|
||
|
OP_NOP10: int = 0xb9
|
||
|
|
||
|
|
||
|
OpCodes = _OpCodes()
|
||
|
|
||
|
|
||
|
# Paranoia to make it hard to create bad scripts
|
||
|
assert OpCodes.OP_DUP == 0x76
|
||
|
assert OpCodes.OP_HASH160 == 0xa9
|
||
|
assert OpCodes.OP_EQUAL == 0x87
|
||
|
assert OpCodes.OP_EQUALVERIFY == 0x88
|
||
|
assert OpCodes.OP_CHECKSIG == 0xac
|
||
|
assert OpCodes.OP_CHECKMULTISIG == 0xae
|
||
|
|
||
|
assert OpCodes.OP_CLAIM_NAME == 0xb5
|
||
|
assert OpCodes.OP_SUPPORT_CLAIM == 0xb6
|
||
|
assert OpCodes.OP_UPDATE_CLAIM == 0xb7
|
||
|
|
||
|
|
||
|
def P2SH_script(hash160_bytes: bytes):
|
||
|
return bytes([OpCodes.OP_HASH160]) + script_push_data(hash160_bytes) + bytes([OpCodes.OP_EQUAL])
|
||
|
|
||
|
|
||
|
def P2PKH_script(hash160_bytes: bytes):
|
||
|
return (bytes([OpCodes.OP_DUP, OpCodes.OP_HASH160])
|
||
|
+ script_push_data(hash160_bytes)
|
||
|
+ bytes([OpCodes.OP_EQUALVERIFY, OpCodes.OP_CHECKSIG]))
|
||
|
|
||
|
|
||
|
def script_push_data(data: bytes):
|
||
|
n = len(data)
|
||
|
if n < OpCodes.OP_PUSHDATA1:
|
||
|
return bytes([n]) + data
|
||
|
if n < 256:
|
||
|
return bytes([OpCodes.OP_PUSHDATA1, n]) + data
|
||
|
if n < 65536:
|
||
|
return bytes([OpCodes.OP_PUSHDATA2]) + pack_le_uint16(n) + data
|
||
|
return bytes([OpCodes.OP_PUSHDATA4]) + pack_le_uint32(n) + data
|
||
|
|
||
|
|
||
|
def script_GetOp(script_bytes: bytes):
|
||
|
i = 0
|
||
|
while i < len(script_bytes):
|
||
|
vch = None
|
||
|
opcode = script_bytes[i]
|
||
|
i += 1
|
||
|
if opcode <= OpCodes.OP_PUSHDATA4:
|
||
|
n_size = opcode
|
||
|
if opcode == OpCodes.OP_PUSHDATA1:
|
||
|
n_size = script_bytes[i]
|
||
|
i += 1
|
||
|
elif opcode == OpCodes.OP_PUSHDATA2:
|
||
|
(n_size,) = unpack_le_uint16_from(script_bytes, i)
|
||
|
i += 2
|
||
|
elif opcode == OpCodes.OP_PUSHDATA4:
|
||
|
(n_size,) = unpack_le_uint32_from(script_bytes, i)
|
||
|
i += 4
|
||
|
if i + n_size > len(script_bytes):
|
||
|
vch = b"_INVALID_" + script_bytes[i:]
|
||
|
i = len(script_bytes)
|
||
|
else:
|
||
|
vch = script_bytes[i:i + n_size]
|
||
|
i += n_size
|
||
|
yield opcode, vch, i
|
||
|
|
||
|
|
||
|
_SCRIPT_TEMPLATES = (
|
||
|
# claim related templates
|
||
|
(OpCodes.OP_CLAIM_NAME, -1, -1, OpCodes.OP_2DROP, OpCodes.OP_DROP),
|
||
|
(OpCodes.OP_UPDATE_CLAIM, -1, -1, OpCodes.OP_2DROP, OpCodes.OP_DROP),
|
||
|
(OpCodes.OP_UPDATE_CLAIM, -1, -1, -1, OpCodes.OP_2DROP, OpCodes.OP_2DROP),
|
||
|
(OpCodes.OP_SUPPORT_CLAIM, -1, -1, OpCodes.OP_2DROP, OpCodes.OP_DROP),
|
||
|
(OpCodes.OP_SUPPORT_CLAIM, -1, -1, -1, OpCodes.OP_2DROP, OpCodes.OP_2DROP),
|
||
|
|
||
|
# receive script templates
|
||
|
(OpCodes.OP_DUP, OpCodes.OP_HASH160, -1, OpCodes.OP_EQUALVERIFY, OpCodes.OP_CHECKSIG),
|
||
|
(OpCodes.OP_HASH160, -1, OpCodes.OP_EQUAL),
|
||
|
(-1, OpCodes.OP_CHECKSIG)
|
||
|
)
|
||
|
_CLAIM_TEMPLATE = 0
|
||
|
_UPDATE_NO_DATA_TEMPLATE = 1
|
||
|
_UPDATE_TEMPLATE = 2
|
||
|
_SUPPORT_TEMPLATE = 3
|
||
|
_SUPPORT_WITH_DATA_TEMPLATE = 4
|
||
|
|
||
|
_TO_ADDRESS_TEMPLATE = 5
|
||
|
_TO_P2SH_TEMPLATE = 6
|
||
|
_TO_PUBKEY_TEMPLATE = 7
|
||
|
|
||
|
|
||
|
def txo_script_parser(script: bytes):
|
||
|
template = None
|
||
|
template_idx = None
|
||
|
values = []
|
||
|
receive_values = []
|
||
|
finished_decoding_claim = False
|
||
|
receive_cur = 0
|
||
|
claim, support, pubkey_hash, pubkey, script_hash = None, None, None, None, None
|
||
|
for cur, (op, data, _) in enumerate(script_GetOp(script)):
|
||
|
if finished_decoding_claim: # we're decoding the receiving part of the script (the last part)
|
||
|
if receive_cur == 0:
|
||
|
if op == OpCodes.OP_DUP:
|
||
|
template_idx = _TO_ADDRESS_TEMPLATE
|
||
|
elif op == OpCodes.OP_HASH160:
|
||
|
template_idx = _TO_P2SH_TEMPLATE
|
||
|
elif op == -1:
|
||
|
template_idx = _TO_PUBKEY_TEMPLATE
|
||
|
else:
|
||
|
break # return the decoded part
|
||
|
template = _SCRIPT_TEMPLATES[template_idx]
|
||
|
expected = template[receive_cur]
|
||
|
if expected == -1 and data is None: # if data data is expected make sure it's there
|
||
|
# print("\texpected data", OpCodes.whatis(op), data)
|
||
|
return
|
||
|
elif expected == -1 and data:
|
||
|
receive_values.append(data)
|
||
|
elif op != expected:
|
||
|
# print("\top mismatch")
|
||
|
return
|
||
|
receive_cur += 1
|
||
|
continue
|
||
|
|
||
|
if cur == 0: # initialize the template
|
||
|
if op == OpCodes.OP_CLAIM_NAME:
|
||
|
template_idx = _CLAIM_TEMPLATE
|
||
|
elif op == OpCodes.OP_UPDATE_CLAIM:
|
||
|
template_idx = _UPDATE_NO_DATA_TEMPLATE
|
||
|
elif op == OpCodes.OP_SUPPORT_CLAIM:
|
||
|
template_idx = _SUPPORT_TEMPLATE # could be a support w/ data
|
||
|
elif op == OpCodes.OP_DUP:
|
||
|
template_idx = _TO_ADDRESS_TEMPLATE
|
||
|
elif op == OpCodes.OP_HASH160:
|
||
|
template_idx = _TO_P2SH_TEMPLATE
|
||
|
elif op == -1:
|
||
|
template_idx = _TO_PUBKEY_TEMPLATE
|
||
|
else:
|
||
|
return
|
||
|
template = _SCRIPT_TEMPLATES[template_idx]
|
||
|
elif cur == 3 and template_idx == _SUPPORT_TEMPLATE and data:
|
||
|
template_idx = _SUPPORT_WITH_DATA_TEMPLATE
|
||
|
template = _SCRIPT_TEMPLATES[template_idx]
|
||
|
elif cur == 3 and template_idx == _UPDATE_NO_DATA_TEMPLATE and data:
|
||
|
template_idx = _UPDATE_TEMPLATE
|
||
|
template = _SCRIPT_TEMPLATES[template_idx]
|
||
|
|
||
|
if cur >= len(template):
|
||
|
return
|
||
|
|
||
|
expected = template[cur]
|
||
|
|
||
|
if expected == -1 and data is None: # if data data is expected make sure it's there
|
||
|
# print("\texpected data", OpCodes.whatis(op), data)
|
||
|
return
|
||
|
elif expected == -1 and data:
|
||
|
if template_idx in (_TO_ADDRESS_TEMPLATE, _TO_P2SH_TEMPLATE, _TO_ADDRESS_TEMPLATE):
|
||
|
receive_values.append(data)
|
||
|
else:
|
||
|
values.append(data)
|
||
|
elif op != expected:
|
||
|
# print("\top mismatch")
|
||
|
return
|
||
|
if cur + 1 == len(template):
|
||
|
finished_decoding_claim = True
|
||
|
if template_idx == _CLAIM_TEMPLATE:
|
||
|
claim = NameClaim(*values)
|
||
|
elif template_idx in (_UPDATE_NO_DATA_TEMPLATE, _UPDATE_TEMPLATE):
|
||
|
claim = ClaimUpdate(*values)
|
||
|
elif template_idx in (_SUPPORT_TEMPLATE, _SUPPORT_WITH_DATA_TEMPLATE):
|
||
|
support = ClaimSupport(*values)
|
||
|
|
||
|
if template_idx == _TO_ADDRESS_TEMPLATE:
|
||
|
pubkey_hash = receive_values[0]
|
||
|
elif template_idx == _TO_P2SH_TEMPLATE:
|
||
|
script_hash = receive_values[0]
|
||
|
elif template_idx == _TO_PUBKEY_TEMPLATE:
|
||
|
pubkey = receive_values[0]
|
||
|
return claim, support, pubkey_hash, script_hash, pubkey
|