From 30d4cb00a4ae548d23a58b1b0771b7e81b5700d4 Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Mon, 15 Sep 2025 17:27:24 +0200 Subject: [PATCH 1/7] Add binana json for CHECKCONTRACTVERIFY --- src/binana/checkcontractverify.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/binana/checkcontractverify.json diff --git a/src/binana/checkcontractverify.json b/src/binana/checkcontractverify.json new file mode 100644 index 000000000000..0358a1fa2045 --- /dev/null +++ b/src/binana/checkcontractverify.json @@ -0,0 +1,9 @@ +{ + "binana": [2025, 443, 0], + "deployment": "CHECKCONTRACTVERIFY", + "scriptverify": true, + "scriptverify_discourage": true, + "opcodes": { + "CHECKCONTRACTVERIFY": "0xbb" + } +} From ef309920e4888810da6120c4d7aeee729f29abc5 Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Sun, 2 Mar 2025 22:50:20 +0100 Subject: [PATCH 2/7] consensus: Cross-input script validation framework Adds a framework for validation checks that persist an individual input's script validation, allowing to persist state across the evaluations of multiple inputs of a transaction. This will be used for the amount logic of OP_CHECKCONTRACTVERIFY, that performs amount checks that are aggregate across multiple inputs. It could also be used for other applications, like batch validation and cross-input Schnorr signature aggregation. --- src/script/interpreter.cpp | 24 ++++++------- src/script/interpreter.h | 51 ++++++++++++++++++++++++++-- src/test/script_p2sh_tests.cpp | 4 ++- src/test/transaction_tests.cpp | 3 +- src/test/txvalidationcache_tests.cpp | 28 ++++++++++----- src/validation.cpp | 19 ++++++++--- src/validation.h | 6 ++-- 7 files changed, 102 insertions(+), 33 deletions(-) diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 0617c424cbbd..c6001ee3b298 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -469,7 +469,7 @@ static bool EvalChecksigFromStack(const valtype& sig, const valtype& msg, const return true; } -bool EvalScript(std::vector >& stack, const CScript& script, script_verify_flags flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) +bool EvalScript(std::vector >& stack, const CScript& script, script_verify_flags flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror, TransactionExecutionData* tx_exec_data) { static const CScriptNum bnZero(0); static const CScriptNum bnOne(1); @@ -1397,10 +1397,10 @@ bool EvalScript(std::vector >& stack, const CScript& return set_success(serror); } -bool EvalScript(std::vector >& stack, const CScript& script, script_verify_flags flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror) +bool EvalScript(std::vector >& stack, const CScript& script, script_verify_flags flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, TransactionExecutionData* tx_exec_data) { ScriptExecutionData execdata; - return EvalScript(stack, script, flags, checker, sigversion, execdata, serror); + return EvalScript(stack, script, flags, checker, sigversion, execdata, serror, tx_exec_data); } namespace { @@ -2141,7 +2141,7 @@ std::optional CheckTapscriptOpSuccess(const CScript& exec_script, script_v return std::nullopt; } -static bool ExecuteWitnessScript(const Span& stack_span, const CScript& exec_script, script_verify_flags flags, SigVersion sigversion, const BaseSignatureChecker& checker, ScriptExecutionData& execdata, ScriptError* serror) +static bool ExecuteWitnessScript(const Span& stack_span, const CScript& exec_script, script_verify_flags flags, SigVersion sigversion, const BaseSignatureChecker& checker, ScriptExecutionData& execdata, TransactionExecutionData* tx_exec_data, ScriptError* serror) { std::vector stack{stack_span.begin(), stack_span.end()}; @@ -2160,7 +2160,7 @@ static bool ExecuteWitnessScript(const Span& stack_span, const CS } // Run the script interpreter. - if (!EvalScript(stack, exec_script, flags, checker, sigversion, execdata, serror)) return false; + if (!EvalScript(stack, exec_script, flags, checker, sigversion, execdata, serror, tx_exec_data)) return false; // Scripts inside witness implicitly require cleanstack behaviour if (stack.size() != 1) return set_error(serror, SCRIPT_ERR_CLEANSTACK); @@ -2214,7 +2214,7 @@ static bool VerifyTaprootCommitment(const std::vector& control, c return q.CheckTapTweak(p, merkle_root, control[0] & 1); } -static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector& program, script_verify_flags flags, const BaseSignatureChecker& checker, ScriptError* serror, bool is_p2sh) +static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector& program, script_verify_flags flags, const BaseSignatureChecker& checker, ScriptError* serror, bool is_p2sh, TransactionExecutionData* tx_exec_data = nullptr) { CScript exec_script; //!< Actually executed script (last stack item in P2WSH; implied P2PKH script in P2WPKH; leaf script in P2TR) Span stack{witness.stack}; @@ -2233,14 +2233,14 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, if (memcmp(hash_exec_script.begin(), program.data(), 32)) { return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH); } - return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, execdata, serror); + return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, execdata, tx_exec_data, serror); } else if (program.size() == WITNESS_V0_KEYHASH_SIZE) { // BIP141 P2WPKH: 20-byte witness v0 program (which encodes Hash160(pubkey)) if (stack.size() != 2) { return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH); // 2 items in witness } exec_script << OP_DUP << OP_HASH160 << program << OP_EQUALVERIFY << OP_CHECKSIG; - return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, execdata, serror); + return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, execdata, tx_exec_data, serror); } else { return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH); } @@ -2280,7 +2280,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, exec_script = CScript(script.begin(), script.end()); execdata.m_validation_weight_left = ::GetSerializeSize(witness.stack) + VALIDATION_WEIGHT_OFFSET; execdata.m_validation_weight_left_init = true; - return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::TAPSCRIPT, checker, execdata, serror); + return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::TAPSCRIPT, checker, execdata, tx_exec_data, serror); } if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) { return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION); @@ -2299,7 +2299,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, // There is intentionally no return statement here, to be able to use "control reaches end of non-void function" warnings to detect gaps in the logic above. } -bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, script_verify_flags flags, const BaseSignatureChecker& checker, ScriptError* serror) +bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, script_verify_flags flags, const BaseSignatureChecker& checker, ScriptError* serror, TransactionExecutionData* tx_exec_data) { static const CScriptWitness emptyWitness; if (witness == nullptr) { @@ -2339,7 +2339,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C // The scriptSig must be _exactly_ CScript(), otherwise we reintroduce malleability. return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED); } - if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /*is_p2sh=*/false)) { + if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /*is_p2sh=*/false, tx_exec_data)) { return false; } // Bypass the cleanstack check at the end. The actual stack is obviously not clean @@ -2384,7 +2384,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C // reintroduce malleability. return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED_P2SH); } - if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /*is_p2sh=*/true)) { + if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /*is_p2sh=*/true, tx_exec_data)) { return false; } // Bypass the cleanstack check at the end. The actual stack is obviously not clean diff --git a/src/script/interpreter.h b/src/script/interpreter.h index e334ed445fcc..82b987ce315a 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -14,12 +14,14 @@ #include