MurmurHash64A: key-free pairs and 2^16-way sets

Key model: MurmurHash64A(key, len, seed) with a uniformly random 64-bit seed (SMHasher3 MurmurHash2-64). The same function is libstdc++'s 64-bit std::_Hash_bytes, which std::hash<std::string> calls with the fixed seed 0xc70f6907 on LP64 targets. Every result here is key-free: it holds for every seed, so it also holds for that fixed seed. Scores use L = number of 8-byte words of the longer message.

What is checked

murmurhash2_witness.c (transcription of SMHasher3 hashes/murmurhash2.cpp; checks 0x27864C1E and 0x1F0D3804 first). Each 8-byte block is k = mix(w) (a public bijection), then h ^= k; h *= m. A XOR difference in bit 63 of h survives * m, so flipping bit 63 of mix(w) in one block and again in the next cancels.

libstdcxx_check.c (checks 0x1F0D3804 first): a transcription of libstdc++ hash_bytes.cc (64-bit branch) agrees with MurmurHash64A on 2^20 random inputs (0 mismatches) and the 8-vs-7 pair collides under the default seed 0xc70f6907.

loop_xxmur.c: the same two-block flip at any word position inside 256 B .. 1 MB of random data (every trial collided); 2^16-member sets in 64 KB and 2^10 in 1 MB (every key). 1 MB example (little-endian word values): offsets 647,944 and 647,952, 1ada7f923ecb5c4b -> 8c3299f7250e5c4b and e5ffc0ab54b26580 -> 74a7a6466e6f6580.

Build and run

cc -std=c11 -O2 -o murmurhash2_witness murmurhash2_witness.c
./murmurhash2_witness 12 0x2 --exhaustive    # about 15 s; the exhaustive part is the 32-bit pair
cc -std=c11 -O2 -o libstdcxx_check libstdcxx_check.c && ./libstdcxx_check

mkdir -p upstream && curl -sL -o upstream/xxhash.h https://raw.githubusercontent.com/Cyan4973/xxHash/v0.8.3/xxhash.h
cc -std=c11 -O2 -pthread -Iupstream -o loop_xxmur loop_xxmur.c -lm
THREADS=8 ./loop_xxmur fixed m64a 1048576 4096
./loop_xxmur cube m64a 65536 16 32

Logs: logs/murmurhash2_witness.log, logs/libstdcxx_check.log, logs/loop_xxmur_m64a.log.

Prior work

Same-length key-free MurmurHash64A collisions and multicollisions: Aumasson, Bernstein and Boßlet, "Hash-flooding DoS reloaded", 29C3, 2012 (https://www.aumasson.jp/siphash/siphashdos_29c3_slides.pdf; the slides inject a bit-63 difference in one block of a 64-bit MurmurHash2-style function and cancel it in the next); Orson Peters, "Breaking CityHash64, MurmurHash2/3, wyhash, and more", 2024 (https://orlp.net/blog/breaking-hash-functions/), which builds 2^n colliding strings of 16n bytes and points out that libstdc++'s string hash is MurmurHash2 with seed 0xc70f6907. The cross-length 8-vs-7-byte pair (absorbing the length term) is an extension of that trail.