Merge bitcoin/bitcoin#30442: precalculate SipHash constant salt XORs

6eb5ba5691 refactor: extract shared `SipHash` state into `SipHashState` (Lőrinc)
118d22ddb4 optimization: cache `PresaltedSipHasher` in `CBlockHeaderAndShortTxIDs` (Lőrinc)
9ca52a4cbe optimization: migrate `SipHashUint256` to `PresaltedSipHasher` (Lőrinc)
ec11b9fede optimization: introduce `PresaltedSipHasher` for repeated hashing (Lőrinc)
20330548cf refactor: extract `SipHash` C0-C3 constants to class scope (Lőrinc)
9f9eb7fbc0 test: rename k1/k2 to k0/k1 in `SipHash` consistency tests (Lőrinc)

Pull request description:

  This change is part of [[IBD] - Tracking PR for speeding up Initial Block Download](https://github.com/bitcoin/bitcoin/pull/32043)

  ### Summary

  The in-memory representation of the UTXO set uses (salted) [SipHash](https://github.com/bitcoin/bitcoin/blob/master/src/coins.h#L226) to avoid key collision attacks.

  Hashing `uint256` keys is performed frequently throughout the codebase. Previously, specialized optimizations existed as standalone functions (`SipHashUint256` and `SipHashUint256Extra`), but the constant salting operations (C0-C3 XOR with keys) were recomputed on every call.

  This PR introduces `PresaltedSipHasher`, a class that caches the initial SipHash state (v0-v3 after XORing constants with keys), eliminating redundant constant computations when hashing multiple values with the same keys. The optimization is applied uniformly across:
  - All `Salted*Hasher` classes (`SaltedUint256Hasher`, `SaltedTxidHasher`, `SaltedWtxidHasher`, `SaltedOutpointHasher`)
  - `CBlockHeaderAndShortTxIDs` for compact block short ID computation

  ### Details

  The change replaces the standalone `SipHashUint256` and `SipHashUint256Extra` functions with `PresaltedSipHasher` class methods that cache the constant-salted state. This is particularly beneficial for hash map operations where the same salt is used repeatedly (as suggested by Sipa in https://github.com/bitcoin/bitcoin/pull/30442#issuecomment-2628994530).

  `CSipHasher` behavior remains unchanged; only the specialized `uint256` paths and callers now reuse the cached state instead of recomputing it.

  ### Measurements

  Benchmarks were run using local `SaltedOutpointHasherBench_*` microbenchmarks (not included in this PR) that exercise `SaltedOutpointHasher` in realistic `std::unordered_set` scenarios.

  <details>
  <summary>Benchmarks</summary>

  ```C++
  diff --git a/src/bench/crypto_hash.cpp b/src/bench/crypto_hash.cpp
  --- a/src/bench/crypto_hash.cpp(revision 9b1a7c3e8d)
  +++ b/src/bench/crypto_hash.cpp(revision e1b4f056b3097e7e34b0eda31f57826d81c9d810)
  @@ -2,7 +2,6 @@
   // Distributed under the MIT software license, see the accompanying
   // file COPYING or http://www.opensource.org/licenses/mit-license.php.

  -
   #include <bench/bench.h>
   #include <crypto/muhash.h>
   #include <crypto/ripemd160.h>
  @@ -12,9 +11,11 @@
   #include <crypto/sha512.h>
   #include <crypto/siphash.h>
   #include <random.h>
  -#include <span.h>
   #include <tinyformat.h>
   #include <uint256.h>
  +#include <primitives/transaction.h>
  +#include <util/hasher.h>
  +#include <unordered_set>

   #include <cstdint>
   #include <vector>
  @@ -205,6 +206,98 @@
       });
   }

  +static void SaltedOutpointHasherBench_hash(benchmark::Bench& bench)
  +{
  +    FastRandomContext rng{/*fDeterministic=*/true};
  +    constexpr size_t size{1000};
  +
  +    std::vector<COutPoint> outpoints(size);
  +    for (auto& outpoint : outpoints) {
  +        outpoint = {Txid::FromUint256(rng.rand256()), rng.rand32()};
  +    }
  +
  +    const SaltedOutpointHasher hasher;
  +    bench.batch(size).run([&] {
  +        size_t result{0};
  +        for (const auto& outpoint : outpoints) {
  +            result ^= hasher(outpoint);
  +        }
  +        ankerl::nanobench::doNotOptimizeAway(result);
  +    });
  +}
  +
  +static void SaltedOutpointHasherBench_match(benchmark::Bench& bench)
  +{
  +    FastRandomContext rng{/*fDeterministic=*/true};
  +    constexpr size_t size{1000};
  +
  +    std::unordered_set<COutPoint, SaltedOutpointHasher> values;
  +    std::vector<COutPoint> value_vector;
  +    values.reserve(size);
  +    value_vector.reserve(size);
  +
  +    for (size_t i{0}; i < size; ++i) {
  +        COutPoint outpoint{Txid::FromUint256(rng.rand256()), rng.rand32()};
  +        values.emplace(outpoint);
  +        value_vector.push_back(outpoint);
  +        assert(values.contains(outpoint));
  +    }
  +
  +    bench.batch(size).run([&] {
  +        bool result{true};
  +        for (const auto& outpoint : value_vector) {
  +            result ^= values.contains(outpoint);
  +        }
  +        ankerl::nanobench::doNotOptimizeAway(result);
  +    });
  +}
  +
  +static void SaltedOutpointHasherBench_mismatch(benchmark::Bench& bench)
  +{
  +    FastRandomContext rng{/*fDeterministic=*/true};
  +    constexpr size_t size{1000};
  +
  +    std::unordered_set<COutPoint, SaltedOutpointHasher> values;
  +    std::vector<COutPoint> missing_value_vector;
  +    values.reserve(size);
  +    missing_value_vector.reserve(size);
  +
  +    for (size_t i{0}; i < size; ++i) {
  +        values.emplace(Txid::FromUint256(rng.rand256()), rng.rand32());
  +        COutPoint missing_outpoint{Txid::FromUint256(rng.rand256()), rng.rand32()};
  +        missing_value_vector.push_back(missing_outpoint);
  +        assert(!values.contains(missing_outpoint));
  +    }
  +
  +    bench.batch(size).run([&] {
  +        bool result{false};
  +        for (const auto& outpoint : missing_value_vector) {
  +            result ^= values.contains(outpoint);
  +        }
  +        ankerl::nanobench::doNotOptimizeAway(result);
  +    });
  +}
  +
  +static void SaltedOutpointHasherBench_create_set(benchmark::Bench& bench)
  +{
  +    FastRandomContext rng{/*fDeterministic=*/true};
  +    constexpr size_t size{1000};
  +
  +    std::vector<COutPoint> outpoints(size);
  +    for (auto& outpoint : outpoints) {
  +        outpoint = {Txid::FromUint256(rng.rand256()), rng.rand32()};
  +    }
  +
  +    bench.batch(size).run([&] {
  +        std::unordered_set<COutPoint, SaltedOutpointHasher> set;
  +        set.reserve(size);
  +        for (const auto& outpoint : outpoints) {
  +            set.emplace(outpoint);
  +        }
  +        ankerl::nanobench::doNotOptimizeAway(set.size());
  +    });
  +}
  +
   static void MuHash(benchmark::Bench& bench)
   {
       MuHash3072 acc;
  @@ -276,6 +369,10 @@
   BENCHMARK(SHA256_32b_AVX2, benchmark::PriorityLevel::HIGH);
   BENCHMARK(SHA256_32b_SHANI, benchmark::PriorityLevel::HIGH);
   BENCHMARK(SipHash_32b, benchmark::PriorityLevel::HIGH);
  +BENCHMARK(SaltedOutpointHasherBench_hash, benchmark::PriorityLevel::HIGH);
  +BENCHMARK(SaltedOutpointHasherBench_match, benchmark::PriorityLevel::HIGH);
  +BENCHMARK(SaltedOutpointHasherBench_mismatch, benchmark::PriorityLevel::HIGH);
  +BENCHMARK(SaltedOutpointHasherBench_create_set, benchmark::PriorityLevel::HIGH);
   BENCHMARK(SHA256D64_1024_STANDARD, benchmark::PriorityLevel::HIGH);
   BENCHMARK(SHA256D64_1024_SSE4, benchmark::PriorityLevel::HIGH);
   BENCHMARK(SHA256D64_1024_AVX2, benchmark::PriorityLevel::HIGH);

  ```

  </details>

  > cmake -B build -DBUILD_BENCH=ON -DCMAKE_BUILD_TYPE=Release && cmake --build build -j$(nproc) && build/bin/bench_bitcoin -filter='SaltedOutpointHasherBench' -min-time=10000

  > Before:

  |               ns/op |                op/s |    err% |     total | benchmark
  |--------------------:|--------------------:|--------:|----------:|:----------
  |               58.60 |       17,065,922.04 |    0.3% |     11.02 | `SaltedOutpointHasherBench_create_set`
  |               11.97 |       83,576,684.83 |    0.1% |     11.01 | `SaltedOutpointHasherBench_hash`
  |               14.50 |       68,985,850.12 |    0.3% |     10.96 | `SaltedOutpointHasherBench_match`
  |               13.90 |       71,942,033.47 |    0.4% |     11.03 | `SaltedOutpointHasherBench_mismatch`

  > After:

  |               ns/op |                op/s |    err% |     total | benchmark
  |--------------------:|--------------------:|--------:|----------:|:----------
  |               57.27 |       17,462,299.19 |    0.1% |     11.02 | `SaltedOutpointHasherBench_create_set`
  |               11.24 |       88,997,888.48 |    0.3% |     11.04 | `SaltedOutpointHasherBench_hash`
  |               13.91 |       71,902,014.20 |    0.2% |     11.01 | `SaltedOutpointHasherBench_match`
  |               13.29 |       75,230,390.31 |    0.1% |     11.00 | `SaltedOutpointHasherBench_mismatch`

  compared to master:
  ```python
  create_set - 17,462,299.19 / 17,065,922.04 - 2.3% faster
  hash       - 88,997,888.48 / 83,576,684.83 - 6.4% faster
  match      - 71,902,014.20 / 68,985,850.12 - 4.2% faster
  mismatch   - 75,230,390.31 / 71,942,033.47 - 4.5% faster
  ```

  > C++ compiler .......................... GNU 13.3.0

  > Before:

  |               ns/op |                op/s |    err% |          ins/op |          cyc/op |    IPC |         bra/op |   miss% |     total | benchmark
  |--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
  |              136.76 |        7,312,133.16 |    0.0% |        1,086.67 |          491.12 |  2.213 |         119.54 |    1.1% |     11.01 | `SaltedOutpointHasherBench_create_set`
  |               23.82 |       41,978,882.62 |    0.0% |          252.01 |           85.57 |  2.945 |           4.00 |    0.0% |     11.00 | `SaltedOutpointHasherBench_hash`
  |               60.42 |       16,549,695.42 |    0.1% |          460.51 |          217.04 |  2.122 |          21.00 |    1.4% |     10.99 | `SaltedOutpointHasherBench_match`
  |               78.66 |       12,713,595.35 |    0.1% |          555.59 |          282.52 |  1.967 |          20.19 |    2.2% |     10.74 | `SaltedOutpointHasherBench_mismatch`

  > After:

  |               ns/op |                op/s |    err% |          ins/op |          cyc/op |    IPC |         bra/op |   miss% |     total | benchmark
  |--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
  |              135.38 |        7,386,349.49 |    0.0% |        1,078.19 |          486.16 |  2.218 |         119.56 |    1.1% |     11.00 | `SaltedOutpointHasherBench_create_set`
  |               23.67 |       42,254,558.08 |    0.0% |          247.01 |           85.01 |  2.906 |           4.00 |    0.0% |     11.00 | `SaltedOutpointHasherBench_hash`
  |               58.95 |       16,962,220.14 |    0.1% |          446.55 |          211.74 |  2.109 |          20.86 |    1.4% |     11.01 | `SaltedOutpointHasherBench_match`
  |               76.98 |       12,991,047.69 |    0.1% |          548.93 |          276.50 |  1.985 |          20.25 |    2.3% |     10.72 | `SaltedOutpointHasherBench_mismatch`

  ```python
  compared to master:
  create_set -  7,386,349.49 / 7,312,133.16  - 1.0% faster
  hash       - 42,254,558.08 / 41,978,882.62 - 0.6% faster
  match      - 16,962,220.14 / 16,549,695.42 - 2.4% faster
  mismatch   - 12,991,047.69 / 12,713,595.35 - 2.1% faster
  ```

ACKs for top commit:
  achow101:
    ACK 6eb5ba5691
  vasild:
    ACK 6eb5ba5691
  sipa:
    ACK 6eb5ba5691

Tree-SHA512: 9688b87e1d79f8af9efc18a8487922c5f1735487a9c5b78029dd46abc1d94f05d499cd1036bd615849aa7d6b17d11653c968086050dd7d04300403ebd0e81210
This commit is contained in:
Ava Chow
2025-12-10 15:22:34 -08:00
9 changed files with 133 additions and 123 deletions

View File

@@ -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;
});

View File

@@ -17,11 +17,14 @@
#include <unordered_map>
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,

View File

@@ -5,6 +5,7 @@
#ifndef BITCOIN_BLOCKENCODINGS_H
#define BITCOIN_BLOCKENCODINGS_H
#include <crypto/siphash.h>
#include <primitives/block.h>
#include <functional>
@@ -87,8 +88,7 @@ typedef enum ReadStatus_t
} ReadStatus;
class CBlockHeaderAndShortTxIDs {
private:
mutable uint64_t shorttxidk0, shorttxidk1;
mutable std::optional<PresaltedSipHasher> 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;

View File

@@ -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<const unsigned char> 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<const unsigned char> 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;

View File

@@ -5,23 +5,34 @@
#ifndef BITCOIN_CRYPTO_SIPHASH_H
#define BITCOIN_CRYPTO_SIPHASH_H
#include <array>
#include <cstdint>
#include <span>
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<uint64_t, 4> 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

View File

@@ -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);
{

View File

@@ -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());
}
}

View File

@@ -7,26 +7,30 @@
#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) :
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<const unsigned char>& script) const
{

View File

@@ -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()); }
};