Provably Fair
This page documents the exact scheme (algorithm version v1) used to commit and verify deck order in rip events. It is written for people who want to check our work.
Commitment (before the event starts)
- Each card slot in the deck has a unique ID. The IDs are sorted lexicographically and joined with
|, and the integrity hash is computed asSHA-256(id1|id2|…|idN). This fingerprints the set of cards in the deck. - A random 128-bit seed is generated and kept secret while the event runs.
- The deck hash is computed as
SHA-256(integrityHash + ":" + seed)and published on the event page before the first pull. - The deck is shuffled with a Fisher–Yates shuffle driven by the
mulberry32PRNG, seeded with the first 32 bits (8 hex characters) of the seed. The resulting order is frozen in the database.
Dealing
Each pack opening deals the next unpulled card in the frozen sequence, in a single atomic database operation. Pull order is recorded and the most recent pulls are shown publicly on the event page.
Reveal & verification (after the event ends)
Once an event has ended, the seed is revealed on the event data. Every event also has its own fairness page, linked from the event itself, listing the sealed deck in sequence order alongside the order cards were actually pulled in. To verify an event:
- Recompute
SHA-256(integrityHash + ":" + seed)and confirm it equals the deck hash that was published while the event was live. This proves the seed was fixed before the first pull. - Re-run the shuffle: seed
mulberry32withparseInt(seed.slice(0, 8), 16)and apply Fisher–Yates to the sorted slot IDs. The resulting order must match the observed pull order.
Reference implementation
function mulberry32(seed) {
return function () {
let t = (seed = (seed + 0x6d2b79f5) | 0);
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
function seededShuffle(ids, seedHex) {
const rng = mulberry32(parseInt(seedHex.slice(0, 8), 16) >>> 0);
const a = ids.slice();
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(rng() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}Because the deck hash binds both the card set and the seed, changing either one after publication would change the hash — any tampering is detectable by anyone who saved the commitment while the event was live.
