lib/tapyrus/script/script_interpreter.rb in tapyrus-0.1.0 vs lib/tapyrus/script/script_interpreter.rb in tapyrus-0.2.0

- old
+ new

@@ -30,36 +30,25 @@ end # eval script # @param [Tapyrus::Script] script_sig a signature script (unlock script which data push only) # @param [Tapyrus::Script] script_pubkey a script pubkey (locking script) - # @param [Tapyrus::ScriptWitness] witness a witness script # @return [Boolean] result - def verify_script(script_sig, script_pubkey, witness = ScriptWitness.new) + def verify_script(script_sig, script_pubkey) return set_error(SCRIPT_ERR_SIG_PUSHONLY) if flag?(SCRIPT_VERIFY_SIGPUSHONLY) && !script_sig.push_only? stack_copy = nil - had_witness = false - return false unless eval_script(script_sig, :base) + return false unless eval_script(script_sig, :base, false) stack_copy = stack.dup if flag?(SCRIPT_VERIFY_P2SH) - return false unless eval_script(script_pubkey, :base) + return false unless eval_script(script_pubkey, :base, false) return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last.htb) - # Bare witness programs - if flag?(SCRIPT_VERIFY_WITNESS) && script_pubkey.witness_program? - had_witness = true - return set_error(SCRIPT_ERR_WITNESS_MALLEATED) unless script_sig.size == 0 - version, program = script_pubkey.witness_data - stack_copy = stack.dup - return false unless verify_witness_program(witness, version, program) - end - # Additional validation for spend-to-script-hash transactions if flag?(SCRIPT_VERIFY_P2SH) && script_pubkey.p2sh? return set_error(SCRIPT_ERR_SIG_PUSHONLY) unless script_sig.push_only? tmp = stack @stack = stack_copy @@ -67,22 +56,12 @@ begin redeem_script = Tapyrus::Script.parse_from_payload(stack.pop.htb) rescue Exception => e return set_error(SCRIPT_ERR_BAD_OPCODE, "Failed to parse serialized redeem script for P2SH. #{e.message}") end - return false unless eval_script(redeem_script, :base) + return false unless eval_script(redeem_script, :base, true) return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last) - - # P2SH witness program - if flag?(SCRIPT_VERIFY_WITNESS) && redeem_script.witness_program? - had_witness = true - # The scriptSig must be _exactly_ a single push of the redeemScript. Otherwise we reintroduce malleability. - return set_error(SCRIPT_ERR_WITNESS_MALLEATED_P2SH) unless script_sig == (Tapyrus::Script.new << redeem_script.to_payload.bth) - - version, program = redeem_script.witness_data - return false unless verify_witness_program(witness, version, program) - end end # The CLEANSTACK check is only performed after potential P2SH evaluation, # as the non-P2SH evaluation of a P2SH script will obviously not result in a clean stack (the P2SH inputs remain). # The same holds for witness evaluation. @@ -91,62 +70,26 @@ # which is not a softfork (and P2SH should be one). raise 'assert' unless flag?(SCRIPT_VERIFY_P2SH) return set_error(SCRIPT_ERR_CLEANSTACK) unless stack.size == 1 end - if flag?(SCRIPT_VERIFY_WITNESS) - raise 'assert' unless flag?(SCRIPT_VERIFY_P2SH) - return set_error(SCRIPT_ERR_WITNESS_UNEXPECTED) if !had_witness && !witness.empty? - end - true end def set_error(err_code, extra_message = nil) @error = ScriptError.new(err_code, extra_message) false end - def verify_witness_program(witness, version, program) - if version == 0 - if program.bytesize == 32 - return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY) if witness.stack.size == 0 - script_pubkey = Tapyrus::Script.parse_from_payload(witness.stack.last) - @stack = witness.stack[0..-2].map{|w|w.bth} - script_hash = Tapyrus.sha256(script_pubkey.to_payload) - return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless script_hash == program - elsif program.bytesize == 20 - return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless witness.stack.size == 2 - script_pubkey = Tapyrus::Script.to_p2pkh(program.bth) - @stack = witness.stack.map{|w|w.bth} - else - return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH) - end - elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) - return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) - else - return true # Higher version witness scripts return true for future softfork compatibility - end - - stack.each do |s| # Disallow stack item size > MAX_SCRIPT_ELEMENT_SIZE in witness stack - return set_error(SCRIPT_ERR_PUSH_SIZE) if s.htb.bytesize > MAX_SCRIPT_ELEMENT_SIZE - end - - return false unless eval_script(script_pubkey, :witness_v0) - - return set_error(SCRIPT_ERR_EVAL_FALSE) unless stack.size == 1 - return set_error(SCRIPT_ERR_EVAL_FALSE) unless cast_to_bool(stack.last) - true - end - - def eval_script(script, sig_version) + def eval_script(script, sig_version, is_redeem_script) return set_error(SCRIPT_ERR_SCRIPT_SIZE) if script.size > MAX_SCRIPT_SIZE begin flow_stack = [] alt_stack = [] last_code_separator_index = 0 op_count = 0 + color_id = nil script.chunks.each_with_index do |c, index| need_exec = !flow_stack.include?(false) return set_error(SCRIPT_ERR_PUSH_SIZE) if c.pushdata? && c.pushed_data.bytesize > MAX_SCRIPT_ELEMENT_SIZE @@ -207,11 +150,11 @@ when OP_IF, OP_NOTIF result = false if need_exec return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if stack.size < 1 value = pop_string.htb - if sig_version == :witness_v0 && flag?(SCRIPT_VERIFY_MINIMALIF) + if flag?(SCRIPT_VERIFY_MINIMALIF) if value.bytesize > 1 || (value.bytesize == 1 && value[0].unpack('C').first != 1) return set_error(SCRIPT_ERR_MINIMALIF) end end result = cast_to_bool(value) @@ -407,13 +350,13 @@ if sig_version == :base tmp = subscript.find_and_delete(Script.new << sig) return set_error(SCRIPT_ERR_SIG_FINDANDDELETE) if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript subscript = tmp end + return false if (sig.htb.bytesize == Tapyrus::Key::COMPACT_SIGNATURE_SIZE ? + !check_schnorr_signature_encoding(sig) : !check_ecdsa_signature_encoding(sig)) || !check_pubkey_encoding(pubkey) - return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set. - success = checker.check_sig(sig, pubkey, subscript, sig_version) # https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki#NULLFAIL if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0 return set_error(SCRIPT_ERR_SIG_NULLFAIL) @@ -426,10 +369,23 @@ stack.pop else return set_error(SCRIPT_ERR_CHECKSIGVERIFY) end end + when OP_CHECKDATASIG, OP_CHECKDATASIGVERIFY + return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 3 + sig, msg, pubkey = pop_string(3) + # check signature encoding without hashtype byte + return false if (sig.htb.bytesize != (Tapyrus::Key::COMPACT_SIGNATURE_SIZE - 1) && !check_ecdsa_signature_encoding(sig, true)) || !check_pubkey_encoding(pubkey) + digest = Tapyrus.sha256(msg) + success = checker.verify_sig(sig, pubkey, digest) + return set_error(SCRIPT_ERR_SIG_NULLFAIL) if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0 + push_int(success ? 1 : 0) + if opcode == OP_CHECKDATASIGVERIFY + stack.pop if success + return set_error(SCRIPT_ERR_CHECKDATASIGVERIFY) unless success + end when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1 pubkey_count = pop_int unless (0..MAX_PUBKEYS_PER_MULTISIG).include?(pubkey_count) return set_error(SCRIPT_ERR_PUBKEY_COUNT) @@ -461,14 +417,21 @@ subscript = tmp end end success = true + current_sig_scheme = nil while success && sig_count > 0 sig = sigs.pop pubkey = pubkeys.pop - return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set. + sig_scheme = sig.htb.bytesize == Tapyrus::Key::COMPACT_SIGNATURE_SIZE ? :schnorr : :ecdsa + current_sig_scheme = sig_scheme if current_sig_scheme.nil? + + return false if (sig_scheme == :schnorr ? !check_schnorr_signature_encoding(sig) : !check_ecdsa_signature_encoding(sig)) || !check_pubkey_encoding(pubkey) # error already set. + + return set_error(SCRIPT_ERR_MIXED_SCHEME_MULTISIG) unless sig_scheme == current_sig_scheme + ok = checker.check_sig(sig, pubkey, subscript, sig_version) if ok sig_count -= 1 else sigs << sig @@ -486,13 +449,12 @@ # A bug causes CHECKMULTISIG to consume one extra argument whose contents were not checked in any way. # Unfortunately this is a potential source of mutability, # so optionally verify it is exactly equal to zero prior to removing it from the stack. return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1 - if flag?(SCRIPT_VERIFY_NULLDUMMY) && stack[-1].size > 0 - return set_error(SCRIPT_ERR_SIG_NULLDUMMY) - end + return set_error(SCRIPT_ERR_SIG_NULLDUMMY) if stack[-1].size > 0 + stack.pop push_int(success ? 1 : 0) if opcode == OP_CHECKMULTISIGVERIFY if success @@ -501,10 +463,29 @@ return set_error(SCRIPT_ERR_CHECKMULTISIGVERIFY) end end when OP_RETURN return set_error(SCRIPT_ERR_OP_RETURN) + when OP_COLOR + # Color id is not permitted in p2sh redeem script + return set_error(SCRIPT_ERR_OP_COLOR_UNEXPECTED) if is_redeem_script + + # if Color id is already initialized this must be an extra + return set_error(SCRIPT_ERR_OP_COLOR_MULTIPLE) if color_id && color_id.type != Tapyrus::Color::TokenTypes::NONE + + # color id is not allowed inside OP_IF + return set_error(SCRIPT_ERR_OP_COLOR_IN_BRANCH) unless flow_stack.empty? + + # pop one stack element and verify that it exists + return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size() < 1 + + color_id = Tapyrus::Color::ColorIdentifier.parse_from_payload(stack.last.htb) + + # check ColorIdentifier is valid + return set_error(SCRIPT_ERR_OP_COLOR_ID_INVALID) unless color_id.valid? + + stack.pop else return set_error(SCRIPT_ERR_BAD_OPCODE) end end end @@ -584,46 +565,44 @@ else false end end - def check_signature_encoding(sig) + def check_ecdsa_signature_encoding(sig, data_sig = false) return true if sig.size.zero? - if (flag?(SCRIPT_VERIFY_DERSIG) || flag?(SCRIPT_VERIFY_LOW_S) || flag?(SCRIPT_VERIFY_STRICTENC)) && !Key.valid_signature_encoding?(sig.htb) + if !Key.valid_signature_encoding?(sig.htb, data_sig) return set_error(SCRIPT_ERR_SIG_DER) - elsif flag?(SCRIPT_VERIFY_LOW_S) && !low_der_signature?(sig) + elsif !low_der_signature?(sig, data_sig) return false - elsif flag?(SCRIPT_VERIFY_STRICTENC) && !defined_hashtype_signature?(sig) + elsif !data_sig && !defined_hashtype_signature?(sig) return set_error(SCRIPT_ERR_SIG_HASHTYPE) end true end - def low_der_signature?(sig) - return set_error(SCRIPT_ERR_SIG_DER) unless Key.valid_signature_encoding?(sig.htb) + def check_schnorr_signature_encoding(sig, data_sig = false) + return false unless sig.htb.bytesize == (data_sig ? 64 : 65) + return set_error(SCRIPT_ERR_SIG_HASHTYPE) if !data_sig && !defined_hashtype_signature?(sig) + true + end + + def low_der_signature?(sig, data_sig = false) + return set_error(SCRIPT_ERR_SIG_DER) unless Key.valid_signature_encoding?(sig.htb, data_sig) return set_error(SCRIPT_ERR_SIG_HIGH_S) unless Key.low_signature?(sig.htb) true end def defined_hashtype_signature?(signature) sig = signature.htb return false if sig.empty? s = sig.unpack('C*') hash_type = s[-1] & (~(SIGHASH_TYPE[:anyonecanpay])) - hash_type &= (~(Tapyrus::SIGHASH_FORK_ID)) if Tapyrus.chain_params.fork_chain? # for fork coin. return false if hash_type < SIGHASH_TYPE[:all] || hash_type > SIGHASH_TYPE[:single] true end - def check_pubkey_encoding(pubkey, sig_version) - if flag?(SCRIPT_VERIFY_STRICTENC) && !Key.compress_or_uncompress_pubkey?(pubkey) - return set_error(SCRIPT_ERR_PUBKEYTYPE) - end - # Only compressed keys are accepted in segwit - if flag?(SCRIPT_VERIFY_WITNESS_PUBKEYTYPE) && - sig_version == :witness_v0 && !Key.compress_pubkey?(pubkey) - return set_error(SCRIPT_ERR_WITNESS_PUBKEYTYPE) - end + def check_pubkey_encoding(pubkey) + return set_error(SCRIPT_ERR_PUBKEYTYPE) unless Key.compress_or_uncompress_pubkey?(pubkey) true end def minimal_push?(data, opcode) if data.bytesize.zero? \ No newline at end of file