lib/bitcoin/script.rb in bitcoin-ruby-0.0.6 vs lib/bitcoin/script.rb in bitcoin-ruby-0.0.7
- old
+ new
@@ -150,28 +150,29 @@
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
+ attr_reader :raw, :chunks, :debug, :stack
# create a new script. +bytes+ is typically input_script + output_script
def initialize(input_script, previous_output_script=nil)
@raw_byte_sizes = [input_script.bytesize, previous_output_script ? previous_output_script.bytesize : 0]
+ @input_script, @previous_output_script = input_script, previous_output_script
- @raw = if previous_output_script
- input_script + [ Bitcoin::Script::OP_CODESEPARATOR ].pack("C") + previous_output_script
+ @raw = if @previous_output_script
+ @input_script + [ Bitcoin::Script::OP_CODESEPARATOR ].pack("C") + @previous_output_script
else
- input_script
+ @input_script
end
- @chunks = parse(input_script)
+ @chunks = parse(@input_script)
if previous_output_script
@script_codeseparator_index = @chunks.size
@chunks << Bitcoin::Script::OP_CODESEPARATOR
- @chunks += parse(previous_output_script)
+ @chunks += parse(@previous_output_script)
end
@stack, @stack_alt, @exec_stack = [], [], []
@last_codeseparator_index = 0
@do_exec = true
@@ -352,12 +353,16 @@
[len].pack("C") + data
end
end
# script object of a string representation
- def self.from_string(script_string)
- new(binary_from_string(script_string))
+ def self.from_string(input_script, previous_output_script=nil)
+ if previous_output_script
+ new(binary_from_string(input_script), binary_from_string(previous_output_script))
+ else
+ new(binary_from_string(input_script))
+ end
end
class ScriptOpcodeError < StandardError; end
# raw script binary of a string representation
@@ -394,19 +399,19 @@
def invalid?
@script_invalid ||= false
end
# run the script. +check_callback+ is called for OP_CHECKSIG operations
- def run(block_timestamp=Time.now.to_i, &check_callback)
+ def run(block_timestamp=Time.now.to_i, opts={}, &check_callback)
return false if @parse_invalid
#p [to_string, block_timestamp, is_p2sh?]
@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?
+ return pay_to_script_hash(block_timestamp, opts, check_callback) if is_p2sh?
end
@debug = []
@chunks.each.with_index{|chunk,idx|
break if invalid?
@@ -428,11 +433,21 @@
case chunk
when *OPCODES_METHOD.keys
m = method( n=OPCODES_METHOD[chunk] )
@debug << n.to_s.upcase
- (m.arity == 1) ? m.call(check_callback) : m.call # invoke opcode method
+ # invoke opcode method
+ case m.arity
+ when 0
+ m.call
+ when 1
+ m.call(check_callback)
+ when -2 # One fixed parameter, one optional
+ m.call(check_callback, opts)
+ else
+ puts "Bitcoin::Script: opcode #{name} method parameters invalid"
+ end
when *OP_2_16
@stack << OP_2_16.index(chunk) + 2
@debug << "OP_#{chunk-80}"
else
name = OPCODES[chunk] || chunk
@@ -471,21 +486,23 @@
end
# pay_to_script_hash: https://en.bitcoin.it/wiki/BIP_0016
#
# <sig> {<pub> OP_CHECKSIG} | OP_HASH160 <script_hash> OP_EQUAL
- def pay_to_script_hash(check_callback)
+ def pay_to_script_hash(block_timestamp, opts, 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]
+ return true if check_callback == :check
script = self.class.new(to_binary(rest) + script).inner_p2sh!(script)
- result = script.run(&check_callback)
+ result = script.run(block_timestamp, opts, &check_callback)
@debug = script.debug
+ @stack = script.stack # Set the execution stack to match the redeem script, so checks on stack contents at end of script execution validate correctly
result
end
def inner_p2sh!(script=nil); @inner_p2sh = true; @inner_script_code = script; self; end
def inner_p2sh?; @inner_p2sh; end
@@ -499,15 +516,27 @@
return nil unless Bitcoin.hash160(script.unpack("H*")[0]) == script_hash.unpack("H*")[0]
script
end
+ # is this a :script_hash (pay-to-script-hash/p2sh) script?
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
+ if @previous_output_script
+ chunks = Bitcoin::Script.new(@previous_output_script).chunks
+ chunks.size == 3 &&
+ chunks[-3] == OP_HASH160 &&
+ chunks[-2].is_a?(String) && chunks[-2].bytesize == 20 &&
+ chunks[-1] == OP_EQUAL
+ else
+ @chunks.size >= 3 &&
+ @chunks[-3] == OP_HASH160 &&
+ @chunks[-2].is_a?(String) && @chunks[-2].bytesize == 20 &&
+ @chunks[-1] == OP_EQUAL &&
+ # make sure the script_sig matches the p2sh hash from the pk_script (if there is one)
+ (@chunks.size > 3 ? pay_to_script_hash(nil, nil, :check) : true)
+ end
end
alias :is_p2sh? :is_pay_to_script_hash?
# check if script is in one of the recognized standard formats
def is_standard?
@@ -538,10 +567,58 @@
# is this an op_return script
def is_op_return?
@chunks[0] == OP_RETURN && @chunks.size <= 2
end
+ # Verify the script is only pushing data onto the stack
+ def is_push_only?(script_data=nil)
+ check_pushes(push_only=true, canonical_only=false, (script_data||@input_script))
+ end
+
+ # Make sure opcodes used to push data match their intended length ranges
+ def pushes_are_canonical?(script_data=nil)
+ check_pushes(push_only=false, canonical_only=true, (script_data||@raw))
+ end
+
+ def check_pushes(push_only=true, canonical_only=false, buf)
+ program = buf.unpack("C*")
+ until program.empty?
+ opcode = program.shift
+ if opcode > OP_16
+ return false if push_only
+ next
+ end
+ if opcode < OP_PUSHDATA1 && opcode > OP_0
+ # Could have used an OP_n code, rather than a 1-byte push.
+ return false if canonical_only && opcode == 1 && program[0] <= 16
+ program.shift(opcode)
+ end
+ if opcode == OP_PUSHDATA1
+ len = program.shift(1)[0]
+ # Could have used a normal n-byte push, rather than OP_PUSHDATA1.
+ return false if canonical_only && len < OP_PUSHDATA1
+ program.shift(len)
+ end
+ if opcode == OP_PUSHDATA2
+ len = program.shift(2).pack("C*").unpack("v")[0]
+ # Could have used an OP_PUSHDATA1.
+ return false if canonical_only && len <= 0xff
+ program.shift(len)
+ end
+ if opcode == OP_PUSHDATA4
+ len = program.shift(4).pack("C*").unpack("V")[0]
+ # Could have used an OP_PUSHDATA2.
+ return false if canonical_only && len <= 0xffff
+ program.shift(len)
+ end
+ end
+ true
+ rescue => ex
+ # catch parsing errors
+ false
+ end
+
# get type of this tx
def type
if is_hash160?; :hash160
elsif is_pubkey?; :pubkey
elsif is_multisig?; :multisig
@@ -669,13 +746,12 @@
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 = [ SIGHASH_TYPE[:all] ].pack("C")
- buf = pack_pushdata(signature + hash_type)
+ def self.to_pubkey_script_sig(signature, pubkey, hash_type = SIGHASH_TYPE[:all])
+ buf = pack_pushdata(signature + [hash_type].pack("C"))
return buf unless pubkey
expected_size = case pubkey[0]
when "\x04"; 65
when "\x02", "\x03"; 33
@@ -702,21 +778,22 @@
# 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)
+ hash_type = sigs.last.is_a?(Numeric) ? sigs.pop : SIGHASH_TYPE[:all]
partial_script = [OP_0].pack("C*")
- sigs.reverse_each{ |sig| partial_script = add_sig_to_multisig_script_sig(sig, partial_script) }
+ sigs.reverse_each{ |sig| partial_script = add_sig_to_multisig_script_sig(sig, partial_script, hash_type) }
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*")
+ def self.add_sig_to_multisig_script_sig(sig, script_sig, hash_type = SIGHASH_TYPE[:all])
+ signature = sig + [hash_type].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.
@@ -724,10 +801,29 @@
# 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
+ # Sort signatures in the given +script_sig+ according to the order of pubkeys in
+ # the redeem script. Also needs the +sig_hash+ to match signatures to pubkeys.
+ def self.sort_p2sh_multisig_signatures script_sig, sig_hash
+ script = new(script_sig)
+ redeem_script = new(script.chunks[-1])
+ pubkeys = redeem_script.get_multisig_pubkeys
+
+ # find the pubkey for each signature by trying to verify it
+ sigs = Hash[script.chunks[1...-1].map.with_index do |sig, idx|
+ pubkey = pubkeys.map {|key|
+ Bitcoin::Key.new(nil, key.hth).verify(sig_hash, sig) ? key : nil }.compact.first
+ raise "Key for signature ##{idx} not found in redeem script!" unless pubkey
+ [pubkey, sig]
+ end]
+
+ [OP_0].pack("C*") + pubkeys.map {|k| sigs[k] ? pack_pushdata(sigs[k]) : nil }.join +
+ pack_pushdata(redeem_script.raw)
+ end
+
def get_signatures_required
return false unless is_multisig?
@chunks[0] - 80
end
@@ -1238,18 +1334,18 @@
# 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)
+ def op_checksig(check_callback, opts={})
return invalid if @stack.size < 2
pubkey = cast_to_string(@stack.pop)
- #return (@stack << 0) unless Bitcoin::Script.is_canonical_pubkey?(pubkey) # only for isStandard
+ return (@stack << 0) unless Bitcoin::Script.check_pubkey_encoding?(pubkey, opts)
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 invalid unless Bitcoin::Script.check_signature_encoding?(signature, opts)
return (@stack << 0) if signature == ""
sig, hash_type = parse_sig(signature)
subscript = sighash_subscript(drop_sigs)
@@ -1269,12 +1365,12 @@
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)
+ def op_checksigverify(check_callback, opts={})
+ op_checksig(check_callback, opts)
op_verify
end
# do a CHECKMULTISIG operation on the current stack,
# asking +check_callback+ to do the actual signature verification.
@@ -1287,11 +1383,11 @@
# see https://en.bitcoin.it/wiki/BIP_0011 for details.
# see https://github.com/bitcoin/bitcoin/blob/master/src/script.cpp#L931
#
# TODO: validate signature order
# TODO: take global opcode count
- def op_checkmultisig(check_callback)
+ def op_checkmultisig(check_callback, opts={})
return invalid if @stack.size < 1
n_pubkeys = pop_int
return invalid unless (0..20).include?(n_pubkeys)
#return invalid if (nOpCount += n_pubkeys) > 201
return invalid if @stack.size < n_pubkeys
@@ -1310,10 +1406,12 @@
subscript = sighash_subscript(drop_sigs)
success = true
while success && n_sigs > 0
sig, pub = sigs.pop, pubkeys.pop
+ return (@stack << 0) unless Bitcoin::Script.check_pubkey_encoding?(pub, opts)
+ return invalid unless Bitcoin::Script.check_signature_encoding?(sig, opts)
unless sig && sig.size > 0
success = false
break
end
signature, hash_type = parse_sig(sig)
@@ -1328,12 +1426,12 @@
@stack << (success ? 1 : 0)
end
# Same as OP_CHECKMULTISIG, but OP_VERIFY is executed afterward.
- def op_checkmultisigverify(check_callback)
- op_checkmultisig(check_callback)
+ def op_checkmultisigverify(check_callback, opts={})
+ op_checkmultisig(check_callback, opts)
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
@@ -1344,11 +1442,16 @@
[ (OPCODES.find{|k,v| v == m.to_s.upcase }.first rescue nil), m ]
}.flatten]
OPCODES_METHOD[0] = :op_0
OPCODES_METHOD[81] = :op_1
- def self.is_canonical_pubkey?(pubkey)
+ def self.check_pubkey_encoding?(pubkey, opts={})
+ return false if opts[:verify_strictenc] && !is_compressed_or_uncompressed_pub_key?(pubkey)
+ true
+ end
+
+ def self.is_compressed_or_uncompressed_pub_key?(pubkey)
return false if pubkey.bytesize < 33 # "Non-canonical public key: too short"
case pubkey[0]
when "\x04"
return false if pubkey.bytesize != 65 # "Non-canonical public key: invalid length for uncompressed key"
when "\x02", "\x03"
@@ -1357,24 +1460,96 @@
return false # "Non-canonical public key: compressed nor uncompressed"
end
true
end
+ # Loosely matches CheckSignatureEncoding()
+ def self.check_signature_encoding?(sig, opts={})
+ return true if sig.bytesize == 0
+ return false if (opts[:verify_dersig] || opts[:verify_low_s] || opts[:verify_strictenc]) and !is_der_signature?(sig)
+ return false if opts[:verify_low_s] && !is_low_der_signature?(sig)
+ return false if opts[:verify_strictenc] && !is_defined_hashtype_signature?(sig)
+ true
+ end
-
- def self.is_canonical_signature?(sig)
+ # Loosely correlates with IsDERSignature() from interpreter.cpp
+ def self.is_der_signature?(sig)
return false if sig.bytesize < 9 # Non-canonical signature: too short
return false if sig.bytesize > 73 # Non-canonical signature: too long
s = sig.unpack("C*")
- hash_type = s[-1] & (~(SIGHASH_TYPE[:anyonecanpay]))
- return false if hash_type < SIGHASH_TYPE[:all] || hash_type > SIGHASH_TYPE[:single] # Non-canonical signature: unknown hashtype byte
-
return false if s[0] != 0x30 # Non-canonical signature: wrong type
return false if s[1] != s.size-3 # Non-canonical signature: wrong length marker
- # TODO: add/port rest from bitcoind
+ length_r = s[3]
+ return false if (5 + length_r) >= s.size # Non-canonical signature: S length misplaced
+ length_s = s[5+length_r]
+ return false if (length_r + length_s + 7) != s.size # Non-canonical signature: R+S length mismatch
+
+ return false if s[2] != 0x02 # Non-canonical signature: R value type mismatch
+
+ return false if length_r == 0 # Non-canonical signature: R length is zero
+
+ r_val = s.slice(4, length_r)
+ return false if r_val[0] & 0x80 != 0 # Non-canonical signature: R value negative
+
+ return false if length_r > 1 && (r_val[0] == 0x00) && !(r_val[1] & 0x80 != 0) # Non-canonical signature: R value excessively padded
+
+ s_val = s.slice(6 + length_r, length_s)
+ return false if s[6 + length_r - 2] != 0x02 # Non-canonical signature: S value type mismatch
+
+ return false if length_s == 0 # Non-canonical signature: S length is zero
+ return false if (s_val[0] & 0x80) != 0 # Non-canonical signature: S value negative
+
+ return false if length_s > 1 && (s_val[0] == 0x00) && !(s_val[1] & 0x80) # Non-canonical signature: S value excessively padded
+
+ true
+ end
+
+ # Compares two arrays of bytes
+ def self.compare_big_endian(c1, c2)
+ c1, c2 = c1.dup, c2.dup # Clone the arrays
+
+ while c1.size > c2.size
+ return 1 if c1.shift > 0
+ end
+
+ while c2.size > c1.size
+ return -1 if c2.shift > 0
+ end
+
+ c1.size.times{|idx| return c1[idx] - c2[idx] if c1[idx] != c2[idx] }
+ 0
+ end
+
+ # Loosely correlates with IsLowDERSignature() from interpreter.cpp
+ def self.is_low_der_signature?(sig)
+ s = sig.unpack("C*")
+
+ length_r = s[3]
+ length_s = s[5+length_r]
+ s_val = s.slice(6 + length_r, length_s)
+
+ # If the S value is above the order of the curve divided by two, its
+ # complement modulo the order could have been used instead, which is
+ # one byte shorter when encoded correctly.
+ max_mod_half_order = [
+ 0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0x5d,0x57,0x6e,0x73,0x57,0xa4,0x50,0x1d,
+ 0xdf,0xe9,0x2f,0x46,0x68,0x1b,0x20,0xa0]
+
+ compare_big_endian(s_val, [0]) > 0 &&
+ compare_big_endian(s_val, max_mod_half_order) <= 0
+ end
+
+ def self.is_defined_hashtype_signature?(sig)
+ 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] # Non-canonical signature: unknown hashtype byte
true
end