Universal Hashing
The Problem with Fixed Hash Functions
Any fixed hash function can be adversarially exploited. If an attacker knows (or reverse-engineers) the hash function, they can craft a set of keys that all map to the same slot:
The result: chaining degrades to a linked list of length , or open addressing probes through all slots. All operations become . This is not just a theoretical concern: it has been used in denial-of-service attacks against web frameworks and network infrastructure.
Randomising the input data or picking a random pivot (as in quicksort) reduces the probability of hitting the worst case, but for hash tables a stronger guarantee exists.
Universal Families of Hash Functions
Universal hashing: the hash function is chosen randomly at runtime from a family of hash functions. The family is designed so that, regardless of which keys the adversary provides, the probability of collision between any two distinct keys is bounded.
Definition: A family of functions mapping to is universal if, for any distinct :
The probability is taken over the random choice of from , not over the choice of keys. The keys can be chosen adversarially after is known; the guarantee still holds.
A Concrete Universal Family
Choose a prime (larger than any possible key value). Define:
where and are chosen uniformly at random. The family is universal.
Proof sketch: For any , the values and are uniformly distributed and pairwise independent over . The probability they collide modulo is at most .
Why Universal Hashing Works
With a universal family and chaining, the expected search time is even against an adversary. The adversary can choose keys knowing the family but not which will be chosen at runtime.
The proof: for any search key , the expected number of other keys in the same chain is:
Thus the expected chain length is , giving expected search when .
Static Dictionaries and Perfect Hashing
For a static dictionary (keys known in advance, no insert/delete after construction), universal hashing can achieve worst-case search time with no collisions, using a two-level scheme:
- Hash keys into primary slots using a universal function.
- For each slot with keys, build a secondary hash table of size using another universal function.
The expected total space is , and with high probability (by choosing random hash functions repeatedly until no collisions occur), all secondary tables are collision-free. This is called perfect hashing.
Practical Considerations
Many production hash table implementations (Python’s dict, Rust’s HashMap) use a fixed high-quality hash function with per-process randomisation (a random seed). This approximates universal hashing: the seed is chosen at startup, making it difficult for an attacker to predict slot assignments.
The Cambridge course treats universal hashing as the theoretical foundation: a fixed function cannot beat an adversary, but a randomly chosen function from a universal family can.
Summary
| Concept | Definition |
|---|---|
| Fixed hash function | Vulnerable to adversarial input |
| Universal family | for any |
| Example family | |
| Expected search time | even against adversary |
| Perfect hashing | Zero collisions, worst-case search, expected space |
| Per-process randomisation | Practical approximation of universal hashing |