coins: reuse cache hasher for txid set
What changed, and why it matters
This is a small internal cleanup in Bitcoin Core's coin-cache code. It makes a temporary set of transaction IDs use the same hash function as the main coins cache, instead of a separate one. The change is described by the authors as a performance/consistency improvement, not a security fix. There is no evidence in the commit that it repairs an exploitable vulnerability.
No immediate action required. Treat as a routine refactor. If reviewing for a security release, verify independently that `SaltedCoinsCacheHasher` provides equivalent or stronger collision resistance compared with `SaltedTxidHasher` for this use case.
Security signals we found
No security-relevant keywords in commit title or message
No bug-fix description or CVE reference present
Change is a hasher substitution with equivalent randomized SipHash-based construction
No bounds, validation, or consensus logic modified
No disclosure or advisory language from the vendor
Evidence from the diff
The commit replaces SaltedTxidHasher with SaltedCoinsCacheHasher for a local std::unordered_set<Txid> in CoinsViewOverlay::StartFetching and in related tests/fuzzers. It adds a Txid overload to SaltedCoinsCacheHasher that hashes the transaction ID as a single SipHash-1-3 jumbo block. The stated rationale is to reuse the cache’s hasher and to satisfy SipHash-1-3-UJ input requirements. The diff removes the util/hasher.h dependency from coins.cpp and the overlay tests, consolidating hashing logic in coins.h.
Changed components
src/coins.cppsrc/coins.hsrc/test/coinsviewoverlay_tests.cppsrc/test/fuzz/coins_view.cppInspect captured patch +12 / −8
diff --git a/src/coins.cpp b/src/coins.cpp
index 50e1aa05..3d3e63fa 100644
--- a/src/coins.cpp
+++ b/src/coins.cpp
@@ -8,7 +8,6 @@
#include <primitives/block.h>
#include <random.h>
#include <uint256.h>
-#include <util/hasher.h>
#include <util/log.h>
#include <util/threadpool.h>
#include <util/trace.h>
@@ -384,7 +383,7 @@ CCoinsViewCache::ResetGuard CoinsViewOverlay::StartFetching(const CBlock& block
// Loop through the block inputs and set their prevouts in the queue.
// Filter inputs that spend outputs created earlier in the same block. These outputs will be created
// directly in the cache from the tx that creates them, so they will not be requested from a base view.
- std::unordered_set<Txid, SaltedTxidHasher> earlier_txids;
+ std::unordered_set<Txid, SaltedCoinsCacheHasher> earlier_txids;
earlier_txids.reserve(block.vtx.size());
for (const auto& tx : block.vtx | std::views::drop(1)) {
for (const auto& input : tx->vin) {
diff --git a/src/coins.h b/src/coins.h
index 71dff015..c854893b 100644
--- a/src/coins.h
+++ b/src/coins.h
@@ -243,6 +243,12 @@ class SaltedCoinsCacheHasher
public:
SaltedCoinsCacheHasher(bool deterministic = false);
+ /** Hash a transaction ID, itself a cryptographic hash, as one jumbo block. */
+ size_t operator()(const Txid& id) const noexcept
+ {
+ return m_hasher.Hash(id.ToUint256());
+ }
+
/** Hash an outpoint as its txid jumbo block followed by the zero-extended index as one normal block. */
size_t operator()(const COutPoint& id) const noexcept
{
diff --git a/src/test/coinsviewoverlay_tests.cpp b/src/test/coinsviewoverlay_tests.cpp
index df497131..b63807da 100644
--- a/src/test/coinsviewoverlay_tests.cpp
+++ b/src/test/coinsviewoverlay_tests.cpp
@@ -10,7 +10,6 @@
#include <txdb.h>
#include <uint256.h>
#include <util/byte_units.h>
-#include <util/hasher.h>
#include <util/threadpool.h>
#include <boost/test/unit_test.hpp>
@@ -57,7 +56,7 @@ void PopulateView(const CBlock& block, CCoinsView& view, bool spent = false)
CCoinsViewCache cache{&view};
cache.SetBestBlock(uint256::ONE);
- std::unordered_set<Txid, SaltedTxidHasher> txids{};
+ std::unordered_set<Txid, SaltedCoinsCacheHasher> txids{};
txids.reserve(block.vtx.size() - 1);
for (const auto& tx : block.vtx | std::views::drop(1)) {
for (const auto& in : tx->vin) {
@@ -75,7 +74,7 @@ void PopulateView(const CBlock& block, CCoinsView& view, bool spent = false)
void CheckCache(const CBlock& block, const CCoinsViewCache& cache)
{
uint32_t counter{0};
- std::unordered_set<Txid, SaltedTxidHasher> txids{};
+ std::unordered_set<Txid, SaltedCoinsCacheHasher> txids{};
txids.reserve(block.vtx.size() - 1);
for (const auto& tx : block.vtx) {
@@ -225,7 +224,7 @@ BOOST_AUTO_TEST_CASE(fetch_out_of_order_input_uses_normal_lookup)
PopulateView(block, main_cache);
std::vector<COutPoint> fetched_inputs;
- std::unordered_set<Txid, SaltedTxidHasher> txids;
+ std::unordered_set<Txid, SaltedCoinsCacheHasher> txids;
txids.reserve(block.vtx.size() - 1);
for (const auto& tx : block.vtx | std::views::drop(1)) {
for (const auto& input : tx->vin) {
diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp
index 10c29ef7..6f5dbd62 100644
--- a/src/test/fuzz/coins_view.cpp
+++ b/src/test/fuzz/coins_view.cpp
@@ -429,7 +429,7 @@ FUZZ_TARGET(coins_view_db, .init = initialize_coins_view)
// called.
FUZZ_TARGET(coins_view_overlay, .init = initialize_coins_view)
{
- SeedRandomStateForTest(SeedRand::ZEROS); // for SaltedTxidHasher
+ SeedRandomStateForTest(SeedRand::ZEROS); // for SaltedCoinsCacheHasher
StartPoolIfNeeded();
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
MutationGuardCoinsViewCache backend_cache{&CoinsViewEmpty::Get(), /*deterministic=*/true};
@@ -441,7 +441,7 @@ FUZZ_TARGET(coins_view_overlay, .init = initialize_coins_view)
FUZZ_TARGET(coins_view_stacked, .init = initialize_coins_view)
{
- SeedRandomStateForTest(SeedRand::ZEROS); // for SaltedTxidHasher
+ SeedRandomStateForTest(SeedRand::ZEROS); // for SaltedCoinsCacheHasher
StartPoolIfNeeded();
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
auto db_params = DBParams{
Why this scored 18/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.