bitcoin-ruby 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,38 @@
1
+ # encoding: ascii-8bit
2
+
3
+ module Bitcoin
4
+ module Protocol
5
+
6
+ class Reject < Struct.new(:message, :ccode, :reason, :data)
7
+ CCODE_TABLE = {
8
+ 0x01 => :malformed,
9
+ 0x10 => :invalid,
10
+ 0x11 => :obsolete,
11
+ 0x12 => :duplicate,
12
+ 0x40 => :nonstandard,
13
+ 0x41 => :dust,
14
+ 0x42 => :insufficientfee,
15
+ 0x43 => :checkpoint,
16
+ }
17
+
18
+ def self.parse(payload)
19
+ message, payload = Bitcoin::Protocol.unpack_var_string(payload)
20
+ ccode, payload = payload.unpack("Ca*")
21
+ reason, payload = Bitcoin::Protocol.unpack_var_string(payload)
22
+ data = payload
23
+
24
+ code = CCODE_TABLE[ccode] || ccode
25
+ new(message, code, reason, data)
26
+ end
27
+
28
+ def tx_hash
29
+ message == "tx" && self[:data].reverse.bth
30
+ end
31
+
32
+ def block_hash
33
+ message == "block" && self[:data].reverse.bth
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -67,13 +67,25 @@ module Bitcoin
67
67
 
68
68
  @ver = buf.read(4).unpack("V")[0]
69
69
 
70
+ return false if buf.eof?
71
+
70
72
  in_size = Protocol.unpack_var_int_from_io(buf)
71
73
  @in = []
72
- in_size.times{ @in << TxIn.from_io(buf) }
74
+ in_size.times{
75
+ break if buf.eof?
76
+ @in << TxIn.from_io(buf)
77
+ }
78
+
79
+ return false if buf.eof?
73
80
 
74
81
  out_size = Protocol.unpack_var_int_from_io(buf)
75
82
  @out = []
76
- out_size.times{ @out << TxOut.from_io(buf) }
83
+ out_size.times{
84
+ break if buf.eof?
85
+ @out << TxOut.from_io(buf)
86
+ }
87
+
88
+ return false if buf.eof?
77
89
 
78
90
  @lock_time = buf.read(4).unpack("V")[0]
79
91
 
@@ -225,6 +237,7 @@ module Bitcoin
225
237
  'in' => @in.map{|i| i.to_hash(options) },
226
238
  'out' => @out.map{|o| o.to_hash(options) }
227
239
  }
240
+ h['nid'] = normalized_hash if options[:with_nid]
228
241
  h
229
242
  end
230
243
 
@@ -240,7 +253,7 @@ module Bitcoin
240
253
  end
241
254
 
242
255
  # parse ruby hash (see also #to_hash)
243
- def self.from_hash(h)
256
+ def self.from_hash(h, do_raise=true)
244
257
  tx = new(nil)
245
258
  tx.ver, tx.lock_time = (h['ver'] || h['version']), h['lock_time']
246
259
  ins = h['in'] || h['inputs']
@@ -248,6 +261,9 @@ module Bitcoin
248
261
  ins .each{|input| tx.add_in TxIn.from_hash(input) }
249
262
  outs.each{|output| tx.add_out TxOut.from_hash(output) }
250
263
  tx.instance_eval{ @hash = hash_from_payload(@payload = to_payload) }
264
+ unless h['hash'] == tx.hash
265
+ raise "Tx hash mismatch! Claimed: #{h['hash']}, Actual: #{tx.hash}" if do_raise
266
+ end
251
267
  tx
252
268
  end
253
269
 
@@ -6,7 +6,9 @@ module Bitcoin
6
6
  class TxIn
7
7
 
8
8
  # previous output hash
9
- attr_accessor :prev_out
9
+ attr_accessor :prev_out_hash
10
+ alias :prev_out :prev_out_hash
11
+ def prev_out=(hash); @prev_out_hash = hash; end
10
12
 
11
13
  # previous output index
12
14
  attr_accessor :prev_out_index
@@ -29,7 +31,7 @@ module Bitcoin
29
31
  COINBASE_INDEX = 0xffffffff
30
32
 
31
33
  def initialize *args
32
- @prev_out, @prev_out_index, @script_sig_length,
34
+ @prev_out_hash, @prev_out_index, @script_sig_length,
33
35
  @script_sig, @sequence = *args
34
36
  @script_sig_length ||= 0
35
37
  @script_sig ||= ''
@@ -38,7 +40,7 @@ module Bitcoin
38
40
 
39
41
  # compare to another txout
40
42
  def ==(other)
41
- @prev_out == other.prev_out &&
43
+ @prev_out_hash == other.prev_out_hash &&
42
44
  @prev_out_index == other.prev_out_index &&
43
45
  @script_sig == other.script_sig &&
44
46
  @sequence == other.sequence
@@ -63,18 +65,22 @@ module Bitcoin
63
65
  end
64
66
 
65
67
  def parse_data_from_io(buf)
66
- @prev_out, @prev_out_index = buf.read(36).unpack("a32V")
68
+ @prev_out_hash, @prev_out_index = buf.read(36).unpack("a32V")
67
69
  @script_sig_length = Protocol.unpack_var_int_from_io(buf)
68
70
  @script_sig = buf.read(@script_sig_length)
69
71
  @sequence = buf.read(4)
70
72
  end
71
73
 
74
+ def parsed_script
75
+ @parsed_script ||= Bitcoin::Script.new(script_sig)
76
+ end
77
+
72
78
  def to_payload(script=@script_sig, sequence=@sequence)
73
- [@prev_out, @prev_out_index].pack("a32V") << Protocol.pack_var_int(script.bytesize) << script << (sequence || DEFAULT_SEQUENCE)
79
+ [@prev_out_hash, @prev_out_index].pack("a32V") << Protocol.pack_var_int(script.bytesize) << script << (sequence || DEFAULT_SEQUENCE)
74
80
  end
75
81
 
76
82
  def to_hash(options = {})
77
- t = { 'prev_out' => { 'hash' => @prev_out.reverse_hth, 'n' => @prev_out_index } }
83
+ t = { 'prev_out' => { 'hash' => @prev_out_hash.reverse_hth, 'n' => @prev_out_index } }
78
84
  if coinbase?
79
85
  t['coinbase'] = @script_sig.unpack("H*")[0]
80
86
  else # coinbase tx
@@ -103,12 +109,12 @@ module Bitcoin
103
109
 
104
110
  # previous output in hex
105
111
  def previous_output
106
- @prev_out.reverse_hth
112
+ @prev_out_hash.reverse_hth
107
113
  end
108
114
 
109
115
  # check if input is coinbase
110
116
  def coinbase?
111
- (@prev_out_index == COINBASE_INDEX) && (@prev_out == NULL_HASH)
117
+ (@prev_out_index == COINBASE_INDEX) && (@prev_out_hash == NULL_HASH)
112
118
  end
113
119
 
114
120
  # set script_sig and script_sig_length
@@ -663,7 +663,7 @@ class Bitcoin::Script
663
663
  Bitcoin::Key.new(nil, pub.unpack("H*")[0]).addr
664
664
  rescue OpenSSL::PKey::ECError, OpenSSL::PKey::EC::Point::Error
665
665
  end
666
- }.compact
666
+ }
667
667
  end
668
668
 
669
669
  def get_p2sh_address
@@ -827,6 +827,11 @@ class Bitcoin::Script
827
827
  @chunks[0] - 80
828
828
  end
829
829
 
830
+ def get_keys_provided
831
+ return false unless is_multisig?
832
+ @chunks[-2] - 80
833
+ end
834
+
830
835
  # This matches CScript::GetSigOpCount(bool fAccurate)
831
836
  # Note: this does not cover P2SH script which is to be unserialized
832
837
  # and checked explicitly when validating blocks.
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.0.7"
2
+ VERSION = "0.0.8"
3
3
  end
@@ -447,6 +447,10 @@ describe 'Bitcoin Address/Hash160/PubKey' do
447
447
  Bitcoin.verify_message(s['address'], s['signature'], s['message']).should == true
448
448
  }
449
449
  }
450
+
451
+ Bitcoin.network = :testnet3
452
+ Bitcoin.verify_message("mwPVMbZQgkpwJJt2YP3sLSgbEBQw3FWZSc", "H5GER0Nz+L7TPZMQzXtv0hnLSsyfPok9lkdHIv01vksREpEpOhTPTonU1xvyPAOIIKhU3++Ol+LaWKWmsfyxDXk=", "A"*500).should == true
453
+ Bitcoin.network = :bitcoin
450
454
  end
451
455
  rescue LoadError
452
456
  end
@@ -103,6 +103,17 @@ describe "Bitcoin::Builder" do
103
103
  tx1.in[0].should == tx2.in[0]
104
104
  end
105
105
 
106
+ it "should provide tx#output shortcut" do
107
+ tx1 = build_tx {|t| t.output(123, @keys[1].addr) }
108
+ tx1.should == build_tx do |t|
109
+ t.output {|o| o.value 123; o.to @keys[1].addr }
110
+ end
111
+
112
+ tx2 = build_tx {|t| t.output(123, @keys[1].pub, :pubkey) }
113
+ tx2.should == build_tx do |t|
114
+ t.output {|o| o.value 123; o.to @keys[1].pub, :pubkey }
115
+ end
116
+ end
106
117
 
107
118
  it "should provide txout#to shortcut" do
108
119
  tx1 = build_tx do |t|
@@ -139,19 +139,19 @@ describe 'Bitcoin::Dogecoin' do
139
139
  block = P::Block.new(data)
140
140
  aux_pow = block.aux_pow
141
141
  aux_pow.nil?.should == false
142
- aux_pow.mrkl_index.should == 0
142
+ aux_pow.coinbase_index.should == 0
143
143
 
144
- parent_block_merkle_root = Bitcoin.mrkl_branch_root(aux_pow.branch.map(&:reverse_hth), aux_pow.tx.hash, aux_pow.mrkl_index)
144
+ parent_block_merkle_root = Bitcoin.mrkl_branch_root(aux_pow.coinbase_branch, aux_pow.coinbase_tx.hash, aux_pow.coinbase_index)
145
145
  parent_block_merkle_root.should == aux_pow.parent_block.mrkl_root.reverse.unpack("H*")[0]
146
146
 
147
147
  # Find the merged mining header in the coinbase input script
148
148
  merged_mining_header = "\xfa\xbemm"
149
- script = aux_pow.tx.in[0].script
149
+ script = aux_pow.coinbase_tx.in[0].script
150
150
  header_idx = script.index(merged_mining_header)
151
151
 
152
152
  header_idx.should == 4
153
153
 
154
- chain_merkle_root = Bitcoin.mrkl_branch_root(aux_pow.aux_branch.map(&:reverse_hth), block_hash, aux_pow.aux_index)
154
+ chain_merkle_root = Bitcoin.mrkl_branch_root(aux_pow.chain_branch, block_hash, aux_pow.chain_index)
155
155
 
156
156
  # Drop everything up to the merged mining data
157
157
  script = script.slice(header_idx + merged_mining_header.length, chain_merkle_root.length / 2 + 8)
@@ -160,7 +160,7 @@ describe 'Bitcoin::Dogecoin' do
160
160
  chain_merkle_root.should == tx_root_hash
161
161
 
162
162
  merkle_branch_size = script.slice(chain_merkle_root.length / 2, 4).unpack("V")[0]
163
- merkle_branch_size.should == (1 << aux_pow.aux_branch.length)
163
+ merkle_branch_size.should == (1 << aux_pow.chain_branch.length)
164
164
 
165
165
  # Choose a pseudo-random slot in the chain merkle tree
166
166
  # but have it be fixed for a size/nonce/chain combination.
@@ -170,7 +170,7 @@ describe 'Bitcoin::Dogecoin' do
170
170
  rand += Bitcoin.network[:auxpow_chain_id]
171
171
  rand = rand * 1103515245 + 12345
172
172
 
173
- aux_pow.aux_index.should == (rand % merkle_branch_size)
173
+ aux_pow.chain_index.should == (rand % merkle_branch_size)
174
174
  end
175
175
 
176
176
  end
@@ -10,8 +10,9 @@ describe 'Bitcoin::Protocol::Parser (addr)' do
10
10
  .split(" ").join].pack("H*")
11
11
 
12
12
  class Addr_Handler < Bitcoin::Protocol::Handler
13
- attr_reader :addr
13
+ attr_reader :addr, :err
14
14
  def on_addr(addr); (@addr ||= []) << addr; end
15
+ def on_error(*err); (@err ||= []) << err; end
15
16
  end
16
17
 
17
18
  parser = Bitcoin::Protocol::Parser.new( handler = Addr_Handler.new )
@@ -24,6 +25,14 @@ describe 'Bitcoin::Protocol::Parser (addr)' do
24
25
  ]
25
26
  end
26
27
 
28
+ it "parses broken address packet" do
29
+ pkt = ["01 00 00 00 00".split(" ").join].pack("H*")
30
+ parser = Bitcoin::Protocol::Parser.new( handler = Addr_Handler.new )
31
+ parser.parse_addr(pkt).should == nil
32
+ handler.addr.should == nil
33
+ handler.err.should == [[:addr, pkt[1..-1]]]
34
+ end
35
+
27
36
  end
28
37
 
29
38
  describe 'Bitcoin::Protocol::Addr' do
@@ -69,4 +78,5 @@ describe 'Bitcoin::Protocol::Addr' do
69
78
  Bitcoin::Protocol::Addr.pkt(addr).should ==
70
79
  Bitcoin::Protocol.pkt("addr", "\x01" + addr.to_payload)
71
80
  end
81
+
72
82
  end
@@ -17,14 +17,14 @@ describe Bitcoin::Protocol::AuxPow do
17
17
  @aux_pow.should != nil
18
18
  @aux_pow.block_hash.hth.should ==
19
19
  "b42124fd99e67ddabe52ebbfcb30a82b8c74268a320b3c5e2311000000000000"
20
- @aux_pow.branch.map(&:hth).should == [
21
- "6d4febe909ffec61fb8e7bf24ef4444f070f74b208502285528a9686ba792fc2",
22
- "d052728459450cec6ff81072c6bd3ec2fd186eaadb09429da7cab0be73646999",
23
- "64c5e7e42a6cde585602fdfda6d7e5d9232db2c176a49278268cec09f3cfcb20",
24
- "4e9406d6ce9ef751242bb9a63e7c2009746b3736c356ed5d738dadd6937531e4" ]
25
- @aux_pow.mrkl_index.should == 0
26
- @aux_pow.aux_branch.should == []
27
- @aux_pow.aux_index.should == 0
20
+ @aux_pow.coinbase_branch.should == [
21
+ "c22f79ba86968a5285225008b2740f074f44f44ef27b8efb61ecff09e9eb4f6d",
22
+ "99696473beb0caa79d4209dbaa6e18fdc23ebdc67210f86fec0c4559847252d0",
23
+ "20cbcff309ec8c267892a476c1b22d23d9e5d7a6fdfd025658de6c2ae4e7c564",
24
+ "e4317593d6ad8d735ded56c336376b7409207c3ea6b92b2451f79eced606944e" ]
25
+ @aux_pow.coinbase_index.should == 0
26
+ @aux_pow.chain_branch.should == []
27
+ @aux_pow.chain_index.should == 0
28
28
  @aux_pow.parent_block.hash.should ==
29
29
  "00000000000011235e3c0b328a26748c2ba830cbbfeb52beda7de699fd2421b4"
30
30
  end
@@ -42,4 +42,4 @@ describe Bitcoin::Protocol::AuxPow do
42
42
  P::Block.from_json(@blk.to_json).to_payload.should == @data
43
43
  end
44
44
 
45
- end
45
+ end
@@ -128,6 +128,7 @@ describe 'Bitcoin::Protocol::Block' do
128
128
  block = Block.from_json(fixtures_file('rawblock-0.json'))
129
129
  h = block.to_hash
130
130
  h['tx'][0]['ver'] = 2
131
+ h['tx'][0]['hash'] = "5ea04451af738d113f0ae8559225b7f893f186f099d88c72230a5e19c0bb830d"
131
132
  -> { Block.from_hash(h) }.should.raise(Exception)
132
133
  .message.should.include?("Block merkle root mismatch!")
133
134
  end
@@ -5,12 +5,12 @@ require_relative '../spec_helper.rb'
5
5
  describe 'Bitcoin::Protocol::Parser - Inventory Vectors' do
6
6
 
7
7
  class Test_Handler < Bitcoin::Protocol::Handler
8
- attr_reader :tx_inv
9
- attr_reader :block_inv
8
+ attr_reader :tx_inv, :block_inv, :err
10
9
  def on_inv_transaction(hash); (@tx_inv ||= []) << hash.hth; end
11
10
  def on_get_transaction(hash); (@tx_inv ||= []) << hash.hth; end
12
11
  def on_inv_block(hash); (@block_inv ||= []) << hash.hth; end
13
12
  def on_get_block(hash); (@block_inv ||= []) << hash.hth; end
13
+ def on_error(*err); (@err ||= []) << err; end
14
14
  end
15
15
 
16
16
 
@@ -123,4 +123,12 @@ describe 'Bitcoin::Protocol::Parser - Inventory Vectors' do
123
123
  }
124
124
  end
125
125
 
126
+ it 'parses broken inv packet' do
127
+ pkt = [("01 00 00 00 00" + " 00"*32).split(" ").join].pack("H*")
128
+ parser = Bitcoin::Protocol::Parser.new( handler = Test_Handler.new )
129
+ parser.parse_inv(pkt).should == nil
130
+ handler.tx_inv.should == nil
131
+ handler.err.should == [[:parse_inv, pkt[1..-1]]]
132
+ end
133
+
126
134
  end
@@ -0,0 +1,50 @@
1
+ # encoding: ascii-8bit
2
+
3
+ require_relative '../spec_helper.rb'
4
+ require 'minitest/mock'
5
+
6
+ include Bitcoin::Protocol
7
+
8
+ describe 'Bitcoin::Protocol::Parser' do
9
+
10
+
11
+ before {
12
+ @pkt= [
13
+ "f9 be b4 d9", # magic head
14
+ "69 6e 76 00 00 00 00 00 00 00 00 00", # command ("inv")
15
+ "49 00 00 00", # message length
16
+ "11 ea 1c 91", # checksum
17
+
18
+ "02", # n hashes
19
+ "01 00 00 00", # type (1=tx)
20
+ "e0 41 c2 38 f7 32 1a 68 0a 34 06 bf fd 72 12 e3 d1 2c b5 12 2a 8c 0b 52 76 de 82 30 b1 00 7a 42",
21
+ "01 00 00 00", # type (1=tx)
22
+ "33 00 09 71 a9 70 7b 6c 6d 6e 77 aa 2e ac 43 f3 e5 67 84 cb 61 b2 35 fb 8d fe e0 86 8b 40 7c f3"
23
+ ].map{|s| s.split(" ")}.flatten.join.htb
24
+ @handler = MiniTest::Mock.new }
25
+ after { @handler.verify }
26
+
27
+ it 'should call appropriate handler' do
28
+ @handler.expect(:on_inv_transaction, nil, [@pkt[29..60].reverse])
29
+ @handler.expect(:on_inv_transaction, nil, [@pkt[-32..-1].reverse])
30
+ Parser.new( @handler ).parse( @pkt ).should == ""
31
+ end
32
+
33
+ it 'should count total packets and bytes' do
34
+ parser = Parser.new
35
+ parser.parse @pkt
36
+ parser.stats.should == {"total_packets"=>1, "total_bytes"=>73, "total_errors" => 0, "inv"=>1}
37
+ end
38
+
39
+ it 'should call error handler for unknown command' do
40
+ @handler.expect(:on_error, nil, [:unknown_packet, ["foo", "626172"]])
41
+ Parser.new( @handler ).process_pkt('foo', "bar").should == nil
42
+ end
43
+
44
+ it 'should count total errors' do
45
+ parser = Parser.new
46
+ parser.process_pkt('foo', 'bar')
47
+ parser.stats['total_errors'].should == 1
48
+ end
49
+
50
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: ascii-8bit
2
+
3
+ require_relative '../spec_helper.rb'
4
+
5
+ describe 'Bitcoin::Protocol::Parser (reject)' do
6
+
7
+ it 'parses alert' do
8
+ payload = "\x02tx\x10\bcoinbase\xB5\x93\x84\x8D\x99\xF4\x1AE\xE9\xD2\x90T\x9919\xF0X %\xBBE\x19\x19\x86\xBC\r\x812\x7F\xC4\xEDN"
9
+
10
+ alert = Bitcoin::Protocol::Reject.parse(payload)
11
+ alert.message.should == "tx"
12
+ alert.ccode.should == :invalid
13
+ alert.reason.should == "coinbase"
14
+ alert.tx_hash.should == "4eedc47f32810dbc86191945bb252058f03931995490d2e9451af4998d8493b5"
15
+ end
16
+
17
+ end
@@ -59,7 +59,7 @@ describe 'Tx' do
59
59
  script.chunks[0].bitcoin_pushdata = Bitcoin::Script::OP_PUSHDATA2
60
60
  script.chunks[0].bitcoin_pushdata_length = script.chunks[0].bytesize
61
61
  new_tx['in'][0]['scriptSig'] = script.to_string
62
- new_tx = Bitcoin::P::Tx.from_hash(new_tx)
62
+ new_tx = Bitcoin::P::Tx.from_hash(new_tx, false)
63
63
 
64
64
  new_tx.hash.should != tx.hash
65
65
  new_tx.normalized_hash.size.should == 64
@@ -84,6 +84,10 @@ describe 'Tx' do
84
84
  tx.to_payload.should == @payload[0]
85
85
  tx.to_hash.should == orig_tx.to_hash
86
86
  Tx.binary_from_hash( orig_tx.to_hash ).should == @payload[0]
87
+
88
+ h = orig_tx.to_hash.merge("ver" => 123)
89
+ -> { tx = Tx.from_hash(h) }.should.raise(Exception)
90
+ .message.should == "Tx hash mismatch! Claimed: 6e9dd16625b62cfcd4bf02edb89ca1f5a8c30c4b1601507090fb28e59f2d02b4, Actual: 395cd28c334ac84ed125ec5ccd5bc29eadcc96b79c337d0a87a19df64ea3b548"
87
91
  end
88
92
 
89
93
  it 'Tx.binary_from_hash' do