CityHash64 v1.1.1 and FarmHash64 NA: a 16-byte every-seed family, and a long-input differential
1. Shortest flooding family: 65,536 sixteen-byte inputs, one unseeded value
CityHash64WithSeed(m, s) = HashLen16(CityHash64(m) - k2, s)
and
CityHash64WithSeeds(m, s0, s1) = HashLen16(CityHash64(m) - s0, s1);
FarmHash64 NA has the same wrapper and the same 9..16-byte path. So
inputs with one unseeded value collide for every seed and both seed
interfaces.
On the 9..16-byte path, with a = w0 + k2, b = w1, mul = k2 + 2·len: c
= ror(b, 37)·mul + a, d = (ror(a, 25) + b)·mul, out = HashLen16(c, d,
mul). HashLen16 inverts in closed form: for a fixed target
H and any 64-bit z there is exactly one (c, d). The remaining single
64-bit equation in a is solved with z3 (city16_solve.py).
About 2^64 sixteen-byte inputs share each value; 65,602 were produced
for H = 0x0f55c17e5eed1616, and the first 65,536 distinct ones are in
city16_members.txt.gz (hex, memory order, one per line).
Examples:
d73194b9d3f216a51455b33a85eb5311
40bde31d7e9d2b39e2ff5d30bb368ee9
Checks:
| program | implementation | result | log |
|---|---|---|---|
mc_city.cpp, mc_farm.cpp (x86-64) |
SMHasher3 cityhash.cpp, farmhash.cpp
(verification values 5FABC5C5, EBC4A679, 5438EF2C) |
all 65,536 share the full 64-bit output: City WithSeed 65,536/65,536 seeds, WithSeeds 1024/1024; Farm NA WithSeed 65,536/65,536, NA WithSeeds 1024/1024, UO WithSeed 1024/1024 | logs/mc_city16_xeon_s16.txt,
logs/mc_farm16_xeon_s16.txt |
indep_city_farm.cc (M2) |
upstream google/cityhash city.cc and google/farmhash
farmhash.cc, unmodified |
unseeded value shared 65,536/65,536 (City and farmhashna); all members equal for 4096/4096 seeds in each of City WithSeed, City WithSeeds, farmhashna WithSeed, farmhashna WithSeeds | logs/indep_city_farm_m2.txt |
Build and run:
python3 -m pip install z3-solver # only for regenerating members
python3 city16_solve.py 0f55c17e5eed1616 1000 8200 part0.txt
c++ -O2 -std=c++17 mc_city.cpp -o mc_city && ./mc_city city16_members.txt 16
c++ -O2 -std=c++17 mc_farm.cpp -o mc_farm && ./mc_farm city16_members.txt 16
# independent check (fetch city.cc, city.h, citycrc.h, farmhash.cc, farmhash.h; see ../SOURCES.md)
c++ -O2 -std=c++17 -I. indep_city_farm.cc city.cc farmhash.cc -o indep_city_farm
gunzip -k city16_members.txt.gz && ./indep_city_farm city16_members.txt 12
Expected: RESULT: PASS / PASS with the
counts above.
2. Long inputs: a rotation differential in WeakHashLen32
In a 64-byte loop chunk, add d = 0x0f0f0f0f0f0f0f0f to word 0 and subtract it from word 3 (or words 4 and 7). The only surviving term cancels when rotr(A + d, 44) - rotr(A, 44) = -d, where A is the public running sum; this holds for 0.886 of random A. The condition is public, so for known content a slot is used only when it holds, and then the pair collides for every seed. Works in any loop block except block 0 and the last 64 bytes, from 192 bytes up.
| check | CityHash64 | FarmHash64 NA |
|---|---|---|
| random content, one slot (words 0/3), 256 B / 1 KiB / 64 KiB / 1 MiB | 0.8841 / 0.8851 / 0.8854 / 0.880 | 0.8848 / 0.8842 / 0.8834 / 0.898 |
| explicit 1024-byte pair | 65,536/65,536 seeds | 65,536/65,536 seeds |
| 2^16 cube at 1064 bytes (16 slots whose public condition holds) | all members equal for 256/256 seeds | 256/256 seeds |
Program city_long.cpp, farm_long.cpp
(single files, SMHasher3 source verbatim):
c++ -O2 -std=c++17 city_long.cpp -o city_long && ./city_long.
Logs: logs/city_long_xeon.txt,
logs/farm_long_xeon.txt.