What changed, and why it matters
This commit adds a new automated test file for Bitcoin Core. It does not change any production code, network rules, or wallet behavior. The test verifies that an internal caching optimization for signature hash calculations returns correct results and does not accidentally reuse values when inputs change. There is no security fix or vulnerability here.
No action required. This is a quality-assurance test addition. Reviewers may optionally run the new unit test to confirm it passes.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff adds a single BOOST_AUTO_TEST_CASE named sighash_caching to src/test/sighash_tests.cpp. The test exercises SignatureHash() with and without a SigHashCache across legacy and segwit v0 sighash versions, multiple hash types, and different scriptcodes. It asserts cache/non-cache parity, cache reuse, cache invalidation on scriptcode change, and manual cache mutation behavior. No implementation code is modified.
Changed components
src/test/sighash_tests.cppInspect captured patch +90 / −0
diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp
index d3320878..6e2ec800 100644
--- a/src/test/sighash_tests.cpp
+++ b/src/test/sighash_tests.cpp
@@ -207,4 +207,94 @@ BOOST_AUTO_TEST_CASE(sighash_from_data)
BOOST_CHECK_MESSAGE(sh.GetHex() == sigHashHex, strTest);
}
}
+
+BOOST_AUTO_TEST_CASE(sighash_caching)
+{
+ // Get a script, transaction and parameters as inputs to the sighash function.
+ CScript scriptcode;
+ RandomScript(scriptcode);
+ CScript diff_scriptcode{scriptcode};
+ diff_scriptcode << OP_1;
+ CMutableTransaction tx;
+ RandomTransaction(tx, /*fSingle=*/false);
+ const auto in_index{static_cast<uint32_t>(m_rng.randrange(tx.vin.size()))};
+ const auto amount{m_rng.rand<CAmount>()};
+
+ // Exercise the sighash function under both legacy and segwit v0.
+ for (const auto sigversion: {SigVersion::BASE, SigVersion::WITNESS_V0}) {
+ // For each, run it against all the 6 standard hash types and a few additional random ones.
+ std::vector<int32_t> hash_types{{SIGHASH_ALL, SIGHASH_SINGLE, SIGHASH_NONE, SIGHASH_ALL | SIGHASH_ANYONECANPAY,
+ SIGHASH_SINGLE | SIGHASH_ANYONECANPAY, SIGHASH_NONE | SIGHASH_ANYONECANPAY,
+ SIGHASH_ANYONECANPAY, 0, std::numeric_limits<int32_t>::max()}};
+ for (int i{0}; i < 10; ++i) {
+ hash_types.push_back(i % 2 == 0 ? m_rng.rand<int8_t>() : m_rng.rand<int32_t>());
+ }
+
+ // Reuse the same cache across script types. This must not cause any issue as the cached value for one hash type must never
+ // be confused for another (instantiating the cache within the loop instead would prevent testing this).
+ SigHashCache cache;
+ for (const auto hash_type: hash_types) {
+ const bool expect_one{sigversion == SigVersion::BASE && ((hash_type & 0x1f) == SIGHASH_SINGLE) && in_index >= tx.vout.size()};
+
+ // The result of computing the sighash should be the same with or without cache.
+ const auto sighash_with_cache{SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, nullptr, &cache)};
+ const auto sighash_no_cache{SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, nullptr, nullptr)};
+ BOOST_CHECK_EQUAL(sighash_with_cache, sighash_no_cache);
+
+ // Calling the cached version again should return the same value again.
+ BOOST_CHECK_EQUAL(sighash_with_cache, SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, nullptr, &cache));
+
+ // While here we might as well also check that the result for legacy is the same as for the old SignatureHash() function.
+ if (sigversion == SigVersion::BASE) {
+ BOOST_CHECK_EQUAL(sighash_with_cache, SignatureHashOld(scriptcode, CTransaction(tx), in_index, hash_type));
+ }
+
+ // Calling with a different scriptcode (for instance in case a CODESEP is encountered) will not return the cache value but
+ // overwrite it. The sighash will always be different except in case of legacy SIGHASH_SINGLE bug.
+ const auto sighash_with_cache2{SignatureHash(diff_scriptcode, tx, in_index, hash_type, amount, sigversion, nullptr, &cache)};
+ const auto sighash_no_cache2{SignatureHash(diff_scriptcode, tx, in_index, hash_type, amount, sigversion, nullptr, nullptr)};
+ BOOST_CHECK_EQUAL(sighash_with_cache2, sighash_no_cache2);
+ if (!expect_one) {
+ BOOST_CHECK_NE(sighash_with_cache, sighash_with_cache2);
+ } else {
+ BOOST_CHECK_EQUAL(sighash_with_cache, sighash_with_cache2);
+ BOOST_CHECK_EQUAL(sighash_with_cache, uint256::ONE);
+ }
+
+ // Calling the cached version again should return the same value again.
+ BOOST_CHECK_EQUAL(sighash_with_cache2, SignatureHash(diff_scriptcode, tx, in_index, hash_type, amount, sigversion, nullptr, &cache));
+
+ // And if we store a different value for this scriptcode and hash type it will return that instead.
+ {
+ HashWriter h{};
+ h << 42;
+ cache.Store(hash_type, scriptcode, h);
+ const auto stored_hash{h.GetHash()};
+ BOOST_CHECK(cache.Load(hash_type, scriptcode, h));
+ const auto loaded_hash{h.GetHash()};
+ BOOST_CHECK_EQUAL(stored_hash, loaded_hash);
+ }
+
+ // And using this mutated cache with the sighash function will return the new value (except in the legacy SIGHASH_SINGLE bug
+ // case in which it'll return 1).
+ if (!expect_one) {
+ BOOST_CHECK_NE(SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, nullptr, &cache), sighash_with_cache);
+ HashWriter h{};
+ BOOST_CHECK(cache.Load(hash_type, scriptcode, h));
+ h << hash_type;
+ const auto new_hash{h.GetHash()};
+ BOOST_CHECK_EQUAL(SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, nullptr, &cache), new_hash);
+ } else {
+ BOOST_CHECK_EQUAL(SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, nullptr, &cache), uint256::ONE);
+ }
+
+ // Wipe the cache and restore the correct cached value for this scriptcode and hash_type before starting the next iteration.
+ HashWriter dummy{};
+ cache.Store(hash_type, diff_scriptcode, dummy);
+ (void)SignatureHash(scriptcode, tx, in_index, hash_type, amount, sigversion, nullptr, &cache);
+ BOOST_CHECK(cache.Load(hash_type, scriptcode, dummy) || expect_one);
+ }
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
Why this scored 15/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.