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.
@@ -5,6 +5,8 @@ module Bitcoin
5
5
  # Elliptic Curve key as used in bitcoin.
6
6
  class Key
7
7
 
8
+ attr_reader :key
9
+
8
10
  # Generate a new keypair.
9
11
  # Bitcoin::Key.generate
10
12
  def self.generate(opts={compressed: true})
@@ -54,6 +56,7 @@ module Bitcoin
54
56
  # Set the private key to +priv+ (in hex).
55
57
  def priv= priv
56
58
  set_priv(priv)
59
+ regenerate_pubkey
57
60
  end
58
61
 
59
62
  # Get the public key (in hex).
@@ -98,12 +101,7 @@ module Bitcoin
98
101
  # key1 = Bitcoin::Key.generate
99
102
  # sig = key1.sign("some data")
100
103
  def sign(data)
101
- sig = @key.dsa_sign_asn1(data)
102
- if Script::is_low_der_signature?(sig)
103
- sig
104
- else
105
- Bitcoin::OpenSSL_EC.signature_to_low_s(sig)
106
- end
104
+ Bitcoin.sign_data(key, data)
107
105
  end
108
106
 
109
107
  # Verify signature +sig+ for +data+.
@@ -2,6 +2,17 @@
2
2
 
3
3
  begin
4
4
  require 'log4r'
5
+ # monkey-patch Log4r to accept level names as symbols
6
+ class Log4r::Logger
7
+ def level= l = 0
8
+ _level = l.is_a?(Fixnum) ? l : Log4r::LNAMES.index(l.to_s.upcase)
9
+ Log4r::Log4rTools.validate_level(_level)
10
+ @level = _level
11
+ LoggerFactory.define_methods(self)
12
+ Log4r::Logger.log_internal {"Logger '#{@fullname}' set to #{LNAMES[@level]}"}
13
+ @level
14
+ end
15
+ end
5
16
  rescue LoadError
6
17
  end
7
18
 
@@ -19,6 +19,7 @@ module Bitcoin
19
19
  autoload :Block, 'bitcoin/protocol/block'
20
20
  autoload :Addr, 'bitcoin/protocol/address'
21
21
  autoload :Alert, 'bitcoin/protocol/alert'
22
+ autoload :Reject, 'bitcoin/protocol/reject'
22
23
  autoload :Version, 'bitcoin/protocol/version'
23
24
  autoload :AuxPow, 'bitcoin/protocol/aux_pow'
24
25
 
@@ -72,15 +73,27 @@ module Bitcoin
72
73
  end
73
74
 
74
75
  def self.unpack_var_string_array(payload) # unpacks set<string>
75
- size, payload = unpack_var_int(payload)
76
- return [nil, payload] if size == 0
77
- [(0...size).map{ s, payload = unpack_var_string(payload); s }, payload]
76
+ buf = StringIO.new(payload)
77
+ size = unpack_var_int_from_io(buf)
78
+ return [nil, buf.read] if size == 0
79
+ strings = []
80
+ size.times{
81
+ break if buf.eof?
82
+ strings << unpack_var_string_from_io(buf)
83
+ }
84
+ [strings, buf.read]
78
85
  end
79
86
 
80
87
  def self.unpack_var_int_array(payload) # unpacks set<int>
81
- size, payload = unpack_var_int(payload)
82
- return [nil, payload] if size == 0
83
- [(0...size).map{ i, payload = unpack_var_int(payload); i }, payload]
88
+ buf = StringIO.new(payload)
89
+ size = unpack_var_int_from_io(buf)
90
+ return [nil, buf.read] if size == 0
91
+ ints = []
92
+ size.times{
93
+ break if buf.eof?
94
+ ints << unpack_var_int_from_io(buf)
95
+ }
96
+ [ints, buf.read]
84
97
  end
85
98
 
86
99
  def self.unpack_boolean(payload)
@@ -4,25 +4,40 @@ module Bitcoin
4
4
  module Protocol
5
5
 
6
6
  # Auxiliary Proof-of-Work for merge-mined blockchains
7
+ # See https://en.bitcoin.it/wiki/Merged_mining_specification.
8
+ #
9
+ # The AuxPow contains all data needed to verify that the child
10
+ # block was included in the parents coinbase transaction, and
11
+ # the parent satisfies the difficulty target.
12
+ #
13
+ # It encodes the +parent_block+ header, and its +coinbase_tx+.
14
+ # The +coinbase_branch+ and +coinbase_index+ can be used to recalculate
15
+ # the parent blocks merkle root and prove the coinbase transaction is
16
+ # really included in it.
17
+ # The +chain_branch+ and +chain_index+ are used to link the child block
18
+ # to the merkle root contained in the +coinbase_tx+. (So there can be
19
+ # more than one merge-mined chain)
20
+ #
21
+ # TODO: decode merged-mining data from +coinbase_tx+
7
22
  class AuxPow
8
23
 
9
- # Coinbase transaction linking the aux to its parent block
10
- attr_accessor :tx
24
+ # Coinbase transaction of the parent block, linking to the child block
25
+ attr_accessor :coinbase_tx
11
26
 
12
- # Hash of the block header
27
+ # Hash of the parent block header
13
28
  attr_accessor :block_hash
14
29
 
15
- # Merkle branches to bring the transaction to the block's merkle root
16
- attr_accessor :branch
30
+ # Merkle branch linking the +coinbase_tx+ to the +parent_block+
31
+ attr_accessor :coinbase_branch
17
32
 
18
- # Index of this transaction in the merkle tree
19
- attr_accessor :mrkl_index
33
+ # Index of the +coinbase_tx+ in the parent blocks merkle tree
34
+ attr_accessor :coinbase_index
20
35
 
21
- # Merkle branches linking this aux chains to the aux root
22
- attr_accessor :aux_branch
36
+ # Merkle branch linking the child block to the +coinbase_tx+
37
+ attr_accessor :chain_branch
23
38
 
24
- # Index of "this" block chain in the aux chain list
25
- attr_accessor :aux_index
39
+ # Index of the child block in the chain merkle tree
40
+ attr_accessor :chain_index
26
41
 
27
42
  # Parent block header
28
43
  attr_accessor :parent_block
@@ -32,47 +47,32 @@ module Bitcoin
32
47
  end
33
48
 
34
49
  def parse_data(data)
35
- @tx = P::Tx.new(nil)
36
- payload = @tx.parse_data(data)
37
- @block_hash, payload = payload.unpack("a32a*")
38
-
39
- branch_count, payload = P.unpack_var_int(payload)
40
- @branch = []
41
- branch_count.times {
42
- b, payload = payload.unpack("a32a*")
43
- @branch << b
44
- }
45
- @mrkl_index, payload = payload.unpack("Ia*")
46
-
47
- @aux_branch = []
48
- aux_branch_count, payload = P.unpack_var_int(payload)
49
- aux_branch_count.times {
50
- b, payload = payload.unpack("a32a*")
51
- @aux_branch << b
52
- }
53
-
54
- @aux_index, payload = payload.unpack("Ia*")
55
- block, payload = payload.unpack("a80a*")
56
- @parent_block = P::Block.new(block)
57
-
58
- payload
50
+ buf = StringIO.new(data)
51
+ parse_data_from_io(buf)
52
+ buf.eof? ? '' : buf.read
59
53
  end
60
54
 
61
55
  def parse_data_from_io(data)
62
- @tx = P::Tx.new(nil)
63
- @tx.parse_data_from_io(data)
56
+ @coinbase_tx = P::Tx.new(nil)
57
+ @coinbase_tx.parse_data_from_io(data)
64
58
 
65
59
  @block_hash = data.read(32)
66
- branch_count = P.unpack_var_int_from_io(data)
67
- @branch = []
68
- branch_count.times{ @branch << data.read(32) }
69
- @mrkl_index = data.read(4).unpack("I")[0]
60
+ coinbase_branch_count = P.unpack_var_int_from_io(data)
61
+ @coinbase_branch = []
62
+ coinbase_branch_count.times{
63
+ break if data.eof?
64
+ @coinbase_branch << data.read(32).reverse.hth
65
+ }
66
+ @coinbase_index = data.read(4).unpack("I")[0]
70
67
 
71
- @aux_branch = []
72
- aux_branch_count = P.unpack_var_int_from_io(data)
73
- aux_branch_count.times{ @aux_branch << data.read(32) }
68
+ @chain_branch = []
69
+ chain_branch_count = P.unpack_var_int_from_io(data)
70
+ chain_branch_count.times{
71
+ break if data.eof?
72
+ @chain_branch << data.read(32).reverse.hth
73
+ }
74
74
 
75
- @aux_index = data.read(4).unpack("I")[0]
75
+ @chain_index = data.read(4).unpack("I")[0]
76
76
  block = data.read(80)
77
77
  @parent_block = P::Block.new(block)
78
78
 
@@ -81,14 +81,14 @@ module Bitcoin
81
81
 
82
82
 
83
83
  def to_payload
84
- payload = @tx.to_payload
84
+ payload = @coinbase_tx.to_payload
85
85
  payload << @block_hash
86
- payload << P.pack_var_int(@branch.count)
87
- payload << @branch.join
88
- payload << [@mrkl_index].pack("I")
89
- payload << P.pack_var_int(@aux_branch.count)
90
- payload << @aux_branch.join
91
- payload << [@aux_index].pack("I")
86
+ payload << P.pack_var_int(@coinbase_branch.count)
87
+ payload << @coinbase_branch.map(&:htb).map(&:reverse).join
88
+ payload << [@coinbase_index].pack("I")
89
+ payload << P.pack_var_int(@chain_branch.count)
90
+ payload << @chain_branch.map(&:htb).map(&:reverse).join
91
+ payload << [@chain_index].pack("I")
92
92
  payload << @parent_block.to_payload
93
93
  payload
94
94
  end
@@ -96,24 +96,24 @@ module Bitcoin
96
96
  def self.from_hash h
97
97
  aux_pow = new(nil)
98
98
  aux_pow.instance_eval do
99
- @tx = P::Tx.from_hash(h['tx'])
99
+ @coinbase_tx = P::Tx.from_hash(h['coinbase_tx'])
100
100
  @block_hash = h['block_hash'].htb
101
- @branch = h['branch'].map(&:htb)
102
- @mrkl_index = h['mrkl_index']
103
- @aux_branch = h['aux_branch'].map(&:htb)
104
- @aux_index = h['aux_index']
101
+ @coinbase_branch = h['coinbase_branch']
102
+ @coinbase_index = h['coinbase_index']
103
+ @chain_branch = h['chain_branch']
104
+ @chain_index = h['chain_index']
105
105
  @parent_block = P::Block.from_hash(h['parent_block'])
106
106
  end
107
107
  aux_pow
108
108
  end
109
109
 
110
110
  def to_hash
111
- { 'tx' => @tx.to_hash,
111
+ { 'coinbase_tx' => @coinbase_tx.to_hash,
112
112
  'block_hash' => @block_hash.hth,
113
- 'branch' => @branch.map(&:hth),
114
- 'mrkl_index' => @mrkl_index,
115
- 'aux_branch' => @aux_branch.map(&:hth),
116
- 'aux_index' => @aux_index,
113
+ 'coinbase_branch' => @coinbase_branch,
114
+ 'coinbase_index' => @coinbase_index,
115
+ 'chain_branch' => @chain_branch,
116
+ 'chain_index' => @chain_index,
117
117
  'parent_block' => @parent_block.to_hash }
118
118
  end
119
119
 
@@ -14,7 +14,9 @@ module Bitcoin
14
14
  attr_accessor :hash
15
15
 
16
16
  # previous block hash
17
- attr_accessor :prev_block
17
+ attr_accessor :prev_block_hash
18
+ alias :prev_block :prev_block_hash
19
+ def prev_block=(hash); @prev_block_hash = hash; end
18
20
 
19
21
  # transactions (Array of Tx)
20
22
  attr_accessor :tx
@@ -52,7 +54,7 @@ module Bitcoin
52
54
  end
53
55
 
54
56
  def prev_block_hex
55
- @prev_block_hex ||= @prev_block.reverse.unpack("H*")[0]
57
+ @prev_block_hex ||= @prev_block_hash.reverse.unpack("H*")[0]
56
58
  end
57
59
 
58
60
  # create block from raw binary +data+
@@ -70,7 +72,7 @@ module Bitcoin
70
72
  # parse raw binary data
71
73
  def parse_data_from_io(buf, header_only=false)
72
74
  buf = buf.is_a?(String) ? StringIO.new(buf) : buf
73
- @ver, @prev_block, @mrkl_root, @time, @bits, @nonce = buf.read(80).unpack("Va32a32VVV")
75
+ @ver, @prev_block_hash, @mrkl_root, @time, @bits, @nonce = buf.read(80).unpack("Va32a32VVV")
74
76
  recalc_block_hash
75
77
 
76
78
  if Bitcoin.network[:auxpow_chain_id] != nil && (@ver & BLOCK_VERSION_AUXPOW) > 0
@@ -84,7 +86,10 @@ module Bitcoin
84
86
  @tx_count = tx_size
85
87
  return buf if header_only
86
88
 
87
- tx_size.times{ break if payload == true
89
+ tx_size.times{
90
+ break if payload == true
91
+ return buf if buf.eof?
92
+
88
93
  t = Tx.new(nil)
89
94
  payload = t.parse_data_from_io(buf)
90
95
  @tx << t
@@ -96,11 +101,11 @@ module Bitcoin
96
101
 
97
102
  # recalculate the block hash
98
103
  def recalc_block_hash
99
- @hash = Bitcoin.block_hash(@prev_block.reverse_hth, @mrkl_root.reverse_hth, @time, @bits, @nonce, @ver)
104
+ @hash = Bitcoin.block_hash(@prev_block_hash.reverse_hth, @mrkl_root.reverse_hth, @time, @bits, @nonce, @ver)
100
105
  end
101
106
 
102
107
  def recalc_block_scrypt_hash
103
- @scrypt_hash = Bitcoin.block_scrypt_hash(@prev_block.reverse_hth, @mrkl_root.reverse_hth, @time, @bits, @nonce, @ver)
108
+ @scrypt_hash = Bitcoin.block_scrypt_hash(@prev_block_hash.reverse_hth, @mrkl_root.reverse_hth, @time, @bits, @nonce, @ver)
104
109
  end
105
110
 
106
111
  def recalc_mrkl_root
@@ -115,12 +120,12 @@ module Bitcoin
115
120
  # get the block header info
116
121
  # [<version>, <prev_block>, <merkle_root>, <time>, <bits>, <nonce>, <txcount>, <size>]
117
122
  def header_info
118
- [@ver, @prev_block.reverse_hth, @mrkl_root.reverse_hth, Time.at(@time), @bits, @nonce, @tx.size, @payload.size]
123
+ [@ver, @prev_block_hash.reverse_hth, @mrkl_root.reverse_hth, Time.at(@time), @bits, @nonce, @tx.size, @payload.size]
119
124
  end
120
125
 
121
126
  # convert to raw binary format
122
127
  def to_payload
123
- head = [@ver, @prev_block, @mrkl_root, @time, @bits, @nonce].pack("Va32a32VVV")
128
+ head = [@ver, @prev_block_hash, @mrkl_root, @time, @bits, @nonce].pack("Va32a32VVV")
124
129
  head << @aux_pow.to_payload if @aux_pow
125
130
  return head if @tx.size == 0
126
131
  head << Protocol.pack_var_int(@tx.size)
@@ -129,13 +134,13 @@ module Bitcoin
129
134
  end
130
135
 
131
136
  # convert to ruby hash (see also #from_hash)
132
- def to_hash
137
+ def to_hash(options = {})
133
138
  h = {
134
139
  'hash' => @hash, 'ver' => @ver,
135
- 'prev_block' => @prev_block.reverse_hth, 'mrkl_root' => @mrkl_root.reverse_hth,
140
+ 'prev_block' => @prev_block_hash.reverse_hth, 'mrkl_root' => @mrkl_root.reverse_hth,
136
141
  'time' => @time, 'bits' => @bits, 'nonce' => @nonce,
137
142
  'n_tx' => @tx.size, 'size' => (@payload||to_payload).bytesize,
138
- 'tx' => @tx.map{|i| i.to_hash },
143
+ 'tx' => @tx.map{|i| i.to_hash(options) },
139
144
  'mrkl_tree' => Bitcoin.hash_mrkl_tree( @tx.map{|i| i.hash } )
140
145
  }
141
146
  h['aux_pow'] = @aux_pow.to_hash if @aux_pow
@@ -177,7 +182,7 @@ module Bitcoin
177
182
  # convert to json representation as seen in the block explorer.
178
183
  # (see also #from_json)
179
184
  def to_json(options = {:space => ''}, *a)
180
- JSON.pretty_generate( to_hash, options )
185
+ JSON.pretty_generate( to_hash(options), options )
181
186
  end
182
187
 
183
188
  # write json representation to a file
@@ -191,12 +196,12 @@ module Bitcoin
191
196
  blk = new(nil)
192
197
  blk.instance_eval{
193
198
  @ver, @time, @bits, @nonce = h.values_at('ver', 'time', 'bits', 'nonce')
194
- @prev_block, @mrkl_root = h.values_at('prev_block', 'mrkl_root').map{|i| i.htb_reverse }
199
+ @prev_block_hash, @mrkl_root = h.values_at('prev_block', 'mrkl_root').map{|i| i.htb_reverse }
195
200
  unless h['hash'] == recalc_block_hash
196
201
  raise "Block hash mismatch! Claimed: #{h['hash']}, Actual: #{@hash}" if do_raise
197
202
  end
198
203
  @aux_pow = AuxPow.from_hash(h['aux_pow']) if h['aux_pow']
199
- h['tx'].each{|tx| @tx << Tx.from_hash(tx) }
204
+ h['tx'].each{|tx| @tx << Tx.from_hash(tx, do_raise) }
200
205
  if h['tx'].any?
201
206
  (raise "Block merkle root mismatch! Block: #{h['hash']}" unless verify_mrkl_root) if do_raise
202
207
  end
@@ -222,7 +227,7 @@ module Bitcoin
222
227
 
223
228
  # block header binary output
224
229
  def block_header
225
- [@ver, @prev_block, @mrkl_root, @time, @bits, @nonce, Protocol.pack_var_int(0)].pack("Va32a32VVVa*")
230
+ [@ver, @prev_block_hash, @mrkl_root, @time, @bits, @nonce, Protocol.pack_var_int(0)].pack("Va32a32VVVa*")
226
231
  end
227
232
 
228
233
  # read binary block from a file
@@ -33,6 +33,10 @@ module Bitcoin
33
33
  puts block.to_json
34
34
  end
35
35
 
36
+ def on_error(message, payload)
37
+ p ['error', message, payload]
38
+ end
39
+
36
40
  end
37
41
 
38
42
  end
@@ -9,29 +9,19 @@ module Bitcoin
9
9
  def initialize(handler=nil)
10
10
  @h = handler || Handler.new
11
11
  @buf = ""
12
- @stats = {
13
- 'total_packets' => 0,
14
- 'total_bytes' => 0
15
- }
12
+ @stats = { 'total_packets' => 0, 'total_bytes' => 0, 'total_errors' => 0 }
16
13
  end
17
14
 
18
- def log
19
- @log ||= Bitcoin::Logger.create("parser")
20
- end
15
+ def log; @log ||= Bitcoin::Logger.create("parser"); end
21
16
 
22
17
  # handles inv/getdata packets
23
- #
24
18
  def parse_inv(payload, type=:put)
25
19
  count, payload = Protocol.unpack_var_int(payload)
26
- payload.each_byte.each_slice(36).with_index{|i, idx|
20
+ payload.each_byte.each_slice(36).with_index do |i, idx|
27
21
  hash = i[4..-1].reverse.pack("C32")
28
22
  case i[0]
29
23
  when 1
30
- if type == :put
31
- @h.on_inv_transaction(hash)
32
- else
33
- @h.on_get_transaction(hash)
34
- end
24
+ type == :put ? @h.on_inv_transaction(hash) : @h.on_get_transaction(hash)
35
25
  when 2
36
26
  if type == :put
37
27
  if @h.respond_to?(:on_inv_block_v2)
@@ -43,28 +33,26 @@ module Bitcoin
43
33
  @h.on_get_block(hash)
44
34
  end
45
35
  else
46
- p ['parse_inv error', i]
36
+ parse_error :parse_inv, i.pack("C*")
47
37
  end
48
- }
38
+ end
49
39
  end
50
40
 
51
41
  def parse_addr(payload)
52
42
  count, payload = Protocol.unpack_var_int(payload)
53
- payload.each_byte.each_slice(30){|i|
54
- begin
55
- addr = Addr.new(i.pack("C*"))
56
- rescue
57
- puts "Error parsing addr: #{i.inspect}"
58
- end
59
- @h.on_addr( addr )
60
- }
43
+ payload.each_byte.each_slice(30) do |i|
44
+ @h.on_addr(Addr.new(i.pack("C*"))) rescue parse_error(:addr, i.pack("C*"))
45
+ end
61
46
  end
62
47
 
63
48
  def parse_headers(payload)
64
49
  return unless @h.respond_to?(:on_headers)
65
50
  buf = StringIO.new(payload)
66
51
  count = Protocol.unpack_var_int_from_io(buf)
67
- headers = count.times.map{ b = Block.new; b.parse_data_from_io(buf, header_only=true); b }
52
+ headers = count.times.map{
53
+ break if buf.eof?
54
+ b = Block.new; b.parse_data_from_io(buf, header_only=true); b
55
+ }
68
56
  @h.on_headers(headers)
69
57
  end
70
58
 
@@ -98,8 +86,9 @@ module Bitcoin
98
86
  when 'getheaders'; @h.on_getheaders(*parse_getblocks(payload)) if @h.respond_to?(:on_getheaders)
99
87
  when 'mempool'; handle_mempool_request(payload)
100
88
  when 'notfound'; handle_notfound_reply(payload)
89
+ when 'reject'; handle_reject(payload)
101
90
  else
102
- p ['unknown-packet', command, payload]
91
+ parse_error :unknown_packet, [command, payload.hth]
103
92
  end
104
93
  end
105
94
 
@@ -113,6 +102,11 @@ module Bitcoin
113
102
  @h.on_alert Bitcoin::Protocol::Alert.parse(payload)
114
103
  end
115
104
 
105
+ def handle_reject(payload)
106
+ return unless @h.respond_to?(:on_reject)
107
+ @h.on_reject Bitcoin::Protocol::Reject.parse(payload)
108
+ end
109
+
116
110
  # https://en.bitcoin.it/wiki/BIP_0035
117
111
  def handle_mempool_request(payload)
118
112
  return unless @version.fields[:version] >= 60002 # Protocol version >= 60002
@@ -123,15 +117,15 @@ module Bitcoin
123
117
  def handle_notfound_reply(payload)
124
118
  return unless @h.respond_to?(:on_notfound)
125
119
  count, payload = Protocol.unpack_var_int(payload)
126
- payload.each_byte.each_slice(36){|i|
120
+ payload.each_byte.each_slice(36) do |i|
127
121
  hash = i[4..-1].reverse.pack("C32")
128
122
  case i[0]
129
123
  when 1; @h.on_notfound(:tx, hash)
130
124
  when 2; @h.on_notfound(:block, hash)
131
125
  else
132
- p ['handle_notfound_reply error', i, hash]
126
+ parse_error(:notfound, [i.pack("C*"), hash])
133
127
  end
134
- }
128
+ end
135
129
  end
136
130
 
137
131
  def parse(buf)
@@ -179,6 +173,13 @@ module Bitcoin
179
173
  log.debug { [type, msg] }
180
174
  end
181
175
  end
176
+
177
+ def parse_error *err
178
+ @stats['total_errors'] += 1
179
+ return unless @h.respond_to?(:on_error)
180
+ @h.on_error *err
181
+ end
182
+
182
183
  end # Parser
183
184
 
184
185
  end