script: (optimization) introduce sighash midstate caching
What changed, and why it matters
This commit adds a performance optimization to Bitcoin Core's transaction-signature checking. It caches an intermediate SHA256 hashing state so that repeated signature checks for the same transaction do not need to recompute as much. There is no indication in the commit or supplied references that this fixes a security bug; it is described purely as an optimization.
No security action required. Treat as a routine performance optimization; standard code review and testing are sufficient.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces a SigHashCache class that stores up to six SHA256 HashWriter midstates keyed by sighash mode (ALL/SINGLE/NONE with/without ANYONECANPAY) and scriptCode. SignatureHash() now attempts to load a matching midstate before falling back to the full sighash computation, and stores the midstate after computing it. The cache is held per GenericTransactionSignatureChecker instance and is used for ECDSA sighashes (bare/P2SH/P2WPKH/P2WSH). The change is additive and does not alter consensus rules or signature semantics.
Changed components
src/script/interpreter.cppsrc/script/interpreter.hSignatureHash()GenericTransactionSignatureChecker::CheckECDSASignature()new SigHashCache classInspect captured patch +62 / −3
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
index 765c415e..07fda48c 100644
--- a/src/script/interpreter.cpp
+++ b/src/script/interpreter.cpp
@@ -1564,8 +1564,35 @@ bool SignatureHashSchnorr(uint256& hash_out, ScriptExecutionData& execdata, cons
return true;
}
+int SigHashCache::CacheIndex(int32_t hash_type) const noexcept
+{
+ // Note that we do not distinguish between BASE and WITNESS_V0 to determine the cache index,
+ // because no input can simultaneously use both.
+ return 3 * !!(hash_type & SIGHASH_ANYONECANPAY) +
+ 2 * ((hash_type & 0x1f) == SIGHASH_SINGLE) +
+ 1 * ((hash_type & 0x1f) == SIGHASH_NONE);
+}
+
+bool SigHashCache::Load(int32_t hash_type, const CScript& script_code, HashWriter& writer) const noexcept
+{
+ auto& entry = m_cache_entries[CacheIndex(hash_type)];
+ if (entry.has_value()) {
+ if (script_code == entry->first) {
+ writer = HashWriter(entry->second);
+ return true;
+ }
+ }
+ return false;
+}
+
+void SigHashCache::Store(int32_t hash_type, const CScript& script_code, const HashWriter& writer) noexcept
+{
+ auto& entry = m_cache_entries[CacheIndex(hash_type)];
+ entry.emplace(script_code, writer);
+}
+
template <class T>
-uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int32_t nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache)
+uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int32_t nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache, SigHashCache* sighash_cache)
{
assert(nIn < txTo.vin.size());
@@ -1581,6 +1608,13 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn
HashWriter ss{};
+ // Try to compute using cached SHA256 midstate.
+ if (sighash_cache && sighash_cache->Load(nHashType, scriptCode, ss)) {
+ // Add sighash type and hash.
+ ss << nHashType;
+ return ss.GetHash();
+ }
+
if (sigversion == SigVersion::WITNESS_V0) {
uint256 hashPrevouts;
uint256 hashSequence;
@@ -1627,6 +1661,11 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn
ss << txTmp;
}
+ // If a cache object was provided, store the midstate there.
+ if (sighash_cache != nullptr) {
+ sighash_cache->Store(nHashType, scriptCode, ss);
+ }
+
// Add sighash type and hash.
ss << nHashType;
return ss.GetHash();
@@ -1661,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);
+ 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 e8c5b090..2108e399 100644
--- a/src/script/interpreter.h
+++ b/src/script/interpreter.h
@@ -239,8 +239,27 @@ extern const HashWriter HASHER_TAPSIGHASH; //!< Hasher with tag "TapSighash" pre
extern const HashWriter HASHER_TAPLEAF; //!< Hasher with tag "TapLeaf" pre-fed to it.
extern const HashWriter HASHER_TAPBRANCH; //!< Hasher with tag "TapBranch" pre-fed to it.
+/** Data structure to cache SHA256 midstates for the ECDSA sighash calculations
+ * (bare, P2SH, P2WPKH, P2WSH). */
+class SigHashCache
+{
+ /** For each sighash mode (ALL, SINGLE, NONE, ALL|ANYONE, SINGLE|ANYONE, NONE|ANYONE),
+ * optionally store a scriptCode which the hash is for, plus a midstate for the SHA256
+ * computation just before adding the hash_type itself. */
+ std::optional<std::pair<CScript, HashWriter>> m_cache_entries[6];
+
+ /** Given a hash_type, find which of the 6 cache entries is to be used. */
+ int CacheIndex(int32_t hash_type) const noexcept;
+
+public:
+ /** Load into writer the SHA256 midstate if found in this cache. */
+ [[nodiscard]] bool Load(int32_t hash_type, const CScript& script_code, HashWriter& writer) const noexcept;
+ /** Store into this cache object the provided SHA256 midstate. */
+ void Store(int32_t hash_type, const CScript& script_code, const HashWriter& writer) noexcept;
+};
+
template <class T>
-uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int32_t nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr);
+uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int32_t nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr, SigHashCache* sighash_cache = nullptr);
class BaseSignatureChecker
{
@@ -289,6 +308,7 @@ private:
unsigned int nIn;
const CAmount amount;
const PrecomputedTransactionData* txdata;
+ mutable SigHashCache m_sighash_cache;
protected:
virtual bool VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
Why this scored 14/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.