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