class Tapyrus::ScriptInterpreter

Constants

DISABLE_OPCODES

Attributes

checker[R]
debug[R]
error[RW]
flags[R]
require_minimal[R]
stack[R]

Public Class Methods

eval(script_sig, script_pubkey) click to toggle source

syntax sugar for simple evaluation for script. @param [Tapyrus::Script] script_sig a scriptSig. @param [Tapyrus::Script] script_pubkey a scriptPubkey.

# File lib/tapyrus/script/script_interpreter.rb, line 33
def self.eval(script_sig, script_pubkey)
  self.new.verify_script(script_sig, script_pubkey)
end
new(flags: SCRIPT_VERIFY_NONE, checker: TxChecker.new) click to toggle source

initialize runner

# File lib/tapyrus/script/script_interpreter.rb, line 38
def initialize(flags: SCRIPT_VERIFY_NONE, checker: TxChecker.new)
  @stack, @debug = [], []
  @flags = flags
  @checker = checker
  @require_minimal = flag?(SCRIPT_VERIFY_MINIMALDATA)
end

Public Instance Methods

eval_script(script, sig_version, is_redeem_script) click to toggle source
# File lib/tapyrus/script/script_interpreter.rb, line 95
def eval_script(script, sig_version, is_redeem_script)
  return set_error(SCRIPT_ERR_SCRIPT_SIZE) if script.size > MAX_SCRIPT_SIZE
  begin
    flow_stack = []
    alt_stack = []
    last_code_separator_index = 0
    op_count = 0
    color_id = nil

    script.chunks.each_with_index do |c, index|
      need_exec = !flow_stack.include?(false)

      return set_error(SCRIPT_ERR_PUSH_SIZE) if c.pushdata? && c.pushed_data.bytesize > MAX_SCRIPT_ELEMENT_SIZE

      opcode = c.opcode

      if need_exec && c.pushdata?
        return set_error(SCRIPT_ERR_MINIMALDATA) if require_minimal && !minimal_push?(c.pushed_data, opcode)
        return set_error(SCRIPT_ERR_BAD_OPCODE) unless verify_pushdata_length(c)
        stack << c.pushed_data.bth
      else
        if opcode > OP_16 && (op_count += 1) > MAX_OPS_PER_SCRIPT
          return set_error(SCRIPT_ERR_OP_COUNT)
        end
        return set_error(SCRIPT_ERR_DISABLED_OPCODE) if DISABLE_OPCODES.include?(opcode)
        if opcode == OP_CODESEPARATOR && sig_version == :base && flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE)
          return set_error(SCRIPT_ERR_OP_CODESEPARATOR)
        end
        next unless (need_exec || (OP_IF <= opcode && opcode <= OP_ENDIF))
        small_int = Opcodes.opcode_to_small_int(opcode)
        if small_int && opcode != OP_0
          push_int(small_int)
        else
          case opcode
          when OP_0
            stack << ''
          when OP_DEPTH
            push_int(stack.size)
          when OP_EQUAL, OP_EQUALVERIFY
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            a, b = pop_string(2)
            result = a == b
            push_int(result ? 1 : 0)
            if opcode == OP_EQUALVERIFY
              if result
                stack.pop
              else
                return set_error(SCRIPT_ERR_EQUALVERIFY)
              end
            end
          when OP_0NOTEQUAL
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            push_int(pop_int == 0 ? 0 : 1)
          when OP_ADD
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            a, b = pop_int(2)
            push_int(a + b)
          when OP_1ADD
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            push_int(pop_int + 1)
          when OP_SUB
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            a, b = pop_int(2)
            push_int(a - b)
          when OP_1SUB
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            push_int(pop_int - 1)
          when OP_IF, OP_NOTIF
            result = false
            if need_exec
              return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if stack.size < 1
              value = pop_string.htb
              if flag?(SCRIPT_VERIFY_MINIMALIF)
                if value.bytesize > 1 || (value.bytesize == 1 && value[0].unpack('C').first != 1)
                  return set_error(SCRIPT_ERR_MINIMALIF)
                end
              end
              result = cast_to_bool(value)
              result = !result if opcode == OP_NOTIF
            end
            flow_stack << result
          when OP_ELSE
            return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if flow_stack.size < 1
            flow_stack << !flow_stack.pop
          when OP_ENDIF
            return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if flow_stack.empty?
            flow_stack.pop
          when OP_NOP
          when OP_NOP1, OP_NOP4..OP_NOP10
            if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS)
              return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS)
            end
          when OP_CHECKLOCKTIMEVERIFY
            next unless flag?(SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY)
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1

            # Note that elsewhere numeric opcodes are limited to operands in the range -2**31+1 to 2**31-1,
            # however it is legal for opcodes to produce results exceeding that range.
            # This limitation is implemented by CScriptNum's default 4-byte limit.
            # If we kept to that limit we'd have a year 2038 problem,
            # even though the nLockTime field in transactions themselves is uint32 which only becomes meaningless after the year 2106.
            # Thus as a special case we tell CScriptNum to accept up to 5-byte bignums,
            # which are good until 2**39-1, well beyond the 2**32-1 limit of the nLockTime field itself.
            locktime = cast_to_int(stack.last, 5)
            return set_error(SCRIPT_ERR_NEGATIVE_LOCKTIME) if locktime < 0
            return set_error(SCRIPT_ERR_UNSATISFIED_LOCKTIME) unless checker.check_locktime(locktime)
          when OP_CHECKSEQUENCEVERIFY
            next unless flag?(SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1

            # nSequence, like nLockTime, is a 32-bit unsigned integer field.
            # See the comment in CHECKLOCKTIMEVERIFY regarding 5-byte numeric operands.
            sequence = cast_to_int(stack.last, 5)

            # In the rare event that the argument may be < 0 due to some arithmetic being done first,
            # you can always use 0 MAX CHECKSEQUENCEVERIFY.
            return set_error(SCRIPT_ERR_NEGATIVE_LOCKTIME) if sequence < 0

            # To provide for future soft-fork extensibility,
            # if the operand has the disabled lock-time flag set, CHECKSEQUENCEVERIFY behaves as a NOP.
            next if (sequence & Tapyrus::TxIn::SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0

            # Compare the specified sequence number with the input.
            return set_error(SCRIPT_ERR_UNSATISFIED_LOCKTIME) unless checker.check_sequence(sequence)
          when OP_DUP
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            stack << stack.last
          when OP_2DUP
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            2.times { stack << stack[-2] }
          when OP_3DUP
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 3
            3.times { stack << stack[-3] }
          when OP_IFDUP
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            stack << stack.last if cast_to_bool(stack.last)
          when OP_RIPEMD160
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            stack << Digest::RMD160.hexdigest(pop_string.htb)
          when OP_SHA1
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            stack << Digest::SHA1.hexdigest(pop_string.htb)
          when OP_SHA256
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            stack << Digest::SHA256.hexdigest(pop_string.htb)
          when OP_HASH160
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            stack << Tapyrus.hash160(pop_string)
          when OP_HASH256
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            stack << Tapyrus.double_sha256(pop_string.htb).bth
          when OP_VERIFY
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            return set_error(SCRIPT_ERR_VERIFY) unless pop_bool
          when OP_TOALTSTACK
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            alt_stack << stack.pop
          when OP_FROMALTSTACK
            return set_error(SCRIPT_ERR_INVALID_ALTSTACK_OPERATION) if alt_stack.size < 1
            stack << alt_stack.pop
          when OP_DROP
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            stack.pop
          when OP_2DROP
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            2.times { stack.pop }
          when OP_NIP
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            stack.delete_at(-2)
          when OP_OVER
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            stack << stack[-2]
          when OP_2OVER
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 4
            2.times { stack << stack[-4] }
          when OP_PICK, OP_ROLL
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            pos = pop_int
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if pos < 0 || pos >= stack.size
            stack << stack[-pos - 1]
            stack.delete_at(-pos - 2) if opcode == OP_ROLL
          when OP_ROT
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 3
            stack << stack[-3]
            stack.delete_at(-4)
          when OP_2ROT
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 6
            2.times { stack << stack[-6] }
            2.times { stack.delete_at(-7) }
          when OP_SWAP
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            tmp = stack.last
            stack[-1] = stack[-2]
            stack[-2] = tmp
          when OP_2SWAP
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 4
            2.times { stack << stack[-4] }
            2.times { stack.delete_at(-5) }
          when OP_TUCK
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            stack.insert(-3, stack.last)
          when OP_ABS
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            v = pop_int
            push_int(v.abs)
          when OP_BOOLAND
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            a, b = pop_int(2)
            push_int((!a.zero? && !b.zero?) ? 1 : 0)
          when OP_BOOLOR
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            a, b = pop_int(2)
            push_int((!a.zero? || !b.zero?) ? 1 : 0)
          when OP_NUMEQUAL, OP_NUMEQUALVERIFY
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            a, b = pop_int(2)
            result = a == b
            push_int(result ? 1 : 0)
            if opcode == OP_NUMEQUALVERIFY
              if result
                stack.pop
              else
                return set_error(SCRIPT_ERR_NUMEQUALVERIFY)
              end
            end
          when OP_LESSTHAN, OP_LESSTHANOREQUAL
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            a, b = pop_int(2)
            push_int(a < b ? 1 : 0) if opcode == OP_LESSTHAN
            push_int(a <= b ? 1 : 0) if opcode == OP_LESSTHANOREQUAL
          when OP_GREATERTHAN, OP_GREATERTHANOREQUAL
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            a, b = pop_int(2)
            push_int(a > b ? 1 : 0) if opcode == OP_GREATERTHAN
            push_int(a >= b ? 1 : 0) if opcode == OP_GREATERTHANOREQUAL
          when OP_MIN
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            push_int(pop_int(2).min)
          when OP_MAX
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            push_int(pop_int(2).max)
          when OP_WITHIN
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 3
            x, a, b = pop_int(3)
            push_int((a <= x && x < b) ? 1 : 0)
          when OP_NOT
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            push_int(pop_int == 0 ? 1 : 0)
          when OP_SIZE
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            item = stack.last
            item = Tapyrus::Script.encode_number(item) if item.is_a?(Numeric)
            size = item.htb.bytesize
            push_int(size)
          when OP_NEGATE
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            push_int(-pop_int)
          when OP_NUMNOTEQUAL
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            a, b = pop_int(2)
            push_int(a == b ? 0 : 1)
          when OP_CODESEPARATOR
            last_code_separator_index = index + 1
          when OP_CHECKSIG, OP_CHECKSIGVERIFY
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 2
            sig, pubkey = pop_string(2)

            subscript = script.subscript(last_code_separator_index..-1)
            if sig_version == :base
              tmp = subscript.find_and_delete(Script.new << sig)
              if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript
                return set_error(SCRIPT_ERR_SIG_FINDANDDELETE)
              end
              subscript = tmp
            end
            if (
                 if sig.htb.bytesize == Tapyrus::Key::COMPACT_SIGNATURE_SIZE
                   !check_schnorr_signature_encoding(sig)
                 else
                   !check_ecdsa_signature_encoding(sig)
                 end
               ) || !check_pubkey_encoding(pubkey)
              return false
            end

            success = checker.check_sig(sig, pubkey, subscript, sig_version)

            # https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#NULLFAIL
            if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0
              return set_error(SCRIPT_ERR_SIG_NULLFAIL)
            end

            push_int(success ? 1 : 0)

            if opcode == OP_CHECKSIGVERIFY
              if success
                stack.pop
              else
                return set_error(SCRIPT_ERR_CHECKSIGVERIFY)
              end
            end
          when OP_CHECKDATASIG, OP_CHECKDATASIGVERIFY
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 3
            sig, msg, pubkey = pop_string(3)

            # check signature encoding without hashtype byte
            if (
                 sig.htb.bytesize != (Tapyrus::Key::COMPACT_SIGNATURE_SIZE - 1) &&
                   !check_ecdsa_signature_encoding(sig, true)
               ) || !check_pubkey_encoding(pubkey)
              return false
            end
            digest = Tapyrus.sha256(msg)
            success = checker.verify_sig(sig, pubkey, digest)
            if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0
              return set_error(SCRIPT_ERR_SIG_NULLFAIL)
            end
            push_int(success ? 1 : 0)
            if opcode == OP_CHECKDATASIGVERIFY
              stack.pop if success
              return set_error(SCRIPT_ERR_CHECKDATASIGVERIFY) unless success
            end
          when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            pubkey_count = pop_int
            return set_error(SCRIPT_ERR_PUBKEY_COUNT) unless (0..MAX_PUBKEYS_PER_MULTISIG).include?(pubkey_count)

            op_count += pubkey_count
            return set_error(SCRIPT_ERR_OP_COUNT) if op_count > MAX_OPS_PER_SCRIPT

            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < pubkey_count

            pubkeys = pop_string(pubkey_count)
            pubkeys = [pubkeys] if pubkeys.is_a?(String)

            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1

            sig_count = pop_int
            return set_error(SCRIPT_ERR_SIG_COUNT) if sig_count < 0 || sig_count > pubkey_count
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < (sig_count)

            sigs = pop_string(sig_count)
            sigs = [sigs] if sigs.is_a?(String)

            subscript = script.subscript(last_code_separator_index..-1)

            if sig_version == :base
              sigs.each do |sig|
                tmp = subscript.find_and_delete(Script.new << sig)
                if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript
                  return set_error(SCRIPT_ERR_SIG_FINDANDDELETE)
                end
                subscript = tmp
              end
            end

            success = true
            current_sig_scheme = nil
            while success && sig_count > 0
              sig = sigs.pop
              pubkey = pubkeys.pop
              sig_scheme = sig.htb.bytesize == Tapyrus::Key::COMPACT_SIGNATURE_SIZE ? :schnorr : :ecdsa
              current_sig_scheme = sig_scheme if current_sig_scheme.nil?

              if (
                   if sig_scheme == :schnorr
                     !check_schnorr_signature_encoding(sig)
                   else
                     !check_ecdsa_signature_encoding(sig)
                   end
                 ) || !check_pubkey_encoding(pubkey)
                return false
              end # error already set.

              return set_error(SCRIPT_ERR_MIXED_SCHEME_MULTISIG) unless sig_scheme == current_sig_scheme

              ok = checker.check_sig(sig, pubkey, subscript, sig_version)
              if ok
                sig_count -= 1
              else
                sigs << sig
              end
              pubkey_count -= 1
              success = false if sig_count > pubkey_count
            end

            if !success && flag?(SCRIPT_VERIFY_NULLFAIL)
              sigs.each do |sig|
                # If the operation failed, we require that all signatures must be empty vector
                return set_error(SCRIPT_ERR_SIG_NULLFAIL) if sig.bytesize > 0
              end
            end

            # A bug causes CHECKMULTISIG to consume one extra argument whose contents were not checked in any way.
            # Unfortunately this is a potential source of mutability,
            # so optionally verify it is exactly equal to zero prior to removing it from the stack.
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
            return set_error(SCRIPT_ERR_SIG_NULLDUMMY) if stack[-1].size > 0

            stack.pop

            push_int(success ? 1 : 0)
            if opcode == OP_CHECKMULTISIGVERIFY
              if success
                stack.pop
              else
                return set_error(SCRIPT_ERR_CHECKMULTISIGVERIFY)
              end
            end
          when OP_RETURN
            return set_error(SCRIPT_ERR_OP_RETURN)
          when OP_COLOR
            # Color id is not permitted in p2sh redeem script
            return set_error(SCRIPT_ERR_OP_COLOR_UNEXPECTED) if is_redeem_script

            # if Color id is already initialized this must be an extra
            if color_id && color_id.type != Tapyrus::Color::TokenTypes::NONE
              return set_error(SCRIPT_ERR_OP_COLOR_MULTIPLE)
            end

            # color id is not allowed inside OP_IF
            return set_error(SCRIPT_ERR_OP_COLOR_IN_BRANCH) unless flow_stack.empty?

            # pop one stack element and verify that it exists
            return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1

            color_id = Tapyrus::Color::ColorIdentifier.parse_from_payload(stack.last.htb)

            # check ColorIdentifier is valid
            return set_error(SCRIPT_ERR_OP_COLOR_ID_INVALID) unless color_id.valid?

            stack.pop
          else
            return set_error(SCRIPT_ERR_BAD_OPCODE)
          end
        end
      end

      # max stack size check
      return set_error(SCRIPT_ERR_STACK_SIZE) if stack.size + alt_stack.size > MAX_STACK_SIZE
    end
  rescue Exception => e
    puts e
    puts e.backtrace
    return set_error(SCRIPT_ERR_UNKNOWN_ERROR, e.message)
  end

  return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) unless flow_stack.empty?

  set_error(SCRIPT_ERR_OK)
  true
end
set_error(err_code, extra_message = nil) click to toggle source
# File lib/tapyrus/script/script_interpreter.rb, line 90
def set_error(err_code, extra_message = nil)
  @error = ScriptError.new(err_code, extra_message)
  false
end
verify_script(script_sig, script_pubkey) click to toggle source

eval script @param [Tapyrus::Script] script_sig a signature script (unlock script which data push only) @param [Tapyrus::Script] script_pubkey a script pubkey (locking script) @return [Boolean] result

# File lib/tapyrus/script/script_interpreter.rb, line 49
def verify_script(script_sig, script_pubkey)
  return set_error(SCRIPT_ERR_SIG_PUSHONLY) if flag?(SCRIPT_VERIFY_SIGPUSHONLY) && !script_sig.push_only?

  stack_copy = nil

  return false unless eval_script(script_sig, :base, false)

  stack_copy = stack.dup if flag?(SCRIPT_VERIFY_P2SH)

  return false unless eval_script(script_pubkey, :base, false)

  return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last.htb)

  # Additional validation for spend-to-script-hash transactions
  if flag?(SCRIPT_VERIFY_P2SH) && script_pubkey.p2sh?
    return set_error(SCRIPT_ERR_SIG_PUSHONLY) unless script_sig.push_only?
    tmp = stack
    @stack = stack_copy
    raise 'stack cannot be empty.' if stack.empty?
    begin
      redeem_script = Tapyrus::Script.parse_from_payload(stack.pop.htb)
    rescue Exception => e
      return set_error(SCRIPT_ERR_BAD_OPCODE, "Failed to parse serialized redeem script for P2SH. #{e.message}")
    end
    return false unless eval_script(redeem_script, :base, true)
    return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last)
  end

  # The CLEANSTACK check is only performed after potential P2SH evaluation,
  # as the non-P2SH evaluation of a P2SH script will obviously not result in a clean stack (the P2SH inputs remain).
  # The same holds for witness evaluation.
  if flag?(SCRIPT_VERIFY_CLEANSTACK)
    # Disallow CLEANSTACK without P2SH, as otherwise a switch CLEANSTACK->P2SH+CLEANSTACK would be possible,
    # which is not a softfork (and P2SH should be one).
    raise 'assert' unless flag?(SCRIPT_VERIFY_P2SH)
    return set_error(SCRIPT_ERR_CLEANSTACK) unless stack.size == 1
  end

  true
end

Private Instance Methods

cast_to_bool(v) click to toggle source

see github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L36-L49

# File lib/tapyrus/script/script_interpreter.rb, line 599
def cast_to_bool(v)
  case v
  when Numeric
    return v != 0
  when String
    v.each_byte.with_index { |b, i| return !(i == (v.bytesize - 1) && b == 0x80) unless b == 0 }
    false
  else
    false
  end
end
cast_to_int(s, max_num_size = DEFAULT_MAX_NUM_SIZE) click to toggle source

cast item to int value.

# File lib/tapyrus/script/script_interpreter.rb, line 561
def cast_to_int(s, max_num_size = DEFAULT_MAX_NUM_SIZE)
  data = s.htb
  raise '"script number overflow"' if data.bytesize > max_num_size
  if require_minimal && data.bytesize > 0
    if data.bytes[-1] & 0x7f == 0 && (data.bytesize <= 1 || data.bytes[data.bytesize - 2] & 0x80 == 0)
      raise 'non-minimally encoded script number'
    end
  end
  Script.decode_number(s)
end
check_ecdsa_signature_encoding(sig, data_sig = false) click to toggle source
# File lib/tapyrus/script/script_interpreter.rb, line 611
def check_ecdsa_signature_encoding(sig, data_sig = false)
  return true if sig.size.zero?
  if !Key.valid_signature_encoding?(sig.htb, data_sig)
    return set_error(SCRIPT_ERR_SIG_DER)
  elsif !low_der_signature?(sig, data_sig)
    return false
  elsif !data_sig && !defined_hashtype_signature?(sig)
    return set_error(SCRIPT_ERR_SIG_HASHTYPE)
  end
  true
end
check_pubkey_encoding(pubkey) click to toggle source
# File lib/tapyrus/script/script_interpreter.rb, line 644
def check_pubkey_encoding(pubkey)
  return set_error(SCRIPT_ERR_PUBKEYTYPE) unless Key.compress_or_uncompress_pubkey?(pubkey)
  true
end
check_schnorr_signature_encoding(sig, data_sig = false) click to toggle source
# File lib/tapyrus/script/script_interpreter.rb, line 623
def check_schnorr_signature_encoding(sig, data_sig = false)
  return false unless sig.htb.bytesize == (data_sig ? 64 : 65)
  return set_error(SCRIPT_ERR_SIG_HASHTYPE) if !data_sig && !defined_hashtype_signature?(sig)
  true
end
defined_hashtype_signature?(signature) click to toggle source
# File lib/tapyrus/script/script_interpreter.rb, line 635
def defined_hashtype_signature?(signature)
  sig = signature.htb
  return false if sig.empty?
  s = sig.unpack('C*')
  hash_type = s[-1] & (~(SIGHASH_TYPE[:anyonecanpay]))
  return false if hash_type < SIGHASH_TYPE[:all] || hash_type > SIGHASH_TYPE[:single]
  true
end
flag?(flag) click to toggle source
# File lib/tapyrus/script/script_interpreter.rb, line 550
def flag?(flag)
  (flags & flag) != 0
end
low_der_signature?(sig, data_sig = false) click to toggle source
# File lib/tapyrus/script/script_interpreter.rb, line 629
def low_der_signature?(sig, data_sig = false)
  return set_error(SCRIPT_ERR_SIG_DER) unless Key.valid_signature_encoding?(sig.htb, data_sig)
  return set_error(SCRIPT_ERR_SIG_HIGH_S) unless Key.low_signature?(sig.htb)
  true
end
minimal_push?(data, opcode) click to toggle source
# File lib/tapyrus/script/script_interpreter.rb, line 649
def minimal_push?(data, opcode)
  if data.bytesize.zero?
    return opcode == OP_0
  elsif data.bytesize == 1 && data.bytes[0] >= 1 && data.bytes[0] <= 16
    return opcode == OP_1 + (data.bytes[0] - 1)
  elsif data.bytesize == 1 && data.bytes[0] == 0x81
    return opcode == OP_1NEGATE
  elsif data.bytesize <= 75
    return opcode == data.bytesize
  elsif data.bytesize <= 255
    return opcode == OP_PUSHDATA1
  elsif data.bytesize <= 65_535
    return opcode == OP_PUSHDATA2
  end
  true
end
pop_bool() click to toggle source

pop the item with the boolean value from the stack.

# File lib/tapyrus/script/script_interpreter.rb, line 594
def pop_bool
  cast_to_bool(pop_string.htb)
end
pop_int(count = 1) click to toggle source

pop the item with the int value for the number specified by count from the stack.

# File lib/tapyrus/script/script_interpreter.rb, line 555
def pop_int(count = 1)
  i = stack.pop(count).map { |s| cast_to_int(s) }
  count == 1 ? i.first : i
end
pop_string(count = 1) click to toggle source

pop the item with the string(hex) value for the number specified by count from the stack.

# File lib/tapyrus/script/script_interpreter.rb, line 578
def pop_string(count = 1)
  s =
    stack
      .pop(count)
      .map do |s|
        case s
        when Numeric
          Script.encode_number(s)
        else
          s
        end
      end
  count == 1 ? s.first : s
end
push_int(i) click to toggle source

push i into stack as encoded by Script#encode_number

# File lib/tapyrus/script/script_interpreter.rb, line 573
def push_int(i)
  stack << Script.encode_number(i)
end
verify_pushdata_length(chunk) click to toggle source
# File lib/tapyrus/script/script_interpreter.rb, line 666
def verify_pushdata_length(chunk)
  buf = StringIO.new(chunk)
  opcode = buf.read(1).ord
  offset = 1
  len =
    case opcode
    when OP_PUSHDATA1
      offset += 1
      buf.read(1).unpack('C').first
    when OP_PUSHDATA2
      offset += 2
      buf.read(2).unpack('v').first
    when OP_PUSHDATA4
      offset += 4
      buf.read(4).unpack('V').first
    else
      opcode
    end
  chunk.bytesize == len + offset
end