diff --git a/src/bench/crypto_hash.cpp b/src/bench/crypto_hash.cpp index 2f1ff564388..05c7788d74b 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 cf6da55aa9a..d48ba400a1e 100644 --- a/src/blockencodings.cpp +++ b/src/blockencodings.cpp @@ -17,11 +17,14 @@ #include -CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block, const uint64_t nonce) : - nonce(nonce), - shorttxids(block.vtx.size() - 1), prefilledtxn(1), header(block) { +CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block, uint64_t nonce) + : nonce(nonce), + shorttxids(block.vtx.size() - 1), + prefilledtxn(1), + header(block) +{ FillShortTxIDSelector(); - //TODO: Use our mempool prior to block acceptance to predictively fill more than just the coinbase + // TODO: Use our mempool prior to block acceptance to predictively fill more than just the coinbase prefilledtxn[0] = {0, block.vtx[0]}; for (size_t i = 1; i < block.vtx.size(); i++) { const CTransaction& tx = *block.vtx[i]; @@ -29,20 +32,21 @@ CBlockHeaderAndShortTxIDs::CBlockHeaderAndShortTxIDs(const CBlock& block, const } } -void CBlockHeaderAndShortTxIDs::FillShortTxIDSelector() const { +void CBlockHeaderAndShortTxIDs::FillShortTxIDSelector() const +{ DataStream stream{}; stream << header << nonce; CSHA256 hasher; hasher.Write((unsigned char*)&(*stream.begin()), stream.end() - stream.begin()); uint256 shorttxidhash; hasher.Finalize(shorttxidhash.begin()); - shorttxidk0 = shorttxidhash.GetUint64(0); - shorttxidk1 = shorttxidhash.GetUint64(1); + m_hasher.emplace(shorttxidhash.GetUint64(0), shorttxidhash.GetUint64(1)); } -uint64_t CBlockHeaderAndShortTxIDs::GetShortID(const Wtxid& wtxid) 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; + return (*Assert(m_hasher))(wtxid.ToUint256()) & 0xffffffffffffL; } /* Reconstructing a compact block is in the hot-path for block relay, diff --git a/src/blockencodings.h b/src/blockencodings.h index 133724b64e8..124df50a3d3 100644 --- a/src/blockencodings.h +++ b/src/blockencodings.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_BLOCKENCODINGS_H #define BITCOIN_BLOCKENCODINGS_H +#include #include #include @@ -87,8 +88,7 @@ typedef enum ReadStatus_t } ReadStatus; class CBlockHeaderAndShortTxIDs { -private: - mutable uint64_t shorttxidk0, shorttxidk1; + mutable std::optional m_hasher; uint64_t nonce; void FillShortTxIDSelector() const; @@ -112,7 +112,7 @@ public: /** * @param[in] nonce This should be randomly generated, and is used for the siphash secret key */ - CBlockHeaderAndShortTxIDs(const CBlock& block, const uint64_t nonce); + CBlockHeaderAndShortTxIDs(const CBlock& block, uint64_t nonce); uint64_t GetShortID(const Wtxid& wtxid) const; diff --git a/src/crypto/siphash.cpp b/src/crypto/siphash.cpp index 1a9eb771a82..89dbad6f44f 100644 --- a/src/crypto/siphash.cpp +++ b/src/crypto/siphash.cpp @@ -19,41 +19,33 @@ v2 = std::rotl(v2, 32); \ } while (0) -CSipHasher::CSipHasher(uint64_t k0, uint64_t k1) -{ - v[0] = 0x736f6d6570736575ULL ^ k0; - v[1] = 0x646f72616e646f6dULL ^ k1; - v[2] = 0x6c7967656e657261ULL ^ k0; - v[3] = 0x7465646279746573ULL ^ k1; - count = 0; - tmp = 0; -} +CSipHasher::CSipHasher(uint64_t k0, uint64_t k1) : m_state{k0, k1} {} CSipHasher& CSipHasher::Write(uint64_t data) { - uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; + uint64_t v0 = m_state.v[0], v1 = m_state.v[1], v2 = m_state.v[2], v3 = m_state.v[3]; - assert(count % 8 == 0); + assert(m_count % 8 == 0); v3 ^= data; SIPROUND; SIPROUND; v0 ^= data; - v[0] = v0; - v[1] = v1; - v[2] = v2; - v[3] = v3; + m_state.v[0] = v0; + m_state.v[1] = v1; + m_state.v[2] = v2; + m_state.v[3] = v3; - count += 8; + m_count += 8; return *this; } CSipHasher& CSipHasher::Write(std::span data) { - uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; - uint64_t t = tmp; - uint8_t c = count; + uint64_t v0 = m_state.v[0], v1 = m_state.v[1], v2 = m_state.v[2], v3 = m_state.v[3]; + uint64_t t = m_tmp; + uint8_t c = m_count; while (data.size() > 0) { t |= uint64_t{data.front()} << (8 * (c % 8)); @@ -68,21 +60,21 @@ CSipHasher& CSipHasher::Write(std::span data) data = data.subspan(1); } - v[0] = v0; - v[1] = v1; - v[2] = v2; - v[3] = v3; - count = c; - tmp = t; + m_state.v[0] = v0; + m_state.v[1] = v1; + m_state.v[2] = v2; + m_state.v[3] = v3; + m_count = c; + m_tmp = t; return *this; } uint64_t CSipHasher::Finalize() const { - uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3]; + uint64_t v0 = m_state.v[0], v1 = m_state.v[1], v2 = m_state.v[2], v3 = m_state.v[3]; - uint64_t t = tmp | (((uint64_t)count) << 56); + uint64_t t = m_tmp | (((uint64_t)m_count) << 56); v3 ^= t; SIPROUND; @@ -96,15 +88,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 = m_state.v[0], v1 = m_state.v[1], v2 = m_state.v[2], v3 = m_state.v[3]; uint64_t d = val.GetUint64(0); - - uint64_t v0 = 0x736f6d6570736575ULL ^ k0; - uint64_t v1 = 0x646f72616e646f6dULL ^ k1; - uint64_t v2 = 0x6c7967656e657261ULL ^ k0; - uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d; + v3 ^= d; SIPROUND; SIPROUND; @@ -136,16 +124,12 @@ uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val) return v0 ^ v1 ^ v2 ^ v3; } -uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra) +/** Specialized implementation for efficiency */ +uint64_t PresaltedSipHasher::operator()(const uint256& val, uint32_t extra) const noexcept { - /* Specialized implementation for efficiency */ + uint64_t v0 = m_state.v[0], v1 = m_state.v[1], v2 = m_state.v[2], v3 = m_state.v[3]; uint64_t d = val.GetUint64(0); - - uint64_t v0 = 0x736f6d6570736575ULL ^ k0; - uint64_t v1 = 0x646f72616e646f6dULL ^ k1; - uint64_t v2 = 0x6c7967656e657261ULL ^ k0; - uint64_t v3 = 0x7465646279746573ULL ^ k1 ^ d; - + v3 ^= d; SIPROUND; SIPROUND; v0 ^= d; diff --git a/src/crypto/siphash.h b/src/crypto/siphash.h index 8d41a083ac8..2f28473a4f1 100644 --- a/src/crypto/siphash.h +++ b/src/crypto/siphash.h @@ -5,23 +5,34 @@ #ifndef BITCOIN_CRYPTO_SIPHASH_H #define BITCOIN_CRYPTO_SIPHASH_H +#include #include #include class uint256; -/** SipHash-2-4 */ -class CSipHasher +/** Shared SipHash internal state v[0..3], initialized from (k0, k1). */ +class SipHashState { -private: - uint64_t v[4]; - uint64_t tmp; - uint8_t count; // Only the low 8 bits of the input size matter. + static constexpr uint64_t C0{0x736f6d6570736575ULL}, C1{0x646f72616e646f6dULL}, C2{0x6c7967656e657261ULL}, C3{0x7465646279746573ULL}; public: - /** Construct a SipHash calculator initialized with 128-bit key (k0, k1) */ + explicit SipHashState(uint64_t k0, uint64_t k1) noexcept : v{C0 ^ k0, C1 ^ k1, C2 ^ k0, C3 ^ k1} {} + + std::array v{}; +}; + +/** General SipHash-2-4 implementation. */ +class CSipHasher +{ + SipHashState m_state; + uint64_t m_tmp{0}; + uint8_t m_count{0}; //!< Only the low 8 bits of the input size matter. + +public: + /** Construct a SipHash calculator initialized with 128-bit key (k0, k1). */ CSipHasher(uint64_t k0, uint64_t k1); - /** Hash a 64-bit integer worth of data + /** Hash a 64-bit integer worth of data. * It is treated as if this was the little-endian interpretation of 8 bytes. * This function can only be used when a multiple of 8 bytes have been written so far. */ @@ -32,17 +43,30 @@ 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); -uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra); +class PresaltedSipHasher +{ + const SipHashState m_state; + +public: + explicit PresaltedSipHasher(uint64_t k0, uint64_t k1) noexcept : m_state{k0, k1} {} + + /** Equivalent to CSipHasher(k0, k1).Write(val).Finalize(). */ + uint64_t operator()(const uint256& val) const noexcept; + + /** + * Equivalent to CSipHasher(k0, k1).Write(val).Write(extra).Finalize(), + * with `extra` encoded as 4 little-endian bytes. + */ + uint64_t operator()(const uint256& val, uint32_t extra) const noexcept; +}; #endif // BITCOIN_CRYPTO_SIPHASH_H diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp index 2e13d4226e1..89c29dbcb70 100644 --- a/src/test/fuzz/integer.cpp +++ b/src/test/fuzz/integer.cpp @@ -118,8 +118,8 @@ FUZZ_TARGET(integer, .init = initialize_integer) } (void)MillisToTimeval(i64); (void)SighashToStr(uch); - (void)SipHashUint256(u64, u64, u256); - (void)SipHashUint256Extra(u64, u64, u256, u32); + (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 2fe44960f46..a5059a8fe8c 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,23 +128,25 @@ 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[Extra]. + // Check consistency between CSipHasher and PresaltedSipHasher. FastRandomContext ctx; for (int i = 0; i < 16; ++i) { + uint64_t k0 = ctx.rand64(); uint64_t k1 = ctx.rand64(); - uint64_t k2 = ctx.rand64(); uint256 x = m_rng.rand256(); + + CSipHasher sip256(k0, k1); + sip256.Write(x); + BOOST_CHECK_EQUAL(PresaltedSipHasher(k0, k1)(x), sip256.Finalize()); + + CSipHasher sip288 = sip256; uint32_t n = ctx.rand32(); uint8_t nb[4]; WriteLE32(nb, n); - CSipHasher sip256(k1, k2); - sip256.Write(x); - CSipHasher sip288 = sip256; sip288.Write(nb); - BOOST_CHECK_EQUAL(SipHashUint256(k1, k2, x), sip256.Finalize()); - BOOST_CHECK_EQUAL(SipHashUint256Extra(k1, k2, x, n), sip288.Finalize()); + BOOST_CHECK_EQUAL(PresaltedSipHasher(k0, k1)(x, n), sip288.Finalize()); } } diff --git a/src/util/hasher.cpp b/src/util/hasher.cpp index c4051ae0013..3d5ffcf89b4 100644 --- a/src/util/hasher.cpp +++ b/src/util/hasher.cpp @@ -7,26 +7,30 @@ #include #include -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) : - k0{deterministic ? 0x8e819f2607a18de6 : FastRandomContext().rand64()}, - k1{deterministic ? 0xf4020d2e3983b0eb : FastRandomContext().rand64()} +SaltedOutpointHasher::SaltedOutpointHasher(bool deterministic) : m_hasher{ + deterministic ? 0x8e819f2607a18de6 : FastRandomContext().rand64(), + deterministic ? 0xf4020d2e3983b0eb : FastRandomContext().rand64()} {} SaltedSipHasher::SaltedSipHasher() : m_k0{FastRandomContext().rand64()}, - m_k1{FastRandomContext().rand64()} {} + m_k1{FastRandomContext().rand64()} +{} size_t SaltedSipHasher::operator()(const std::span& script) const { diff --git a/src/util/hasher.h b/src/util/hasher.h index be5d9eb13df..cdf2250bf3c 100644 --- a/src/util/hasher.h +++ b/src/util/hasher.h @@ -17,52 +17,46 @@ 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 { -private: - /** Salt */ - const uint64_t k0, k1; + const PresaltedSipHasher m_hasher; public: SaltedOutpointHasher(bool deterministic = false); @@ -76,13 +70,13 @@ public: * * @see https://gcc.gnu.org/onlinedocs/gcc-13.2.0/libstdc++/manual/manual/unordered_associative.html */ - size_t operator()(const COutPoint& id) const noexcept { - return SipHashUint256Extra(k0, k1, id.hash.ToUint256(), id.n); + size_t operator()(const COutPoint& id) const noexcept + { + return m_hasher(id.hash.ToUint256(), id.n); } }; -struct FilterHeaderHasher -{ +struct FilterHeaderHasher { size_t operator()(const uint256& hash) const { return ReadLE64(hash.begin()); } };