optimization: migrate `SipHashUint256` to `PresaltedSipHasher`
What changed, and why it matters
This is a code cleanup and performance optimization change in Bitcoin Core. It replaces a standalone function called SipHashUint256 with a class method inside PresaltedSipHasher, and updates several hash helper classes to use the new method. The commit message and code comments explicitly state the goal is to enable caching of the initial hash state for speed while keeping behavior unchanged. There is no indication of a security fix or vulnerability being addressed.
No security action required. Treat as a routine refactoring/optimization. Reviewers may optionally verify that the new operator() produces identical output to the removed SipHashUint256 for the existing test vectors, which the included tests already assert.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors SipHash-2-4 usage for uint256 values. It removes the free function SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val) and adds PresaltedSipHasher::operator()(const uint256&). The implementation reuses the precomputed v[0..3] state already stored in PresaltedSipHasher, avoiding recomputation of the key XOR constants on each call. SaltedUint256Hasher, SaltedTxidHasher, and SaltedWtxidHasher are updated to hold a PresaltedSipHasher member instead of raw k0/k1 salts. Tests and benchmarks are updated to call the new API. The expected test vectors remain the same, confirming behavioral equivalence.
Changed components
src/crypto/siphash.hsrc/crypto/siphash.cppsrc/util/hasher.hsrc/util/hasher.cppsrc/blockencodings.cppsrc/bench/crypto_hash.cppsrc/test/hash_tests.cppsrc/test/fuzz/integer.cppInspect captured patch +45 / −55
diff --git a/src/bench/crypto_hash.cpp b/src/bench/crypto_hash.cpp
index 2f1ff564..05c7788d 100644
--- a/src/bench/crypto_hash.cpp
+++ b/src/bench/crypto_hash.cpp
@@ -193,13 +193,11 @@ static void SHA512(benchmark::Bench& bench)
static void SipHash_32b(benchmark::Bench& bench)
{
FastRandomContext rng{/*fDeterministic=*/true};
- auto k0{rng.rand64()}, k1{rng.rand64()};
+ PresaltedSipHasher presalted_sip_hasher(rng.rand64(), rng.rand64());
auto val{rng.rand256()};
auto i{0U};
bench.run([&] {
- ankerl::nanobench::doNotOptimizeAway(SipHashUint256(k0, k1, val));
- ++k0;
- ++k1;
+ ankerl::nanobench::doNotOptimizeAway(presalted_sip_hasher(val));
++i;
val.data()[i % uint256::size()] ^= i & 0xFF;
});
diff --git a/src/blockencodings.cpp b/src/blockencodings.cpp
index cf6da55a..55c8f8a0 100644
--- a/src/blockencodings.cpp
+++ b/src/blockencodings.cpp
@@ -42,7 +42,8 @@ void CBlockHeaderAndShortTxIDs::FillShortTxIDSelector() const {
uint64_t CBlockHeaderAndShortTxIDs::GetShortID(const Wtxid& wtxid) const {
static_assert(SHORTTXIDS_LENGTH == 6, "shorttxids calculation assumes 6-byte shorttxids");
- return SipHashUint256(shorttxidk0, shorttxidk1, wtxid.ToUint256()) & 0xffffffffffffL;
+ PresaltedSipHasher hasher(shorttxidk0, shorttxidk1); // TODO extract
+ return hasher(wtxid.ToUint256()) & 0xffffffffffffL;
}
/* Reconstructing a compact block is in the hot-path for block relay,
diff --git a/src/crypto/siphash.cpp b/src/crypto/siphash.cpp
index a6029716..49cc2349 100644
--- a/src/crypto/siphash.cpp
+++ b/src/crypto/siphash.cpp
@@ -96,16 +96,11 @@ uint64_t CSipHasher::Finalize() const
return v0 ^ v1 ^ v2 ^ v3;
}
-uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val)
+uint64_t PresaltedSipHasher::operator()(const uint256& val) const noexcept
{
- /* Specialized implementation for efficiency */
+ uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3];
uint64_t d = val.GetUint64(0);
-
- // TODO moved in followup commit
- uint64_t v0 = CSipHasher::C0 ^ k0;
- uint64_t v1 = CSipHasher::C1 ^ k1;
- uint64_t v2 = CSipHasher::C2 ^ k0;
- uint64_t v3 = CSipHasher::C3 ^ k1 ^ d;
+ v3 ^= d;
SIPROUND;
SIPROUND;
diff --git a/src/crypto/siphash.h b/src/crypto/siphash.h
index a4aeb3ae..b81b9557 100644
--- a/src/crypto/siphash.h
+++ b/src/crypto/siphash.h
@@ -37,18 +37,15 @@ public:
uint64_t Finalize() const;
};
-/** Optimized SipHash-2-4 implementation for uint256.
+/**
+ * Optimized SipHash-2-4 implementation for uint256.
*
- * It is identical to:
- * SipHasher(k0, k1)
- * .Write(val.GetUint64(0))
- * .Write(val.GetUint64(1))
- * .Write(val.GetUint64(2))
- * .Write(val.GetUint64(3))
- * .Finalize()
+ * This class caches the initial SipHash v[0..3] state derived from (k0, k1)
+ * and implements a specialized hashing path for uint256 values, with or
+ * without an extra 32-bit word. The internal state is immutable, so
+ * PresaltedSipHasher instances can be reused for multiple hashes with the
+ * same key.
*/
-uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val);
-
class PresaltedSipHasher
{
uint64_t v[4];
@@ -61,6 +58,7 @@ public:
v[3] = CSipHasher::C3 ^ k1;
}
+ uint64_t operator()(const uint256& val) const noexcept;
uint64_t operator()(const uint256& val, uint32_t extra) const noexcept;
};
diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp
index 81516fd9..89c29dbc 100644
--- a/src/test/fuzz/integer.cpp
+++ b/src/test/fuzz/integer.cpp
@@ -118,7 +118,7 @@ FUZZ_TARGET(integer, .init = initialize_integer)
}
(void)MillisToTimeval(i64);
(void)SighashToStr(uch);
- (void)SipHashUint256(u64, u64, u256);
+ (void)PresaltedSipHasher(u64, u64)(u256);
(void)PresaltedSipHasher(u64, u64)(u256, u32);
(void)ToLower(ch);
(void)ToUpper(ch);
diff --git a/src/test/hash_tests.cpp b/src/test/hash_tests.cpp
index d4592df7..a5059a8f 100644
--- a/src/test/hash_tests.cpp
+++ b/src/test/hash_tests.cpp
@@ -104,7 +104,7 @@ BOOST_AUTO_TEST_CASE(siphash)
hasher.Write(0x2F2E2D2C2B2A2928ULL);
BOOST_CHECK_EQUAL(hasher.Finalize(), 0xe612a3cb9ecba951ull);
- BOOST_CHECK_EQUAL(SipHashUint256(0x0706050403020100ULL, 0x0F0E0D0C0B0A0908ULL, uint256{"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"}), 0x7127512f72f27cceull);
+ BOOST_CHECK_EQUAL(PresaltedSipHasher(0x0706050403020100ULL, 0x0F0E0D0C0B0A0908ULL)(uint256{"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"}), 0x7127512f72f27cceull);
// Check test vectors from spec, one byte at a time
CSipHasher hasher2(0x0706050403020100ULL, 0x0F0E0D0C0B0A0908ULL);
@@ -128,9 +128,9 @@ BOOST_AUTO_TEST_CASE(siphash)
// and the test would be affected by default tx version bumps if not fixed.
tx.version = 1;
ss << TX_WITH_WITNESS(tx);
- BOOST_CHECK_EQUAL(SipHashUint256(1, 2, ss.GetHash()), 0x79751e980c2a0a35ULL);
+ BOOST_CHECK_EQUAL(PresaltedSipHasher(1, 2)(ss.GetHash()), 0x79751e980c2a0a35ULL);
- // Check consistency between CSipHasher and SipHashUint256 and PresaltedSipHasher.
+ // Check consistency between CSipHasher and PresaltedSipHasher.
FastRandomContext ctx;
for (int i = 0; i < 16; ++i) {
uint64_t k0 = ctx.rand64();
@@ -139,7 +139,7 @@ BOOST_AUTO_TEST_CASE(siphash)
CSipHasher sip256(k0, k1);
sip256.Write(x);
- BOOST_CHECK_EQUAL(SipHashUint256(k0, k1, x), sip256.Finalize()); // TODO modified in follow-up commit
+ BOOST_CHECK_EQUAL(PresaltedSipHasher(k0, k1)(x), sip256.Finalize());
CSipHasher sip288 = sip256;
uint32_t n = ctx.rand32();
diff --git a/src/util/hasher.cpp b/src/util/hasher.cpp
index cac602a8..3d5ffcf8 100644
--- a/src/util/hasher.cpp
+++ b/src/util/hasher.cpp
@@ -7,17 +7,20 @@
#include <span.h>
#include <util/hasher.h>
-SaltedUint256Hasher::SaltedUint256Hasher() :
- k0{FastRandomContext().rand64()},
- k1{FastRandomContext().rand64()} {}
+SaltedUint256Hasher::SaltedUint256Hasher() : m_hasher{
+ FastRandomContext().rand64(),
+ FastRandomContext().rand64()}
+{}
-SaltedTxidHasher::SaltedTxidHasher() :
- k0{FastRandomContext().rand64()},
- k1{FastRandomContext().rand64()} {}
+SaltedTxidHasher::SaltedTxidHasher() : m_hasher{
+ FastRandomContext().rand64(),
+ FastRandomContext().rand64()}
+{}
-SaltedWtxidHasher::SaltedWtxidHasher() :
- k0{FastRandomContext().rand64()},
- k1{FastRandomContext().rand64()} {}
+SaltedWtxidHasher::SaltedWtxidHasher() : m_hasher{
+ FastRandomContext().rand64(),
+ FastRandomContext().rand64()}
+{}
SaltedOutpointHasher::SaltedOutpointHasher(bool deterministic) : m_hasher{
deterministic ? 0x8e819f2607a18de6 : FastRandomContext().rand64(),
diff --git a/src/util/hasher.h b/src/util/hasher.h
index e7e69530..cdf2250b 100644
--- a/src/util/hasher.h
+++ b/src/util/hasher.h
@@ -17,47 +17,43 @@
class SaltedUint256Hasher
{
-private:
- /** Salt */
- const uint64_t k0, k1;
+ const PresaltedSipHasher m_hasher;
public:
SaltedUint256Hasher();
- size_t operator()(const uint256& hash) const {
- return SipHashUint256(k0, k1, hash);
+ size_t operator()(const uint256& hash) const
+ {
+ return m_hasher(hash);
}
};
class SaltedTxidHasher
{
-private:
- /** Salt */
- const uint64_t k0, k1;
+ const PresaltedSipHasher m_hasher;
public:
SaltedTxidHasher();
- size_t operator()(const Txid& txid) const {
- return SipHashUint256(k0, k1, txid.ToUint256());
+ size_t operator()(const Txid& txid) const
+ {
+ return m_hasher(txid.ToUint256());
}
};
class SaltedWtxidHasher
{
-private:
- /** Salt */
- const uint64_t k0, k1;
+ const PresaltedSipHasher m_hasher;
public:
SaltedWtxidHasher();
- size_t operator()(const Wtxid& wtxid) const {
- return SipHashUint256(k0, k1, wtxid.ToUint256());
+ size_t operator()(const Wtxid& wtxid) const
+ {
+ return m_hasher(wtxid.ToUint256());
}
};
-
class SaltedOutpointHasher
{
const PresaltedSipHasher m_hasher;
@@ -80,8 +76,7 @@ public:
}
};
-struct FilterHeaderHasher
-{
+struct FilterHeaderHasher {
size_t operator()(const uint256& hash) const { return ReadLE64(hash.begin()); }
};
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.