Class: Tapyrus::TxChecker

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tx: nil, input_index: nil) ⇒ TxChecker

Returns a new instance of TxChecker.



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

def initialize(tx: nil, input_index: nil)
  @tx = tx
  @input_index = input_index
end

Instance Attribute Details

#input_indexObject

Returns the value of attribute input_index.



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

def input_index
  @input_index
end

#txObject

Returns the value of attribute tx.



3
4
5
# File 'lib/tapyrus/script/tx_checker.rb', line 3

def tx
  @tx
end

Instance Method Details

#check_locktime(locktime) ⇒ Object



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

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_sequence(sequence) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/tapyrus/script/tx_checker.rb', line 67

def check_sequence(sequence)
  tx_sequence = tx.inputs[input_index].sequence

  # 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(script_sig, pubkey, script_code) ⇒ Boolean

check signature

Parameters:

Returns:

  • (Boolean)

    if check is passed return true, otherwise false.



16
17
18
19
20
21
22
23
# File 'lib/tapyrus/script/tx_checker.rb', line 16

def check_sig(script_sig, pubkey, script_code)
  return false if script_sig.empty?
  script_sig = script_sig.htb
  hash_type = script_sig[-1].unpack("C").first
  sig = script_sig[0..-2]
  sighash = tx.sighash_for_input(input_index, script_code, hash_type: hash_type)
  verify_sig(sig.bth, pubkey, sighash)
end

#verify_sig(sig, pubkey, digest, allow_hybrid: false) ⇒ Boolean

Check data signature.

Parameters:

  • sig (String)

    a signature with hex format.

  • pubkey (String)

    a public key with hex format.

  • digest (String)

    a message digest with binary format to be verified.

Returns:

  • (Boolean)

    if check is passed return true, otherwise false.



30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/tapyrus/script/tx_checker.rb', line 30

def verify_sig(sig, pubkey, digest, allow_hybrid: false)
  key_type =
    pubkey.start_with?("02") || pubkey.start_with?("03") ? Key::TYPES[:compressed] : Key::TYPES[:uncompressed]
  sig = sig.htb
  algo = sig.bytesize == 64 ? :schnorr : :ecdsa
  begin
    key = Key.new(pubkey: pubkey, key_type: key_type, allow_hybrid: allow_hybrid)
    key.verify(sig, digest, algo: algo)
  rescue Exception
    false
  end
end