From 66153cca78cfadb90cfb1a512e29aa725188d4c2 Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Sat, 29 Mar 2025 23:46:03 +0100 Subject: [PATCH 1/5] chainparams: add versionbits deployment for OP_CHECKCONTRACTVERIFY Only active on regtest. This is only for PR hygiene: CCV is not proposed as a soft-fork on its own, but having minimal deployment code in the PR helps to keep things clean. --- src/consensus/params.h | 1 + src/deploymentinfo.cpp | 4 ++++ src/kernel/chainparams.cpp | 30 ++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/src/consensus/params.h b/src/consensus/params.h index dd29b9408e2..2b0957629ac 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -32,6 +32,7 @@ constexpr bool ValidDeployment(BuriedDeployment dep) { return dep <= DEPLOYMENT_ enum DeploymentPos : uint16_t { DEPLOYMENT_TESTDUMMY, DEPLOYMENT_TAPROOT, // Deployment of Schnorr/Taproot (BIPs 340-342) + DEPLOYMENT_CHECKCONTRACTVERIFY, // NOTE: Also add new deployments to VersionBitsDeploymentInfo in deploymentinfo.cpp MAX_VERSION_BITS_DEPLOYMENTS }; diff --git a/src/deploymentinfo.cpp b/src/deploymentinfo.cpp index 185a7dcb54c..482309a4fcd 100644 --- a/src/deploymentinfo.cpp +++ b/src/deploymentinfo.cpp @@ -17,6 +17,10 @@ const struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B /*.name =*/ "taproot", /*.gbt_force =*/ true, }, + { + /*.name =*/ "checkcontractverify", + /*.gbt_force =*/ true, + }, }; std::string DeploymentName(Consensus::BuriedDeployment dep) diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index 4a747e7317c..264ca9ce50c 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -117,6 +117,12 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = 1628640000; // August 11th, 2021 consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 709632; // Approximately November 12th, 2021 + // Deployment of OP_CHECKCONTRACTVERIFY + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].bit = 3; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].min_activation_height = 0; + consensus.nMinimumChainWork = uint256{"0000000000000000000000000000000000000000b1f3b93b65b16d035a82be84"}; consensus.defaultAssumeValid = uint256{"00000000000000000001b658dd1120e82e66d2790811f89ede9742ada3ed6d77"}; // 886157 @@ -229,6 +235,12 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = 1628640000; // August 11th, 2021 consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay + // Deployment of OP_CHECKCONTRACTVERIFY + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].bit = 3; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].min_activation_height = 0; + consensus.nMinimumChainWork = uint256{"0000000000000000000000000000000000000000000015f5e0c9f13455b0eb17"}; consensus.defaultAssumeValid = uint256{"00000000000003fc7967410ba2d0a8a8d50daedc318d43e8baf1a9782c236a57"}; // 3974606 @@ -322,6 +334,12 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay + // Deployment of OP_CHECKCONTRACTVERIFY + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].bit = 3; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].min_activation_height = 0; + consensus.nMinimumChainWork = uint256{"0000000000000000000000000000000000000000000001d6dce8651b6094e4c1"}; consensus.defaultAssumeValid = uint256{"0000000000003ed4f08dbdf6f7d6b271a6bcffce25675cb40aa9fa43179a89f3"}; // 72600 @@ -454,6 +472,12 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay + // Deployment of OP_CHECKCONTRACTVERIFY + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].bit = 3; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].min_activation_height = 0; + // message start is defined as the first 4 bytes of the sha256d of the block script HashWriter h{}; h << consensus.signet_challenge; @@ -529,6 +553,12 @@ public: consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_TAPROOT].min_activation_height = 0; // No activation delay + // Deployment of OP_CHECKCONTRACTVERIFY + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].bit = 3; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].nStartTime = Consensus::BIP9Deployment::ALWAYS_ACTIVE; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + consensus.vDeployments[Consensus::DEPLOYMENT_CHECKCONTRACTVERIFY].min_activation_height = 0; + consensus.nMinimumChainWork = uint256{}; consensus.defaultAssumeValid = uint256{}; From 2c1deefedc16e59e008e1d56e8d1ef453a79da02 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/5] 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 | 30 +++++++++++----- src/validation.h | 6 ++-- 7 files changed, 110 insertions(+), 36 deletions(-) diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 61ea7f4503c..2ff63118aff 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -403,7 +403,7 @@ static bool EvalChecksig(const valtype& sig, const valtype& pubkey, CScript::con assert(false); } -bool EvalScript(std::vector >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) +bool EvalScript(std::vector >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror, TransactionExecutionData* tx_exec_data) { static const CScriptNum bnZero(0); static const CScriptNum bnOne(1); @@ -1233,10 +1233,10 @@ bool EvalScript(std::vector >& stack, const CScript& return set_success(serror); } -bool EvalScript(std::vector >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror) +bool EvalScript(std::vector >& stack, const CScript& script, unsigned int 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 { @@ -1785,7 +1785,7 @@ bool GenericTransactionSignatureChecker::CheckSequence(const CScriptNum& nSeq template class GenericTransactionSignatureChecker; template class GenericTransactionSignatureChecker; -static bool ExecuteWitnessScript(const std::span& stack_span, const CScript& exec_script, unsigned int flags, SigVersion sigversion, const BaseSignatureChecker& checker, ScriptExecutionData& execdata, ScriptError* serror) +static bool ExecuteWitnessScript(const std::span& stack_span, const CScript& exec_script, unsigned int flags, SigVersion sigversion, const BaseSignatureChecker& checker, ScriptExecutionData& execdata, TransactionExecutionData* tx_exec_data, ScriptError* serror) { std::vector stack{stack_span.begin(), stack_span.end()}; @@ -1817,7 +1817,7 @@ static bool ExecuteWitnessScript(const std::span& stack_span, con } // 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); @@ -1870,7 +1870,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, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror, bool is_p2sh) +static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector& program, unsigned int 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) std::span stack{witness.stack}; @@ -1889,14 +1889,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); } @@ -1936,7 +1936,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); @@ -1955,7 +1955,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, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror) +bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror, TransactionExecutionData* tx_exec_data) { static const CScriptWitness emptyWitness; if (witness == nullptr) { @@ -1995,7 +1995,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 @@ -2040,7 +2040,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 e8c5b09045f..8fe1a415b26 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -11,11 +11,13 @@ #include #include