Crash hash chains explained: what a 10-million-round chain proves
How multiplayer crash games use a pre-generated SHA-256 hash chain, how to verify a round against the terminal hash, and the one thing the chain cannot prove.
Crash is the odd one out among provably fair games. Everyone at the table sees the same rising multiplier and the same crash point, so the result cannot depend on your personal client seed. If it did, every player would see a different round.
The standard answer is a hash chain: a long sequence of seeds generated in advance, where each seed is the SHA-256 hash of the next one. This guide shows how the chain is built, how to check a round against it, and where its guarantee stops.
Building the chain
The operator starts with one secret random value and hashes it over and over:
v0 = secret
v1 = SHA256(v0)
v2 = SHA256(v1)
...
vN = SHA256(vN-1) ← the terminal hash, published
On Rubio's crash game, the chain has 10,000,000 values. The last one, the terminal hash, was published together with a fixed salt before the first round of the chain was played. Rounds then use the chain backwards: the first round uses the value just before the terminal hash, the second round the one before that, and so on.
Because SHA-256 cannot be reversed, nobody can compute the next round's seed from the ones already revealed. But anyone can go forward: hash a revealed seed and you get the previous round's seed. Keep hashing and you eventually reach the terminal hash that was published at the start.

From seed to crash point
Each round's result is computed with HMAC-SHA256 keyed with that round's seed, over a message made of the published salt and the round number. The first 4 bytes of the digest become a number r between 0 and 1, and the crash point is:
crash = floor(100 × (1 − edge) ÷ (1 − r)) ÷ 100, never below 1.00×
With a 4% edge, about 4.95% of rounds end at 1.00× (that is 1 − 0.96 ÷ 1.01), and the chance that a round reaches any target m is close to 0.96 ÷ m. A 2× target is reached in about 48% of rounds; a 10× target in about 9.6%.

This reciprocal shape is shared by every multiplier game: halve the probability and the payout doubles, minus the edge. The edge sits inside the formula, which is why the expected return is the same for any cash-out target.
Verifying a round
You need three public things and one revealed thing:
- The terminal hash and salt, published when the chain was created.
- The round number.
- The seed hash shown before the round, which equals the previous round's seed.
- The revealed seed, published when the round crashes.
Then:
import hashlib, hmac
def sha(x: str) -> str:
return hashlib.sha256(x.encode()).hexdigest()
def crash_point(seed: str, salt: str, round_no: int, edge: float = 0.04) -> float:
h = hmac.new(seed.encode(), f"{salt}:{round_no}".encode(), hashlib.sha256).hexdigest()
r = int(h[:8], 16) / 2**32
return max(1.0, (100 * (1 - edge) // (1 - r)) / 100)
Check that sha(revealed_seed) equals the seed hash shown before the round. For a stronger check, hash forward repeatedly from any revealed seed: after as many steps as the round's position in the chain, you must land on the published terminal hash. If you do, that seed was part of the chain from the day it was published.
Hashing up to 10 million times sounds heavy, but a laptop does it in seconds in Python and far faster in compiled code.
What the chain proves
- No seed was swapped after publication. Changing any seed would break every hash link after it, and the terminal hash would no longer match.
- The order of results was fixed in advance. Round 5,000 will use exactly the seed that sits at position 5,000 from the end.
- The operator cannot react to bets. A big bet on round 5,000 cannot change a value that was fixed months earlier.
What it does not prove
The chain fixes results once it exists. It does not prove how the starting secret was chosen. In principle an operator could generate many chains and publish the one that happened to start with a bad run for players. Mixing in a value nobody controls at creation time, such as a future Bitcoin block hash, closes this gap. A fixed house salt does not.
It is worth giving credit where it is due: Rubio's own rules page states this limitation plainly, noting that its salt is a fixed house text rather than a future block hash, so the chain does not prove the house did not pick it in advance. That kind of honesty is exactly what to look for. Sites that claim their crash game is "100% provably fair" without mentioning this are overselling.
In practice, picking a lucky chain would give the house a small, temporary advantage that averages out over millions of rounds, while the published edge still determines long-run returns. The bigger practical question for players is the edge itself.
Practical tips for crash players
- A higher cash-out target does not improve your expected return; it only changes how often you win and how much.
- Auto cash-out removes reaction time from the equation, which matters for low targets.
- Check how the operator handles disconnects: does an auto cash-out still trigger server-side?
FAQ
Why can't I use my own client seed in crash?
Every player shares one round. If each client seed changed the result, players would see different crash points for the same round.
How do I find the terminal hash?
Operators that use chains publish it on their fairness page or a public endpoint, together with the salt and the chain length.
Does the hash chain make crash fairer than dice?
It makes crash verifiable in a similar way, but with a weaker guarantee about how the chain was created. Dice with your own client seed gives you a slightly stronger guarantee.