bitcoin-ruby 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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