Source code for zpywallet.transactions.script

# Copyright (C) 2018-2022 The python-bitcoin-utils developers
#
# This file is part of python-bitcoin-utils
#
# It is subject to the license terms in the LICENSE file found in the top-level
# directory of this distribution.
#
# No part of python-bitcoin-utils, including this file, may be copied, modified,
# propagated, or distributed except according to the terms contained in the
# LICENSE file.

import struct
import copy
import hashlib
from binascii import unhexlify, hexlify

from ..network import BitcoinSegwitMainNet

from ..utils.base58 import b58encode_check

from ..utils.bech32 import bech32_encode

from ..utils.ripemd160 import ripemd160


[docs]def hash160(data): return ripemd160(hashlib.sha256(data).digest())
[docs]def p2sh_address(redeem_script, network=BitcoinSegwitMainNet): # Generate a P2SH (3) address from a redeem script. return b58encode_check( bytes([network.SCRIPT_ADDRESS]) + bytes.fromhex(redeem_script) )
[docs]def to_bytes(string, unhex=True): # Convert a hex string to bytes if not string: return b"" if unhex: try: if isinstance(string, bytes): string = string.decode() s = bytes.fromhex(string) return s except (TypeError, ValueError): pass if isinstance(string, bytes): return string else: return bytes(string, "utf8")
[docs]def parse_varint(data): def bytes_to_int(b): return int.from_bytes(b, byteorder="little") varint_type = data[0] if varint_type < 0xFD: return varint_type, 1 elif varint_type == 0xFD: return bytes_to_int(data[1:3]), 3 elif varint_type == 0xFE: return bytes_to_int(data[1:5]), 5 elif varint_type == 0xFF: return bytes_to_int(data[1:9]), 9
# Bitcoin's op codes. Complete list at: https://en.bitcoin.it/wiki/Script OP_CODES = { # constants "OP_0": b"\x00", "OP_FALSE": b"\x00", "OP_PUSHDATA1": b"\x4c", "OP_PUSHDATA2": b"\x4d", "OP_PUSHDATA4": b"\x4e", "OP_1NEGATE": b"\x4f", "OP_1": b"\x51", "OP_TRUE": b"\x51", "OP_2": b"\x52", "OP_3": b"\x53", "OP_4": b"\x54", "OP_5": b"\x55", "OP_6": b"\x56", "OP_7": b"\x57", "OP_8": b"\x58", "OP_9": b"\x59", "OP_10": b"\x5a", "OP_11": b"\x5b", "OP_12": b"\x5c", "OP_13": b"\x5d", "OP_14": b"\x5e", "OP_15": b"\x5f", "OP_16": b"\x60", # flow control "OP_NOP": b"\x61", "OP_IF": b"\x63", "OP_NOTIF": b"\x64", "OP_ELSE": b"\x67", "OP_ENDIF": b"\x68", "OP_VERIFY": b"\x69", "OP_RETURN": b"\x6a", # stack "OP_TOALTSTACK": b"\x6b", "OP_FROMALTSTACK": b"\x6c", "OP_IFDUP": b"\x73", "OP_DEPTH": b"\x74", "OP_DROP": b"\x75", "OP_DUP": b"\x76", "OP_NIP": b"\x77", "OP_OVER": b"\x78", "OP_PICK": b"\x79", "OP_ROLL": b"\x7a", "OP_ROT": b"\x7b", "OP_SWAP": b"\x7c", "OP_TUCK": b"\x7d", "OP_2DROP": b"\x6d", "OP_2DUP": b"\x6e", "OP_3DUP": b"\x6f", "OP_2OVER": b"\x70", "OP_2ROT": b"\x71", "OP_2SWAP": b"\x72", # splice # 'OP_CAT' : b'\x7e', # 'OP_SUBSTR' : b'\x7f', # 'OP_LEFT' : b'\x80', # 'OP_RIGHT' : b'\x81', "OP_SIZE": b"\x82", # bitwise logic # 'OP_INVERT' : b'\x83', # 'OP_AND' : b'\x84', # 'OP_OR' : b'\x85', # 'OP_XOR' : b'\x86', "OP_EQUAL": b"\x87", "OP_EQUALVERIFY": b"\x88", # arithmetic "OP_1ADD": b"\x8b", "OP_1SUB": b"\x8c", # 'OP_2MUL' : b'\x8d', # 'OP_2DIV' : b'\x8e', "OP_NEGATE": b"\x8f", "OP_ABS": b"\x90", "OP_NOT": b"\x91", "OP_0NOTEQUAL": b"\x92", "OP_ADD": b"\x93", "OP_SUB": b"\x94", # 'OP_MUL' : b'\x95', # 'OP_DIV' : b'\x96', # 'OP_MOD' : b'\x97', # 'OP_LSHIFT' : b'\x98', # 'OP_RSHIFT' : b'\x99', "OP_BOOLAND": b"\x9a", "OP_BOOLOR": b"\x9b", "OP_NUMEQUAL": b"\x9c", "OP_NUMEQUALVERIFY": b"\x9d", "OP_NUMNOTEQUAL": b"\x9e", "OP_LESSTHAN": b"\x9f", "OP_GREATERTHAN": b"\xa0", "OP_LESSTHANOREQUAL": b"\xa1", "OP_GREATERTHANOREQUAL": b"\xa2", "OP_MIN": b"\xa3", "OP_MAX": b"\xa4", "OP_WITHIN": b"\xa5", # crypto "OP_RIPEMD160": b"\xa6", "OP_SHA1": b"\xa7", "OP_SHA256": b"\xa8", "OP_HASH160": b"\xa9", "OP_HASH256": b"\xaa", "OP_CODESEPARATOR": b"\xab", "OP_CHECKSIG": b"\xac", "OP_CHECKSIGVERIFY": b"\xad", "OP_CHECKMULTISIG": b"\xae", "OP_CHECKMULTISIGVERIFY": b"\xaf", # locktime "OP_NOP2": b"\xb1", "OP_CHECKLOCKTIMEVERIFY": b"\xb1", "OP_NOP3": b"\xb2", "OP_CHECKSEQUENCEVERIFY": b"\xb2", } CODE_OPS = { # constants b"\x00": "OP_0", b"\x4c": "OP_PUSHDATA1", b"\x4d": "OP_PUSHDATA2", b"\x4e": "OP_PUSHDATA4", b"\x4f": "OP_1NEGATE", b"\x51": "OP_1", b"\x52": "OP_2", b"\x53": "OP_3", b"\x54": "OP_4", b"\x55": "OP_5", b"\x56": "OP_6", b"\x57": "OP_7", b"\x58": "OP_8", b"\x59": "OP_9", b"\x5a": "OP_10", b"\x5b": "OP_11", b"\x5c": "OP_12", b"\x5d": "OP_13", b"\x5e": "OP_14", b"\x5f": "OP_15", b"\x60": "OP_16", # flow control b"\x61": "OP_NOP", b"\x63": "OP_IF", b"\x64": "OP_NOTIF", b"\x67": "OP_ELSE", b"\x68": "OP_ENDIF", b"\x69": "OP_VERIFY", b"\x6a": "OP_RETURN", # stack b"\x6b": "OP_TOALTSTACK", b"\x6c": "OP_FROMALTSTACK", b"\x73": "OP_IFDUP", b"\x74": "OP_DEPTH", b"\x75": "OP_DROP", b"\x76": "OP_DUP", b"\x77": "OP_NIP", b"\x78": "OP_OVER", b"\x79": "OP_PICK", b"\x7a": "OP_ROLL", b"\x7b": "OP_ROT", b"\x7c": "OP_SWAP", b"\x7d": "OP_TUCK", b"\x6d": "OP_2DROP", b"\x6e": "OP_2DUP", b"\x6f": "OP_3DUP", b"\x70": "OP_2OVER", b"\x71": "OP_2ROT", b"\x72": "OP_2SWAP", # splice b"\x82": "OP_SIZE", # bitwise logic b"\x87": "OP_EQUAL", b"\x88": "OP_EQUALVERIFY", # arithmetic b"\x8b": "OP_1ADD", b"\x8c": "OP_1SUB", b"\x8f": "OP_NEGATE", b"\x90": "OP_ABS", b"\x91": "OP_NOT", b"\x92": "OP_0NOTEQUAL", b"\x93": "OP_ADD", b"\x94": "OP_SUB", b"\x9a": "OP_BOOLAND", b"\x9b": "OP_BOOLOR", b"\x9c": "OP_NUMEQUAL", b"\x9d": "OP_NUMEQUALVERIFY", b"\x9e": "OP_NUMNOTEQUAL", b"\x9f": "OP_LESSTHAN", b"\xa0": "OP_GREATERTHAN", b"\xa1": "OP_LESSTHANOREQUAL", b"\xa2": "OP_GREATERTHANOREQUAL", b"\xa3": "OP_MIN", b"\xa4": "OP_MAX", b"\xa5": "OP_WITHIN", # crypto b"\xa6": "OP_RIPEMD160", b"\xa7": "OP_SHA1", b"\xa8": "OP_SHA256", b"\xa9": "OP_HASH160", b"\xaa": "OP_HASH256", b"\xab": "OP_CODESEPARATOR", b"\xac": "OP_CHECKSIG", b"\xad": "OP_CHECKSIGVERIFY", b"\xae": "OP_CHECKMULTISIG", b"\xaf": "OP_CHECKMULTISIGVERIFY", # locktime # b'\xb1': 'OP_NOP2' , b"\xb1": "OP_CHECKLOCKTIMEVERIFY", # b'\xb2': 'OP_NOP3' , b"\xb2": "OP_CHECKSEQUENCEVERIFY", }
[docs]class Script: """Represents a script in Bitcoin. Can also represent most scripts in other altcoins like Litecoin, Dogecoin, and Bitcoin Cash. A Script contains just a list of OP_CODES and also knows how to serialize into bytes. Attributes: script (list): The list with all the script OP_CODES and data. Raises: ValueError: If string data is too large or integer is negative. """ def __init__(self, script, network=BitcoinSegwitMainNet): self.script_bytes = script self.network = network
[docs] @classmethod def copy(cls, script): scripts = copy.deepcopy(script.script) return cls(scripts)
def __str__(self): return str(self.script_bytes) def __repr__(self): return self.__str__() def _op_push_data(self, data): # Converts data to appropriate OP_PUSHDATA OP code including length # # 0x01-0x4b -> just length plus data bytes # 0x4c-0xff -> OP_PUSHDATA1 plus 1-byte-length plus data bytes # 0x0100-0xffff -> OP_PUSHDATA2 plus 2-byte-length plus data bytes # 0x010000-0xffffffff -> OP_PUSHDATA4 plus 4-byte-length plus data bytes # # Also note that according to standarardness rules (BIP-62) the minimum # possible PUSHDATA operator must be used! # expects data in hexadecimal characters and converts appropriately data_bytes = unhexlify(data) if len(data_bytes) < 0x4C: return chr(len(data_bytes)).encode() + data_bytes elif len(data_bytes) < 0xFF: return b"\x4c" + chr(len(data_bytes)).encode() + data_bytes elif len(data_bytes) < 0xFFFF: return b"\x4d" + struct.pack("<H", len(data_bytes)) + data_bytes elif len(data_bytes) < 0xFFFFFFFF: return b"\x4e" + struct.pack("<I", len(data_bytes)) + data_bytes else: raise ValueError("Data too large. Cannot push into script") def _segwit_op_push_data(self, data): # expects data in hexadecimal characters and converts to bytes with # varint (or compact size) length prefix. data_bytes = unhexlify(data) # return prepended varint (compact size) length to data bytes return parse_varint(data_bytes)[0] + data_bytes def _push_integer(self, integer): # Converts integer to bytes; as signed little-endian integer # # Currently supports only positive integers if integer < 0: raise ValueError("Integer is currently required to be positive.") # bytes required to represent the integer number_of_bytes = (integer.bit_length() + 7) // 8 # convert to little-endian bytes integer_bytes = integer.to_bytes(number_of_bytes, byteorder="little") # if last bit is set then we need to add sign to signify positive # integer if integer & (1 << number_of_bytes * 8 - 1): integer_bytes += b"\x00" return self._op_push_data(hexlify(integer_bytes))
[docs] def to_bytes(self): """Returns a serialized byte version of the script If an OP code the appropriate byte is included according to: https://en.bitcoin.it/wiki/Script If not consider it data (signature, public key, public key hash, etc.) and and include with appropriate OP_PUSHDATA OP code plus length """ script_bytes = b"" for token in self.script_bytes: # add op codes directly if token in OP_CODES: script_bytes += OP_CODES[token] # if integer between 0 and 16 add the appropriate op code elif type(token) is int and token >= 0 and token <= 16: script_bytes += OP_CODES["OP_" + str(token)] # it is data, so add accordingly else: if type(token) is int: script_bytes += self._push_integer(token) else: script_bytes += self._op_push_data(token) return script_bytes
[docs] @staticmethod def from_raw(scriptraw, has_segwit=False, network=BitcoinSegwitMainNet): """ Imports a Script commands list from raw hexadecimal data Args: txinputraw : string (hex) The hexadecimal raw string representing the Script commands has_segwit : boolean Is the Tx Input segwit or not """ scriptraw = to_bytes(scriptraw) commands = [] index = 0 while index < len(scriptraw): byte = scriptraw[index] if bytes([byte]) in CODE_OPS: commands.append(CODE_OPS[bytes([byte])]) index = index + 1 # handle the 3 special bytes 0x4c,0x4d,0x4e if the transaction # is not segwit type elif has_segwit is False and bytes([byte]) == b"\x4c": bytes_to_read = int.from_bytes(scriptraw[index + 1], "little") index = index + 1 commands.append(scriptraw[index : index + bytes_to_read].hex()) index = index + bytes_to_read elif has_segwit is False and bytes([byte]) == b"\x4d": bytes_to_read = int.from_bytes(scriptraw[index : index + 2], "little") index = index + 2 commands.append(scriptraw[index : index + bytes_to_read].hex()) index = index + bytes_to_read elif has_segwit is False and bytes([byte]) == b"\x4e": bytes_to_read = int.from_bytes(scriptraw[index : index + 4], "little") index = index + 4 commands.append(scriptraw[index : index + bytes_to_read].hex()) index = index + bytes_to_read else: data_size, size = parse_varint(scriptraw[index : index + 9]) commands.append( scriptraw[index + size : index + size + data_size].hex() ) index = index + data_size + size return Script(script=commands, network=network)
[docs] def to_hex(self): """Converts the script to hexadecimal""" b = self.to_bytes() return hexlify(b).decode("utf-8")
[docs] def get_script(self): """Returns script as array of strings""" return self.script_bytes
[docs] def is_p2pkh(self): """Checks whether the transaction is P2PKH""" try: return ( self.script_bytes[0] == "OP_DUP" and self.script_bytes[1] == "OP_HASH160" and self.script_bytes[3] == "OP_EQUALVERIFY" and self.script_bytes[4] == "OP_CHECKSIG" and len(self.script_bytes) == 5 ) except KeyError: return False
[docs] def is_p2sh(self): """Checks whether the transaction is P2SH""" try: return ( self.script_bytes[0] == "OP_HASH160" and self.script_bytes[2] == "OP_EQUAL" and len(self.script_bytes) == 3 ) except KeyError: return False
[docs] def is_p2wpkh(self): """Checks whether the transaction is P2WPKH""" # The script is in hex try: return ( self.script_bytes[0] == "OP_0" and len(self.script_bytes[1]) == 20 * 2 and len(self.script_bytes) == 2 ) except KeyError: return False
[docs] def is_p2wsh(self): """Checks whether the transaction is P2WSH""" # The script is in hex try: return ( self.script_bytes[0] == "OP_0" and len(self.script_bytes[1]) == 32 * 2 and len(self.script_bytes) == 2 ) except KeyError: return False
[docs] def is_p2tr(self): """Checks whether the transaction is P2TR""" # The script is in hex try: return ( self.script_bytes[0] == "OP_1" and len(self.script_bytes[1]) == 32 * 2 and len(self.script_bytes) == 2 ) except KeyError: return False
[docs] def to_p2pkh(self): """Creates the P2PKH address from the script.""" if self.is_p2pkh(): return b58encode_check( bytes([self.network.PUBKEY_ADDRESS]) + bytes.fromhex(self.script_bytes[2]) ) else: return None
[docs] def to_p2sh(self): """Creates the P2PKH address from the script.""" if self.is_p2sh(): return b58encode_check( bytes([self.network.SCRIPT_ADDRESS]) + bytes.fromhex(self.script_bytes[1]) ) else: return None
[docs] def to_p2wpkh(self): """Creates the P2WPKH address from the script.""" if self.is_p2wpkh(): return bech32_encode( self.network.BECH32_PREFIX, 0, bytes.fromhex(self.script_bytes[1]) ) else: return None
[docs] def to_p2wsh(self): """Creates the P2WSH address from the script.""" if self.is_p2wsh(): return bech32_encode( self.network.BECH32_PREFIX, 0, bytes.fromhex(self.script_bytes[1]) ) else: return None
[docs] def to_p2tr(self): """Creates the P2TR address from the script.""" if self.is_p2tr(): return bech32_encode( self.network.BECH32_PREFIX, 1, bytes.fromhex(self.script_bytes[1]) ) else: return None
[docs] def to_p2sh_script_pub_key(self): """Converts script to p2sh scriptPubKey (locking script) Calculates the hash160 (via the address) of the script and uses it to construct a P2SH script. """ redeem_script = hash160(self.to_hex()) # script hash return Script(["OP_HASH160", redeem_script, "OP_EQUAL"])
[docs] def to_p2wsh_script_pub_key(self): """Converts script to p2wsh scriptPubKey (locking script) Calculates the sha256 of the script and uses it to construct a P2WSH script. """ sha256 = hashlib.sha256(self.to_bytes()).digest() return Script(["OP_0", hexlify(sha256).decode("utf-8")])