lib/bitcoin/script/tx_checker.rb in bitcoinrb-0.5.0 vs lib/bitcoin/script/tx_checker.rb in bitcoinrb-0.6.0
- old
+ new
@@ -2,34 +2,73 @@
class TxChecker
attr_reader :tx
attr_reader :input_index
attr_reader :amount
+ attr_reader :prevouts
+ attr_accessor :error_code
- def initialize(tx: nil, amount: 0, input_index: nil)
+ def initialize(tx: nil, amount: 0, input_index: nil, prevouts: [])
@tx = tx
- @amount = amount
@input_index = input_index
+ @prevouts = prevouts
+ @amount = input_index && prevouts[input_index] ? prevouts[input_index].value : amount
end
- # check signature
- # @param [String] script_sig
- # @param [String] pubkey
+ # check ecdsa signature
+ # @param [String] sig signature with hex format
+ # @param [String] pubkey with hex format.
# @param [Bitcoin::Script] script_code
# @param [Integer] sig_version
- def check_sig(script_sig, pubkey, script_code, sig_version)
- 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,
- amount: amount, sig_version: sig_version)
+ # @return [Boolean] verification result
+ 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]
- key = Key.new(pubkey: pubkey, key_type: key_type)
- key.verify(sig, sighash)
+ begin
+ key = Key.new(pubkey: pubkey, key_type: key_type, allow_hybrid: allow_hybrid)
+ key.verify(sig, sighash)
+ rescue Exception
+ false
+ end
end
+ # check schnorr signature.
+ # @param [String] sig schnorr signature with hex format.
+ # @param [String] pubkey a public key with hex fromat.
+ # @param [Symbol] sig_version whether :taproot or :tapscript
+ # @return [Boolean] verification result
+ 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))
+
+ opts[:prevouts] = prevouts
+
+ begin
+ sighash = tx.sighash_for_input(input_index, opts: opts, hash_type: hash_type, sig_version: sig_version)
+ key = Key.new(pubkey: "02#{pubkey}", key_type: Key::TYPES[:compressed])
+ key.verify(sig, sighash, algo: :schnorr)
+ rescue ArgumentError
+ return set_error(SCRIPT_ERR_SCHNORR_SIG_HASHTYPE)
+ end
+ end
+
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.
@@ -73,9 +112,20 @@
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
+
+ def has_error?
+ !@error_code.nil?
+ end
+
+ private
+
+ def set_error(code)
+ @error_code = code
+ false
end
end
end
\ No newline at end of file