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.
@@ -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