lib/bitcoin/script.rb in bitcoin-ruby-0.0.5 vs lib/bitcoin/script.rb in bitcoin-ruby-0.0.6

- old
+ new

@@ -2,14 +2,30 @@ require 'bitcoin' class Bitcoin::Script - OP_1 = 81 - OP_TRUE = 81 OP_0 = 0 OP_FALSE = 0 + OP_1 = 81 + OP_TRUE = 81 + OP_2 = 0x52 + OP_3 = 0x53 + OP_4 = 0x54 + OP_5 = 0x55 + OP_6 = 0x56 + OP_7 = 0x57 + OP_8 = 0x58 + OP_9 = 0x59 + OP_10 = 0x5a + OP_11 = 0x5b + OP_12 = 0x5c + OP_13 = 0x5d + OP_14 = 0x5e + OP_15 = 0x5f + OP_16 = 0x60 + OP_PUSHDATA0 = 0 OP_PUSHDATA1 = 76 OP_PUSHDATA2 = 77 OP_PUSHDATA4 = 78 OP_PUSHDATA_INVALID = 238 # 0xEE @@ -48,10 +64,11 @@ OP_NOP10 = 185 OP_CODESEPARATOR = 171 OP_MIN = 163 OP_MAX = 164 OP_2OVER = 112 + OP_2ROT = 113 OP_2SWAP = 114 OP_IFDUP = 115 OP_DEPTH = 116 OP_1NEGATE = 79 OP_WITHIN = 165 @@ -131,18 +148,34 @@ OPCODES_ALIAS.each{|k,v| OPCODES_PARSE_STRING[k] = v } 2.upto(16).each{|i| OPCODES_PARSE_STRING["OP_#{i}"] = OP_2_16[i-2] } 2.upto(16).each{|i| OPCODES_PARSE_STRING["#{i}" ] = OP_2_16[i-2] } [1,2,4].each{|i| OPCODES_PARSE_STRING.delete("OP_PUSHDATA#{i}") } + SIGHASH_TYPE = { all: 1, none: 2, single: 3, anyonecanpay: 128 } attr_reader :raw, :chunks, :debug # create a new script. +bytes+ is typically input_script + output_script - def initialize(bytes, offset=0) - @raw = bytes + def initialize(input_script, previous_output_script=nil) + @raw_byte_sizes = [input_script.bytesize, previous_output_script ? previous_output_script.bytesize : 0] + + @raw = if previous_output_script + input_script + [ Bitcoin::Script::OP_CODESEPARATOR ].pack("C") + previous_output_script + else + input_script + end + + @chunks = parse(input_script) + + if previous_output_script + @script_codeseparator_index = @chunks.size + @chunks << Bitcoin::Script::OP_CODESEPARATOR + @chunks += parse(previous_output_script) + end + @stack, @stack_alt, @exec_stack = [], [], [] - @chunks = parse(bytes, offset) + @last_codeseparator_index = 0 @do_exec = true end class ::String attr_accessor :bitcoin_pushdata @@ -152,18 +185,18 @@ # parse raw script def parse(bytes, offset=0) program = bytes.unpack("C*") chunks = [] until program.empty? - opcode = program.shift(1)[0] + opcode = program.shift if (opcode > 0) && (opcode < OP_PUSHDATA1) len, tmp = opcode, program[0] chunks << program.shift(len).pack("C*") # 0x16 = 22 due to OP_2_16 from_string parsing - if len == 1 && tmp <= 22 + if len == 1 && tmp && tmp <= 22 chunks.last.bitcoin_pushdata = OP_PUSHDATA0 chunks.last.bitcoin_pushdata_length = len else raise "invalid OP_PUSHDATA0" if len != chunks.last.bytesize end @@ -200,17 +233,18 @@ else chunks << opcode end end chunks - rescue Exception => ex + rescue => ex # bail out! #run returns false but serialization roundtrips still create the right payload. + chunks.pop if ex.message.include?("invalid OP_PUSHDATA") @parse_invalid = true c = bytes.unpack("C*").pack("C*") c.bitcoin_pushdata = OP_PUSHDATA_INVALID c.bitcoin_pushdata_length = c.bytesize - chunks = [ c ] + chunks << c end # string representation of the script def to_string(chunks=nil) string = "" @@ -242,16 +276,49 @@ end }.join end alias :to_payload :to_binary - def to_binary_without_signatures(drop_signatures, chunks=nil) - to_binary( (chunks || @chunks).select{|i| drop_signatures.none?{|e| e == i } } ) + buf = [] + (chunks || @chunks).each.with_index{|chunk,idx| + if chunk == OP_CODESEPARATOR and idx <= @last_codeseparator_index + buf.clear + elsif chunk == OP_CODESEPARATOR + if idx == @script_codeseparator_index + break + else + # skip + end + elsif drop_signatures.none?{|e| e == chunk } + buf << chunk + end + } + to_binary(buf) end + # Adds opcode (OP_0, OP_1, ... OP_CHECKSIG etc.) + # Returns self. + def append_opcode(opcode) + raise "Opcode should be a Fixnum" if !opcode.is_a?(Fixnum) + if opcode >= OP_0 && opcode <= 0xff + @chunks << opcode + else + raise "Opcode should be within [0x00, 0xff]" + end + self + end + # Adds binary string as pushdata. Pushdata will be encoded in the most compact form + # (unless the string contains internal info about serialization that's added by Script class) + # Returns self. + def append_pushdata(pushdata_string) + raise "Pushdata should be a string" if !pushdata_string.is_a?(String) + @chunks << pushdata_string + self + end + def self.pack_pushdata(data) size = data.bytesize if data.bitcoin_pushdata size = data.bitcoin_pushdata_length @@ -331,19 +398,21 @@ # run the script. +check_callback+ is called for OP_CHECKSIG operations def run(block_timestamp=Time.now.to_i, &check_callback) return false if @parse_invalid #p [to_string, block_timestamp, is_p2sh?] - @script_invalid = true if @raw.bytesize > 10_000 + @script_invalid = true if @raw_byte_sizes.any?{|size| size > 10_000 } + @last_codeseparator_index = 0 if block_timestamp >= 1333238400 # Pay to Script Hash (BIP 0016) return pay_to_script_hash(check_callback) if is_p2sh? end @debug = [] - @chunks.each{|chunk| + @chunks.each.with_index{|chunk,idx| break if invalid? + @chunk_last_index = idx @debug << @stack.map{|i| i.unpack("H*") rescue i} @do_exec = @exec_stack.count(false) == 0 ? true : false #p [@stack, @do_exec] @@ -353,11 +422,11 @@ @script_invalid = true @debug << "DISABLED_#{OPCODES[chunk]}" break end - next unless (@do_exec || (OP_IF <= chunk && chunk <= OP_ENDIF)) + next @debug.pop unless (@do_exec || (OP_IF <= chunk && chunk <= OP_ENDIF)) case chunk when *OPCODES_METHOD.keys m = method( n=OPCODES_METHOD[chunk] ) @debug << n.to_s.upcase @@ -372,10 +441,12 @@ end when String if @do_exec @debug << "PUSH DATA #{chunk.unpack("H*")[0]}" @stack << chunk + else + @debug.pop end end } @debug << @stack.map{|i| i.unpack("H*") rescue i } #if @do_exec @@ -384,11 +455,11 @@ @debug << "INVALID TRANSACTION" end @debug << "RESULT" return false if @stack.empty? - return false if [0, ''].include?(@stack.pop) + return false if cast_to_bool(@stack.pop) == false true end def invalid @script_invalid = true; nil @@ -403,63 +474,81 @@ # # <sig> {<pub> OP_CHECKSIG} | OP_HASH160 <script_hash> OP_EQUAL def pay_to_script_hash(check_callback) return false if @chunks.size < 4 *rest, script, _, script_hash, _ = @chunks + script = rest.pop if script == OP_CODESEPARATOR script, script_hash = cast_to_string(script), cast_to_string(script_hash) return false unless Bitcoin.hash160(script.unpack("H*")[0]) == script_hash.unpack("H*")[0] - rest.delete_at(0) if rest[0] && cast_to_bignum(rest[0]) == 0 script = self.class.new(to_binary(rest) + script).inner_p2sh!(script) result = script.run(&check_callback) @debug = script.debug result end def inner_p2sh!(script=nil); @inner_p2sh = true; @inner_script_code = script; self; end def inner_p2sh?; @inner_p2sh; end + # get the inner p2sh script + def inner_p2sh_script + return nil if @chunks.size < 4 + *rest, script, _, script_hash, _ = @chunks + script = rest.pop if script == OP_CODESEPARATOR + script, script_hash = cast_to_string(script), cast_to_string(script_hash) + + return nil unless Bitcoin.hash160(script.unpack("H*")[0]) == script_hash.unpack("H*")[0] + script + end + def is_pay_to_script_hash? + return false if @inner_p2sh return false unless @chunks[-2].is_a?(String) @chunks.size >= 3 && @chunks[-3] == OP_HASH160 && @chunks[-2].bytesize == 20 && @chunks[-1] == OP_EQUAL end alias :is_p2sh? :is_pay_to_script_hash? # check if script is in one of the recognized standard formats def is_standard? - is_pubkey? || is_hash160? || is_multisig? || is_p2sh? + is_pubkey? || is_hash160? || is_multisig? || is_p2sh? || is_op_return? end - # is this a pubkey tx + # is this a pubkey script def is_pubkey? return false if @chunks.size != 2 - (@chunks[1] == OP_CHECKSIG) && @chunks[0].size > 1 + (@chunks[1] == OP_CHECKSIG) && @chunks[0] && (@chunks[0].is_a?(String)) && @chunks[0] != OP_RETURN end alias :is_send_to_ip? :is_pubkey? - # is this a hash160 (address) tx + # is this a hash160 (address) script def is_hash160? return false if @chunks.size != 5 (@chunks[0..1] + @chunks[-2..-1]) == [OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG] && @chunks[2].is_a?(String) && @chunks[2].bytesize == 20 end - # is this a multisig tx + # is this a multisig script def is_multisig? - return false if @chunks.size > 6 || @chunks.size < 4 || !@chunks[-2].is_a?(Fixnum) + return false if @chunks.size < 4 || !@chunks[-2].is_a?(Fixnum) @chunks[-1] == OP_CHECKMULTISIG and get_multisig_pubkeys.all?{|c| c.is_a?(String) } end + # is this an op_return script + def is_op_return? + @chunks[0] == OP_RETURN && @chunks.size <= 2 + end + # get type of this tx def type if is_hash160?; :hash160 elsif is_pubkey?; :pubkey elsif is_multisig?; :multisig elsif is_p2sh?; :p2sh + elsif is_op_return?;:op_return else; :unknown end end # get the public key for this pubkey script @@ -502,10 +591,16 @@ def get_p2sh_address Bitcoin.hash160_to_p2sh_address(get_hash160) end + # get the data possibly included in an OP_RETURN script + def get_op_return_data + return nil unless is_op_return? + cast_to_string(@chunks[1]).unpack("H*")[0] if @chunks[1] + end + # get all addresses this script corresponds to (if possible) def get_addresses return [get_pubkey_address] if is_pubkey? return [get_hash160_address] if is_hash160? return get_multisig_addresses if is_multisig? @@ -517,77 +612,193 @@ def get_address addrs = get_addresses addrs.is_a?(Array) ? addrs[0] : addrs end - # generate pubkey tx script for given +pubkey+ + # generate pubkey tx script for given +pubkey+. returns a raw binary script of the form: + # <pubkey> OP_CHECKSIG def self.to_pubkey_script(pubkey) - pk = [pubkey].pack("H*") - [[pk.bytesize].pack("C"), pk, "\xAC"].join + pack_pushdata([pubkey].pack("H*")) + [ OP_CHECKSIG ].pack("C") end - # generate hash160 tx for given +address+ + # generate hash160 tx for given +address+. returns a raw binary script of the form: + # OP_DUP OP_HASH160 <hash160> OP_EQUALVERIFY OP_CHECKSIG def self.to_hash160_script(hash160) return nil unless hash160 # DUP HASH160 length hash160 EQUALVERIFY CHECKSIG [ ["76", "a9", "14", hash160, "88", "ac"].join ].pack("H*") end + # generate p2sh output script for given +p2sh+ hash160. returns a raw binary script of the form: + # OP_HASH160 <p2sh> OP_EQUAL def self.to_p2sh_script(p2sh) return nil unless p2sh # HASH160 length hash EQUAL [ ["a9", "14", p2sh, "87"].join ].pack("H*") end + # generate hash160 or p2sh output script, depending on the type of the given +address+. + # see #to_hash160_script and #to_p2sh_script. def self.to_address_script(address) hash160 = Bitcoin.hash160_from_address(address) case Bitcoin.address_type(address) when :hash160; to_hash160_script(hash160) when :p2sh; to_p2sh_script(hash160) end end - # generate multisig tx for given +pubkeys+, expecting +m+ signatures + # generate multisig output script for given +pubkeys+, expecting +m+ signatures. + # returns a raw binary script of the form: + # <m> <pubkey> [<pubkey> ...] <n_pubkeys> OP_CHECKMULTISIG def self.to_multisig_script(m, *pubkeys) - pubs = pubkeys.map{|pk|p=[pk].pack("H*"); [p.bytesize].pack("C") + p} - [ [80 + m.to_i].pack("C"), *pubs, [80 + pubs.size].pack("C"), "\xAE"].join + raise "invalid m-of-n number" unless [m, pubkeys.size].all?{|i| (0..20).include?(i) } + raise "invalid m-of-n number" if pubkeys.size < m + pubs = pubkeys.map{|pk| pack_pushdata([pk].pack("H*")) } + + m = m > 16 ? pack_pushdata([m].pack("C")) : [80 + m.to_i].pack("C") + n = pubkeys.size > 16 ? pack_pushdata([pubkeys.size].pack("C")) : [80 + pubs.size].pack("C") + + [ m, *pubs, n, [OP_CHECKMULTISIG].pack("C")].join end - # generate pubkey script sig for given +signature+ and +pubkey+ + # generate OP_RETURN output script with given data. returns a raw binary script of the form: + # OP_RETURN <data> + def self.to_op_return_script(data = nil) + buf = [ OP_RETURN ].pack("C") + return buf unless data + return buf + pack_pushdata( [data].pack("H*") ) + end + + # generate input script sig spending a pubkey output with given +signature+ and +pubkey+. + # returns a raw binary script sig of the form: + # <signature> [<pubkey>] def self.to_pubkey_script_sig(signature, pubkey) - hash_type = "\x01" - #pubkey = [pubkey].pack("H*") if pubkey.bytesize != 65 - return [ [signature.bytesize+1].pack("C"), signature, hash_type ].join unless pubkey + hash_type = [ SIGHASH_TYPE[:all] ].pack("C") + buf = pack_pushdata(signature + hash_type) + return buf unless pubkey - case pubkey[0] - when "\x04" - expected_size = 65 - when "\x02", "\x03" - expected_size = 33 - end + expected_size = case pubkey[0] + when "\x04"; 65 + when "\x02", "\x03"; 33 + end - if !expected_size || pubkey.bytesize != expected_size - raise "pubkey is not in binary form" - end + raise "pubkey is not in binary form" if !expected_size || pubkey.bytesize != expected_size - [ [signature.bytesize+1].pack("C"), signature, hash_type, [pubkey.bytesize].pack("C"), pubkey ].join + return buf + pack_pushdata(pubkey) end + # generate p2sh multisig output script for given +args+. + # returns the p2sh output script, and the redeem script needed to spend it. + # see #to_multisig_script for the redeem script, and #to_p2sh_script for the p2sh script. + def self.to_p2sh_multisig_script(*args) + redeem_script = to_multisig_script(*args) + p2sh_script = to_p2sh_script(Bitcoin.hash160(redeem_script.hth)) + return p2sh_script, redeem_script + end + # alias for #to_pubkey_script_sig def self.to_signature_pubkey_script(*a) to_pubkey_script_sig(*a) end + # generate input script sig spending a multisig output script. + # returns a raw binary script sig of the form: + # OP_0 <sig> [<sig> ...] def self.to_multisig_script_sig(*sigs) - from_string("0 #{sigs.map{|s|s.unpack('H*')[0]}.join(' ')}").raw + partial_script = [OP_0].pack("C*") + sigs.reverse_each{ |sig| partial_script = add_sig_to_multisig_script_sig(sig, partial_script) } + partial_script end + # take a multisig script sig (or p2sh multisig script sig) and add + # another signature to it after the OP_0. Used to sign a tx by + # multiple parties. Signatures must be in the same order as the + # pubkeys in the output script being redeemed. + def self.add_sig_to_multisig_script_sig(sig, script_sig) + signature = sig + [SIGHASH_TYPE[:all]].pack("C*") + offset = script_sig.empty? ? 0 : 1 + script_sig.insert(offset, pack_pushdata(signature)) + end + + # generate input script sig spending a p2sh-multisig output script. + # returns a raw binary script sig of the form: + # OP_0 <sig> [<sig> ...] <redeem_script> + def self.to_p2sh_multisig_script_sig(redeem_script, *sigs) + to_multisig_script_sig(*sigs.flatten) + pack_pushdata(redeem_script) + end + def get_signatures_required return false unless is_multisig? @chunks[0] - 80 end + # This matches CScript::GetSigOpCount(bool fAccurate) + # Note: this does not cover P2SH script which is to be unserialized + # and checked explicitly when validating blocks. + def sigops_count_accurate(is_accurate) + count = 0 + last_opcode = nil + @chunks.each do |chunk| # pushdate or opcode + if chunk == OP_CHECKSIG || chunk == OP_CHECKSIGVERIFY + count += 1 + elsif chunk == OP_CHECKMULTISIG || chunk == OP_CHECKMULTISIGVERIFY + # Accurate mode counts exact number of pubkeys required (not signatures, but pubkeys!). Only used in P2SH scripts. + # Inaccurate mode counts every multisig as 20 signatures. + if is_accurate && last_opcode && last_opcode.is_a?(Fixnum) && last_opcode >= OP_1 && last_opcode <= OP_16 + count += ::Bitcoin::Script.decode_OP_N(last_opcode) + else + count += 20 + end + end + last_opcode = chunk + end + count + end + + # This method applies to script_sig that is an input for p2sh output. + # Bitcoind has somewhat special way to return count for invalid input scripts: + # it returns 0 when the opcode can't be parsed or when it's over OP_16. + # Also, if the OP_{N} is used anywhere it's treated as 0-length data. + # See CScript::GetSigOpCount(const CScript& scriptSig) in bitcoind. + def sigops_count_for_p2sh + # This is a pay-to-script-hash scriptPubKey; + # get the last item that the scriptSig + # pushes onto the stack: + + return 0 if @chunks.size == 0 + + data = nil + @chunks.each do |chunk| + case chunk + when Fixnum + data = "" + return 0 if chunk > OP_16 + when String + data = chunk + end + end + return 0 if data == "" + + ::Bitcoin::Script.new(data).sigops_count_accurate(true) + end + + # Converts OP_{0,1,2,...,16} into 0, 1, 2, ..., 16. + # Returns nil for other opcodes. + def self.decode_OP_N(opcode) + if opcode == OP_0 + return 0 + end + if opcode.is_a?(Fixnum) && opcode >= OP_1 && opcode <= OP_16 + return opcode - (OP_1 - 1); + else + nil + end + end + + + + ## OPCODES # Does nothing def op_nop; end def op_nop1; end @@ -737,10 +948,11 @@ def op_1add a = pop_int @stack << (a + 1) end + # 1 is subtracted from the input. def op_1sub a = pop_int @stack << (a - 1) end @@ -755,19 +967,18 @@ @stack.pop end # Returns 1 if the inputs are exactly equal, 0 otherwise. def op_equal - #a, b = @stack.pop(2) - a, b = pop_int(2) + a, b = pop_string(2) @stack << (a == b ? 1 : 0) end # Marks transaction as invalid if top stack value is not true. True is removed, but false is not. def op_verify res = pop_int - if res == 0 + if cast_to_bool(res) == false @stack << res @script_invalid = true # raise 'transaction invalid' ? else @script_invalid = false end @@ -811,11 +1022,11 @@ @stack += p1 += p2 end # If the input is true, duplicate it. def op_ifdup - if cast_to_bignum(@stack.last) != 0 + if cast_to_bool(@stack.last) == true @stack << @stack.last end end # The number -1 is pushed onto the stack. @@ -859,22 +1070,22 @@ # If the top stack value is not 0, the statements are executed. The top stack value is removed. def op_if value = false if @do_exec - return if @stack.size < 1 - value = pop_int == 1 ? true : false + (invalid; return) if @stack.size < 1 + value = cast_to_bool(pop_string) == false ? false : true end @exec_stack << value end # If the top stack value is 0, the statements are executed. The top stack value is removed. def op_notif value = false if @do_exec - return if @stack.size < 1 - value = pop_int == 1 ? false : true + (invalid; return) if @stack.size < 1 + value = cast_to_bool(pop_string) == false ? true : false end @exec_stack << value end # If the preceding OP_IF or OP_NOTIF or OP_ELSE was not executed then these statements are and if the preceding OP_IF or OP_NOTIF or OP_ELSE was executed then these statements are not. @@ -889,18 +1100,28 @@ @exec_stack.pop end # The item n back in the stack is copied to the top. def op_pick + return invalid if @stack.size < 2 pos = pop_int + return invalid if (pos < 0) || (pos >= @stack.size) item = @stack[-(pos+1)] @stack << item if item end + # The fifth and sixth items back are moved to the top of the stack. + def op_2rot + return invalid if @stack.size < 6 + @stack[-6..-1] = [ *@stack[-4..-1], *@stack[-6..-5] ] + end + # The item n back in the stack is moved to the top. def op_roll + return invalid if @stack.size < 2 pos = pop_int + return invalid if (pos < 0) || (pos >= @stack.size) idx = -(pos+1) item = @stack[idx] if item @stack.delete_at(idx) @stack << item if item @@ -957,34 +1178,57 @@ return cast_to_string(@stack.pop) unless count @stack.pop(count).map{|i| cast_to_string(i) } end def cast_to_bignum(buf) + return (invalid; 0) unless buf case buf - when Numeric; buf - when String; OpenSSL::BN.new([buf.bytesize].pack("N") + buf.reverse, 0).to_i + when Numeric + invalid if OpenSSL::BN.new(buf.to_s).to_s(0).unpack("N")[0] > 4 + buf + when String + invalid if buf.bytesize > 4 + OpenSSL::BN.new([buf.bytesize].pack("N") + buf.reverse, 0).to_i else; raise TypeError, 'cast_to_bignum: failed to cast: %s (%s)' % [buf, buf.class] end end def cast_to_string(buf) + return (invalid; "") unless buf case buf - when Numeric; OpenSSL::BN.new(buf.to_s).to_s(0)[4..-1] + when Numeric; OpenSSL::BN.new(buf.to_s).to_s(0)[4..-1].reverse when String; buf; else; raise TypeError, 'cast_to_string: failed to cast: %s (%s)' % [buf, buf.class] end end + def cast_to_bool(buf) + buf = cast_to_string(buf).unpack("C*") + size = buf.size + buf.each.with_index{|byte,index| + if byte != 0 + # Can be negative zero + if (index == (size-1)) && byte == 0x80 + return false + else + return true + end + end + } + return false + end + # Same as OP_NUMEQUAL, but runs OP_VERIFY afterward. def op_numequalverify op_numequal; op_verify end # All of the signature checking words will only match signatures # to the data after the most recently-executed OP_CODESEPARATOR. def op_codeseparator @codehash_start = @chunks.size - @chunks.reverse.index(OP_CODESEPARATOR) + @last_codeseparator_index = @chunk_last_index end def codehash_script(opcode) # CScript scriptCode(pbegincodehash, pend); script = to_string(@chunks[(@codehash_start||0)...@chunks.size-@chunks.reverse.index(opcode)]) @@ -996,35 +1240,39 @@ # do a CHECKSIG operation on the current stack, # asking +check_callback+ to do the actual signature verification. # This is used by Protocol::Tx#verify_input_signature def op_checksig(check_callback) return invalid if @stack.size < 2 - pubkey = @stack.pop + pubkey = cast_to_string(@stack.pop) #return (@stack << 0) unless Bitcoin::Script.is_canonical_pubkey?(pubkey) # only for isStandard - drop_sigs = [ @stack[-1] ] + drop_sigs = [ cast_to_string(@stack[-1]) ] signature = cast_to_string(@stack.pop) #return (@stack << 0) unless Bitcoin::Script.is_canonical_signature?(signature) # only for isStandard return (@stack << 0) if signature == "" sig, hash_type = parse_sig(signature) - if inner_p2sh? - script_code = @inner_script_code || to_binary_without_signatures(drop_sigs) - drop_sigs = nil - else - script_code, drop_sigs = nil, nil - end + subscript = sighash_subscript(drop_sigs) if check_callback == nil # for tests @stack << 1 else # real signature check callback @stack << - ((check_callback.call(pubkey, sig, hash_type, drop_sigs, script_code) == true) ? 1 : 0) + ((check_callback.call(pubkey, sig, hash_type, subscript) == true) ? 1 : 0) end end + def sighash_subscript(drop_sigs) + if inner_p2sh? && @inner_script_code + ::Bitcoin::Script.new(@inner_script_code).to_binary_without_signatures(drop_sigs) + else + to_binary_without_signatures(drop_sigs) + end + end + + # Same as OP_CHECKSIG, but OP_VERIFY is executed afterward. def op_checksigverify(check_callback) op_checksig(check_callback) op_verify end @@ -1051,37 +1299,44 @@ return invalid if @stack.size < 1 n_sigs = pop_int return invalid if n_sigs < 0 || n_sigs > n_pubkeys return invalid if @stack.size < n_sigs - sigs = drop_sigs = pop_string(n_sigs) + sigs = pop_string(n_sigs) + drop_sigs = sigs.dup - @stack.pop if @stack[-1] && cast_to_bignum(@stack[-1]) == 0 # remove OP_0 from stack + # Bitcoin-core removes an extra item from the stack + @stack.pop - if inner_p2sh? - script_code = @inner_script_code || to_binary_without_signatures(drop_sigs) - drop_sigs = nil - else - script_code, drop_sigs = nil, nil - end + subscript = sighash_subscript(drop_sigs) success = true while success && n_sigs > 0 sig, pub = sigs.pop, pubkeys.pop + unless sig && sig.size > 0 + success = false + break + end signature, hash_type = parse_sig(sig) - if check_callback.call(pub, signature, hash_type, drop_sigs, script_code) + if pub.size > 0 && check_callback.call(pub, signature, hash_type, subscript) n_sigs -= 1 else sigs << sig end n_pubkeys -= 1 success = false if n_sigs > n_pubkeys end - @stack << (success ? 1 : (invalid; 0)) + @stack << (success ? 1 : 0) end + # Same as OP_CHECKMULTISIG, but OP_VERIFY is executed afterward. + def op_checkmultisigverify(check_callback) + op_checkmultisig(check_callback) + op_verify + end + # op_eval: https://en.bitcoin.it/wiki/BIP_0012 # the BIP was never accepted and must be handled as old OP_NOP1 def op_nop1 end @@ -1103,10 +1358,9 @@ end true end - SIGHASH_TYPE = { all: 1, none: 2, single: 3, anyonecanpay: 128 } def self.is_canonical_signature?(sig) return false if sig.bytesize < 9 # Non-canonical signature: too short return false if sig.bytesize > 73 # Non-canonical signature: too long