lib/bitcoin/script.rb in bitcoin-ruby-0.0.1 vs lib/bitcoin/script.rb in bitcoin-ruby-0.0.2
- old
+ new
@@ -1,16 +1,20 @@
+# encoding: ascii-8bit
+
require 'bitcoin'
class Bitcoin::Script
OP_1 = 81
OP_TRUE = 81
OP_0 = 0
OP_FALSE = 0
+ OP_PUSHDATA0 = 0
OP_PUSHDATA1 = 76
OP_PUSHDATA2 = 77
OP_PUSHDATA4 = 78
+ OP_PUSHDATA_INVALID = 238 # 0xEE
OP_NOP = 97
OP_DUP = 118
OP_HASH160 = 169
OP_EQUAL = 135
OP_VERIFY = 105
@@ -30,208 +34,394 @@
OP_DROP = 117
OP_HASH256 = 170
OP_SHA256 = 168
OP_SHA1 = 167
OP_RIPEMD160 = 166
- OP_EVAL = 176
+ OP_NOP1 = 176
OP_NOP2 = 177
- OP_CHECKHASHVERIFY = 177
+ OP_NOP3 = 178
+ OP_NOP4 = 179
+ OP_NOP5 = 180
+ OP_NOP6 = 181
+ OP_NOP7 = 182
+ OP_NOP8 = 183
+ OP_NOP9 = 184
+ OP_NOP10 = 185
OP_CODESEPARATOR = 171
OP_MIN = 163
OP_MAX = 164
OP_2OVER = 112
OP_2SWAP = 114
OP_IFDUP = 115
OP_DEPTH = 116
OP_1NEGATE = 79
- # OP_IF = 99
- # OP_NOTIF = 100
- # OP_ELSE = 103
- # OP_ENDIF = 104
+ OP_WITHIN = 165
+ OP_NUMEQUAL = 156
+ OP_NUMEQUALVERIFY = 157
+ OP_LESSTHAN = 159
+ OP_LESSTHANOREQUAL = 161
+ OP_GREATERTHAN = 160
+ OP_NOT = 145
+ OP_0NOTEQUAL = 146
+ OP_ABS = 144
+ OP_1ADD = 139
+ OP_1SUB = 140
+ OP_NEGATE = 143
+ OP_BOOLOR = 155
+ OP_NUMNOTEQUAL = 158
+ OP_RETURN = 106
+ OP_OVER = 120
+ OP_IF = 99
+ OP_NOTIF = 100
+ OP_ELSE = 103
+ OP_ENDIF = 104
+ OP_PICK = 121
+ OP_SIZE = 130
+ OP_VER = 98
+ OP_ROLL = 122
+ OP_ROT = 123
+ OP_2DROP = 109
+ OP_2DUP = 110
+ OP_3DUP = 111
+ OP_NIP = 119
+ OP_CAT = 126
+ OP_SUBSTR = 127
+ OP_LEFT = 128
+ OP_RIGHT = 129
+ OP_INVERT = 131
+ OP_AND = 132
+ OP_OR = 133
+ OP_XOR = 134
+ OP_2MUL = 141
+ OP_2DIV = 142
+ OP_MUL = 149
+ OP_DIV = 150
+ OP_MOD = 151
+ OP_LSHIFT = 152
+ OP_RSHIFT = 153
+
+
OPCODES = Hash[*constants.grep(/^OP_/).map{|i| [const_get(i), i.to_s] }.flatten]
OPCODES[0] = "0"
OPCODES[81] = "1"
OPCODES_ALIAS = {
"OP_TRUE" => OP_1,
"OP_FALSE" => OP_0,
- "OP_NOP1" => OP_EVAL,
- "OP_NOP2" => OP_CHECKHASHVERIFY
+ "OP_EVAL" => OP_NOP1,
+ "OP_CHECKHASHVERIFY" => OP_NOP2,
}
+ DISABLED_OPCODES = [
+ OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT, OP_INVERT,
+ OP_AND, OP_OR, OP_XOR, OP_2MUL, OP_2DIV, OP_MUL,
+ OP_DIV, OP_MOD, OP_LSHIFT, OP_RSHIFT
+ ]
+
OP_2_16 = (82..96).to_a
+
+ OPCODES_PARSE_BINARY = {}
+ OPCODES.each{|k,v| OPCODES_PARSE_BINARY[k] = v }
+ OP_2_16.each{|i| OPCODES_PARSE_BINARY[i] = (OP_2_16.index(i)+2).to_s }
+
+ OPCODES_PARSE_STRING = {}
+ OPCODES.each{|k,v| OPCODES_PARSE_STRING[v] = k }
+ 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}") }
+
+
attr_reader :raw, :chunks, :debug
# create a new script. +bytes+ is typically input_script + output_script
def initialize(bytes, offset=0)
@raw = bytes
- @stack, @stack_alt = [], []
+ @stack, @stack_alt, @exec_stack = [], [], []
@chunks = parse(bytes, offset)
+ @do_exec = true
end
+ class ::String
+ attr_accessor :bitcoin_pushdata
+ attr_accessor :bitcoin_pushdata_length
+ end
+
# parse raw script
def parse(bytes, offset=0)
program = bytes.unpack("C*")
chunks = []
until program.empty?
opcode = program.shift(1)[0]
- if opcode >= 0xf0
- opcode = (opcode << 8) | program.shift(1)[0]
- end
if (opcode > 0) && (opcode < OP_PUSHDATA1)
- len = opcode
+ 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
+ chunks.last.bitcoin_pushdata = OP_PUSHDATA0
+ chunks.last.bitcoin_pushdata_length = len
+ else
+ raise "invalid OP_PUSHDATA0" if len != chunks.last.bytesize
+ end
elsif (opcode == OP_PUSHDATA1)
len = program.shift(1)[0]
chunks << program.shift(len).pack("C*")
+
+ unless len > OP_PUSHDATA1 && len <= 0xff
+ chunks.last.bitcoin_pushdata = OP_PUSHDATA1
+ chunks.last.bitcoin_pushdata_length = len
+ else
+ raise "invalid OP_PUSHDATA1" if len != chunks.last.bytesize
+ end
elsif (opcode == OP_PUSHDATA2)
- len = program.shift(2).pack("C*").unpack("n")[0]
+ len = program.shift(2).pack("C*").unpack("v")[0]
chunks << program.shift(len).pack("C*")
+
+ unless len > 0xff && len <= 0xffff
+ chunks.last.bitcoin_pushdata = OP_PUSHDATA2
+ chunks.last.bitcoin_pushdata_length = len
+ else
+ raise "invalid OP_PUSHDATA2" if len != chunks.last.bytesize
+ end
elsif (opcode == OP_PUSHDATA4)
- len = program.shift(4).pack("C*").unpack("N")[0]
+ len = program.shift(4).pack("C*").unpack("V")[0]
chunks << program.shift(len).pack("C*")
+
+ unless len > 0xffff # && len <= 0xffffffff
+ chunks.last.bitcoin_pushdata = OP_PUSHDATA4
+ chunks.last.bitcoin_pushdata_length = len
+ else
+ raise "invalid OP_PUSHDATA4" if len != chunks.last.bytesize
+ end
else
chunks << opcode
end
end
chunks
+ rescue Exception => ex
+ # bail out! #run returns false but serialization roundtrips still create the right payload.
+ @parse_invalid = true
+ c = bytes.unpack("C*").pack("C*")
+ c.bitcoin_pushdata = OP_PUSHDATA_INVALID
+ c.bitcoin_pushdata_length = c.bytesize
+ chunks = [ c ]
end
# string representation of the script
def to_string(chunks=nil)
- (chunks || @chunks).map{|i|
- case i
+ string = ""
+ (chunks || @chunks).each.with_index{|i,idx|
+ string << " " unless idx == 0
+ string << case i
when Fixnum
- case i
- when *OPCODES.keys; OPCODES[i]
- when *OP_2_16; (OP_2_16.index(i)+2).to_s
- else "(opcode #{i})"
+ if opcode = OPCODES_PARSE_BINARY[i]
+ opcode
+ else
+ "(opcode-#{i})"
end
when String
- i.unpack("H*")[0]
+ if i.bitcoin_pushdata
+ "#{i.bitcoin_pushdata}:#{i.bitcoin_pushdata_length}:".force_encoding('binary') + i.unpack("H*")[0]
+ else
+ i.unpack("H*")[0]
+ end
end
- }.join(" ")
+ }
+ string
end
+ def to_binary(chunks=nil)
+ (chunks || @chunks).map{|chunk|
+ case chunk
+ when Fixnum; [chunk].pack("C*")
+ when String; self.class.pack_pushdata(chunk)
+ 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 } } )
+ end
+
+
+ def self.pack_pushdata(data)
+ size = data.bytesize
+
+ if data.bitcoin_pushdata
+ size = data.bitcoin_pushdata_length
+ pack_pushdata_align(data.bitcoin_pushdata, size, data)
+ else
+ head = if size < OP_PUSHDATA1
+ [size].pack("C")
+ elsif size <= 0xff
+ [OP_PUSHDATA1, size].pack("CC")
+ elsif size <= 0xffff
+ [OP_PUSHDATA2, size].pack("Cv")
+ #elsif size <= 0xffffffff
+ else
+ [OP_PUSHDATA4, size].pack("CV")
+ end
+ head + data
+ end
+ end
+
+ def self.pack_pushdata_align(pushdata, len, data)
+ case pushdata
+ when OP_PUSHDATA1
+ [OP_PUSHDATA1, len].pack("CC") + data
+ when OP_PUSHDATA2
+ [OP_PUSHDATA2, len].pack("Cv") + data
+ when OP_PUSHDATA4
+ [OP_PUSHDATA4, len].pack("CV") + data
+ when OP_PUSHDATA_INVALID
+ data
+ else # OP_PUSHDATA0
+ [len].pack("C") + data
+ end
+ end
+
# script object of a string representation
def self.from_string(script_string)
new(binary_from_string(script_string))
end
class ScriptOpcodeError < StandardError; end
# raw script binary of a string representation
def self.binary_from_string(script_string)
- script_string.split(" ").map{|i|
- case i
- when /^OP_PUSHDATA[124]$/; # skip
- when *OPCODES.values; OPCODES.find{|k,v| v == i }.first
- when *OPCODES_ALIAS.keys; OPCODES_ALIAS.find{|k,v| k == i }.last
- when /^([2-9]|1[0-6])$/; OP_2_16[$1.to_i-2]
- when /\(opcode (\d+)\)/; $1.to_i
- when /OP_(.+)$/; raise ScriptOpcodeError, "#{i} not defined!"
- else
- data = [i].pack("H*")
- size = data.bytesize
-
- head = if size < OP_PUSHDATA1
- [size].pack("C")
- elsif size > OP_PUSHDATA1 && size <= 0xff
- [OP_PUSHDATA1, size].pack("CC")
- elsif size > 0xff && size <= 0xffff
- [OP_PUSHDATA2, size].pack("Cv")
- elsif size > 0xffff && size <= 0xffffffff
- [OP_PUSHDATA4, size].pack("CV")
- end
-
- head + data
+ buf = ""
+ script_string.split(" ").each{|i|
+ i = if opcode = OPCODES_PARSE_STRING[i]
+ opcode
+ else
+ case i
+ when /OP_PUSHDATA/ # skip
+ when /OP_(.+)$/; raise ScriptOpcodeError, "#{i} not defined!"
+ when /\(opcode\-(\d+)\)/; $1.to_i
+ when "(opcode"; # skip # fix invalid opcode parsing
+ when /^(\d+)\)/; $1.to_i # fix invalid opcode parsing
+ when /(\d+):(\d+):(.+)?/
+ pushdata, len, data = $1.to_i, $2.to_i, $3
+ pack_pushdata_align(pushdata, len, [data].pack("H*"))
+ else
+ data = [i].pack("H*")
+ pack_pushdata(data)
+ end
end
- }.map{|i|
- i.is_a?(Fixnum) ? [i].pack("C*") : i # TODO yikes, implement/pack 2 byte opcodes.
- }.join
+
+ buf << if i.is_a?(Fixnum)
+ i < 256 ? [i].pack("C") : [OpenSSL::BN.new(i.to_s,10).to_hex].pack("H*")
+ else
+ i
+ end if i
+ }
+ buf
end
def invalid?
@script_invalid ||= false
end
# run the script. +check_callback+ is called for OP_CHECKSIG operations
- def run(&check_callback)
- return pay_to_script_hash(check_callback) if is_p2sh?
+ 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
+
+ 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|
break if invalid?
+
@debug << @stack.map{|i| i.unpack("H*") rescue i}
-
+ @do_exec = @exec_stack.count(false) == 0 ? true : false
+ #p [@stack, @do_exec]
+
case chunk
when Fixnum
- case chunk
+ if DISABLED_OPCODES.include?(chunk)
+ @script_invalid = true
+ @debug << "DISABLED_#{OPCODES[chunk]}"
+ break
+ end
+ next 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
(m.arity == 1) ? m.call(check_callback) : m.call # invoke opcode method
-
when *OP_2_16
@stack << OP_2_16.index(chunk) + 2
@debug << "OP_#{chunk-80}"
else
name = OPCODES[chunk] || chunk
+ puts "Bitcoin::Script: opcode #{name} unkown or not implemented\n#{to_string.inspect}"
raise "opcode #{name} unkown or not implemented"
end
when String
- @debug << "PUSH DATA #{chunk.unpack("H*")[0]}"
- @stack << chunk
+ if @do_exec
+ @debug << "PUSH DATA #{chunk.unpack("H*")[0]}"
+ @stack << chunk
+ end
end
}
- @debug << @stack.map{|i| i.unpack("H*") rescue i }
+ @debug << @stack.map{|i| i.unpack("H*") rescue i } #if @do_exec
if @script_invalid
@stack << 0
@debug << "INVALID TRANSACTION"
end
@debug << "RESULT"
return false if @stack.empty?
- return false if @stack.pop == 0
+ return false if [0, ''].include?(@stack.pop)
true
end
def invalid
@script_invalid = true; nil
end
- def codehash_script(opcode)
- # CScript scriptCode(pbegincodehash, pend);
- script = to_string(@chunks[(@codehash_start||0)...@chunks.size-@chunks.reverse.index(opcode)])
- checkhash = Bitcoin.hash160(Bitcoin::Script.binary_from_string(script).unpack("H*")[0])
- [script, checkhash]
- end
-
def self.drop_signatures(script_pubkey, drop_signatures)
script = new(script_pubkey).to_string.split(" ").delete_if{|c| drop_signatures.include?(c) }.join(" ")
script_pubkey = binary_from_string(script)
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)
- return false unless @chunks.size == 5
- script_hash = @chunks[-2]
- script = @chunks[-4]
- sig = self.class.from_string(@chunks[0].unpack("H*")[0]).raw
+ return false if @chunks.size < 4
+ *rest, script, _, script_hash, _ = @chunks
+ 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]
- script = self.class.new(sig + script)
- script.run(&check_callback)
+ 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
+
def is_pay_to_script_hash?
+ 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?
@@ -256,19 +446,20 @@
end
# is this a multisig tx
def is_multisig?
return false if @chunks.size > 6 || @chunks.size < 4
- @chunks[-1] == OP_CHECKMULTISIG
+ @chunks[-1] == OP_CHECKMULTISIG and get_multisig_pubkeys.all?{|c| c.is_a?(String) }
end
+ # get type of this tx
def type
- if is_hash160?; :hash160
- elsif is_pubkey?; :pubkey
- elsif is_multisig?; :multisig
- elsif is_p2sh?; :p2sh
- else; :unknown
+ if is_hash160?; :hash160
+ elsif is_pubkey?; :pubkey
+ elsif is_multisig?; :multisig
+ elsif is_p2sh?; :p2sh
+ else; :unknown
end
end
# get the public key for this pubkey script
def get_pubkey
@@ -279,11 +470,11 @@
# get the pubkey address for this pubkey script
def get_pubkey_address
Bitcoin.pubkey_to_address(get_pubkey)
end
- # get the hash160 for this hash160 script
+ # get the hash160 for this hash160 or pubkey script
def get_hash160
return @chunks[2..-3][0].unpack("H*")[0] if is_hash160?
return Bitcoin.hash160(get_pubkey) if is_pubkey?
end
@@ -292,23 +483,29 @@
Bitcoin.hash160_to_address(get_hash160)
end
# get the public keys for this multisig script
def get_multisig_pubkeys
- 1.upto(@chunks[-2] - 80).map {|i| @chunks[i]}
+ 1.upto(@chunks[-2] - 80).map{|i| @chunks[i] }
end
# get the pubkey addresses for this multisig script
def get_multisig_addresses
- get_multisig_pubkeys.map {|p| Bitcoin::Key.new(nil, p.unpack("H*")[0]).addr}
+ get_multisig_pubkeys.map{|pub|
+ begin
+ Bitcoin::Key.new(nil, pub.unpack("H*")[0]).addr
+ rescue OpenSSL::PKey::ECError, OpenSSL::PKey::EC::Point::Error
+ end
+ }.compact
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_pubkey_address] if is_pubkey?
+ return [get_hash160_address] if is_hash160?
return get_multisig_addresses if is_multisig?
+ []
end
# get single address, or first for multisig script
def get_address
addrs = get_addresses
@@ -350,11 +547,23 @@
# generate pubkey script sig for given +signature+ and +pubkey+
def self.to_pubkey_script_sig(signature, pubkey)
hash_type = "\x01"
#pubkey = [pubkey].pack("H*") if pubkey.bytesize != 65
- raise "pubkey is not in binary form" unless pubkey.bytesize == 65 && pubkey[0] == "\x04"
+ return [ [signature.bytesize+1].pack("C"), signature, hash_type ].join unless pubkey
+
+ case pubkey[0]
+ when "\x04"
+ expected_size = 65
+ when "\x02", "\x03"
+ expected_size = 33
+ end
+
+ if !expected_size || pubkey.bytesize != expected_size
+ raise "pubkey is not in binary form"
+ end
+
[ [signature.bytesize+1].pack("C"), signature, hash_type, [pubkey.bytesize].pack("C"), pubkey ].join
end
# alias for #to_pubkey_script_sig
def self.to_signature_pubkey_script(*a)
@@ -371,45 +580,54 @@
end
## OPCODES
# Does nothing
- def op_nop
- end
+ def op_nop; end
+ def op_nop1; end
+ def op_nop2; end
+ def op_nop3; end
+ def op_nop4; end
+ def op_nop5; end
+ def op_nop6; end
+ def op_nop7; end
+ def op_nop8; end
+ def op_nop9; end
+ def op_nop10; end
# Duplicates the top stack item.
def op_dup
@stack << (@stack[-1].dup rescue @stack[-1])
end
# The input is hashed using SHA-256.
def op_sha256
- buf = @stack.pop
+ buf = pop_string
@stack << Digest::SHA256.digest(buf)
end
# The input is hashed using SHA-1.
def op_sha1
- buf = @stack.pop
+ buf = pop_string
@stack << Digest::SHA1.digest(buf)
end
# The input is hashed twice: first with SHA-256 and then with RIPEMD-160.
def op_hash160
- buf = @stack.pop
+ buf = pop_string
@stack << Digest::RMD160.digest(Digest::SHA256.digest(buf))
end
# The input is hashed using RIPEMD-160.
def op_ripemd160
- buf = @stack.pop
+ buf = pop_string
@stack << Digest::RMD160.digest(buf)
end
# The input is hashed two times with SHA-256.
def op_hash256
- buf = @stack.pop
+ buf = pop_string
@stack << Digest::SHA256.digest(Digest::SHA256.digest(buf))
end
# Puts the input onto the top of the alt stack. Removes it from the main stack.
def op_toaltstack
@@ -426,51 +644,122 @@
@stack[-2..-1] = [ @stack[-1], *@stack[-2..-1] ]
end
# The top two items on the stack are swapped.
def op_swap
- @stack[-2..-1] = @stack[-2..-1].reverse
+ @stack[-2..-1] = @stack[-2..-1].reverse if @stack[-2]
end
# If both a and b are not 0, the output is 1. Otherwise 0.
def op_booland
- a, b = @stack.pop(2)
+ a, b = pop_int(2)
@stack << (![a,b].any?{|n| n == 0 } ? 1 : 0)
end
+ # If a or b is not 0, the output is 1. Otherwise 0.
+ def op_boolor
+ a, b = pop_int(2)
+ @stack << ( (a != 0 || b != 0) ? 1 : 0 )
+ end
+
# a is added to b.
def op_add
- a, b = @stack.pop(2).reverse
+ a, b = pop_int(2)
@stack << a + b
end
# b is subtracted from a.
def op_sub
- a, b = @stack.pop(2).reverse
+ a, b = pop_int(2)
@stack << a - b
end
+ # Returns 1 if a is less than b, 0 otherwise.
+ def op_lessthan
+ a, b = pop_int(2)
+ @stack << (a < b ? 1 : 0)
+ end
+
+ # Returns 1 if a is less than or equal to b, 0 otherwise.
+ def op_lessthanorequal
+ a, b = pop_int(2)
+ @stack << (a <= b ? 1 : 0)
+ end
+
+ # Returns 1 if a is greater than b, 0 otherwise.
+ def op_greaterthan
+ a, b = pop_int(2)
+ @stack << (a > b ? 1 : 0)
+ end
+
# Returns 1 if a is greater than or equal to b, 0 otherwise.
def op_greaterthanorequal
- a, b = @stack.pop(2).reverse
+ a, b = pop_int(2)
@stack << (a >= b ? 1 : 0)
end
+ # If the input is 0 or 1, it is flipped. Otherwise the output will be 0.
+ def op_not
+ a = pop_int
+ @stack << (a == 0 ? 1 : 0)
+ end
+
+ def op_0notequal
+ a = pop_int
+ @stack << (a != 0 ? 1 : 0)
+ end
+
+ # The input is made positive.
+ def op_abs
+ a = pop_int
+ @stack << a.abs
+ end
+
+ # The input is divided by 2. Currently disabled.
+ def op_2div
+ a = pop_int
+ @stack << (a >> 1)
+ end
+
+ # The input is multiplied by 2. Currently disabled.
+ def op_2mul
+ a = pop_int
+ @stack << (a << 1)
+ end
+
+ # 1 is added to the input.
+ def op_1add
+ a = pop_int
+ @stack << (a + 1)
+ end
+
+ def op_1sub
+ a = pop_int
+ @stack << (a - 1)
+ end
+
+ # The sign of the input is flipped.
+ def op_negate
+ a = pop_int
+ @stack << -a
+ end
+
# Removes the top stack item.
def op_drop
@stack.pop
end
# Returns 1 if the inputs are exactly equal, 0 otherwise.
def op_equal
- a, b = @stack.pop(2).reverse
+ #a, b = @stack.pop(2)
+ a, b = pop_int(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 = @stack.pop
+ res = pop_int
if res == 0
@stack << res
@script_invalid = true # raise 'transaction invalid' ?
else
@script_invalid = false
@@ -492,74 +781,225 @@
@stack << 1
end
# Returns the smaller of a and b.
def op_min
- @stack << @stack.pop(2).min
+ @stack << pop_int(2).min
end
# Returns the larger of a and b.
def op_max
- @stack << @stack.pop(2).max
+ @stack << pop_int(2).max
end
-
+
# Copies the pair of items two spaces back in the stack to the front.
def op_2over
@stack << @stack[-4]
@stack << @stack[-4]
end
-
+
# Swaps the top two pairs of items.
def op_2swap
p1 = @stack.pop(2)
p2 = @stack.pop(2)
@stack += p1 += p2
end
-
+
# If the input is true, duplicate it.
def op_ifdup
- if @stack.last != 0
+ if cast_to_bignum(@stack.last) != 0
@stack << @stack.last
end
end
# The number -1 is pushed onto the stack.
def op_1negate
@stack << -1
end
-
+
# Puts the number of stack items onto the stack.
def op_depth
@stack << @stack.size
end
- # https://en.bitcoin.it/wiki/BIP_0017 (old OP_NOP2)
- # TODO: don't rely on it yet. add guards from wikipage too.
- def op_checkhashverify
- unless @checkhash && (@checkhash == @stack[-1].unpack("H*")[0])
- @script_invalid = true
+ # Returns 1 if x is within the specified range (left-inclusive), 0 otherwise.
+ def op_within
+ bn1, bn2, bn3 = pop_int(3)
+ @stack << ( (bn2 <= bn1 && bn1 < bn3) ? 1 : 0 )
+ end
+
+ # Returns 1 if the numbers are equal, 0 otherwise.
+ def op_numequal
+ a, b = pop_int(2)
+ @stack << (a == b ? 1 : 0)
+ end
+
+ # Returns 1 if the numbers are not equal, 0 otherwise.
+ def op_numnotequal
+ a, b = pop_int(2)
+ @stack << (a != b ? 1 : 0)
+ end
+
+ # Marks transaction as invalid.
+ def op_return
+ @script_invalid = true; nil
+ end
+
+ # Copies the second-to-top stack item to the top.
+ def op_over
+ item = @stack[-2]
+ @stack << item if item
+ end
+
+ # 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
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
+ 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.
+ def op_else
+ return if @exec_stack.empty?
+ @exec_stack[-1] = !@exec_stack[-1]
+ end
+
+ # Ends an if/else block.
+ def op_endif
+ return if @exec_stack.empty?
+ @exec_stack.pop
+ end
+
+ # The item n back in the stack is copied to the top.
+ def op_pick
+ pos = pop_int
+ item = @stack[-(pos+1)]
+ @stack << item if item
+ end
+
+ # The item n back in the stack is moved to the top.
+ def op_roll
+ pos = pop_int
+ idx = -(pos+1)
+ item = @stack[idx]
+ if item
+ @stack.delete_at(idx)
+ @stack << item if item
+ end
+ end
+
+ # The top three items on the stack are rotated to the left.
+ def op_rot
+ return if @stack.size < 3
+ @stack[-3..-1] = [ @stack[-2], @stack[-1], @stack[-3] ]
+ end
+
+ # Removes the top two stack items.
+ def op_2drop
+ @stack.pop(2)
+ end
+
+ # Duplicates the top two stack items.
+ def op_2dup
+ @stack.push(*@stack[-2..-1])
+ end
+
+ # Duplicates the top three stack items.
+ def op_3dup
+ @stack.push(*@stack[-3..-1])
+ end
+
+ # Removes the second-to-top stack item.
+ def op_nip
+ @stack.delete_at(-2)
+ end
+
+ # Returns the length of the input string.
+ def op_size
+ item = @stack[-1]
+ size = case item
+ when String; item.bytesize
+ when Numeric; OpenSSL::BN.new(item.to_s).to_mpi.size - 4
+ end
+ @stack << size
+ end
+
+ # Transaction is invalid unless occuring in an unexecuted OP_IF branch
+ def op_ver
+ invalid if @do_exec
+ end
+
+ def pop_int(count=nil)
+ return cast_to_bignum(@stack.pop) unless count
+ @stack.pop(count).map{|i| cast_to_bignum(i) }
+ end
+
+ def pop_string(count=nil)
+ return cast_to_string(@stack.pop) unless count
+ @stack.pop(count).map{|i| cast_to_string(i) }
+ end
+
+ def cast_to_bignum(buf)
+ case buf
+ when Numeric; buf
+ when String; 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)
+ case buf
+ when Numeric; OpenSSL::BN.new(buf.to_s).to_s(0)[4..-1]
+ when String; buf;
+ else; raise TypeError, 'cast_to_string: failed to cast: %s (%s)' % [buf, buf.class]
+ end
+ 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)
end
+ def codehash_script(opcode)
+ # CScript scriptCode(pbegincodehash, pend);
+ script = to_string(@chunks[(@codehash_start||0)...@chunks.size-@chunks.reverse.index(opcode)])
+ checkhash = Bitcoin.hash160(Bitcoin::Script.binary_from_string(script).unpack("H*")[0])
+ [script, checkhash]
+ end
+
+
# 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
- drop_sigs = [@stack[-1].unpack("H*")[0]]
+ drop_sigs = [ @stack[-1] ]
sig, hash_type = parse_sig(@stack.pop)
- if @chunks.include?(OP_CHECKHASHVERIFY)
- # Subset of script starting at the most recent codeseparator to OP_CHECKSIG
- script_code, @checkhash = codehash_script(OP_CHECKSIG)
+ 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
if check_callback == nil # for tests
@@ -587,43 +1027,60 @@
# 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)
- n_pubkeys = @stack.pop
+ n_pubkeys = pop_int
return invalid unless (0..20).include?(n_pubkeys)
return invalid unless @stack.last(n_pubkeys).all?{|e| e.is_a?(String) && e != '' }
#return invalid if ((@op_count ||= 0) += n_pubkeys) > 201
- pubkeys = @stack.pop(n_pubkeys)
+ pubkeys = pop_string(n_pubkeys)
- n_sigs = @stack.pop
+ n_sigs = pop_int
return invalid unless (0..n_pubkeys).include?(n_sigs)
return invalid unless @stack.last(n_sigs).all?{|e| e.is_a?(String) && e != '' }
- sigs = (drop_sigs = @stack.pop(n_sigs)).map{|s| parse_sig(s) }
+ sigs = (drop_sigs = pop_string(n_sigs)).map{|s| parse_sig(s) }
@stack.pop if @stack[-1] == '' # remove OP_NOP from stack
- if @chunks.include?(OP_CHECKHASHVERIFY)
- # Subset of script starting at the most recent codeseparator to OP_CHECKMULTISIG
- script_code, @checkhash = codehash_script(OP_CHECKMULTISIG)
- drop_sigs.map!{|i| i.unpack("H*")[0] }
+ 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
valid_sigs = 0
sigs.each{|sig, hash_type| pubkeys.each{|pubkey|
valid_sigs += 1 if check_callback.call(pubkey, sig, hash_type, drop_sigs, script_code)
}}
- @stack << ((valid_sigs == n_sigs) ? 1 : (invalid; 0))
+ @stack << ((valid_sigs >= n_sigs) ? 1 : (invalid; 0))
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
+
OPCODES_METHOD = Hash[*instance_methods.grep(/^op_/).map{|m|
[ (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)
+ 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"
+ return false if pubkey.bytesize != 33 # "Non-canonical public key: invalid length for compressed key"
+ else
+ return false # "Non-canonical public key: compressed nor uncompressed"
+ end
+ true
+ end
private
def parse_sig(sig)
hash_type = sig[-1].unpack("C")[0]