How a provably fair crash round is actually generated

7 min readProvable fairnessCrashEngineering

A crash multiplier is derived from a hash of a server seed the operator committed to before the round, combined with a client seed the player controls. Publish the commitment first and anyone can check afterwards that the result was not chosen.

The problem it solves

A player cannot see your random number generator. They have to take your word that the 1.02x they got was not chosen because they had staked more than usual. "Provably fair" is the arrangement that removes the need to take anybody's word for it.

It works by making the operator commit to the outcome before knowing the stake, and by making that commitment checkable afterwards. Nothing about it requires trusting the operator, the player, or us.

The three inputs

  • A server seed, generated by us and kept secret until the round ends. Its SHA-256 hash is published before the round opens: that hash is the commitment.
  • A client seed, which the player sets and can change whenever they like. This is what stops us from choosing a server seed that produces a convenient result, because we would have to know the client seed first.
  • A nonce, the round number for that pair of seeds. It makes each round in a sequence distinct without needing new seeds every time.

Turning a hash into a multiplier

The three inputs are concatenated and hashed. The first bytes of that hash are read as an integer and mapped onto a distribution whose expected return matches the game's published RTP.

The house edge lives in exactly one place: a small share of outcomes is forced to the lowest multiplier. Everything else follows from the hash. That is what makes the maths auditable — there is one constant to check, not a model to trust.

// The verification, in full. Nothing here is specific to us.
const crypto = require('node:crypto');

function crashPoint(serverSeed, clientSeed, nonce, houseEdge = 0.01) {
  const h = crypto
    .createHmac('sha256', serverSeed)
    .update(`${clientSeed}:${nonce}`)
    .digest('hex');

  // The instant-bust share. This is the house edge, and it is the only
  // number in the scheme that is a choice rather than a consequence.
  const bustChance = Math.floor(2 ** 52 / houseEdge);
  if (parseInt(h.slice(0, 13), 16) % bustChance === 0) return 1.00;

  const r = parseInt(h.slice(0, 13), 16) / 2 ** 52;
  return Math.floor((100 * (1 - houseEdge)) / (1 - r)) / 100;
}

What to check, as a player

After a round, the server seed is revealed. Three things must hold, and all three are arithmetic rather than opinion:

  • The SHA-256 of the revealed server seed equals the hash published before the round.
  • Running the function above on the revealed seed, your client seed and the nonce reproduces the multiplier you saw.
  • The nonce increased by exactly one since your last round on that seed pair.

If any of those fails, the result was changed after the fact. If all three hold, it could not have been.

Where this is weaker than it sounds

Provable fairness proves that a specific round was not tampered with. It does not prove the house edge is what was advertised, and it does not prove the operator is solvent. Those are different questions and it is worth being honest that one scheme does not answer all three.

The edge is checkable a different way: play enough rounds and the distribution is what it is. Publish the constant, and anybody with the round history can test it. We publish ours on every game page for that reason.


Try it against a real API

Sandbox keys are issued instantly, with play money and every game enabled. No card, no call, and the documentation is public whether or not you sign up.