lib/tapyrus/script/script_interpreter.rb in tapyrus-0.3.1 vs lib/tapyrus/script/script_interpreter.rb in tapyrus-0.3.2

- old
+ new

@@ -1,16 +1,22 @@ module Tapyrus class ScriptInterpreter include Tapyrus::Opcodes - attr_reader :stack + attr_accessor :stack attr_reader :debug attr_reader :flags attr_accessor :error attr_reader :checker attr_reader :require_minimal + attr_accessor :flow_stack + attr_accessor :alt_stack + attr_accessor :last_code_separator_index + attr_accessor :op_count + attr_accessor :color_id + DISABLE_OPCODES = [ OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT, @@ -49,15 +55,15 @@ 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) + return false unless eval_script(script_sig, false) stack_copy = stack.dup if flag?(SCRIPT_VERIFY_P2SH) - return false unless eval_script(script_pubkey, :base, false) + return false unless eval_script(script_pubkey, 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? @@ -68,11 +74,11 @@ 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 false unless eval_script(redeem_script, 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). @@ -90,463 +96,479 @@ def set_error(err_code, extra_message = nil) @error = ScriptError.new(err_code, extra_message) false end - def eval_script(script, sig_version, is_redeem_script) + def eval_script(script, 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 + reset_params + script.chunks.each_with_index do |chunk, index| + result = next_step(script, chunk, index, is_redeem_script) + return result if result.is_a?(FalseClass) + end + rescue Exception => e + puts e + puts e.backtrace + return set_error(SCRIPT_ERR_UNKNOWN_ERROR, e.message) + end - script.chunks.each_with_index do |c, index| - need_exec = !flow_stack.include?(false) + return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) unless @flow_stack.empty? - return set_error(SCRIPT_ERR_PUSH_SIZE) if c.pushdata? && c.pushed_data.bytesize > MAX_SCRIPT_ELEMENT_SIZE + set_error(SCRIPT_ERR_OK) + true + end - opcode = c.opcode + def next_step(script, c, index, is_redeem_script) + need_exec = !@flow_stack.include?(false) - 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 + return set_error(SCRIPT_ERR_PUSH_SIZE) if c.pushdata? && c.pushed_data.bytesize > MAX_SCRIPT_ELEMENT_SIZE - # 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 + opcode = c.opcode - # 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) + 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 && flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) + return set_error(SCRIPT_ERR_OP_CODESEPARATOR) + end - # 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 + # next unless (need_exec || (OP_IF <= opcode && opcode <= OP_ENDIF)) + return 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 - 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 + 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 - 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) + 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 + return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS) if flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) + when OP_CHECKLOCKTIMEVERIFY + # next unless flag?(SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY) + return unless flag?(SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY) - 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 + return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1 - success = checker.check_sig(sig, pubkey, subscript, sig_version) + # 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 unless flag?(SCRIPT_VERIFY_CHECKSEQUENCEVERIFY) + return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1 - # 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 + # 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) - push_int(success ? 1 : 0) + # 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 - 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) + # 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 + return if (sequence & Tapyrus::TxIn::SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0 - # 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) + # 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) - op_count += pubkey_count - return set_error(SCRIPT_ERR_OP_COUNT) if op_count > MAX_OPS_PER_SCRIPT + subscript = script.subscript(@last_code_separator_index..-1) + tmp = subscript.find_and_delete(Script.new << sig) + return set_error(SCRIPT_ERR_SIG_FINDANDDELETE) if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript + subscript = tmp - return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < pubkey_count + 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 - pubkeys = pop_string(pubkey_count) - pubkeys = [pubkeys] if pubkeys.is_a?(String) + success = checker.check_sig(sig, pubkey, subscript) - return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1 + # https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#NULLFAIL + return set_error(SCRIPT_ERR_SIG_NULLFAIL) if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0 - 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) + push_int(success ? 1 : 0) - sigs = pop_string(sig_count) - sigs = [sigs] if sigs.is_a?(String) + 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) - subscript = script.subscript(last_code_separator_index..-1) + # 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) + return set_error(SCRIPT_ERR_SIG_NULLFAIL) if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0 + 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) - 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 + @op_count += pubkey_count + return set_error(SCRIPT_ERR_OP_COUNT) if @op_count > MAX_OPS_PER_SCRIPT - 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? + return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < pubkey_count - 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. + pubkeys = pop_string(pubkey_count) + pubkeys = [pubkeys] if pubkeys.is_a?(String) - return set_error(SCRIPT_ERR_MIXED_SCHEME_MULTISIG) unless sig_scheme == current_sig_scheme + return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1 - 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 + 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) - 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 + sigs = pop_string(sig_count) + sigs = [sigs] if sigs.is_a?(String) - # 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 + subscript = script.subscript(@last_code_separator_index..-1) - stack.pop + 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 - 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 + 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 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 + 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. - # color id is not allowed inside OP_IF - return set_error(SCRIPT_ERR_OP_COLOR_IN_BRANCH) unless flow_stack.empty? + return set_error(SCRIPT_ERR_MIXED_SCHEME_MULTISIG) unless sig_scheme == current_sig_scheme - # pop one stack element and verify that it exists - return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1 + ok = checker.check_sig(sig, pubkey, subscript) + if ok + sig_count -= 1 + else + sigs << sig + end + pubkey_count -= 1 + success = false if sig_count > pubkey_count + end - color_id = Tapyrus::Color::ColorIdentifier.parse_from_payload(stack.last.htb) + 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 - # check ColorIdentifier is valid - return set_error(SCRIPT_ERR_OP_COLOR_ID_INVALID) unless color_id.valid? + # 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_BAD_OPCODE) + return set_error(SCRIPT_ERR_CHECKMULTISIGVERIFY) end 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 - # max stack size check - return set_error(SCRIPT_ERR_STACK_SIZE) if stack.size + alt_stack.size > MAX_STACK_SIZE + # 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 - 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? + # max stack size check + return set_error(SCRIPT_ERR_STACK_SIZE) if stack.size + @alt_stack.size > MAX_STACK_SIZE + end - set_error(SCRIPT_ERR_OK) - true + def reset_params + @flow_stack = [] + @alt_stack = [] + @last_code_separator_index = 0 + @op_count = 0 + @color_id = nil end + # see https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L36-L49 + 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 + private def flag?(flag) (flags & flag) != 0 end @@ -591,22 +613,9 @@ end # pop the item with the boolean value from the stack. def pop_bool cast_to_bool(pop_string.htb) - end - - # see https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L36-L49 - 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 def check_ecdsa_signature_encoding(sig, data_sig = false) return true if sig.size.zero? if !Key.valid_signature_encoding?(sig.htb, data_sig)