# Copyright (C) 2012-2015 The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # # It is subject to the license terms in the LICENSE file found in the top-level # directory of this distribution. # # No part of python-bitcoinlib, including this file, may be copied, modified, # propagated, or distributed except according to the terms contained in the # LICENSE file. """Scripts Functionality to build scripts, as well as SignatureHash(). Script evaluation is in bitcoin.core.scripteval """ from __future__ import absolute_import, division, print_function import sys _bchr = chr _bord = ord if sys.version > '3': long = int _bchr = lambda x: bytes([x]) _bord = lambda x: x from io import BytesIO as _BytesIO else: from cStringIO import StringIO as _BytesIO import struct import bitcoin.core import bitcoin.core._bignum from .serialize import * MAX_SCRIPT_SIZE = 10000 MAX_SCRIPT_ELEMENT_SIZE = 520 MAX_SCRIPT_OPCODES = 201 OPCODE_NAMES = {} _opcode_instances = [] class CScriptOp(int): """A single script opcode""" __slots__ = [] @staticmethod def encode_op_pushdata(d): """Encode a PUSHDATA op, returning bytes""" if len(d) < 0x4c: return b'' + _bchr(len(d)) + d # OP_PUSHDATA elif len(d) <= 0xff: return b'\x4c' + _bchr(len(d)) + d # OP_PUSHDATA1 elif len(d) <= 0xffff: return b'\x4d' + struct.pack(b' OP_PUSHDATA4: yield (opcode, None, sop_idx) else: datasize = None pushdata_type = None if opcode < OP_PUSHDATA1: pushdata_type = 'PUSHDATA(%d)' % opcode datasize = opcode elif opcode == OP_PUSHDATA1: pushdata_type = 'PUSHDATA1' if i >= len(self): raise CScriptInvalidError('PUSHDATA1: missing data length') datasize = _bord(self[i]) i += 1 elif opcode == OP_PUSHDATA2: pushdata_type = 'PUSHDATA2' if i + 1 >= len(self): raise CScriptInvalidError('PUSHDATA2: missing data length') datasize = _bord(self[i]) + (_bord(self[i+1]) << 8) i += 2 elif opcode == OP_PUSHDATA4: pushdata_type = 'PUSHDATA4' if i + 3 >= len(self): raise CScriptInvalidError('PUSHDATA4: missing data length') datasize = _bord(self[i]) + (_bord(self[i+1]) << 8) + (_bord(self[i+2]) << 16) + (_bord(self[i+3]) << 24) i += 4 else: assert False # shouldn't happen data = bytes(self[i:i+datasize]) # Check for truncation if len(data) < datasize: raise CScriptTruncatedPushDataError('%s: truncated data' % pushdata_type, data) i += datasize yield (opcode, data, sop_idx) def __iter__(self): """'Cooked' iteration Returns either a CScriptOP instance, an integer, or bytes, as appropriate. See raw_iter() if you need to distinguish the different possible PUSHDATA encodings. """ for (opcode, data, sop_idx) in self.raw_iter(): if opcode == 0: yield 0 elif data is not None: yield data else: opcode = CScriptOp(opcode) if opcode.is_small_int(): yield opcode.decode_op_n() else: yield CScriptOp(opcode) def __repr__(self): # For Python3 compatibility add b before strings so testcases don't # need to change def _repr(o): if isinstance(o, bytes): return "x('%s')" % bitcoin.core.b2x(o) else: return repr(o) ops = [] i = iter(self) while True: op = None try: op = _repr(next(i)) except CScriptTruncatedPushDataError as err: op = '%s...' % (_repr(err.data), err) break except CScriptInvalidError as err: op = '' % err break except StopIteration: break finally: if op is not None: ops.append(op) return "CScript([%s])" % ', '.join(ops) def is_p2sh(self): """Test if the script is a p2sh scriptPubKey Note that this test is consensus-critical. """ return (len(self) == 23 and _bord(self[0]) == OP_HASH160 and _bord(self[1]) == 0x14 and _bord(self[22]) == OP_EQUAL) def is_witness_scriptpubkey(self): """Returns true if this is a scriptpubkey signaling segregated witness data. A witness program is any valid CScript that consists of a 1-byte push opcode followed by a data push between 2 and 40 bytes. """ size = len(self) if size < 4 or size > 42: return False head = struct.unpack(' OP_16: return False except CScriptInvalidError: return False return True def has_canonical_pushes(self): """Test if script only uses canonical pushes Not yet consensus critical; may be in the future. """ try: for (op, data, idx) in self.raw_iter(): if op > OP_16: continue elif op < OP_PUSHDATA1 and op > OP_0 and len(data) == 1 and _bord(data[0]) <= 16: # Could have used an OP_n code, rather than a 1-byte push. return False elif op == OP_PUSHDATA1 and len(data) < OP_PUSHDATA1: # Could have used a normal n-byte push, rather than OP_PUSHDATA1. return False elif op == OP_PUSHDATA2 and len(data) <= 0xFF: # Could have used a OP_PUSHDATA1. return False elif op == OP_PUSHDATA4 and len(data) <= 0xFFFF: # Could have used a OP_PUSHDATA2. return False except CScriptInvalidError: # Invalid pushdata return False return True def is_unspendable(self): """Test if the script is provably unspendable""" return (len(self) > 0 and _bord(self[0]) == OP_RETURN) def is_valid(self): """Return True if the script is valid, False otherwise The script is valid if all PUSHDATA's are valid; invalid opcodes do not make is_valid() return False. """ try: list(self) except CScriptInvalidError: return False return True def to_p2sh_scriptPubKey(self, checksize=True): """Create P2SH scriptPubKey from this redeemScript That is, create the P2SH scriptPubKey that requires this script as a redeemScript to spend. checksize - Check if the redeemScript is larger than the 520-byte max pushdata limit; raise ValueError if limit exceeded. Since a >520-byte PUSHDATA makes EvalScript() fail, it's not actually possible to redeem P2SH outputs with redeem scripts >520 bytes. """ if checksize and len(self) > MAX_SCRIPT_ELEMENT_SIZE: raise ValueError("redeemScript exceeds max allowed size; P2SH output would be unspendable") return CScript([OP_HASH160, bitcoin.core.Hash160(self), OP_EQUAL]) def GetSigOpCount(self, fAccurate): """Get the SigOp count. fAccurate - Accurately count CHECKMULTISIG, see BIP16 for details. Note that this is consensus-critical. """ n = 0 lastOpcode = OP_INVALIDOPCODE for (opcode, data, sop_idx) in self.raw_iter(): if opcode in (OP_CHECKSIG, OP_CHECKSIGVERIFY): n += 1 elif opcode in (OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY): if fAccurate and (OP_1 <= lastOpcode <= OP_16): n += opcode.decode_op_n() else: n += 20 lastOpcode = opcode return n class CScriptWitness(ImmutableSerializable): """An encoding of the data elements on the initial stack for (segregated witness) """ __slots__ = ['stack'] def __init__(self, stack=()): object.__setattr__(self, 'stack', stack) def __len__(self): return len(self.stack) def __iter__(self): return iter(self.stack) def __repr__(self): return 'CScriptWitness(' + ','.join("x('%s')" % bitcoin.core.b2x(s) for s in self.stack) + ')' def is_null(self): return len(self.stack) == 0 @classmethod def stream_deserialize(cls, f): n = VarIntSerializer.stream_deserialize(f) stack = tuple(BytesSerializer.stream_deserialize(f) for i in range(n)) return cls(stack) def stream_serialize(self, f): VarIntSerializer.stream_serialize(len(self.stack), f) for s in self.stack: BytesSerializer.stream_serialize(s, f) SIGHASH_ALL = 1 SIGHASH_NONE = 2 SIGHASH_SINGLE = 3 SIGHASH_ANYONECANPAY = 0x80 def FindAndDelete(script, sig): """Consensus critical, see FindAndDelete() in Satoshi codebase""" r = b'' last_sop_idx = sop_idx = 0 skip = True for (opcode, data, sop_idx) in script.raw_iter(): if not skip: r += script[last_sop_idx:sop_idx] last_sop_idx = sop_idx if script[sop_idx:sop_idx + len(sig)] == sig: skip = True else: skip = False if not skip: r += script[last_sop_idx:] return CScript(r) def IsLowDERSignature(sig): """ Loosely correlates with IsLowDERSignature() from script/interpreter.cpp Verifies that the S value in a DER signature is the lowest possible value. Used by BIP62 malleability fixes. """ length_r = sig[3] if isinstance(length_r, str): length_r = int(struct.unpack('B', length_r)[0]) length_s = sig[5 + length_r] if isinstance(length_s, str): length_s = int(struct.unpack('B', length_s)[0]) s_val = list(struct.unpack(str(length_s) + 'B', sig[6 + length_r:6 + length_r + length_s])) # If the S value is above the order of the curve divided by two, its # complement modulo the order could have been used instead, which is # one byte shorter when encoded correctly. max_mod_half_order = [ 0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, 0x5d,0x57,0x6e,0x73,0x57,0xa4,0x50,0x1d, 0xdf,0xe9,0x2f,0x46,0x68,0x1b,0x20,0xa0] return CompareBigEndian(s_val, [0]) > 0 and \ CompareBigEndian(s_val, max_mod_half_order) <= 0 def CompareBigEndian(c1, c2): """ Loosely matches CompareBigEndian() from eccryptoverify.cpp Compares two arrays of bytes, and returns a negative value if the first is less than the second, 0 if they're equal, and a positive value if the first is greater than the second. """ c1 = list(c1) c2 = list(c2) # Adjust starting positions until remaining lengths of the two arrays match while len(c1) > len(c2): if c1.pop(0) > 0: return 1 while len(c2) > len(c1): if c2.pop(0) > 0: return -1 while len(c1) > 0: diff = c1.pop(0) - c2.pop(0) if diff != 0: return diff return 0 def RawSignatureHash(script, txTo, inIdx, hashtype): """Consensus-correct SignatureHash Returns (hash, err) to precisely match the consensus-critical behavior of the SIGHASH_SINGLE bug. (inIdx is *not* checked for validity) If you're just writing wallet software you probably want SignatureHash() instead. """ HASH_ONE = b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' if inIdx >= len(txTo.vin): return (HASH_ONE, "inIdx %d out of range (%d)" % (inIdx, len(txTo.vin))) txtmp = bitcoin.core.CMutableTransaction.from_tx(txTo) for txin in txtmp.vin: txin.scriptSig = b'' txtmp.vin[inIdx].scriptSig = FindAndDelete(script, CScript([OP_CODESEPARATOR])) if (hashtype & 0x1f) == SIGHASH_NONE: txtmp.vout = [] for i in range(len(txtmp.vin)): if i != inIdx: txtmp.vin[i].nSequence = 0 elif (hashtype & 0x1f) == SIGHASH_SINGLE: outIdx = inIdx if outIdx >= len(txtmp.vout): return (HASH_ONE, "outIdx %d out of range (%d)" % (outIdx, len(txtmp.vout))) tmp = txtmp.vout[outIdx] txtmp.vout = [] for i in range(outIdx): txtmp.vout.append(bitcoin.core.CTxOut()) txtmp.vout.append(tmp) for i in range(len(txtmp.vin)): if i != inIdx: txtmp.vin[i].nSequence = 0 if hashtype & SIGHASH_ANYONECANPAY: tmp = txtmp.vin[inIdx] txtmp.vin = [] txtmp.vin.append(tmp) txtmp.wit = bitcoin.core.CTxWitness() s = txtmp.serialize() s += struct.pack(b"