Class: Bitcoin::TxChecker

Inherits:
Object
  • Object
show all
Defined in:
lib/bitcoin/script/tx_checker.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tx: nil, amount: 0, input_index: nil, prevouts: []) ⇒ TxChecker

Returns a new instance of TxChecker.



10
11
12
13
14
15
# File 'lib/bitcoin/script/tx_checker.rb', line 10

def initialize(tx: nil, amount: 0, input_index: nil, prevouts: [])
  @tx = tx
  @input_index = input_index
  @prevouts = prevouts
  @amount = input_index && prevouts[input_index] ? prevouts[input_index].value : amount
end

Instance Attribute Details

#amountObject (readonly)

Returns the value of attribute amount.



6
7
8
# File 'lib/bitcoin/script/tx_checker.rb', line 6

def amount
  @amount
end

#error_codeObject

Returns the value of attribute error_code.



8
9
10
# File 'lib/bitcoin/script/tx_checker.rb', line 8

def error_code
  @error_code
end

#input_indexObject (readonly)

Returns the value of attribute input_index.



5
6
7
# File 'lib/bitcoin/script/tx_checker.rb', line 5

def input_index
  @input_index
end

#prevoutsObject (readonly)

Returns the value of attribute prevouts.



7
8
9
# File 'lib/bitcoin/script/tx_checker.rb', line 7

def prevouts
  @prevouts
end

#txObject (readonly)

Returns the value of attribute tx.



4
5
6
# File 'lib/bitcoin/script/tx_checker.rb', line 4

def tx
  @tx
end

Instance Method Details

#check_locktime(locktime) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/bitcoin/script/tx_checker.rb', line 73

def check_locktime(locktime)
  # There are two kinds of nLockTime: lock-by-blockheight and lock-by-blocktime,
  # distinguished by whether nLockTime < LOCKTIME_THRESHOLD.

  # We want to compare apples to apples, so fail the script unless the type of nLockTime being tested is the same as the nLockTime in the transaction.
  unless ((tx.lock_time < LOCKTIME_THRESHOLD && locktime < LOCKTIME_THRESHOLD) ||
      (tx.lock_time >= LOCKTIME_THRESHOLD && locktime >= LOCKTIME_THRESHOLD))
    return false
  end

  # Now that we know we're comparing apples-to-apples, the comparison is a simple numeric one.
  return false if locktime > tx.lock_time

  # Finally the nLockTime feature can be disabled and thus CHECKLOCKTIMEVERIFY bypassed if every txin has been finalized by setting nSequence to maxint.
  # The transaction would be allowed into the blockchain, making the opcode ineffective.
  # Testing if this vin is not final is sufficient to prevent this condition.
  # Alternatively we could test all inputs, but testing just this input minimizes the data required to prove correct CHECKLOCKTIMEVERIFY execution.
  return false if TxIn::SEQUENCE_FINAL == tx.inputs[input_index].sequence

  true
end

#check_schnorr_sig(sig, pubkey, sig_version, opts = {}) ⇒ Boolean

check schnorr signature.

Parameters:

  • sig (String)

    schnorr signature with hex format.

  • pubkey (String)

    a public key with hex format.

  • sig_version (Symbol)

    whether :taproot or :tapscript

Returns:

  • (Boolean)

    verification result

Raises:

  • (ArgumentError)

    If prevouts does not cover every input of the tx.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/bitcoin/script/tx_checker.rb', line 44

def check_schnorr_sig(sig, pubkey, sig_version, opts = {})
  return false unless [:taproot, :tapscript].include?(sig_version)
  # A taproot sighash commits to the amount and scriptPubkey of every prevout, so a partial
  # set does not fail to verify, it verifies against a sighash consensus never computes.
  # Checked here rather than left to the generator, since the rescue below would turn it
  # into a script error.
  raise ArgumentError, 'prevouts must be specified for all inputs.' unless prevouts.size == tx.in.size

  sig = sig.htb
  return set_error(SCRIPT_ERR_SCHNORR_SIG_SIZE) unless [64, 65].include?(sig.bytesize)

  hash_type = SIGHASH_TYPE[:default]
  if sig.bytesize == 65
    hash_type = sig[-1].unpack1('C')
    sig = sig[0..-2]
    return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE) if hash_type == SIGHASH_TYPE[:default] # hash type can not specify 0x00.
  end

  return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE) unless (hash_type <= 0x03 || (hash_type >= 0x81 && hash_type <= 0x83))

  begin
    sighash = tx.sighash_for_input(input_index, opts: opts, hash_type: hash_type, sig_version: sig_version, prevouts: prevouts)
    key = Key.from_xonly_pubkey(pubkey)
    key.verify(sig, sighash, algo: :schnorr)
  rescue ArgumentError
    return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE)
  end
end

#check_sequence(sequence) ⇒ Object



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
# File 'lib/bitcoin/script/tx_checker.rb', line 95

def check_sequence(sequence)
  tx_sequence = tx.inputs[input_index].sequence
  # Fail if the transaction's version number is not set high enough to trigger BIP 68 rules.
  return false if tx.version < 2

  # Sequence numbers with their most significant bit set are not consensus constrained.
  # Testing that the transaction's sequence number do not have this bit set prevents using this property to get around a CHECKSEQUENCEVERIFY check.
  return false unless tx_sequence & TxIn::SEQUENCE_LOCKTIME_DISABLE_FLAG == 0

  # Mask off any bits that do not have consensus-enforced meaning before doing the integer comparisons
  locktime_mask = TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | TxIn::SEQUENCE_LOCKTIME_MASK
  tx_sequence_masked = tx_sequence & locktime_mask
  sequence_masked = sequence & locktime_mask

  # There are two kinds of nSequence: lock-by-blockheight and lock-by-blocktime,
  # distinguished by whether sequence_masked < TxIn#SEQUENCE_LOCKTIME_TYPE_FLAG.
  # We want to compare apples to apples, so fail the script
  # unless the type of nSequenceMasked being tested is the same as the nSequenceMasked in the transaction.
  unless ((tx_sequence_masked < TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG && sequence_masked < TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG) ||
      (tx_sequence_masked >= TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG && sequence_masked >= TxIn::SEQUENCE_LOCKTIME_TYPE_FLAG))
    return false
  end

  # Now that we know we're comparing apples-to-apples, the comparison is a simple numeric one.
  sequence_masked <= tx_sequence_masked
end

#check_sig(sig, pubkey, script_code, sig_version, allow_hybrid: false) ⇒ Boolean

check ecdsa signature

Parameters:

Returns:

  • (Boolean)

    verification result



23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/bitcoin/script/tx_checker.rb', line 23

def check_sig(sig, pubkey, script_code, sig_version, allow_hybrid: false)
  return false if sig.empty?
  sig = sig.htb
  hash_type = sig[-1].unpack1('C')
  sig = sig[0..-2]
  sighash = tx.sighash_for_input(input_index, script_code, opts: {amount: amount}, hash_type: hash_type, sig_version: sig_version)
  key_type = pubkey.start_with?('02') || pubkey.start_with?('03') ? Key::TYPES[:compressed] : Key::TYPES[:uncompressed]
  begin
    key = Key.new(pubkey: pubkey, key_type: key_type, allow_hybrid: allow_hybrid)
    key.verify(sig, sighash)
  rescue StandardError
    false
  end
end

#has_error?Boolean

Returns:

  • (Boolean)


122
123
124
# File 'lib/bitcoin/script/tx_checker.rb', line 122

def has_error?
  !@error_code.nil?
end