bitcoin-ruby 0.0.10 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
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