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