Skip to content

Introduce per-txin sighash midstate cache for legacy/p2sh/segwitv0 scripts - #32473

Merged
fanquake merged 5 commits into
bitcoin:masterfrom
sipa:202504_sighash_cache
Aug 11, 2025
Merged

fanquake merged 5 commits into
bitcoin:masterfrom
sipa:202504_sighash_cache

Conversation

@sipa

@sipa sipa commented May 12, 2025 •

Copy link
Copy Markdown
Member

This introduces a per-txin cache for sighash midstate computation to the script interpreter for legacy (bare), P2SH, P2WSH, and (as collateral effect, but not actually useful) P2WPKH. This reduces the impact of certain types of quadratic hashing attacks that use standard transactions. It is not known to improve the situation for attacks involving non-standard transaction attacks.

The cache works by remembering for each of the 6 sighash modes a (scriptCode, midstate) tuple, which gives a midstate CSHA256 object right before the appending of the sighash type itself (to permit all 256, rather than just the 6 ones that match the modes). The midstate is only reused if the scriptCode matches. This works because - within a single input - only the sighash type and the scriptCode affect the actual sighash used.

The PR implements two different approaches:

  • The initial commits introduce the caching effect always, for both consensus and relay relation validation. Despite being primarily intended for improving the situation for standard transactions only, I chose this approach as the code paths are already largely common between the two, and this approach I believe involves fewer code changes than a more targetted approach, and furthermore, it should not hurt (it may even help common multisig cases slightly).
  • The final commit changes the behavior to only using the cache for non-consensus script validation. I'm open to feedback about whether adding this commit is worth it.

Functional tests are included that construct contrived cases with many sighash types (standard and non-standard ones) and OP_CODESEPARATORs in all script types (including P2TR, which isn't modified by this PR).

@DrahtBot

DrahtBot commented May 12, 2025 •

Copy link
Copy Markdown
Contributor

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32473.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK dergoegge, darosior, achow101
Concept ACK l0rinc

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #32301 (test: cover invalid codesep positions for signature in taproot by instagibbs)
  • #32247 (BIP-348 (OP_CHECKSIGFROMSTACK) (regtest only) by jamesob)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

Comment thread test/functional/feature_taproot.py Outdated

@darosior darosior left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat. Concept ACK.

I'm open to discussing alternatives, however.

I think enabling it by consensus is unnecessarily risky. I would rather take some slightly more complicate logic, for instance by having CScriptCheck own the sighash cache:

Click to see diff
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
index ffac2acb948..fe24dae6c54 100644
--- a/src/script/interpreter.cpp
+++ b/src/script/interpreter.cpp
@@ -1700,7 +1700,7 @@ bool GenericTransactionSignatureChecker<T>::CheckECDSASignature(const std::vecto
     // Witness sighashes need the amount.
     if (sigversion == SigVersion::WITNESS_V0 && amount < 0) return HandleMissingData(m_mdb);
 
-    uint256 sighash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, this->txdata, &m_sighash_cache);
+    uint256 sighash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, this->txdata, m_sighash_cache);
 
     if (!VerifyECDSASignature(vchSig, pubkey, sighash))
         return false;
diff --git a/src/script/interpreter.h b/src/script/interpreter.h
index 2108e3992fc..6c84c6dd74c 100644
--- a/src/script/interpreter.h
+++ b/src/script/interpreter.h
@@ -308,15 +308,15 @@ private:
     unsigned int nIn;
     const CAmount amount;
     const PrecomputedTransactionData* txdata;
-    mutable SigHashCache m_sighash_cache;
+    mutable SigHashCache* m_sighash_cache;
 
 protected:
     virtual bool VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
     virtual bool VerifySchnorrSignature(std::span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const;
 
 public:
-    GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, MissingDataBehavior mdb) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(nullptr) {}
-    GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn, MissingDataBehavior mdb) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {}
+    GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, MissingDataBehavior mdb, SigHashCache* sighash_cache = nullptr) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(nullptr), m_sighash_cache(sighash_cache) {}
+    GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn, MissingDataBehavior mdb, SigHashCache* sighash_cache = nullptr) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(&txdataIn), m_sighash_cache(sighash_cache) {}
     bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override;
     bool CheckSchnorrSignature(std::span<const unsigned char> sig, std::span<const unsigned char> pubkey, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror = nullptr) const override;
     bool CheckLockTime(const CScriptNum& nLockTime) const override;
diff --git a/src/script/sigcache.h b/src/script/sigcache.h
index ca4b44910e6..89aa5e85ad8 100644
--- a/src/script/sigcache.h
+++ b/src/script/sigcache.h
@@ -67,7 +67,7 @@ private:
     SignatureCache& m_signature_cache;
 
 public:
-    CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, bool storeIn, SignatureCache& signature_cache, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn, MissingDataBehavior::ASSERT_FAIL), store(storeIn), m_signature_cache(signature_cache)  {}
+    CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, bool storeIn, SignatureCache& signature_cache, PrecomputedTransactionData& txdataIn, SigHashCache* sighash_cache = nullptr) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn, MissingDataBehavior::ASSERT_FAIL, sighash_cache), store(storeIn), m_signature_cache(signature_cache) {}
 
     bool VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const override;
     bool VerifySchnorrSignature(std::span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const override;
diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp
index af36a956931..d2f97f47461 100644
--- a/src/test/txvalidationcache_tests.cpp
+++ b/src/test/txvalidationcache_tests.cpp
@@ -24,7 +24,8 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
                        const CCoinsViewCache& inputs, unsigned int flags, bool cacheSigStore,
                        bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
                        ValidationCache& validation_cache,
-                       std::vector<CScriptCheck>* pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+                       std::vector<CScriptCheck>* pvChecks,
+                       bool cache_sighash = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
 BOOST_AUTO_TEST_SUITE(txvalidationcache_tests)
 
diff --git a/src/validation.cpp b/src/validation.cpp
index 5ad2ebdcd7e..58bdc8baaf3 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -142,7 +142,8 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
                        const CCoinsViewCache& inputs, unsigned int flags, bool cacheSigStore,
                        bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
                        ValidationCache& validation_cache,
-                       std::vector<CScriptCheck>* pvChecks = nullptr)
+                       std::vector<CScriptCheck>* pvChecks = nullptr,
+                       bool cache_sighash = false)
                        EXCLUSIVE_LOCKS_REQUIRED(cs_main);
 
 bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& tx)
@@ -1237,13 +1238,13 @@ bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws)
 
     // Check input scripts and signatures.
     // This is done last to help prevent CPU exhaustion denial-of-service attacks.
-    if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, true, false, ws.m_precomputed_txdata, GetValidationCache())) {
+    if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, true, false, ws.m_precomputed_txdata, GetValidationCache(), /* pvChecks= */ nullptr, /* cache_sighash= */ true)) {
         // SCRIPT_VERIFY_CLEANSTACK requires SCRIPT_VERIFY_WITNESS, so we
         // need to turn both off, and compare against just turning off CLEANSTACK
         // to see if the failure is specifically due to witness validation.
         TxValidationState state_dummy; // Want reported failures to be from first CheckInputScripts
-        if (!tx.HasWitness() && CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, ws.m_precomputed_txdata, GetValidationCache()) &&
-                !CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, ws.m_precomputed_txdata, GetValidationCache())) {
+        if (!tx.HasWitness() && CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, ws.m_precomputed_txdata, GetValidationCache(), /* pvChecks= */ nullptr, /* cache_sighash= */ true) &&
+                !CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, ws.m_precomputed_txdata, GetValidationCache(), /* pvChecks= */ nullptr, /* cache_sighash= */ true)) {
             // Only the witness is missing, so the transaction itself may be fine.
             state.Invalid(TxValidationResult::TX_WITNESS_STRIPPED,
                     state.GetRejectReason(), state.GetDebugMessage());
@@ -2119,7 +2120,7 @@ std::optional<std::pair<ScriptError, std::string>> CScriptCheck::operator()() {
     const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
     const CScriptWitness *witness = &ptxTo->vin[nIn].scriptWitness;
     ScriptError error{SCRIPT_ERR_UNKNOWN_ERROR};
-    if (VerifyScript(scriptSig, m_tx_out.scriptPubKey, witness, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, m_tx_out.nValue, cacheStore, *m_signature_cache, *txdata), &error)) {
+    if (VerifyScript(scriptSig, m_tx_out.scriptPubKey, witness, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, m_tx_out.nValue, cacheStore, *m_signature_cache, *txdata, m_sighash_cache.get()), &error)) {
         return std::nullopt;
     } else {
         auto debug_str = strprintf("input %i of %s (wtxid %s), spending %s:%i", nIn, ptxTo->GetHash().ToString(), ptxTo->GetWitnessHash().ToString(), ptxTo->vin[nIn].prevout.hash.ToString(), ptxTo->vin[nIn].prevout.n);
@@ -2166,7 +2167,8 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
                        const CCoinsViewCache& inputs, unsigned int flags, bool cacheSigStore,
                        bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
                        ValidationCache& validation_cache,
-                       std::vector<CScriptCheck>* pvChecks)
+                       std::vector<CScriptCheck>* pvChecks,
+                       bool cache_sighash)
 {
     if (tx.IsCoinBase()) return true;
 
diff --git a/src/validation.h b/src/validation.h
index 7411ab400c2..842e3f0d28a 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -338,10 +338,11 @@ private:
     bool cacheStore;
     PrecomputedTransactionData *txdata;
     SignatureCache* m_signature_cache;
+    std::unique_ptr<SigHashCache> m_sighash_cache;
 
 public:
-    CScriptCheck(const CTxOut& outIn, const CTransaction& txToIn, SignatureCache& signature_cache, unsigned int nInIn, unsigned int nFlagsIn, bool cacheIn, PrecomputedTransactionData* txdataIn) :
-        m_tx_out(outIn), ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), txdata(txdataIn), m_signature_cache(&signature_cache) { }
+    CScriptCheck(const CTxOut& outIn, const CTransaction& txToIn, SignatureCache& signature_cache, unsigned int nInIn, unsigned int nFlagsIn, bool cacheIn, PrecomputedTransactionData* txdataIn, bool cache_sighash = false) :
+        m_tx_out(outIn), ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), txdata(txdataIn), m_signature_cache(&signature_cache), m_sighash_cache(cache_sighash ? std::make_unique<SigHashCache>() : nullptr) { }
 
     CScriptCheck(const CScriptCheck&) = delete;
     CScriptCheck& operator=(const CScriptCheck&) = delete;

Comment thread src/script/interpreter.cpp Outdated
@instagibbs

Copy link
Copy Markdown
Member

@darosior just noting the tests are specifically written to not worry about standardness, so at least these would have to be augmented to cover the cases this PR is ostensibly supporting

@sipa

sipa commented May 13, 2025

Copy link
Copy Markdown
Member Author

@instagibbs Yes and no, depending on the meaning of standardness I guess. The worrisome transactions are non-standard (in the sense that they are not relayed/accepted), but are standard in the IsStandard() sense, and thus not immediately rejected before script execution even begins.

@sipa
sipa force-pushed the 202504_sighash_cache branch from 26fced4 to dac139b Compare May 13, 2025 18:29
Comment thread test/functional/feature_taproot.py Outdated
@theuni

theuni commented May 13, 2025 •

Copy link
Copy Markdown
Member

Hmm, not relevant to what's being addressed here, but couldn't the per-tx cache->hashPrevouts and cache->hashSequence be replaced with a similar midstate cache trick?

Edit: Given their size I guess that's not nearly as interesting.

@sipa

sipa commented Jul 9, 2025

Copy link
Copy Markdown
Member Author

@darosior I did it slightly differently, but added a commit that makes the use of the sighash cache optional. I avoided using default values as they can result in unexpected/missed call sites.

@sipa
sipa force-pushed the 202504_sighash_cache branch from e1f8349 to 0b646fa Compare July 9, 2025 21:32
@DrahtBot

DrahtBot commented Jul 9, 2025

Copy link
Copy Markdown
Contributor

🚧 At least one of the CI tasks failed.
Task tidy: https://github.com/bitcoin/bitcoin/runs/45674313491
LLM reason (✨ experimental): Clang-tidy reported argument comment mismatches caused by warnings treated as errors, leading to build failure.

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Nov 5, 2025
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Nov 5, 2025
tomt1664 pushed a commit to tomt1664/elements that referenced this pull request Nov 20, 2025
…e for legacy/p2sh/segwitv0 scripts

8395027 qa: unit test sighash caching (Antoine Poinsot)
b221aa8 qa: simple differential fuzzing for sighash with/without caching (Antoine Poinsot)
92af9f7 script: (optimization) introduce sighash midstate caching (Pieter Wuille)
8f3ddb0 script: (refactor) prepare for introducing sighash midstate cache (Pieter Wuille)
9014d40 tests: add sighash caching tests to feature_taproot (Pieter Wuille)

Pull request description:

  This introduces a per-txin cache for sighash midstate computation to the script interpreter for legacy (bare), P2SH, P2WSH, and (as collateral effect, but not actually useful) P2WPKH. This reduces the impact of certain types of quadratic hashing attacks that use standard transactions. It is not known to improve the situation for attacks involving non-standard transaction attacks.

  The cache works by remembering for each of the 6 sighash modes a `(scriptCode, midstate)` tuple, which gives a midstate `CSHA256` object right before the appending of the sighash type itself (to permit all 256, rather than just the 6 ones that match the modes). The midstate is only reused if the `scriptCode` matches. This works because - within a single input - only the sighash type and the `scriptCode` affect the actual sighash used.

  The PR implements two different approaches:
  * The initial commits introduce the caching effect always, for both consensus and relay relation validation. Despite being primarily intended for improving the situation for standard transactions only, I chose this approach as the code paths are already largely common between the two, and this approach I believe involves fewer code changes than a more targetted approach, and furthermore, it should not hurt (it may even help common multisig cases slightly).
  * The final commit changes the behavior to only using the cache for non-consensus script validation. I'm open to feedback about whether adding this commit is worth it.

  Functional tests are included that construct contrived cases with many sighash types (standard and non-standard ones) and `OP_CODESEPARATOR`s in all script types (including P2TR, which isn't modified by this PR).

ACKs for top commit:
  achow101:
    ACK 8395027
  dergoegge:
    Code review ACK 8395027
  darosior:
    re-ACK 8395027

Tree-SHA512: 65ae8635429a4d563b19969bac8128038ac2cbe01d9c9946abd4cac3c0780974d1e8b9aae9bb83f414e5d247a59f4a18fef5b37d93ad59ed41b6f11c3fe05af4
tomt1664 pushed a commit to tomt1664/elements that referenced this pull request Nov 20, 2025
…e for legacy/p2sh/segwitv0 scripts

8395027 qa: unit test sighash caching (Antoine Poinsot)
b221aa8 qa: simple differential fuzzing for sighash with/without caching (Antoine Poinsot)
92af9f7 script: (optimization) introduce sighash midstate caching (Pieter Wuille)
8f3ddb0 script: (refactor) prepare for introducing sighash midstate cache (Pieter Wuille)
9014d40 tests: add sighash caching tests to feature_taproot (Pieter Wuille)

Pull request description:

  This introduces a per-txin cache for sighash midstate computation to the script interpreter for legacy (bare), P2SH, P2WSH, and (as collateral effect, but not actually useful) P2WPKH. This reduces the impact of certain types of quadratic hashing attacks that use standard transactions. It is not known to improve the situation for attacks involving non-standard transaction attacks.

  The cache works by remembering for each of the 6 sighash modes a `(scriptCode, midstate)` tuple, which gives a midstate `CSHA256` object right before the appending of the sighash type itself (to permit all 256, rather than just the 6 ones that match the modes). The midstate is only reused if the `scriptCode` matches. This works because - within a single input - only the sighash type and the `scriptCode` affect the actual sighash used.

  The PR implements two different approaches:
  * The initial commits introduce the caching effect always, for both consensus and relay relation validation. Despite being primarily intended for improving the situation for standard transactions only, I chose this approach as the code paths are already largely common between the two, and this approach I believe involves fewer code changes than a more targetted approach, and furthermore, it should not hurt (it may even help common multisig cases slightly).
  * The final commit changes the behavior to only using the cache for non-consensus script validation. I'm open to feedback about whether adding this commit is worth it.

  Functional tests are included that construct contrived cases with many sighash types (standard and non-standard ones) and `OP_CODESEPARATOR`s in all script types (including P2TR, which isn't modified by this PR).

ACKs for top commit:
  achow101:
    ACK 8395027
  dergoegge:
    Code review ACK 8395027
  darosior:
    re-ACK 8395027

Tree-SHA512: 65ae8635429a4d563b19969bac8128038ac2cbe01d9c9946abd4cac3c0780974d1e8b9aae9bb83f414e5d247a59f4a18fef5b37d93ad59ed41b6f11c3fe05af4
tomt1664 pushed a commit to tomt1664/elements that referenced this pull request Nov 20, 2025
…e for legacy/p2sh/segwitv0 scripts

8395027 qa: unit test sighash caching (Antoine Poinsot)
b221aa8 qa: simple differential fuzzing for sighash with/without caching (Antoine Poinsot)
92af9f7 script: (optimization) introduce sighash midstate caching (Pieter Wuille)
8f3ddb0 script: (refactor) prepare for introducing sighash midstate cache (Pieter Wuille)
9014d40 tests: add sighash caching tests to feature_taproot (Pieter Wuille)

Pull request description:

  This introduces a per-txin cache for sighash midstate computation to the script interpreter for legacy (bare), P2SH, P2WSH, and (as collateral effect, but not actually useful) P2WPKH. This reduces the impact of certain types of quadratic hashing attacks that use standard transactions. It is not known to improve the situation for attacks involving non-standard transaction attacks.

  The cache works by remembering for each of the 6 sighash modes a `(scriptCode, midstate)` tuple, which gives a midstate `CSHA256` object right before the appending of the sighash type itself (to permit all 256, rather than just the 6 ones that match the modes). The midstate is only reused if the `scriptCode` matches. This works because - within a single input - only the sighash type and the `scriptCode` affect the actual sighash used.

  The PR implements two different approaches:
  * The initial commits introduce the caching effect always, for both consensus and relay relation validation. Despite being primarily intended for improving the situation for standard transactions only, I chose this approach as the code paths are already largely common between the two, and this approach I believe involves fewer code changes than a more targetted approach, and furthermore, it should not hurt (it may even help common multisig cases slightly).
  * The final commit changes the behavior to only using the cache for non-consensus script validation. I'm open to feedback about whether adding this commit is worth it.

  Functional tests are included that construct contrived cases with many sighash types (standard and non-standard ones) and `OP_CODESEPARATOR`s in all script types (including P2TR, which isn't modified by this PR).

ACKs for top commit:
  achow101:
    ACK 8395027
  dergoegge:
    Code review ACK 8395027
  darosior:
    re-ACK 8395027

Tree-SHA512: 65ae8635429a4d563b19969bac8128038ac2cbe01d9c9946abd4cac3c0780974d1e8b9aae9bb83f414e5d247a59f4a18fef5b37d93ad59ed41b6f11c3fe05af4
tomt1664 pushed a commit to tomt1664/elements that referenced this pull request Nov 21, 2025
…e for legacy/p2sh/segwitv0 scripts

8395027 qa: unit test sighash caching (Antoine Poinsot)
b221aa8 qa: simple differential fuzzing for sighash with/without caching (Antoine Poinsot)
92af9f7 script: (optimization) introduce sighash midstate caching (Pieter Wuille)
8f3ddb0 script: (refactor) prepare for introducing sighash midstate cache (Pieter Wuille)
9014d40 tests: add sighash caching tests to feature_taproot (Pieter Wuille)

Pull request description:

  This introduces a per-txin cache for sighash midstate computation to the script interpreter for legacy (bare), P2SH, P2WSH, and (as collateral effect, but not actually useful) P2WPKH. This reduces the impact of certain types of quadratic hashing attacks that use standard transactions. It is not known to improve the situation for attacks involving non-standard transaction attacks.

  The cache works by remembering for each of the 6 sighash modes a `(scriptCode, midstate)` tuple, which gives a midstate `CSHA256` object right before the appending of the sighash type itself (to permit all 256, rather than just the 6 ones that match the modes). The midstate is only reused if the `scriptCode` matches. This works because - within a single input - only the sighash type and the `scriptCode` affect the actual sighash used.

  The PR implements two different approaches:
  * The initial commits introduce the caching effect always, for both consensus and relay relation validation. Despite being primarily intended for improving the situation for standard transactions only, I chose this approach as the code paths are already largely common between the two, and this approach I believe involves fewer code changes than a more targetted approach, and furthermore, it should not hurt (it may even help common multisig cases slightly).
  * The final commit changes the behavior to only using the cache for non-consensus script validation. I'm open to feedback about whether adding this commit is worth it.

  Functional tests are included that construct contrived cases with many sighash types (standard and non-standard ones) and `OP_CODESEPARATOR`s in all script types (including P2TR, which isn't modified by this PR).

ACKs for top commit:
  achow101:
    ACK 8395027
  dergoegge:
    Code review ACK 8395027
  darosior:
    re-ACK 8395027

Tree-SHA512: 65ae8635429a4d563b19969bac8128038ac2cbe01d9c9946abd4cac3c0780974d1e8b9aae9bb83f414e5d247a59f4a18fef5b37d93ad59ed41b6f11c3fe05af4
tomt1664 pushed a commit to tomt1664/elements that referenced this pull request Nov 21, 2025
…e for legacy/p2sh/segwitv0 scripts

8395027 qa: unit test sighash caching (Antoine Poinsot)
b221aa8 qa: simple differential fuzzing for sighash with/without caching (Antoine Poinsot)
92af9f7 script: (optimization) introduce sighash midstate caching (Pieter Wuille)
8f3ddb0 script: (refactor) prepare for introducing sighash midstate cache (Pieter Wuille)
9014d40 tests: add sighash caching tests to feature_taproot (Pieter Wuille)

Pull request description:

  This introduces a per-txin cache for sighash midstate computation to the script interpreter for legacy (bare), P2SH, P2WSH, and (as collateral effect, but not actually useful) P2WPKH. This reduces the impact of certain types of quadratic hashing attacks that use standard transactions. It is not known to improve the situation for attacks involving non-standard transaction attacks.

  The cache works by remembering for each of the 6 sighash modes a `(scriptCode, midstate)` tuple, which gives a midstate `CSHA256` object right before the appending of the sighash type itself (to permit all 256, rather than just the 6 ones that match the modes). The midstate is only reused if the `scriptCode` matches. This works because - within a single input - only the sighash type and the `scriptCode` affect the actual sighash used.

  The PR implements two different approaches:
  * The initial commits introduce the caching effect always, for both consensus and relay relation validation. Despite being primarily intended for improving the situation for standard transactions only, I chose this approach as the code paths are already largely common between the two, and this approach I believe involves fewer code changes than a more targetted approach, and furthermore, it should not hurt (it may even help common multisig cases slightly).
  * The final commit changes the behavior to only using the cache for non-consensus script validation. I'm open to feedback about whether adding this commit is worth it.

  Functional tests are included that construct contrived cases with many sighash types (standard and non-standard ones) and `OP_CODESEPARATOR`s in all script types (including P2TR, which isn't modified by this PR).

ACKs for top commit:
  achow101:
    ACK 8395027
  dergoegge:
    Code review ACK 8395027
  darosior:
    re-ACK 8395027

Tree-SHA512: 65ae8635429a4d563b19969bac8128038ac2cbe01d9c9946abd4cac3c0780974d1e8b9aae9bb83f414e5d247a59f4a18fef5b37d93ad59ed41b6f11c3fe05af4
tomt1664 pushed a commit to tomt1664/elements that referenced this pull request Nov 21, 2025
…e for legacy/p2sh/segwitv0 scripts

8395027 qa: unit test sighash caching (Antoine Poinsot)
b221aa8 qa: simple differential fuzzing for sighash with/without caching (Antoine Poinsot)
92af9f7 script: (optimization) introduce sighash midstate caching (Pieter Wuille)
8f3ddb0 script: (refactor) prepare for introducing sighash midstate cache (Pieter Wuille)
9014d40 tests: add sighash caching tests to feature_taproot (Pieter Wuille)

Pull request description:

  This introduces a per-txin cache for sighash midstate computation to the script interpreter for legacy (bare), P2SH, P2WSH, and (as collateral effect, but not actually useful) P2WPKH. This reduces the impact of certain types of quadratic hashing attacks that use standard transactions. It is not known to improve the situation for attacks involving non-standard transaction attacks.

  The cache works by remembering for each of the 6 sighash modes a `(scriptCode, midstate)` tuple, which gives a midstate `CSHA256` object right before the appending of the sighash type itself (to permit all 256, rather than just the 6 ones that match the modes). The midstate is only reused if the `scriptCode` matches. This works because - within a single input - only the sighash type and the `scriptCode` affect the actual sighash used.

  The PR implements two different approaches:
  * The initial commits introduce the caching effect always, for both consensus and relay relation validation. Despite being primarily intended for improving the situation for standard transactions only, I chose this approach as the code paths are already largely common between the two, and this approach I believe involves fewer code changes than a more targetted approach, and furthermore, it should not hurt (it may even help common multisig cases slightly).
  * The final commit changes the behavior to only using the cache for non-consensus script validation. I'm open to feedback about whether adding this commit is worth it.

  Functional tests are included that construct contrived cases with many sighash types (standard and non-standard ones) and `OP_CODESEPARATOR`s in all script types (including P2TR, which isn't modified by this PR).

ACKs for top commit:
  achow101:
    ACK 8395027
  dergoegge:
    Code review ACK 8395027
  darosior:
    re-ACK 8395027

Tree-SHA512: 65ae8635429a4d563b19969bac8128038ac2cbe01d9c9946abd4cac3c0780974d1e8b9aae9bb83f414e5d247a59f4a18fef5b37d93ad59ed41b6f11c3fe05af4
tomt1664 pushed a commit to tomt1664/elements that referenced this pull request Nov 21, 2025
…e for legacy/p2sh/segwitv0 scripts

8395027 qa: unit test sighash caching (Antoine Poinsot)
b221aa8 qa: simple differential fuzzing for sighash with/without caching (Antoine Poinsot)
92af9f7 script: (optimization) introduce sighash midstate caching (Pieter Wuille)
8f3ddb0 script: (refactor) prepare for introducing sighash midstate cache (Pieter Wuille)
9014d40 tests: add sighash caching tests to feature_taproot (Pieter Wuille)

Pull request description:

  This introduces a per-txin cache for sighash midstate computation to the script interpreter for legacy (bare), P2SH, P2WSH, and (as collateral effect, but not actually useful) P2WPKH. This reduces the impact of certain types of quadratic hashing attacks that use standard transactions. It is not known to improve the situation for attacks involving non-standard transaction attacks.

  The cache works by remembering for each of the 6 sighash modes a `(scriptCode, midstate)` tuple, which gives a midstate `CSHA256` object right before the appending of the sighash type itself (to permit all 256, rather than just the 6 ones that match the modes). The midstate is only reused if the `scriptCode` matches. This works because - within a single input - only the sighash type and the `scriptCode` affect the actual sighash used.

  The PR implements two different approaches:
  * The initial commits introduce the caching effect always, for both consensus and relay relation validation. Despite being primarily intended for improving the situation for standard transactions only, I chose this approach as the code paths are already largely common between the two, and this approach I believe involves fewer code changes than a more targetted approach, and furthermore, it should not hurt (it may even help common multisig cases slightly).
  * The final commit changes the behavior to only using the cache for non-consensus script validation. I'm open to feedback about whether adding this commit is worth it.

  Functional tests are included that construct contrived cases with many sighash types (standard and non-standard ones) and `OP_CODESEPARATOR`s in all script types (including P2TR, which isn't modified by this PR).

ACKs for top commit:
  achow101:
    ACK 8395027
  dergoegge:
    Code review ACK 8395027
  darosior:
    re-ACK 8395027

Tree-SHA512: 65ae8635429a4d563b19969bac8128038ac2cbe01d9c9946abd4cac3c0780974d1e8b9aae9bb83f414e5d247a59f4a18fef5b37d93ad59ed41b6f11c3fe05af4
tomt1664 pushed a commit to tomt1664/elements that referenced this pull request Nov 21, 2025
…e for legacy/p2sh/segwitv0 scripts

8395027 qa: unit test sighash caching (Antoine Poinsot)
b221aa8 qa: simple differential fuzzing for sighash with/without caching (Antoine Poinsot)
92af9f7 script: (optimization) introduce sighash midstate caching (Pieter Wuille)
8f3ddb0 script: (refactor) prepare for introducing sighash midstate cache (Pieter Wuille)
9014d40 tests: add sighash caching tests to feature_taproot (Pieter Wuille)

Pull request description:

  This introduces a per-txin cache for sighash midstate computation to the script interpreter for legacy (bare), P2SH, P2WSH, and (as collateral effect, but not actually useful) P2WPKH. This reduces the impact of certain types of quadratic hashing attacks that use standard transactions. It is not known to improve the situation for attacks involving non-standard transaction attacks.

  The cache works by remembering for each of the 6 sighash modes a `(scriptCode, midstate)` tuple, which gives a midstate `CSHA256` object right before the appending of the sighash type itself (to permit all 256, rather than just the 6 ones that match the modes). The midstate is only reused if the `scriptCode` matches. This works because - within a single input - only the sighash type and the `scriptCode` affect the actual sighash used.

  The PR implements two different approaches:
  * The initial commits introduce the caching effect always, for both consensus and relay relation validation. Despite being primarily intended for improving the situation for standard transactions only, I chose this approach as the code paths are already largely common between the two, and this approach I believe involves fewer code changes than a more targetted approach, and furthermore, it should not hurt (it may even help common multisig cases slightly).
  * The final commit changes the behavior to only using the cache for non-consensus script validation. I'm open to feedback about whether adding this commit is worth it.

  Functional tests are included that construct contrived cases with many sighash types (standard and non-standard ones) and `OP_CODESEPARATOR`s in all script types (including P2TR, which isn't modified by this PR).

ACKs for top commit:
  achow101:
    ACK 8395027
  dergoegge:
    Code review ACK 8395027
  darosior:
    re-ACK 8395027

Tree-SHA512: 65ae8635429a4d563b19969bac8128038ac2cbe01d9c9946abd4cac3c0780974d1e8b9aae9bb83f414e5d247a59f4a18fef5b37d93ad59ed41b6f11c3fe05af4
delta1 added a commit to delta1/elements that referenced this pull request Nov 27, 2025
… for legacy/p2sh/segwitv0 scripts

eb64d24 Merge bitcoin/bitcoin#32473: Introduce per-txin sighash midstate cache for legacy/p2sh/segwitv0 scripts (merge-script)

Pull request description:

  Backport of bitcoin/bitcoin#32473

  Additional cache index bit added for sighash combination with `SIGHASH_RANGEPROOF`
delta1 added a commit to ElementsProject/elements that referenced this pull request Nov 27, 2025
…h/segwitv0 scripts

eb64d24 Merge bitcoin/bitcoin#32473: Introduce per-txin sighash midstate cache for legacy/p2sh/segwitv0 scripts (merge-script)

Pull request description:

  Backport of bitcoin/bitcoin#32473

  Additional cache index bit added for sighash combination with `SIGHASH_RANGEPROOF`

ACKs for top commit:
  delta1:
    ACK eb64d24

Tree-SHA512: 94014e726ff16473809672f3e01a8e4d11c4abd38b3ae3ae64b8b9ae61be2e3c246157dca5a3a3afc4b85cc8ec69e23181af8f11c36e0b43cfac2dcb3e2e8d70
morozow pushed a commit to morozow/bitcoin_rd that referenced this pull request May 8, 2026
morozow pushed a commit to morozow/bitcoin_rd that referenced this pull request May 8, 2026
morozow pushed a commit to morozow/bitcoin_rd that referenced this pull request May 8, 2026
morozow pushed a commit to morozow/bitcoin_rd that referenced this pull request May 8, 2026
morozow pushed a commit to morozow/bitcoin_rd that referenced this pull request May 8, 2026
roqqit pushed a commit to doged-io/doged that referenced this pull request Jun 4, 2026
Summary:
This is a partial backport of [[bitcoin/bitcoin#32473 | core#32473]]
bitcoin/bitcoin@8f3ddb0

Test Plan: `ninja all check-all`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Differential Revision: https://reviews.bitcoinabc.org/D18848
roqqit pushed a commit to doged-io/doged that referenced this pull request Jun 4, 2026
Summary:
This introduces a per-txin cache for sighash midstate computation to the script interpreter for legacy (bare) and P2SH.
This reduces the impact of certain types of quadratic hashing attacks that use standard transactions. It is not known to improve the situation for attacks involving non-standard transaction attacks.

The cache works by remembering for each of the 12 sighash modes a (scriptCode, midstate) tuple, which gives a midstate CSHA256 object right before the appending of the sighash type itself (to permit all 256, rather than just the 12 ones that match the modes). The midstate is only reused if the scriptCode matches. This works because - within a single input - only the sighash type and the scriptCode affect the actual sighash used.

This concludes backport of [[bitcoin/bitcoin#32473 | core#32473]]
bitcoin/bitcoin@92af9f7
bitcoin/bitcoin@b221aa8
bitcoin/bitcoin@8395027

Depends on D18848

Test Plan: `ninja all check-all bitcoin-fuzzers`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Subscribers: Fabien

Differential Revision: https://reviews.bitcoinabc.org/D18849
Kino1994 pushed a commit to Kino1994/bitcoin-full-history that referenced this pull request Jun 28, 2026
…e for legacy/p2sh/segwitv0 scripts

c32ada9 qa: unit test sighash caching (Antoine Poinsot)
26f3783 qa: simple differential fuzzing for sighash with/without caching (Antoine Poinsot)
c01fc2f script: (optimization) introduce sighash midstate caching (Pieter Wuille)
7ad58bd script: (refactor) prepare for introducing sighash midstate cache (Pieter Wuille)
1208aa1 tests: add sighash caching tests to feature_taproot (Pieter Wuille)

Pull request description:

  This introduces a per-txin cache for sighash midstate computation to the script interpreter for legacy (bare), P2SH, P2WSH, and (as collateral effect, but not actually useful) P2WPKH. This reduces the impact of certain types of quadratic hashing attacks that use standard transactions. It is not known to improve the situation for attacks involving non-standard transaction attacks.

  The cache works by remembering for each of the 6 sighash modes a `(scriptCode, midstate)` tuple, which gives a midstate `CSHA256` object right before the appending of the sighash type itself (to permit all 256, rather than just the 6 ones that match the modes). The midstate is only reused if the `scriptCode` matches. This works because - within a single input - only the sighash type and the `scriptCode` affect the actual sighash used.

  The PR implements two different approaches:
  * The initial commits introduce the caching effect always, for both consensus and relay relation validation. Despite being primarily intended for improving the situation for standard transactions only, I chose this approach as the code paths are already largely common between the two, and this approach I believe involves fewer code changes than a more targetted approach, and furthermore, it should not hurt (it may even help common multisig cases slightly).
  * The final commit changes the behavior to only using the cache for non-consensus script validation. I'm open to feedback about whether adding this commit is worth it.

  Functional tests are included that construct contrived cases with many sighash types (standard and non-standard ones) and `OP_CODESEPARATOR`s in all script types (including P2TR, which isn't modified by this PR).

ACKs for top commit:
  achow101:
    ACK c32ada9
  dergoegge:
    Code review ACK c32ada9
  darosior:
    re-ACK c32ada9

Tree-SHA512: 65ae8635429a4d563b19969bac8128038ac2cbe01d9c9946abd4cac3c0780974d1e8b9aae9bb83f414e5d247a59f4a18fef5b37d93ad59ed41b6f11c3fe05af4
BigcoinBGC pushed a commit to BigcoinBGC/bigcoin that referenced this pull request Jun 30, 2026
…e for legacy/p2sh/segwitv0 scripts

c084d94 qa: unit test sighash caching (Antoine Poinsot)
3318cdc qa: simple differential fuzzing for sighash with/without caching (Antoine Poinsot)
793934b script: (optimization) introduce sighash midstate caching (Pieter Wuille)
21c913b script: (refactor) prepare for introducing sighash midstate cache (Pieter Wuille)
2dd75a6 tests: add sighash caching tests to feature_taproot (Pieter Wuille)

Pull request description:

  This introduces a per-txin cache for sighash midstate computation to the script interpreter for legacy (bare), P2SH, P2WSH, and (as collateral effect, but not actually useful) P2WPKH. This reduces the impact of certain types of quadratic hashing attacks that use standard transactions. It is not known to improve the situation for attacks involving non-standard transaction attacks.

  The cache works by remembering for each of the 6 sighash modes a `(scriptCode, midstate)` tuple, which gives a midstate `CSHA256` object right before the appending of the sighash type itself (to permit all 256, rather than just the 6 ones that match the modes). The midstate is only reused if the `scriptCode` matches. This works because - within a single input - only the sighash type and the `scriptCode` affect the actual sighash used.

  The PR implements two different approaches:
  * The initial commits introduce the caching effect always, for both consensus and relay relation validation. Despite being primarily intended for improving the situation for standard transactions only, I chose this approach as the code paths are already largely common between the two, and this approach I believe involves fewer code changes than a more targetted approach, and furthermore, it should not hurt (it may even help common multisig cases slightly).
  * The final commit changes the behavior to only using the cache for non-consensus script validation. I'm open to feedback about whether adding this commit is worth it.

  Functional tests are included that construct contrived cases with many sighash types (standard and non-standard ones) and `OP_CODESEPARATOR`s in all script types (including P2TR, which isn't modified by this PR).

ACKs for top commit:
  achow101:
    ACK c084d94
  dergoegge:
    Code review ACK c084d94
  darosior:
    re-ACK c084d94

Tree-SHA512: 65ae8635429a4d563b19969bac8128038ac2cbe01d9c9946abd4cac3c0780974d1e8b9aae9bb83f414e5d247a59f4a18fef5b37d93ad59ed41b6f11c3fe05af4
Kino1994 pushed a commit to Kino1994/bitcoin-full-history that referenced this pull request Aug 19, 2026
…e for legacy/p2sh/segwitv0 scripts

83950275eddacac56c58a7a3648ed435a5593328 qa: unit test sighash caching (Antoine Poinsot)
b221aa80a081579b8d3b460e3403f7ac0daa7139 qa: simple differential fuzzing for sighash with/without caching (Antoine Poinsot)
92af9f74d74e76681f7d98f293eab226972137b4 script: (optimization) introduce sighash midstate caching (Pieter Wuille)
8f3ddb0bccebc930836b4a6745a7cf29b41eb302 script: (refactor) prepare for introducing sighash midstate cache (Pieter Wuille)
9014d4016ad9351cb59b587541895e55f5d589cc tests: add sighash caching tests to feature_taproot (Pieter Wuille)

Pull request description:

  This introduces a per-txin cache for sighash midstate computation to the script interpreter for legacy (bare), P2SH, P2WSH, and (as collateral effect, but not actually useful) P2WPKH. This reduces the impact of certain types of quadratic hashing attacks that use standard transactions. It is not known to improve the situation for attacks involving non-standard transaction attacks.

  The cache works by remembering for each of the 6 sighash modes a `(scriptCode, midstate)` tuple, which gives a midstate `CSHA256` object right before the appending of the sighash type itself (to permit all 256, rather than just the 6 ones that match the modes). The midstate is only reused if the `scriptCode` matches. This works because - within a single input - only the sighash type and the `scriptCode` affect the actual sighash used.

  The PR implements two different approaches:
  * The initial commits introduce the caching effect always, for both consensus and relay relation validation. Despite being primarily intended for improving the situation for standard transactions only, I chose this approach as the code paths are already largely common between the two, and this approach I believe involves fewer code changes than a more targetted approach, and furthermore, it should not hurt (it may even help common multisig cases slightly).
  * The final commit changes the behavior to only using the cache for non-consensus script validation. I'm open to feedback about whether adding this commit is worth it.

  Functional tests are included that construct contrived cases with many sighash types (standard and non-standard ones) and `OP_CODESEPARATOR`s in all script types (including P2TR, which isn't modified by this PR).

ACKs for top commit:
  achow101:
    ACK 83950275eddacac56c58a7a3648ed435a5593328
  dergoegge:
    Code review ACK 83950275eddacac56c58a7a3648ed435a5593328
  darosior:
    re-ACK 83950275eddacac56c58a7a3648ed435a5593328

Tree-SHA512: 65ae8635429a4d563b19969bac8128038ac2cbe01d9c9946abd4cac3c0780974d1e8b9aae9bb83f414e5d247a59f4a18fef5b37d93ad59ed41b6f11c3fe05af4
delta1 pushed a commit to ElementsProject/elements that referenced this pull request Aug 28, 2026
…PROOF keying

Backport of bitcoin/bitcoin#32473 as integrated on elements-23.x
(eb64d24), reapplied against master's tree.

The SIGHASH_RANGEPROOF (0x40) bit changes the sighash preimage: segwit v0
appends hashRangeproofs, and the legacy serializer appends each output's
rangeproof and surjectionproof. It is therefore part of the cache key, and
the table has 16 entries rather than upstream's 6. Dropping that term is a
consensus split with deployed elements-23.x in both directions.

Upstream added SigHashCache as a pure addition, so a merge of this file will
auto-merge without prompting for the Elements adaptation. Hence the explicit
markers.
delta1 added a commit to ElementsProject/elements that referenced this pull request Aug 28, 2026
…h/segwitv0 scripts

ec89c80 Add test that sighash midstate cache treats the SIGHASH_RANGEPROOF bit as part of its key (Tom Trevethan)
1484750 tests: add sighash caching tests to feature_taproot (Pieter Wuille)
903be1e ELEMENTS: backport per-txin sighash midstate cache with SIGHASH_RANGEPROOF keying (Tom Trevethan)

Pull request description:

  Merge of bitcoin/bitcoin#32473 with additional cache index bit added for sighash combination with SIGHASH_RANGEPROOF.

ACKs for top commit:
  delta1:
    ACK 9631bd3; tested locally

Tree-SHA512: 71263dd590b419aba19a3a5f9b492e5616fc074e951348f164c13833797ad9a5486c9eb808e474a939a4b8b30e258409dfcb9475e46e7ceff6befc58f5516699
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants