Class: Bitcoin::Protocol::Tx

Inherits:
Object
  • Object
show all
Defined in:
lib/bitcoin/protocol/tx.rb

Overview

Constant Summary collapse

MARKER =
0
FLAG =
1
SIGHASH_TYPE =
Script::SIGHASH_TYPE
DEFAULT_BLOCK_PRIORITY_SIZE =
27_000

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data = nil) ⇒ Tx

create tx from raw binary data


52
53
54
55
56
57
58
59
60
# File 'lib/bitcoin/protocol/tx.rb', line 52

def initialize(data = nil)
  @ver = 1
  @lock_time = 0
  @in = []
  @out = []
  @scripts = []
  @enable_bitcoinconsensus = !ENV['USE_BITCOINCONSENSUS'].nil?
  parse_data_from_io(data) if data
end

Instance Attribute Details

#flagObject

Returns the value of attribute flag


36
37
38
# File 'lib/bitcoin/protocol/tx.rb', line 36

def flag
  @flag
end

#hashObject (readonly)

transaction hash


15
16
17
# File 'lib/bitcoin/protocol/tx.rb', line 15

def hash
  @hash
end

#inObject (readonly) Also known as: inputs

inputs (Array of TxIn)


18
19
20
# File 'lib/bitcoin/protocol/tx.rb', line 18

def in
  @in
end

#lock_timeObject

lock time


30
31
32
# File 'lib/bitcoin/protocol/tx.rb', line 30

def lock_time
  @lock_time
end

#markerObject

Returns the value of attribute marker


35
36
37
# File 'lib/bitcoin/protocol/tx.rb', line 35

def marker
  @marker
end

#outObject (readonly) Also known as: outputs

outputs (Array of TxOut)


21
22
23
# File 'lib/bitcoin/protocol/tx.rb', line 21

def out
  @out
end

#payloadObject (readonly)

raw protocol payload


24
25
26
# File 'lib/bitcoin/protocol/tx.rb', line 24

def payload
  @payload
end

#scriptsObject (readonly)

parsed / evaluated input scripts cached for later use


33
34
35
# File 'lib/bitcoin/protocol/tx.rb', line 33

def scripts
  @scripts
end

#verObject

version (usually 1)


27
28
29
# File 'lib/bitcoin/protocol/tx.rb', line 27

def ver
  @ver
end

Class Method Details

.binary_from_hash(h) ⇒ Object

convert ruby hash to raw binary


485
486
487
488
# File 'lib/bitcoin/protocol/tx.rb', line 485

def self.binary_from_hash(h)
  tx = from_hash(h)
  tx.to_payload
end

.binary_from_json(json_string) ⇒ Object

convert json representation to raw binary


496
497
498
# File 'lib/bitcoin/protocol/tx.rb', line 496

def self.binary_from_json(json_string)
  from_json(json_string).to_payload
end

.from_file(path) ⇒ Object

read binary block from a file


501
502
503
# File 'lib/bitcoin/protocol/tx.rb', line 501

def self.from_file(path)
  new(Bitcoin::Protocol.read_binary_file(path))
end

.from_hash(h, do_raise = true) ⇒ Object

parse ruby hash (see also #to_hash)


466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
# File 'lib/bitcoin/protocol/tx.rb', line 466

def self.from_hash(h, do_raise = true)
  tx = new(nil)
  tx.ver = (h['ver'] || h['version'])
  tx.lock_time = h['lock_time']
  ins  = h['in']  || h['inputs']
  outs = h['out'] || h['outputs']
  ins.each { |input| tx.add_in(TxIn.from_hash(input)) }
  outs.each { |output| tx.add_out TxOut.from_hash(output) }
  tx.instance_eval do
    @hash = hash_from_payload(to_old_payload)
    @payload = to_payload
  end
  if h['hash'] && (h['hash'] != tx.hash)
    raise "Tx hash mismatch! Claimed: #{h['hash']}, Actual: #{tx.hash}" if do_raise
  end
  tx
end

.from_json(json_string) ⇒ Object

parse json representation


491
492
493
# File 'lib/bitcoin/protocol/tx.rb', line 491

def self.from_json(json_string)
  from_hash(JSON.parse(json_string))
end

.from_json_file(path) ⇒ Object

read json block from a file


506
507
508
# File 'lib/bitcoin/protocol/tx.rb', line 506

def self.from_json_file(path)
  from_json(Bitcoin::Protocol.read_binary_file(path))
end

Instance Method Details

#==(other) ⇒ Object

compare to another tx


42
43
44
# File 'lib/bitcoin/protocol/tx.rb', line 42

def ==(other)
  @hash == other.hash
end

#add_in(input) ⇒ Object

add an input


74
75
76
# File 'lib/bitcoin/protocol/tx.rb', line 74

def add_in(input)
  (@in ||= []) << input
end

#add_out(output) ⇒ Object

add an output


79
80
81
# File 'lib/bitcoin/protocol/tx.rb', line 79

def add_out(output)
  (@out ||= []) << output
end

#binary_hashObject

return the tx hash in binary format


47
48
49
# File 'lib/bitcoin/protocol/tx.rb', line 47

def binary_hash
  @binary_hash ||= [@hash].pack('H*').reverse
end

#bitcoinconsensus_verify_script(in_idx, outpoint_data, block_timestamp = Time.now.to_i, opts = {}) ⇒ Object

rubocop:enable CyclomaticComplexity,PerceivedComplexity


420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
# File 'lib/bitcoin/protocol/tx.rb', line 420

def bitcoinconsensus_verify_script(
  in_idx, outpoint_data, block_timestamp = Time.now.to_i, opts = {}
)
  consensus_available = Bitcoin::BitcoinConsensus.lib_available?
  raise 'Bitcoin::BitcoinConsensus shared library not found' unless consensus_available

  outpoint_idx  = @in[in_idx].prev_out_index
  script_pubkey = script_pubkey_from_outpoint_data(outpoint_data, outpoint_idx)

  flags  = Bitcoin::BitcoinConsensus::SCRIPT_VERIFY_NONE
  flags |= Bitcoin::BitcoinConsensus::SCRIPT_VERIFY_P2SH if block_timestamp >= 1_333_238_400
  flags |= Bitcoin::BitcoinConsensus::SCRIPT_VERIFY_SIGPUSHONLY if opts[:verify_sigpushonly]
  flags |= Bitcoin::BitcoinConsensus::SCRIPT_VERIFY_MINIMALDATA if opts[:verify_minimaldata]
  flags |= Bitcoin::BitcoinConsensus::SCRIPT_VERIFY_CLEANSTACK if opts[:verify_cleanstack]
  flags |= Bitcoin::BitcoinConsensus::SCRIPT_VERIFY_LOW_S if opts[:verify_low_s]

  payload ||= to_payload
  Bitcoin::BitcoinConsensus.verify_script(in_idx, script_pubkey, payload, flags)
end

#calculate_minimum_fee(allow_free = true, mode = :block) ⇒ Object

rubocop:disable PerceivedComplexity


562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
# File 'lib/bitcoin/protocol/tx.rb', line 562

def calculate_minimum_fee(allow_free = true, mode = :block)
  # Base fee is either nMinTxFee or nMinRelayTxFee
  base_fee = if mode == :relay
               Bitcoin.network[:min_relay_tx_fee]
             else
               Bitcoin.network[:min_tx_fee]
             end
  tx_size = to_payload.bytesize
  min_fee = (1 + tx_size / 1_000) * base_fee

  if allow_free
    # There is a free transaction area in blocks created by most miners,
    # * If we are relaying we allow transactions up to DEFAULT_BLOCK_PRIORITY_SIZE - 1000
    #   to be considered to fall into this category. We don't want to encourage sending
    #   multiple transactions instead of one big transaction to avoid fees.
    # * If we are creating a transaction we allow transactions up to 1,000 bytes
    #   to be considered safe and assume they can likely make it into this section.
    min_free_size = if mode == :block
                      Bitcoin.network[:free_tx_bytes]
                    else
                      DEFAULT_BLOCK_PRIORITY_SIZE - 1_000
                    end
    min_fee = 0 if tx_size < min_free_size
  end

  # This code can be removed after enough miners have upgraded to version 0.9.
  # Until then, be safe when sending and require a fee if any output is less than CENT
  if min_fee < base_fee && mode == :block
    outputs.each do |output|
      if output.value < Bitcoin.network[:dust]
        # If per dust fee, then we add min fee for each output less than dust.
        # Otherwise, we set to min fee if there is any output less than dust.
        if Bitcoin.network[:per_dust_fee]
          min_fee += base_fee
        else
          min_fee = base_fee
          break
        end
      end
    end
  end

  min_fee = Bitcoin.network[:max_money] unless min_fee.between?(
    0, Bitcoin.network[:max_money]
  )
  min_fee
end

#coinbase?Boolean

Returns:

  • (Boolean)

616
617
618
# File 'lib/bitcoin/protocol/tx.rb', line 616

def coinbase?
  inputs.size == 1 && inputs.first.coinbase?
end

#final?(block_height, block_time) ⇒ Boolean

Checks if transaction is final taking into account height and time of a block in which it is located (or about to be included if it's unconfirmed tx).

Returns:

  • (Boolean)

521
522
523
524
525
526
527
528
529
530
531
532
533
534
# File 'lib/bitcoin/protocol/tx.rb', line 521

def final?(block_height, block_time)
  # No time lock - tx is final.
  return true if lock_time.zero?

  # Time based nLockTime implemented in 0.1.6
  # If lock_time is below the magic threshold treat it as a block height.
  # If lock_time is above the threshold, it's a unix timestamp.
  lock_threshold = lock_time < Bitcoin::LOCKTIME_THRESHOLD ? block_height : block_time
  return true if lock_time < lock_threshold

  inputs.each { |input| return false unless input.final? }

  true
end

#hash_from_payload(payload) ⇒ Object Also known as: generate_hash

generate the tx hash for given payload in hex format


63
64
65
# File 'lib/bitcoin/protocol/tx.rb', line 63

def hash_from_payload(payload)
  Digest::SHA256.digest(Digest::SHA256.digest(payload)).reverse_hth
end

#is_coinbase?Boolean

rubocop:enable PerceivedComplexity

Returns:

  • (Boolean)

611
612
613
614
# File 'lib/bitcoin/protocol/tx.rb', line 611

def is_coinbase? # rubocop:disable Naming/PredicateName
  warn '[DEPRECATION] `Tx.is_coinbase?` is deprecated. Use `coinbase?` instead.'
  coinbase?
end

#is_final?(block_height, block_time) ⇒ Boolean

rubocop:disable Naming/PredicateName

Returns:

  • (Boolean)

514
515
516
517
# File 'lib/bitcoin/protocol/tx.rb', line 514

def is_final?(block_height, block_time) # rubocop:disable Naming/PredicateName
  warn '[DEPRECATION] `Tx.is_final?` is deprecated. Use `final?` instead.'
  final?(block_height, block_time)
end

#legacy_sigops_countObject


536
537
538
539
540
541
542
543
544
545
546
547
548
549
# File 'lib/bitcoin/protocol/tx.rb', line 536

def legacy_sigops_count
  # Note: input scripts normally never have any opcodes since every input script
  # can be statically reduced to a pushdata-only script.
  # However, anyone is allowed to create a non-standard transaction
  # with any opcodes in the inputs.
  count = 0
  self.in.each do |txin|
    count += Bitcoin::Script.new(txin.script_sig).sigops_count_accurate(false)
  end
  out.each do |txout|
    count += Bitcoin::Script.new(txout.pk_script).sigops_count_accurate(false)
  end
  count
end

#lexicographical_sort!Object

sort transaction inputs and outputs under BIP 69 github.com/bitcoin/bips/blob/master/bip-0069.mediawiki


633
634
635
636
# File 'lib/bitcoin/protocol/tx.rb', line 633

def lexicographical_sort!
  inputs.sort_by! { |i| [i.previous_output, i.prev_out_index] }
  outputs.sort_by! { |o| [o.amount, o.pk_script.bth] }
end

#minimum_block_feeObject


557
558
559
# File 'lib/bitcoin/protocol/tx.rb', line 557

def minimum_block_fee
  calculate_minimum_fee(true, :block)
end

#minimum_relay_feeObject


553
554
555
# File 'lib/bitcoin/protocol/tx.rb', line 553

def minimum_relay_fee
  calculate_minimum_fee(true, :relay)
end

#normalized_hashObject Also known as: nhash


620
621
622
# File 'lib/bitcoin/protocol/tx.rb', line 620

def normalized_hash
  signature_hash_for_input(-1, nil, SIGHASH_TYPE[:all]).reverse.hth
end

#parse_data_from_io(data) ⇒ Object Also known as: parse_data

parse raw binary data rubocop:disable CyclomaticComplexity,PerceivedComplexity


85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/bitcoin/protocol/tx.rb', line 85

def parse_data_from_io(data)
  buf = data.is_a?(String) ? StringIO.new(data) : data

  @ver = buf.read(4).unpack('V')[0]

  return false if buf.eof?

  # segwit serialization format is defined by https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki
  # Also note that it is impossible to parse 0 input transactions. Regular transactions
  # with 0 inputs look like malformed segwit transactions.
  @marker = buf.read(1).unpack('c').first
  @flag = buf.read(1).unpack('c').first

  witness = @marker.zero? && !@flag.zero?

  # Non-segwit format does not contain marker or flag fields.
  buf.seek(buf.pos - 2) unless witness

  in_size = Protocol.unpack_var_int_from_io(buf)

  @in = []
  in_size.times do
    break if buf.eof?
    @in << TxIn.from_io(buf)
  end

  return false if buf.eof?

  out_size = Protocol.unpack_var_int_from_io(buf)
  @out = []
  out_size.times do
    break if buf.eof?
    @out << TxOut.from_io(buf)
  end

  return false if buf.eof?

  if witness
    in_size.times do |i|
      witness_count = Protocol.unpack_var_int_from_io(buf)
      witness_count.times do
        size = Protocol.unpack_var_int_from_io(buf)
        @in[i].script_witness.stack << buf.read(size)
      end
    end
  end

  @lock_time = buf.read(4).unpack('V')[0]

  @hash = hash_from_payload(to_old_payload)
  @payload = to_payload

  if buf.eof?
    true
  else
    data.is_a?(StringIO) ? buf : buf.read
  end
end

#refresh_hashObject

refresh_hash recalculates the tx hash and sets it on the instance


69
70
71
# File 'lib/bitcoin/protocol/tx.rb', line 69

def refresh_hash
  @hash = generate_hash(to_old_payload)
end

#signature_hash_for_input(input_idx, subscript, hash_type = nil, prev_out_value = nil, fork_id = nil) ⇒ Object

generate a signature hash for input input_idx. either pass the outpoint_tx or the script_pubkey directly. rubocop:disable CyclomaticComplexity,PerceivedComplexity


183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/bitcoin/protocol/tx.rb', line 183

def signature_hash_for_input(
  input_idx, subscript, hash_type = nil, prev_out_value = nil, fork_id = nil
)
  # https://github.com/bitcoin/bitcoin/blob/e071a3f6c06f41068ad17134189a4ac3073ef76b/script.cpp#L834
  # http://code.google.com/p/bitcoinj/source/browse/trunk/src/com/google/bitcoin/core/Script.java#318
  # https://en.bitcoin.it/wiki/OP_CHECKSIG#How_it_works
  # https://github.com/bitcoin/bitcoin/blob/c2e8c8acd8ae0c94c70b59f55169841ad195bb99/src/script.cpp#L1058
  # https://en.bitcoin.it/wiki/OP_CHECKSIG

  hash_type ||= SIGHASH_TYPE[:all]

  # fork_id is optional and if set, SIGHASH_FORKID flag as defined by the
  # Bitcoin Cash protocol will be respected.
  #
  # https://github.com/Bitcoin-ABC/bitcoin-abc/blob/master/doc/abc/replay-protected-sighash.md
  if fork_id && (hash_type & SIGHASH_TYPE[:forkid]) != 0
    raise 'SIGHASH_FORKID is enabled, so prev_out_value is required' if prev_out_value.nil?

    # According to the spec, we should modify the sighash by replacing the 24 most significant
    # bits with the fork ID. However, Bitcoin ABC does not currently implement this since the
    # fork_id is an implicit 0 and it would make the sighash JSON tests fail. Will leave as a
    # TODO for now.
    raise NotImplementedError, 'fork_id must be 0' unless fork_id.zero?

    script_code = Bitcoin::Protocol.pack_var_string(subscript)
    return signature_hash_for_input_bip143(input_idx, script_code, prev_out_value, hash_type)
  end

  # Note: BitcoinQT checks if input_idx >= @in.size and returns 1 with an error message.
  # But this check is never actually useful because BitcoinQT would crash right before
  # VerifyScript if input index is out of bounds (inside CScriptCheck::operator()()).
  # That's why we don't need to do such a check here.
  #
  # However, if you look at the case SIGHASH_TYPE[:single] below, we must return 1
  # because it's possible to have more inputs than outputs and BitcoinQT returns 1 as well.

  # ERROR: SignatureHash() : input_idx=%d out of range
  return "\x01".ljust(32, "\x00") if input_idx >= @in.size

  pin = @in.map.with_index do |input, idx|
    if idx == input_idx
      # legacy api (outpoint_tx)
      subscript = subscript.out[input.prev_out_index].script if subscript.respond_to?(:out)

      # Remove all instances of OP_CODESEPARATOR from the script.
      parsed_subscript = Script.new(subscript)
      parsed_subscript.chunks.delete(Script::OP_CODESEPARATOR)
      subscript = parsed_subscript.to_binary

      input.to_payload(subscript)
    else
      case (hash_type & 0x1f)
      when SIGHASH_TYPE[:none] then   input.to_payload('', "\x00\x00\x00\x00")
      when SIGHASH_TYPE[:single] then input.to_payload('', "\x00\x00\x00\x00")
      else; input.to_payload('')
      end
    end
  end

  pout = @out.map(&:to_payload)
  in_size = Protocol.pack_var_int(@in.size)
  out_size = Protocol.pack_var_int(@out.size)

  case (hash_type & 0x1f)
  when SIGHASH_TYPE[:none]
    pout = ''
    out_size = Protocol.pack_var_int(0)
  when SIGHASH_TYPE[:single]
    # ERROR: SignatureHash() : input_idx=%d out of range (SIGHASH_SINGLE)
    return "\x01".ljust(32, "\x00") if input_idx >= @out.size

    pout = @out[0...(input_idx + 1)].map.with_index do |out, idx|
      idx == input_idx ? out.to_payload : out.to_null_payload
    end.join

    out_size = Protocol.pack_var_int(input_idx + 1)
  end

  if (hash_type & SIGHASH_TYPE[:anyonecanpay]) != 0
    in_size = Protocol.pack_var_int(1)
    pin = [pin[input_idx]]
  end

  buf = [
    [@ver].pack('V'), in_size, pin, out_size,
    pout, [@lock_time, hash_type].pack('VV')
  ].join
  Digest::SHA256.digest(Digest::SHA256.digest(buf))
end

#signature_hash_for_witness_input(input_idx, witness_program, prev_out_value, witness_script = nil, hash_type = nil, skip_separator_index = 0) ⇒ Object

generate a witness signature hash for input input_idx. github.com/bitcoin/bips/blob/master/bip-0143.mediawiki rubocop:disable Metrics/ParameterLists


277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/bitcoin/protocol/tx.rb', line 277

def signature_hash_for_witness_input(
  input_idx, witness_program, prev_out_value,
  witness_script = nil, hash_type = nil, skip_separator_index = 0
)
  # ERROR: SignatureHash() : input_idx=%d out of range
  return "\x01".ljust(32, "\x00") if input_idx >= @in.size

  hash_type ||= SIGHASH_TYPE[:all]
  script = Bitcoin::Script.new(witness_program)
  raise 'ScriptPubkey does not contain witness program.' unless script.is_witness?

  if script.is_witness_v0_keyhash?
    script_code = [['1976a914', script.get_hash160, '88ac'].join].pack('H*')
  elsif script.is_witness_v0_scripthash?
    witness_pubkey_script_match = Bitcoin::Script.to_witness_p2sh_script(
      Digest::SHA256.digest(witness_script).bth
    ) == witness_program
    raise 'witness script does not match script pubkey' unless witness_pubkey_script_match

    script = if skip_separator_index > 0
               s = Bitcoin::Script.new(witness_script)
               s.subscript_codeseparator(skip_separator_index)
             else
               witness_script
             end
    script_code = Bitcoin::Protocol.pack_var_string(script)
  end

  signature_hash_for_input_bip143(input_idx, script_code, prev_out_value, hash_type)
end

#sizeObject


510
511
512
# File 'lib/bitcoin/protocol/tx.rb', line 510

def size
  payload.bytesize
end

#to_hash(options = {}) ⇒ Object

convert to ruby hash (see also #from_hash)


441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/bitcoin/protocol/tx.rb', line 441

def to_hash(options = {})
  @hash ||= hash_from_payload(to_old_payload)
  h = {
    'hash' => @hash, 'ver' => @ver, # 'nid' => normalized_hash,
    'vin_sz' => @in.size, 'vout_sz' => @out.size,
    'lock_time' => @lock_time, 'size' => (@payload ||= to_payload).bytesize,
    'in'  =>  @in.map { |i| i.to_hash(options) },
    'out' => @out.map { |o| o.to_hash(options) }
  }
  h['nid'] = normalized_hash if options[:with_nid]
  h
end

#to_json(options = { space: '' }, *_a) ⇒ Object

generates rawblock json as seen in the block explorer.


455
456
457
# File 'lib/bitcoin/protocol/tx.rb', line 455

def to_json(options = { space: '' }, *_a)
  JSON.pretty_generate(to_hash(options), options)
end

#to_json_file(path) ⇒ Object

write json representation to a file (see also #to_json)


461
462
463
# File 'lib/bitcoin/protocol/tx.rb', line 461

def to_json_file(path)
  File.open(path, 'wb') { |f| f.print to_json; }
end

#to_old_payloadObject


152
153
154
155
156
157
158
159
160
161
# File 'lib/bitcoin/protocol/tx.rb', line 152

def to_old_payload
  pin = ''
  @in.each { |input| pin << input.to_payload }
  pout = ''
  @out.each { |output| pout << output.to_payload }

  [@ver].pack('V') << Protocol.pack_var_int(@in.size) \
                   << pin << Protocol.pack_var_int(@out.size) \
                   << pout << [@lock_time].pack('V')
end

#to_payloadObject

output transaction in raw binary format


148
149
150
# File 'lib/bitcoin/protocol/tx.rb', line 148

def to_payload
  witness? ? to_witness_payload : to_old_payload
end

#to_witness_payloadObject

output transaction in raw binary format with witness


164
165
166
167
168
169
170
# File 'lib/bitcoin/protocol/tx.rb', line 164

def to_witness_payload
  buf = [@ver, MARKER, FLAG].pack('Vcc')
  buf << Protocol.pack_var_int(@in.length) << @in.map(&:to_payload).join
  buf << Protocol.pack_var_int(@out.length) << @out.map(&:to_payload).join
  buf << witness_payload << [@lock_time].pack('V')
  buf
end

#verify_input_signature(in_idx, outpoint_data, block_timestamp = Time.now.to_i, opts = {}) ⇒ Object

verify input signature in_idx against the corresponding output in outpoint_tx outpoint. This arg can also be a Script or TxOut.

options are: verify_sigpushonly, verify_minimaldata, verify_cleanstack,

verify_dersig, verify_low_s, verify_strictenc, fork_id

315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/bitcoin/protocol/tx.rb', line 315

def verify_input_signature(in_idx, outpoint_data, block_timestamp = Time.now.to_i, opts = {})
  if @enable_bitcoinconsensus
    return bitcoinconsensus_verify_script(in_idx, outpoint_data, block_timestamp, opts)
  end

  # If FORKID is enabled, we also ensure strict encoding.
  opts[:verify_strictenc] ||= !opts[:fork_id].nil?

  outpoint_idx  = @in[in_idx].prev_out_index
  script_sig    = @in[in_idx].script_sig

  amount = amount_from_outpoint_data(outpoint_data, outpoint_idx)
  script_pubkey = script_pubkey_from_outpoint_data(outpoint_data, outpoint_idx)

  if opts[:fork_id] && amount.nil?
    raise 'verify_input_signature must be called with a previous transaction or ' \
      'transaction output if SIGHASH_FORKID is enabled'
  end

  @scripts[in_idx] = Bitcoin::Script.new(script_sig, script_pubkey)
  return false if opts[:verify_sigpushonly] && !@scripts[in_idx].is_push_only?(script_sig)
  return false if opts[:verify_minimaldata] && !@scripts[in_idx].pushes_are_canonical?
  sig_valid = @scripts[in_idx].run(
    block_timestamp, opts
  ) do |pubkey, sig, hash_type, subscript|
    hash = signature_hash_for_input(in_idx, subscript, hash_type, amount, opts[:fork_id])
    Bitcoin.verify_signature(hash, sig, pubkey.unpack('H*')[0])
  end
  # BIP62 rule #6
  return false if opts[:verify_cleanstack] && !@scripts[in_idx].stack.empty?

  sig_valid
end

#verify_witness_input_signature(in_idx, outpoint_data, prev_out_amount, block_timestamp = Time.now.to_i, opts = {}) ⇒ Object

verify witness input signature in_idx against the corresponding output in outpoint_tx outpoint. This arg can also be a Script or TxOut

options are: verify_sigpushonly, verify_minimaldata, verify_cleanstack,

verify_dersig, verify_low_s, verify_strictenc

rubocop:disable CyclomaticComplexity,PerceivedComplexity


357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/bitcoin/protocol/tx.rb', line 357

def verify_witness_input_signature(
  in_idx, outpoint_data, prev_out_amount, block_timestamp = Time.now.to_i, opts = {}
)
  if @enable_bitcoinconsensus
    return bitcoinconsensus_verify_script(in_idx, outpoint_data, block_timestamp, opts)
  end

  outpoint_idx  = @in[in_idx].prev_out_index
  script_sig    = ''

  script_pubkey = script_pubkey_from_outpoint_data(outpoint_data, outpoint_idx)
  script_pubkey = Bitcoin::Script.new(script_pubkey)

  if script_pubkey.is_p2sh?
    redeem_script = Bitcoin::Script.new(@in[in_idx].script_sig).get_pubkey

    # P2SH-P2WPKH or P2SH-P2WSH
    script_pubkey = if Bitcoin.hash160(redeem_script) == script_pubkey.get_hash160
                      Bitcoin::Script.new(redeem_script.htb)
                    end
  end

  @in[in_idx].script_witness.stack.each { |s| script_sig << Bitcoin::Script.pack_pushdata(s) }
  code_separator_index = 0

  if script_pubkey.is_witness_v0_keyhash? # P2WPKH
    @scripts[in_idx] = Bitcoin::Script.new(
      script_sig, Bitcoin::Script.to_hash160_script(script_pubkey.get_hash160)
    )
  elsif script_pubkey.is_witness_v0_scripthash? # P2WSH
    witness_hex = @in[in_idx].script_witness.stack.last.bth
    witness_script = Bitcoin::Script.new(witness_hex.htb)
    return false unless Bitcoin.sha256(witness_hex) == script_pubkey.get_hash160
    @scripts[in_idx] = Bitcoin::Script.new(
      script_sig, Bitcoin::Script.to_p2sh_script(Bitcoin.hash160(witness_hex))
    )
  else
    return false
  end

  return false if opts[:verify_sigpushonly] && !@scripts[in_idx].is_push_only?(script_sig)
  return false if opts[:verify_minimaldata] && !@scripts[in_idx].pushes_are_canonical?
  sig_valid = @scripts[in_idx].run(block_timestamp, opts) do |pubkey, sig, hash_type, _|
    if script_pubkey.is_witness_v0_keyhash?
      hash = signature_hash_for_witness_input(
        in_idx, script_pubkey.to_payload, prev_out_amount, nil, hash_type
      )
    elsif script_pubkey.is_witness_v0_scripthash?
      hash = signature_hash_for_witness_input(
        in_idx, script_pubkey.to_payload, prev_out_amount,
        witness_hex.htb, hash_type, code_separator_index
      )
      code_separator_index += 1 if witness_script.codeseparator_count > code_separator_index
    end
    Bitcoin.verify_signature(hash, sig, pubkey.unpack('H*')[0])
  end
  # BIP62 rule #6
  return false if opts[:verify_cleanstack] && !@scripts[in_idx].stack.empty?

  sig_valid
end

#witness?Boolean

Returns:

  • (Boolean)

176
177
178
# File 'lib/bitcoin/protocol/tx.rb', line 176

def witness?
  !@in.find { |i| !i.script_witness.empty? }.nil?
end

#witness_hashObject

get witness hash


627
628
629
# File 'lib/bitcoin/protocol/tx.rb', line 627

def witness_hash
  hash_from_payload(to_witness_payload)
end

#witness_payloadObject


172
173
174
# File 'lib/bitcoin/protocol/tx.rb', line 172

def witness_payload
  @in.map { |i| i.script_witness.to_payload }.join
end