# ChainHash: complete SMHasher3 and rurban/smhasher suites

Run date: 2026-09-19. All full-suite runs finished.

The 64-bit ChainHash function (`include/chainhash.h` in
[thomasahle/chainhash](https://github.com/thomasahle/chainhash); comb PH,
Horner chain, twisted quintic finalizer; the Lean-proved function) was put
through the complete SMHasher3 suite on two hosts and through the complete
rurban/smhasher suite on one host, registered as `chainhash`. The
128-bit function also passes the full SMHasher3 suite on Xeon: 188 of 188 tests. Its run and log are recorded in the ChainHash-128 section below; no rurban full-suite result is supplied for that function here.

## Header and verification constants

| Registration | SMHasher3 LE | SMHasher3 BE | rurban (LE) |
| --- | --- | --- | --- |
| `chainhash` | `66672BD6` | `FA8A8D3B` | `66672BD6` |

The standalone evaluator reproduces the constants independently of either
suite, on both the portable and the PMULL path. The BE registration swaps
only the digest serialization; message words stay canonical little-endian.

## SMHasher3 full suite (`--test=All`)

| Host | Registration | Passed | Failed | Wall minutes |
| --- | --- | ---: | ---: | ---: |
| xeon | `chainhash` | 200/200 | 0 | 22.92 |
| m2 | `chainhash` | 200/200 | 0 | 20.43 |

The hash passes every test on both hosts, and the two hosts print
identical diagnostics (all 1863 collision/distribution rows and the
p-value histograms agree line for line). The suite is deterministic given
the hash, so this is expected; it also rules out an ISA-specific backend
bug, since the Xeon binary exercises the PCLMUL/VPCLMUL paths and the M2
binary the NEON PMULL paths.

Suite composition. Upstream SMHasher3 at the pinned commit
`3de870c7ab449ad11cf450848d9270e3f54102d1` reports 188 cases for
`--test=All`. The test tree adds one family, SeedDifferential (fixed
message pairs, 2^24 random seeds per pair, 12 message lengths 16–1024
bytes), which brings the reported total to 200. This is the same 200-case
suite the paper's measurements table reports against, so the counts are
directly comparable. The 22 sections run are Sanity, Speed, Hashmap speed,
Avalanche, BIC, Keyset Zeroes / Cyclic / Sparse / Permutation / Text /
TwoBytes / PerlinNoise / Bitflip, Seed Zeroes, SeedSparse, Seed
BlockLength / BlockOffset, Keyset Seed, Seed Avalanche, Seed BIC, Seed
Bitflip, and Seed Differential. `BadSeeds` is not part of `All` at this
revision. This is upstream's standard `All`, not the separate `--extra`
torture mode. Every run used `--test=All --ncpu=8 --noexit-on-failure
--exit-code-on-failure`, so a failure would have been printed and counted
rather than aborting the run.

The `-log2(p-value)` histogram over the 6883 individual statistics
(identical on both hosts):

| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12+ |
| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| 4405 | 1280 | 592 | 289 | 162 | 79 | 34 | 21 | 13 | 6 | 0 | 2 | 0 |

The worst statistic has p ≈ 2^-11; nothing reaches the failure region.

Hosts. Xeon: Intel Xeon Platinum 8375C, Clang 21.1.8, CMake 3.31.8, each
invocation `taskset -c 8-15 nice -n 10 …`, runs sequential. Other users'
jobs ran on the host throughout (1-minute load 37–50 on 64 cores), so the
Speed sections embedded in these logs are not a benchmark and must not be
cited as one. M2: Apple M2 Pro, Apple Clang, `nice -n 10`. The M2 runner
admitted each run only when no `SMHasher3` process existed and the
1-minute load was below 4.5; the gate only checks at launch, and another
short timing job very likely resumed while the 20-minute run was in
flight. This does not affect the quality verdicts, because the suite is
deterministic and the M2 diagnostics equal the Xeon's.

## rurban/smhasher full suite (`--test=All,BIC`)

| Host | Registration | Quality sections passed | Failed sections | Wall minutes |
| --- | --- | ---: | ---: | ---: |
| xeon | `chainhash` | 17/17 | 0 | 40.03 |

Pinned to commit `2688c165595ad68a0543f96940617adc5e22d87e`; Clang
21.1.8. Each invocation is `taskset -c 8-15 nice -n 10 SMHasher
--test=All,BIC chainhash`. At this revision `All` runs Sanity, Speed,
Hashmap, Avalanche, Sparse, Permutation, Window, Cyclic, TwoBytes, Text,
Zeroes, Seed, PerlinNoise, Diff, DiffDist, MomentChi2, Prng and BadSeeds;
it adds BIC only for hashes wider than 64 bits under `--extra`, so BIC is
selected explicitly. LongNeighbors is compiled out upstream. `--extra`
(the exhaustive 32-bit bad-seed scan) is not the standard suite and was
not used.

The 17 quality sections (Avalanche, Sparse, Permutation, Window, Cyclic,
TwoBytes, Text, Zeroes, Seed, PerlinNoise, Diff, DiffDist, MomentChi2,
Prng, BIC, BadSeeds, plus the Sanity checks) print no failure mark; the
Sanity section confirms the verification value `66672BD6`. Worst
statistics: Avalanche worst bias 0.84% over 24–1024-bit keys; BIC max bias
0.0088; MomentChi2 1.61 / 0.93 (bits 1/0), rated "Great"; Prng 2^25 values
with no 64-bit collision and 32-bit collision ratio 1.00×; BadSeeds seed 0
PASS. The suite's own timer reports 2520 s. As with SMHasher3, the
embedded Speed sections are not a benchmark: rurban's small-key loop
changes the seed after every call, so its cycle counts include a full key
setup, and the host was shared.

This fork prints no aggregate counter. A failing statistic is marked
` !!!!!` and a failing test `*********FAIL*********`; the summary counts
both, and the table reports sections (excluding the two timing sections)
with and without such marks. Exit status is not used as evidence of a
pass, because this fork can exit zero after a reported failure. The
`pfHash` API carries a 32-bit seed, zero-extended into the same SplitMix64
mapping as SMHasher3 (below), so rurban's seed tests cover 32-bit seeds.
With no bad seeds registered, its standard BadSeeds section tests seed
zero.

## Seed model (outside the theorem)

SMHasher3 hands the hash a 64-bit seed. The adapter expands it with
SplitMix64 starting from that seed, takes eight outputs as
`s, y, c0..c4, tau` and calls the header's `chainhash_key_from_seed`,
which sets the block keys to powers of `s` in GF(2^64). Keys live in
thread-local storage returned by the suite's seed callback.

This expansion is outside the collision theorem: a 64-bit (or, for
rurban, 32-bit) seed does not supply eight independent uniform words. The
suites therefore test a deterministic seeded adapter; they neither prove
the theorem's assumptions nor establish cryptographic strength.

## The twist, and what a failure would have meant

The header keeps the integer addition `v = P ⊞ tau` before the degree-5
GF(2^64) circuit. The paper's twist section explains why it is there: over
GF(2^64), `v ↦ v^e` has F2-degree equal to the popcount of `e`, so a
polynomial of degree ≤ 6 is at most quadratic in the input bits, with
affine second derivatives, and SMHasher3's fixed-seed keysets (Zeroes,
Sparse, Permutation, TwoBytes, Bitflip) detect exactly that structure. The
paper's measurements on the same 200-case suite:

| Finalizer | Twist | Tests passed |
| --- | --- | ---: |
| degree 7 (4 mult.) | none | 200/200 |
| degree 5 (3 mult.) | none | 178/200 (22 failures: Zeroes, Sparse, Permutation, TwoBytes, Bitflip) |
| degree 5 (3 mult.) | input, `v ⊞ tau` | 200/200 |
| degree 3 (2 mult.) | input | 183/200 |

ChainHash is the third row; an SMHasher3 failure here would have
contradicted the paper. There was none. No untwisted control was rerun
in this lane. The paper is explicit that the twist's sufficiency for a
given suite is empirical, not a theorem, and the exact seeded adapter is
documented above so the result can be reproduced.

## Files

`summary.json` beside this report lists each run (suite, host, start,
end, exit code, verdict and section counts). No upstream pull request has
been filed for either suite.


## ChainHash-128 (registration `chainhash-128`, 0x1FCA728C)

SMHasher3 `--test=All --ncpu=8` on the Xeon 8375C (suite commit 3de870c7, GCC 11.5): **pass, 188 of 188 tests** (the suite runs 188 tests for a 128-bit output); log [smh-xeon-chainhash-128-all.log](smh-xeon-chainhash-128-all.log), run on 2026-09-19 against the header of ChainHash commit d268351.
