From 221e604ca9e3dba2e2e5b0a77d0e16ded8d55ff0 Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Mon, 8 Dec 2025 11:56:01 -0500 Subject: [PATCH 1/5] binana: add OP_TEMPLATEHASH --- src/binana/templatehash.json | 9 +++++++++ test/functional/test_framework/script.py | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/binana/templatehash.json diff --git a/src/binana/templatehash.json b/src/binana/templatehash.json new file mode 100644 index 000000000000..ae29f8f00f7d --- /dev/null +++ b/src/binana/templatehash.json @@ -0,0 +1,9 @@ +{ + "binana": [2026, 1, 0], + "deployment": "TEMPLATEHASH", + "scriptverify": true, + "scriptverify_discourage": true, + "opcodes": { + "TEMPLATEHASH": "0xce" + } +} diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 0a33838b1276..f83f24798e02 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -256,6 +256,8 @@ def __new__(cls, n): OP_CHECKSIGFROMSTACK = CScriptOp(0xcc) OP_INTERNALKEY = CScriptOp(0xcb) +# BIP xx opcode (Tapscript-only, formerly OP_SUCCESS187) +OP_TEMPLATEHASH = CScriptOp(0xce) OP_INVALIDOPCODE = CScriptOp(0xff) @@ -372,6 +374,7 @@ def __new__(cls, n): OP_NOP9: 'OP_NOP9', OP_NOP10: 'OP_NOP10', OP_CHECKSIGADD: 'OP_CHECKSIGADD', + OP_TEMPLATEHASH: 'OP_TEMPLATEHASH', OP_INVALIDOPCODE: 'OP_INVALIDOPCODE', }) @@ -969,7 +972,7 @@ def taproot_construct(pubkey, scripts=None, *, keyver=None, treat_internal_as_in return TaprootInfo(CScript([OP_1, tweaked]), pubkey, negated + 0, tweak, leaves, h, tweaked, keyver) def is_op_success(o): - if o in [OP_CAT, OP_CHECKSIGFROMSTACK, OP_INTERNALKEY]: + if o in [OP_CAT, OP_CHECKSIGFROMSTACK, OP_INTERNALKEY, OP_TEMPLATEHASH]: return False return o == 0x50 or o == 0x62 or o == 0x89 or o == 0x8a or o == 0x8d or o == 0x8e or (o >= 0x7e and o <= 0x81) or (o >= 0x83 and o <= 0x86) or (o >= 0x95 and o <= 0x99) or (o >= 0xbb and o <= 0xfe) From 21e6fd8f58fec400f434e0a92813067a672c58f7 Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Tue, 10 Jun 2025 17:25:39 -0400 Subject: [PATCH 2/5] script: implement OP_TEMPLATEHASH This introduces a new Script operation exclusively available in Tapscript context: OP_TEMPLATEHASH. This operation pushes the hash of the spending transaction on the stack. See BIP 446 for details. This operation is introduced as replacing OP_SUCCESS206 (0xce). Co-Authored-By: Greg Sanders --- src/script/interpreter.cpp | 37 +++++++++++++++++++++++++++++++++++++ src/script/interpreter.h | 11 +++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 0617c424cbbd..be2eafc6f8a0 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1377,6 +1377,20 @@ bool EvalScript(std::vector >& stack, const CScript& break; } + case OP_TEMPLATEHASH: + { + // OP_TEMPLATEHASH is only available in Tapscript. Note this is the exact same error + // returned in the default case, which would be the one hit by OP_SUCCESS187 before + // the introduction of OP_TEMPLATEHASH. + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) { + return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + } + + const uint256 template_hash{checker.GetTemplateHash(execdata)}; + stack.emplace_back(template_hash.begin(), template_hash.end()); + } + break; + default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } @@ -1698,6 +1712,7 @@ template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTr const HashWriter HASHER_TAPSIGHASH{TaggedHash("TapSighash")}; const HashWriter HASHER_TAPLEAF{TaggedHash("TapLeaf")}; const HashWriter HASHER_TAPBRANCH{TaggedHash("TapBranch")}; +const HashWriter HASHER_TEMPLATEHASH{TaggedHash("TemplateHash")}; static bool HandleMissingData(MissingDataBehavior mdb) { @@ -2098,6 +2113,28 @@ bool GenericTransactionSignatureChecker::CheckDefaultCheckTemplateVerifyHash( return HandleMissingData(m_mdb); } } + +template +uint256 GenericTransactionSignatureChecker::GetTemplateHash(ScriptExecutionData& execdata) const +{ + assert(txdata && txdata->m_bip341_taproot_ready); + assert(execdata.m_annex_init); + + HashWriter ss{HASHER_TEMPLATEHASH}; + ss << txTo->version; + ss << txTo->nLockTime; + ss << txdata->m_sequences_single_hash; + ss << txdata->m_outputs_single_hash; + const uint8_t annex_present = (execdata.m_annex_present ? 1 : 0); + ss << annex_present; + ss << nIn; + if (execdata.m_annex_present) { + ss << execdata.m_annex_hash; + } + + return ss.GetSHA256(); +} + // explicit instantiation template class GenericTransactionSignatureChecker; template class GenericTransactionSignatureChecker; diff --git a/src/script/interpreter.h b/src/script/interpreter.h index e334ed445fcc..7b06316d4feb 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -335,6 +335,11 @@ class BaseSignatureChecker return false; } + virtual uint256 GetTemplateHash(ScriptExecutionData& execdata) const + { + return {}; + } + virtual ~BaseSignatureChecker() = default; }; @@ -373,6 +378,7 @@ class GenericTransactionSignatureChecker : public BaseSignatureChecker bool CheckLockTime(const CScriptNum& nLockTime) const override; bool CheckSequence(const CScriptNum& nSequence) const override; bool CheckDefaultCheckTemplateVerifyHash(const Span& hash) const override; + uint256 GetTemplateHash(ScriptExecutionData& execdata) const override; }; using TransactionSignatureChecker = GenericTransactionSignatureChecker; @@ -404,6 +410,11 @@ class DeferringSignatureChecker : public BaseSignatureChecker { return m_checker.CheckSequence(nSequence); } + + uint256 GetTemplateHash(ScriptExecutionData& execdata) const override + { + return m_checker.GetTemplateHash(execdata); + } }; /** Compute the BIP341 tapleaf hash from leaf version & script. */ From 33192a8014669b80158cbcaceaecba2a8705f6d6 Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Wed, 11 Jun 2025 15:56:14 -0400 Subject: [PATCH 3/5] qa: unit tests to sanity check OP_TEMPLATEHASH Sanity check the template hash by using it to commit to the transaction that must spend an output. Malleating committed fields must lead to a consensus failure, and changing non-committed fields is fine. We also add the option to generate test vectors from this unit test. --- src/test/script_tests.cpp | 286 ++++++++++++++++++++++++++++++++++++++ src/test/util/script.h | 39 ++++++ 2 files changed, 325 insertions(+) diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index feaf56bacb4f..e1b910a0170e 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include