Commit 909540db6e9aca57c96cf5c22cac51cd2284c97c

Authored by Marius Hanne
1 parent 388427aa43

implement basic op_checkmultisig

TODO: check order of signatures

Showing 2 changed files with 99 additions and 9 deletions Side-by-side Diff

lib/bitcoin/script.rb
... ... @@ -265,7 +265,7 @@
265 265 @debug = []
266 266 @chunks.each{|chunk|
267 267 break if invalid?
268   - @debug << @stack.map{|i| i.unpack("H*")}
  268 + @debug << @stack.map{|i| i.unpack("H*") rescue i}
269 269 case chunk
270 270 when Fixnum
271 271 case chunk
272 272  
... ... @@ -275,12 +275,16 @@
275 275 @debug << m.to_s.upcase
276 276 send(m) # invoke opcode method
277 277  
  278 + when *OP_2_16
  279 + @stack << OP_2_16.index(chunk) + 2
  280 +
278 281 when OP_CHECKSIG
279 282 @debug << "OP_CHECKSIG"
280 283 op_checksig(check_callback)
281 284  
282   - when OP_2_16
283   - @stack << OP_2_16.index(chunk)+2
  285 + when OP_CHECKMULTISIG
  286 + @debug << "OP_CHECKMULTISIG"
  287 + op_checkmultisig(check_callback)
284 288  
285 289 else
286 290 name = OPCODES[chunk] || chunk
... ... @@ -309,9 +313,7 @@
309 313 def op_checksig(check_callback)
310 314 return nil if @stack.size < 2
311 315 pubkey = @stack.pop
312   - sig_and_hash_type = @stack.pop
313   - hash_type = sig_and_hash_type[-1].unpack("C")[0]
314   - sig = sig_and_hash_type[0...-1]
  316 + sig, hash_type = parse_sig(@stack.pop)
315 317  
316 318 if check_callback == nil # for tests
317 319 @stack << 1
... ... @@ -321,6 +323,39 @@
321 323 end
322 324 end
323 325  
  326 + # do a CHECKMULTISIG operation on the current stack,
  327 + # asking +check_callback+ to do the actual signature verification.
  328 + #
  329 + # CHECKMULTISIG does a m-of-n signatures verification on scripts of the form:
  330 + # 0 <sig1> <sig2> | 2 <pub1> <pub2> 2 OP_CHECKMULTISIG
  331 + # 0 <sig1> <sig2> | 2 <pub1> <pub2> <pub3> 3 OP_CHECKMULTISIG
  332 + # 0 <sig1> <sig2> <sig3> | 3 <pub1> <pub2> <pub3> 3 OP_CHECKMULTISIG
  333 + #
  334 + # see https://en.bitcoin.it/wiki/BIP_0011 for details.
  335 + #
  336 + # TODO: validate signature order
  337 + def op_checkmultisig(check_callback)
  338 + n_pubkeys = @stack.pop
  339 + pubkeys = []; n_pubkeys.times { pubkeys << @stack.pop }
  340 +
  341 + n_sigs = @stack.pop
  342 + return nil if @stack.size != n_sigs + 1
  343 + sigs = []; n_sigs.times { sigs << parse_sig(@stack.pop) }
  344 +
  345 + @stack.pop # remove OP_NOP from stack
  346 +
  347 + valid_sigs = 0
  348 + sigs.each do |sig, hash_type|
  349 + pubkeys.each do |pubkey|
  350 + if check_callback.call(pubkey, sig, hash_type)
  351 + valid_sigs += 1
  352 + end
  353 + end
  354 + end
  355 +
  356 + @stack << 1 if valid_sigs >= n_sigs
  357 + end
  358 +
324 359 def is_standard? # TODO: add
325 360 # https://github.com/bitcoin/bitcoin/blob/master/src/script.cpp#L967
326 361 end
... ... @@ -382,6 +417,15 @@
382 417 raise "pubkey is not in binary form" unless pubkey.bytesize == 65 && pubkey[0] == "\x04"
383 418 [ [signature.bytesize+1].pack("C"), signature, hash_type, [pubkey.bytesize].pack("C"), pubkey ].join
384 419 end
  420 +
  421 + private
  422 +
  423 + def parse_sig(sig)
  424 + hash_type = sig[-1].unpack("C")[0]
  425 + sig = sig[0...-1]
  426 + return sig, hash_type
  427 + end
  428 +
385 429 end
386 430 end
spec/bitcoin/script_spec.rb
... ... @@ -99,7 +99,7 @@
99 99 Bitcoin::Script.from_string("bar foo OP_DUP OP_DROP bar OP_EQUAL")
100 100 .run.should == false
101 101  
102   - -> { Bitcoin::Script.from_string("1 OP_DROP 2").run }.should.raise RuntimeError
  102 + Bitcoin::Script.from_string("1 OP_DROP 2").run.should == false
103 103 end
104 104  
105 105 it "should generate address script" do
106 106  
... ... @@ -319,11 +319,57 @@
319 319 sig = (key.sign("foobar") + "\x01").unpack("H*")[0]
320 320 script = Bitcoin::Script.from_string("#{sig} #{key.pub} OP_CHECKSIG")
321 321 script.run{|pk, sig, hash_type|
322   - k = Bitcoin::Key.new
323   - k.pub = pk.unpack("H*")[0]
  322 + k = Bitcoin::Key.new nil, pk.unpack("H*")[0]
324 323 k.verify("foobar", sig)
325 324 }.should == true
326 325 script.stack.should == []
  326 + end
  327 +
  328 +
  329 + def run_script(string, hash)
  330 + script = Bitcoin::Script.from_string(string)
  331 + script.run do |pk, sig, hash_type|
  332 + k = Bitcoin::Key.new nil, pk.unpack("H*")[0]
  333 + k.verify(hash, sig) rescue false
  334 + end
  335 + end
  336 +
  337 + it "should do OP_CHECKMULTISIG" do
  338 + k1 = Bitcoin::Key.new; k1.generate
  339 + k2 = Bitcoin::Key.new; k2.generate
  340 + k3 = Bitcoin::Key.new; k3.generate
  341 + sig1 = (k1.sign("foobar") + "\x01").unpack("H*")[0]
  342 + sig2 = (k2.sign("foobar") + "\x01").unpack("H*")[0]
  343 + sig3 = (k3.sign("foobar") + "\x01").unpack("H*")[0]
  344 +
  345 + script = "0 #{sig1} #{sig2} 2 #{k1.pub} #{k2.pub} 2 OP_CHECKMULTISIG"
  346 + run_script(script, "foobar").should == true
  347 +
  348 + script = "0 #{sig1} #{sig2} 2 #{k1.pub} #{k2.pub} #{k3.pub} 3 OP_CHECKMULTISIG"
  349 + run_script(script, "foobar").should == true
  350 +
  351 + script = "0 #{sig2} #{sig3} 2 #{k1.pub} #{k2.pub} #{k3.pub} 3 OP_CHECKMULTISIG"
  352 + run_script(script, "foobar").should == true
  353 +
  354 + script = "0 #{sig1} #{sig2} #{sig3} 3 #{k1.pub} #{k2.pub} #{k3.pub} 3 OP_CHECKMULTISIG"
  355 + run_script(script, "foobar").should == true
  356 +
  357 + script = "0 #{sig2} f0f0f0f0 2 #{k1.pub} #{k2.pub} 2 OP_CHECKMULTISIG"
  358 + run_script(script, "foobar").should == false
  359 +
  360 + script = "0 afafafaf #{sig2} 2 #{k1.pub} #{k2.pub} #{k3.pub} 3 OP_CHECKMULTISIG"
  361 + run_script(script, "foobar").should == false
  362 +
  363 + script = "0 #{sig1} f0f0f0f0 #{sig3} 3 #{k1.pub} #{k2.pub} #{k3.pub} 3 OP_CHECKMULTISIG"
  364 + run_script(script, "foobar").should == false
  365 +
  366 + # # TODO: check signature order; these assertions should fail:
  367 + # script = "0 #{sig2} #{sig1} 2 #{k1.pub} #{k2.pub} #{k3.pub} 3 OP_CHECKMULTISIG"
  368 + # run_script(script, "foobar").should == false
  369 + # script = "0 #{sig3} #{sig2} 2 #{k1.pub} #{k2.pub} #{k3.pub} 3 OP_CHECKMULTISIG"
  370 + # run_script(script, "foobar").should == false
  371 + # script = "0 #{sig1} #{sig3} #{sig2} 3 #{k1.pub} #{k2.pub} #{k3.pub} 3 OP_CHECKMULTISIG"
  372 + # run_script(script, "foobar").should == false
327 373 end
328 374  
329 375 end