bitcoin-ruby 0.0.10 → 0.0.11

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/COPYING +1 -1
  4. data/Gemfile.lock +9 -10
  5. data/README.rdoc +1 -1
  6. data/Rakefile +4 -2
  7. data/lib/bitcoin.rb +4 -2
  8. data/lib/bitcoin/bloom_filter.rb +125 -0
  9. data/lib/bitcoin/builder.rb +34 -9
  10. data/lib/bitcoin/ext_key.rb +191 -0
  11. data/lib/bitcoin/ffi/openssl.rb +1 -1
  12. data/lib/bitcoin/key.rb +6 -4
  13. data/lib/bitcoin/protocol.rb +13 -11
  14. data/lib/bitcoin/protocol/block.rb +38 -2
  15. data/lib/bitcoin/protocol/parser.rb +8 -0
  16. data/lib/bitcoin/protocol/partial_merkle_tree.rb +61 -0
  17. data/lib/bitcoin/protocol/script_witness.rb +31 -0
  18. data/lib/bitcoin/protocol/tx.rb +170 -10
  19. data/lib/bitcoin/protocol/txin.rb +8 -0
  20. data/lib/bitcoin/protocol/version.rb +2 -1
  21. data/lib/bitcoin/script.rb +58 -8
  22. data/lib/bitcoin/version.rb +1 -1
  23. data/spec/bitcoin/bloom_filter_spec.rb +23 -0
  24. data/spec/bitcoin/builder_spec.rb +12 -0
  25. data/spec/bitcoin/ext_key_spec.rb +180 -0
  26. data/spec/bitcoin/fixtures/filteredblock-0.bin +0 -0
  27. data/spec/bitcoin/fixtures/rawblock-testnet-1151351.bin +0 -0
  28. data/spec/bitcoin/fixtures/rawtx-p2wpkh.bin +0 -0
  29. data/spec/bitcoin/fixtures/rawtx-p2wpkh.json +67 -0
  30. data/spec/bitcoin/fixtures/tx-0a6a357e2f7796444e02638749d9611c008b253fb55f5dc88b739b230ed0c4c3.json +139 -0
  31. data/spec/bitcoin/fixtures/tx-28204cad1d7fc1d199e8ef4fa22f182de6258a3eaafe1bbe56ebdcacd3069a5f.json +34 -0
  32. data/spec/bitcoin/protocol/bip143_spec.rb +116 -0
  33. data/spec/bitcoin/protocol/block_spec.rb +27 -0
  34. data/spec/bitcoin/protocol/partial_merkle_tree_spec.rb +38 -0
  35. data/spec/bitcoin/protocol/tx_spec.rb +134 -1
  36. data/spec/bitcoin/script/script_spec.rb +53 -2
  37. metadata +27 -3
@@ -11,7 +11,7 @@ module OpenSSL_EC
11
11
  if FFI::Platform.windows?
12
12
  ffi_lib 'libeay32', 'ssleay32'
13
13
  else
14
- ffi_lib 'ssl'
14
+ ffi_lib [ 'libssl.so.1.0.0', 'ssl' ]
15
15
  end
16
16
 
17
17
  NID_secp256k1 = 714
data/lib/bitcoin/key.rb CHANGED
@@ -69,13 +69,15 @@ module Bitcoin
69
69
  end
70
70
 
71
71
  def pub_compressed
72
- @key.public_key.group.point_conversion_form = :compressed
73
- @key.public_key.to_hex.rjust(66, '0')
72
+ public_key = @key.public_key
73
+ public_key.group.point_conversion_form = :compressed
74
+ public_key.to_hex.rjust(66, '0')
74
75
  end
75
76
 
76
77
  def pub_uncompressed
77
- @key.public_key.group.point_conversion_form = :uncompressed
78
- @key.public_key.to_hex.rjust(130, '0')
78
+ public_key = @key.public_key
79
+ public_key.group.point_conversion_form = :uncompressed
80
+ public_key.to_hex.rjust(130, '0')
79
81
  end
80
82
 
81
83
  def compressed
@@ -13,15 +13,17 @@ module Bitcoin
13
13
  # BIP 0031, pong message, is enabled for all versions AFTER this one
14
14
  BIP0031_VERSION = 60000
15
15
 
16
- autoload :TxIn, 'bitcoin/protocol/txin'
17
- autoload :TxOut, 'bitcoin/protocol/txout'
18
- autoload :Tx, 'bitcoin/protocol/tx'
19
- autoload :Block, 'bitcoin/protocol/block'
20
- autoload :Addr, 'bitcoin/protocol/address'
21
- autoload :Alert, 'bitcoin/protocol/alert'
22
- autoload :Reject, 'bitcoin/protocol/reject'
23
- autoload :Version, 'bitcoin/protocol/version'
24
- autoload :AuxPow, 'bitcoin/protocol/aux_pow'
16
+ autoload :ScriptWitness, 'bitcoin/protocol/script_witness'
17
+ autoload :TxIn, 'bitcoin/protocol/txin'
18
+ autoload :TxOut, 'bitcoin/protocol/txout'
19
+ autoload :Tx, 'bitcoin/protocol/tx'
20
+ autoload :Block, 'bitcoin/protocol/block'
21
+ autoload :Addr, 'bitcoin/protocol/address'
22
+ autoload :Alert, 'bitcoin/protocol/alert'
23
+ autoload :Reject, 'bitcoin/protocol/reject'
24
+ autoload :Version, 'bitcoin/protocol/version'
25
+ autoload :AuxPow, 'bitcoin/protocol/aux_pow'
26
+ autoload :PartialMerkleTree, 'bitcoin/protocol/partial_merkle_tree'
25
27
 
26
28
  autoload :Handler, 'bitcoin/protocol/handler'
27
29
  autoload :Parser, 'bitcoin/protocol/parser'
@@ -141,7 +143,7 @@ module Bitcoin
141
143
  pkt("verack", "")
142
144
  end
143
145
 
144
- TypeLookup = Hash[:tx, 1, :block, 2, nil, 0]
146
+ TypeLookup = Hash[:tx, 1, :block, 2, :filtered_block, 3, nil, 0]
145
147
 
146
148
  def self.getdata_pkt(type, hashes)
147
149
  return if hashes.size > MAX_INV_SZ
@@ -158,7 +160,7 @@ module Bitcoin
158
160
  DEFAULT_STOP_HASH = "00"*32
159
161
 
160
162
  def self.locator_payload(version, locator_hashes, stop_hash)
161
- payload = [
163
+ [
162
164
  [version].pack("V"),
163
165
  pack_var_int(locator_hashes.size),
164
166
  locator_hashes.map{|l| l.htb_reverse }.join,
@@ -42,6 +42,8 @@ module Bitcoin
42
42
  # AuxPow linking the block to a merge-mined chain
43
43
  attr_accessor :aux_pow
44
44
 
45
+ attr_reader :partial_merkle_tree
46
+
45
47
  alias :transactions :tx
46
48
 
47
49
  # compare to another block
@@ -82,6 +84,24 @@ module Bitcoin
82
84
 
83
85
  return buf if buf.eof?
84
86
 
87
+ if header_only == :filtered
88
+ @tx_count = buf.read(4).unpack("V")[0]
89
+
90
+ nhashes = Protocol.unpack_var_int_from_io(buf)
91
+ hashes = []
92
+ nhashes.times do
93
+ hashes << buf.read(256 / 8)
94
+ end
95
+
96
+ nflags = Protocol.unpack_var_int_from_io(buf)
97
+ flags = buf.read(nflags)
98
+
99
+ @partial_merkle_tree = PartialMerkleTree.new(@tx_count, hashes, flags)
100
+ @partial_merkle_tree.set_value
101
+
102
+ return buf
103
+ end
104
+
85
105
  tx_size = Protocol.unpack_var_int_from_io(buf)
86
106
  @tx_count = tx_size
87
107
  return buf if header_only
@@ -109,12 +129,28 @@ module Bitcoin
109
129
  end
110
130
 
111
131
  def recalc_mrkl_root
112
- @mrkl_root = Bitcoin.hash_mrkl_tree( @tx.map(&:hash) ).last.htb_reverse
132
+ @mrkl_root = if partial_merkle_tree
133
+ partial_merkle_tree.root.value.htb_reverse
134
+ else
135
+ Bitcoin.hash_mrkl_tree( @tx.map(&:hash) ).last.htb_reverse
136
+ end
113
137
  end
114
138
 
115
139
  # verify mrkl tree
116
140
  def verify_mrkl_root
117
- @mrkl_root.reverse_hth == Bitcoin.hash_mrkl_tree( @tx.map(&:hash) ).last
141
+ if partial_merkle_tree
142
+ partial_merkle_tree.valid_tree?(@mrkl_root.reverse_hth)
143
+ else
144
+ @mrkl_root.reverse_hth == Bitcoin.hash_mrkl_tree( @tx.map(&:hash) ).last
145
+ end
146
+ end
147
+
148
+ def tx_hashes
149
+ if partial_merkle_tree
150
+ partial_merkle_tree.tx_hashes
151
+ else
152
+ @tx.map(&:hash)
153
+ end
118
154
  end
119
155
 
120
156
  # get the block header info
@@ -56,6 +56,13 @@ module Bitcoin
56
56
  @h.on_headers(headers)
57
57
  end
58
58
 
59
+ def parse_mrkle_block(payload)
60
+ return unless @h.respond_to?(:on_mrkle_block)
61
+ b = Block.new
62
+ b.parse_data_from_io(payload, header_only= :filtered)
63
+ @h.on_mrkle_block(b)
64
+ end
65
+
59
66
  def parse_getblocks(payload)
60
67
  version, payload = payload.unpack('Va*')
61
68
  count, payload = Protocol.unpack_var_int(payload)
@@ -86,6 +93,7 @@ module Bitcoin
86
93
  when 'getheaders'; @h.on_getheaders(*parse_getblocks(payload)) if @h.respond_to?(:on_getheaders)
87
94
  when 'mempool'; handle_mempool_request(payload)
88
95
  when 'notfound'; handle_notfound_reply(payload)
96
+ when 'merkleblock'; parse_mrkle_block(payload)
89
97
  when 'reject'; handle_reject(payload)
90
98
  else
91
99
  parse_error :unknown_packet, [command, payload.hth]
@@ -0,0 +1,61 @@
1
+ class Bitcoin::Protocol::PartialMerkleTree
2
+ Node = Struct.new(:value, :left, :right, :width_idx)
3
+
4
+ BIT_MASK = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80]
5
+
6
+ def initialize(total_txs, hashes, flags)
7
+ @total_txs, @flags = total_txs, flags
8
+ @hashes = hashes.map{|h| h.reverse_hth }
9
+ @visit_idx = 0
10
+ end
11
+
12
+ def tx_hashes
13
+ @leaves.reject{|n| n.value.nil? }.map{|n| n.value }
14
+ end
15
+
16
+ def build_tree
17
+ lay = @leaves = @total_txs.times.map{ Node.new(nil, nil, nil) }
18
+ while lay.size > 1
19
+ lay = lay.each_slice(2).map do |left, right|
20
+ Node.new(nil, left, right)
21
+ end
22
+ end
23
+ return lay[0]
24
+ end
25
+
26
+ def current_flag
27
+ @flags[@visit_idx / 8].ord & BIT_MASK[@visit_idx % 8] == 0
28
+ end
29
+
30
+ def root
31
+ @root ||= build_tree
32
+ end
33
+
34
+ def set_value(node = root)
35
+ if current_flag || (node.left.nil? && node.right.nil?)
36
+ node.value = @hashes.shift
37
+ return
38
+ end
39
+
40
+ if node.left
41
+ @visit_idx += 1
42
+ set_value(node.left)
43
+ end
44
+ if node.right
45
+ @visit_idx += 1
46
+ set_value(node.right)
47
+ end
48
+
49
+ right = node.right || node.left
50
+ node.value = Bitcoin.bitcoin_mrkl(node.left.value, right.value)
51
+
52
+ return
53
+ end
54
+
55
+ def valid_tree?(mrkl_root_hash)
56
+ return false unless @hashes.empty?
57
+ return false if ((@visit_idx + 1)/8.0).ceil != @flags.length
58
+ return false if mrkl_root_hash != root.value
59
+ return true
60
+ end
61
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: ascii-8bit
2
+
3
+ module Bitcoin
4
+
5
+ module Protocol
6
+
7
+ class ScriptWitness
8
+
9
+ # witness stack
10
+ attr_reader :stack
11
+
12
+ def initialize
13
+ @stack = []
14
+ end
15
+
16
+ # check empty
17
+ def empty?
18
+ stack.empty?
19
+ end
20
+
21
+ # output script in raw binary format
22
+ def to_payload
23
+ payload = Bitcoin::Protocol.pack_var_int(stack.size)
24
+ payload << stack.map{|e| Bitcoin::Protocol.pack_var_int(e.bytesize) << e }.join
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -7,6 +7,9 @@ module Bitcoin
7
7
 
8
8
  class Tx
9
9
 
10
+ MARKER = 0
11
+ FLAG = 1
12
+
10
13
  # transaction hash
11
14
  attr_reader :hash
12
15
 
@@ -28,6 +31,9 @@ module Bitcoin
28
31
  # parsed / evaluated input scripts cached for later use
29
32
  attr_reader :scripts
30
33
 
34
+ attr_accessor :marker
35
+ attr_accessor :flag
36
+
31
37
  alias :inputs :in
32
38
  alias :outputs :out
33
39
 
@@ -63,13 +69,22 @@ module Bitcoin
63
69
  # parse raw binary data
64
70
  def parse_data_from_io(data)
65
71
  buf = data.is_a?(String) ? StringIO.new(data) : data
66
- payload_start = buf.pos
67
72
 
68
73
  @ver = buf.read(4).unpack("V")[0]
69
74
 
70
75
  return false if buf.eof?
71
76
 
72
77
  in_size = Protocol.unpack_var_int_from_io(buf)
78
+
79
+ # segwit serialization format is defined by https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki
80
+ witness = false
81
+ if in_size.zero?
82
+ @marker = 0
83
+ @flag = buf.read(1).unpack('c').first
84
+ in_size = Protocol.unpack_var_int_from_io(buf)
85
+ witness = true
86
+ end
87
+
73
88
  @in = []
74
89
  in_size.times{
75
90
  break if buf.eof?
@@ -87,12 +102,19 @@ module Bitcoin
87
102
 
88
103
  return false if buf.eof?
89
104
 
105
+ if witness
106
+ in_size.times do |i|
107
+ witness_count = Protocol.unpack_var_int_from_io(buf)
108
+ witness_count.times do
109
+ size = Protocol.unpack_var_int_from_io(buf)
110
+ @in[i].script_witness.stack << buf.read(size)
111
+ end
112
+ end
113
+ end
114
+
90
115
  @lock_time = buf.read(4).unpack("V")[0]
91
116
 
92
- payload_end = buf.pos;
93
- buf.seek(payload_start)
94
- @payload = buf.read( payload_end-payload_start )
95
- @hash = hash_from_payload(@payload)
117
+ @hash = hash_from_payload(to_old_payload)
96
118
 
97
119
  if buf.eof?
98
120
  true
@@ -105,6 +127,10 @@ module Bitcoin
105
127
 
106
128
  # output transaction in raw binary format
107
129
  def to_payload
130
+ witness? ? to_witness_payload : to_old_payload
131
+ end
132
+
133
+ def to_old_payload
108
134
  pin = ""
109
135
  @in.each{|input| pin << input.to_payload }
110
136
  pout = ""
@@ -113,6 +139,22 @@ module Bitcoin
113
139
  [@ver].pack("V") << Protocol.pack_var_int(@in.size) << pin << Protocol.pack_var_int(@out.size) << pout << [@lock_time].pack("V")
114
140
  end
115
141
 
142
+ # output transaction in raw binary format with witness
143
+ def to_witness_payload
144
+ buf = [@ver, MARKER, FLAG].pack('Vcc')
145
+ buf << Protocol.pack_var_int(@in.length) << @in.map(&:to_payload).join
146
+ buf << Protocol.pack_var_int(@out.length) << @out.map(&:to_payload).join
147
+ buf << witness_payload << [@lock_time].pack('V')
148
+ buf
149
+ end
150
+
151
+ def witness_payload
152
+ @in.map { |i| i.script_witness.to_payload }.join
153
+ end
154
+
155
+ def witness?
156
+ !@in.find { |i| !i.script_witness.empty? }.nil?
157
+ end
116
158
 
117
159
  SIGHASH_TYPE = { all: 1, none: 2, single: 3, anyonecanpay: 128 }
118
160
 
@@ -170,6 +212,50 @@ module Bitcoin
170
212
  Digest::SHA256.digest( Digest::SHA256.digest( buf ) )
171
213
  end
172
214
 
215
+ # generate a witness signature hash for input +input_idx+.
216
+ # https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
217
+ def signature_hash_for_witness_input(input_idx, witness_program, prev_out_value, witness_script = nil, hash_type=nil, skip_separator_index = 0)
218
+ return "\x01".ljust(32, "\x00") if input_idx >= @in.size # ERROR: SignatureHash() : input_idx=%d out of range
219
+
220
+ hash_type ||= SIGHASH_TYPE[:all]
221
+
222
+ script = Bitcoin::Script.new(witness_program)
223
+ raise "ScriptPubkey does not contain witness program." unless script.is_witness?
224
+
225
+ hash_prevouts = Digest::SHA256.digest(Digest::SHA256.digest(@in.map{|i| [i.prev_out_hash, i.prev_out_index].pack("a32V")}.join))
226
+ hash_sequence = Digest::SHA256.digest(Digest::SHA256.digest(@in.map{|i|i.sequence}.join))
227
+ outpoint = [@in[input_idx].prev_out_hash, @in[input_idx].prev_out_index].pack("a32V")
228
+ amount = [prev_out_value].pack("Q")
229
+ nsequence = @in[input_idx].sequence
230
+
231
+ if script.is_witness_v0_keyhash?
232
+ script_code = [["1976a914", script.get_hash160, "88ac"].join].pack("H*")
233
+ elsif script.is_witness_v0_scripthash?
234
+ raise "witness script does not match script pubkey" unless Bitcoin::Script.to_witness_p2sh_script(Digest::SHA256.digest(witness_script).bth) == witness_program
235
+ script = skip_separator_index > 0 ? Bitcoin::Script.new(witness_script).subscript_codeseparator(skip_separator_index) : witness_script
236
+ script_code = Bitcoin::Protocol.pack_var_string(script)
237
+ end
238
+
239
+ hash_outputs = Digest::SHA256.digest(Digest::SHA256.digest(@out.map{|o|o.to_payload}.join))
240
+
241
+ case (hash_type & 0x1f)
242
+ when SIGHASH_TYPE[:single]
243
+ hash_outputs = input_idx >= @out.size ? "\x00".ljust(32, "\x00") : Digest::SHA256.digest(Digest::SHA256.digest(@out[input_idx].to_payload))
244
+ hash_sequence = "\x00".ljust(32, "\x00")
245
+ when SIGHASH_TYPE[:none]
246
+ hash_sequence = hash_outputs = "\x00".ljust(32, "\x00")
247
+ end
248
+
249
+ if (hash_type & SIGHASH_TYPE[:anyonecanpay]) != 0
250
+ hash_prevouts = hash_sequence ="\x00".ljust(32, "\x00")
251
+ end
252
+
253
+ buf = [ [@ver].pack("V"), hash_prevouts, hash_sequence, outpoint,
254
+ script_code, amount, nsequence, hash_outputs, [@lock_time, hash_type].pack("VV")].join
255
+
256
+ Digest::SHA256.digest( Digest::SHA256.digest( buf ) )
257
+ end
258
+
173
259
  # verify input signature +in_idx+ against the corresponding
174
260
  # output in +outpoint_tx+
175
261
  # outpoint
@@ -204,6 +290,63 @@ module Bitcoin
204
290
  return sig_valid
205
291
  end
206
292
 
293
+ # verify witness input signature +in_idx+ against the corresponding
294
+ # output in +outpoint_tx+
295
+ # outpoint
296
+ #
297
+ # options are: verify_sigpushonly, verify_minimaldata, verify_cleanstack, verify_dersig, verify_low_s, verify_strictenc
298
+ def verify_witness_input_signature(in_idx, outpoint_tx_or_script, prev_out_amount, block_timestamp=Time.now.to_i, opts={})
299
+ if @enable_bitcoinconsensus
300
+ return bitcoinconsensus_verify_script(in_idx, outpoint_tx_or_script, block_timestamp, opts)
301
+ end
302
+
303
+ outpoint_idx = @in[in_idx].prev_out_index
304
+ script_sig = ''
305
+
306
+ # If given an entire previous transaction, take the script from it
307
+ script_pubkey = if outpoint_tx_or_script.respond_to?(:out)
308
+ Bitcoin::Script.new(outpoint_tx_or_script.out[outpoint_idx].pk_script)
309
+ else
310
+ # Otherwise, it's already a script.
311
+ Bitcoin::Script.new(outpoint_tx_or_script)
312
+ end
313
+
314
+ if script_pubkey.is_p2sh?
315
+ redeem_script = Bitcoin::Script.new(@in[in_idx].script_sig).get_pubkey
316
+ script_pubkey = Bitcoin::Script.new(redeem_script.htb) if Bitcoin.hash160(redeem_script) == script_pubkey.get_hash160 # P2SH-P2WPKH or P2SH-P2WSH
317
+ end
318
+
319
+ @in[in_idx].script_witness.stack.each{|s|script_sig << Bitcoin::Script.pack_pushdata(s)}
320
+ code_separator_index = 0
321
+
322
+ if script_pubkey.is_witness_v0_keyhash? # P2WPKH
323
+ @scripts[in_idx] = Bitcoin::Script.new(script_sig, Bitcoin::Script.to_hash160_script(script_pubkey.get_hash160))
324
+ elsif script_pubkey.is_witness_v0_scripthash? # P2WSH
325
+ witness_hex = @in[in_idx].script_witness.stack.last.bth
326
+ witness_script = Bitcoin::Script.new(witness_hex.htb)
327
+ return false unless Bitcoin.sha256(witness_hex) == script_pubkey.get_hash160
328
+ @scripts[in_idx] = Bitcoin::Script.new(script_sig, Bitcoin::Script.to_p2sh_script(Bitcoin.hash160(witness_hex)))
329
+ else
330
+ return false
331
+ end
332
+
333
+ return false if opts[:verify_sigpushonly] && !@scripts[in_idx].is_push_only?(script_sig)
334
+ return false if opts[:verify_minimaldata] && !@scripts[in_idx].pushes_are_canonical?
335
+ sig_valid = @scripts[in_idx].run(block_timestamp, opts) do |pubkey,sig,hash_type,subscript|
336
+ if script_pubkey.is_witness_v0_keyhash?
337
+ hash = signature_hash_for_witness_input(in_idx, script_pubkey.to_payload, prev_out_amount, nil, hash_type)
338
+ elsif script_pubkey.is_witness_v0_scripthash?
339
+ hash = signature_hash_for_witness_input(in_idx, script_pubkey.to_payload, prev_out_amount, witness_hex.htb, hash_type, code_separator_index)
340
+ code_separator_index += 1 if witness_script.codeseparator_count > code_separator_index
341
+ end
342
+ Bitcoin.verify_signature( hash, sig, pubkey.unpack("H*")[0] )
343
+ end
344
+ # BIP62 rule #6
345
+ return false if opts[:verify_cleanstack] && !@scripts[in_idx].stack.empty?
346
+
347
+ return sig_valid
348
+ end
349
+
207
350
  def bitcoinconsensus_verify_script(in_idx, outpoint_tx_or_script, block_timestamp=Time.now.to_i, opts={})
208
351
  raise "Bitcoin::BitcoinConsensus shared library not found" unless Bitcoin::BitcoinConsensus.lib_available?
209
352
 
@@ -229,12 +372,12 @@ module Bitcoin
229
372
 
230
373
  # convert to ruby hash (see also #from_hash)
231
374
  def to_hash(options = {})
232
- @hash ||= hash_from_payload(to_payload)
375
+ @hash ||= hash_from_payload(to_old_payload)
233
376
  h = {
234
377
  'hash' => @hash, 'ver' => @ver, # 'nid' => normalized_hash,
235
378
  'vin_sz' => @in.size, 'vout_sz' => @out.size,
236
379
  'lock_time' => @lock_time, 'size' => (@payload ||= to_payload).bytesize,
237
- 'in' => @in.map{|i| i.to_hash(options) },
380
+ 'in' => @in.map{|i|i.to_hash(options)},
238
381
  'out' => @out.map{|o| o.to_hash(options) }
239
382
  }
240
383
  h['nid'] = normalized_hash if options[:with_nid]
@@ -258,9 +401,11 @@ module Bitcoin
258
401
  tx.ver, tx.lock_time = (h['ver'] || h['version']), h['lock_time']
259
402
  ins = h['in'] || h['inputs']
260
403
  outs = h['out'] || h['outputs']
261
- ins .each{|input| tx.add_in TxIn.from_hash(input) }
404
+ ins .each{|input|
405
+ tx.add_in(TxIn.from_hash(input))
406
+ }
262
407
  outs.each{|output| tx.add_out TxOut.from_hash(output) }
263
- tx.instance_eval{ @hash = hash_from_payload(@payload = to_payload) }
408
+ tx.instance_eval{ @hash = hash_from_payload(@payload = to_old_payload) }
264
409
  if h['hash'] && (h['hash'] != tx.hash)
265
410
  raise "Tx hash mismatch! Claimed: #{h['hash']}, Actual: #{tx.hash}" if do_raise
266
411
  end
@@ -268,7 +413,10 @@ module Bitcoin
268
413
  end
269
414
 
270
415
  # convert ruby hash to raw binary
271
- def self.binary_from_hash(h); from_hash(h).to_payload; end
416
+ def self.binary_from_hash(h)
417
+ tx = from_hash(h)
418
+ tx.to_payload
419
+ end
272
420
 
273
421
  # parse json representation
274
422
  def self.from_json(json_string); from_hash( JSON.load(json_string) ); end
@@ -367,6 +515,18 @@ module Bitcoin
367
515
  end
368
516
  alias :nhash :normalized_hash
369
517
 
518
+ # get witness hash
519
+ def witness_hash
520
+ hash_from_payload(to_witness_payload)
521
+ end
522
+
523
+ # sort transaction inputs and outputs under BIP 69
524
+ # https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki
525
+ def lexicographical_sort!
526
+ inputs.sort_by!{|i| [i.previous_output, i.prev_out_index]}
527
+ outputs.sort_by!{|o| [o.amount, o.pk_script.bth]}
528
+ end
529
+
370
530
  end
371
531
  end
372
532
  end