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



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/bitcoin/script/tx_checker.rb', line 68

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

  • sig_version (Symbol)

    whether :taproot or :tapscript

Returns:

  • (Boolean)

    verification result



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

def check_schnorr_sig(sig, pubkey, sig_version, opts = {})
  return false unless [:taproot, :tapscript].include?(sig_version)
  return false if prevouts.size < input_index

  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



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

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 Exception
    false
  end
end

#has_error?Boolean

Returns:

  • (Boolean)


117
118
119
# File 'lib/bitcoin/script/tx_checker.rb', line 117

def has_error?
  !@error_code.nil?
end