Este documento se publica en inglés. La versión en inglés es la única vinculante.
Provably Fair
Edition of November 5, 2026
In every game built by KOZYR — cases, upgrade, Dice, Limbo, Plinko, Mines, Crash, jackpot, battles and coinflip — the result is fixed in advance and can be verified by you. We cannot change the outcome after you bet, and you cannot predict it before you bet. Third-party provider slots run on their own certified generators.
01How it works
Every player has a pair of seeds:
- the server seed — 32 random bytes generated by the server. Before you bet you only see its
SHA-256hash; - the client seed — a string you can set yourself in your profile;
- the nonce — the number of the bet on this seed pair; it starts at zero and goes up by one after every game.
The result of any game comes from HMAC-SHA256(server seed, "client seed:nonce:cursor"). Each 32-byte block yields eight 4-byte numbers (big-endian). When they run out, cursor goes up by one and the next block is computed.
To map a number to a range without bias, rejection sampling is used: numbers greater than or equal to floor(2^32 / n) × n are discarded, and the remainder of division by n is taken from the rest.
02How to verify your game
- Note the server seed hash and the nonce of the bet — both are in your profile and on the result screen.
- Rotate the seed pair in your profile. The server reveals the old server seed.
- Check that the
SHA-256of the old seed equals the hash you saw before the bet. - Recompute the result with the formulas below — it must match what the game showed.
03Games
Dice
A number from 0 to 9999 (00.00–99.99) = nextInt(10000). An “under” bet wins if the number is below the chance; an “over” bet wins if the number is at least 10000 − chance. Payout = bet × 99 / chance in percent; the house edge is 1%.
Limbo
The first two numbers of the stream, a and b, give r = a × 2^20 + floor(b / 2^12) — 52 random bits. The result = max(1.00, floor(99 × 2^52 / (2^52 − r)) / 100). The bet wins if the result is at least the chosen target; the payout = bet × target. The chance to win is 0.99 / target, and the house edge is 1%.
Plinko
Each row takes nextInt(2): 0 sends the ball left, 1 sends it right. The landing slot is the number of right bounces. The payout = bet × the slot multiplier from the table for the chosen number of rows and risk level. The tables are shown in the game, and each returns between 98.4% and 98.9%.
Cases
The sum of all item weights is W, and r = nextInt(W). Items are walked in ascending order of their id; each item’s weight is subtracted from r, and the item that makes r negative drops. An item’s chance is its weight / W, as shown in the case. The id order does not depend on prices, so any past opening recomputes the same way.
Upgrade
r = nextInt(1 000 000). The upgrade succeeds if r is below the chance in parts per million. Chance = stake value / target value × (1 − 8%), capped at 75%.
Mines
The cells are numbered 0 to 24. The deck [0…24] is shuffled with Fisher–Yates: for i from 24 down to 1, j = nextInt(i + 1) and cells i and j are swapped. The first m cells of the deck are mines. The payout after k safe cells = bet × 0.99 × C(25, k) / C(25 − m, k).
Crash
Crash has one shared round for all players and a separate server seed for each round. The seed hash is published before betting opens and the seed itself after the crash. The first 52 bits of HMAC-SHA256(round seed, "crash") are taken as a number r, and the multiplier = max(1.00, floor(99 × 2^52 / (2^52 − r)) / 100). The probability of reaching multiplier x is 0.99 / x, so any auto cash-out target returns 99% on average.
Battles
Every battle has its own server seed whose hash is visible from creation. The item for round R and seat S is picked as in cases, but the number stream uses the client seed battle:ID and the nonce R × 4 + S. A tie is broken by nextInt(number of leaders) from the stream with seed battle:ID:tie and nonce 0.
Jackpot
Every round has its own server seed whose hash is visible from the start. Every cent staked is one ticket; tickets are numbered from zero in the order of the bets. The winning ticket = nextInt(pot in cents) from the stream with client seed jackpot:ID and nonce 0. The timer starts when a second player joins, so nobody knows the final pot in advance. The winner receives the pot minus the 4% commission.
Coinflip
Every game has its own server seed whose hash is visible from creation. The first number of the stream with client seed coinflip:ID and a nonce equal to the joining player’s internal ID (0 for the bot) is taken. An even number is heads, an odd one is tails. Nobody knows in advance who will join, so the creator cannot pick the outcome.
04Verification code
This Node.js code repeats the server’s calculations:
const { createHmac, createHash } = require('node:crypto');
function* stream(serverSeed, clientSeed, nonce) {
for (let cursor = 0; ; cursor++) {
const block = createHmac('sha256', serverSeed).update(`${clientSeed}:${nonce}:${cursor}`).digest();
for (let i = 0; i < 32; i += 4) yield block.readUInt32BE(i);
}
}
function nextInt(s, n) {
const limit = Math.floor(2 ** 32 / n) * n;
for (;;) {
const v = s.next().value;
if (v < limit) return v % n;
}
}
const sha256 = (s) => createHash('sha256').update(s).digest('hex');
const dice = (ss, cs, nonce) => nextInt(stream(ss, cs, nonce), 10000);
const upgrade = (ss, cs, nonce) => nextInt(stream(ss, cs, nonce), 1000000);
// weights — the case item weights in ascending id order
function caseItem(ss, cs, nonce, weights) {
let r = nextInt(stream(ss, cs, nonce), weights.reduce((a, b) => a + b, 0));
return weights.findIndex((w) => (r -= w) < 0);
}
function mines(ss, cs, nonce, m) {
const s = stream(ss, cs, nonce);
const deck = [...Array(25).keys()];
for (let i = 24; i > 0; i--) {
const j = nextInt(s, i + 1);
[deck[i], deck[j]] = [deck[j], deck[i]];
}
return deck.slice(0, m).sort((a, b) => a - b);
}
function crash(roundSeed) {
const h = createHmac('sha256', roundSeed).update('crash').digest();
const r = BigInt('0x' + h.subarray(0, 7).toString('hex')) >> 4n;
const e = 1n << 52n;
return Math.max(100, Number((99n * e) / (e - r))) / 100;
}
function limbo(ss, cs, nonce) {
const s = stream(ss, cs, nonce);
const r = (BigInt(s.next().value) << 20n) | (BigInt(s.next().value) >> 12n);
const e = 1n << 52n;
return Math.max(100, Number((99n * e) / (e - r))) / 100;
}
function plinko(ss, cs, nonce, rows) {
const s = stream(ss, cs, nonce);
return Array.from({ length: rows }, () => nextInt(s, 2));
}
const jackpot = (ss, roundId, potCents) => nextInt(stream(ss, `jackpot:${roundId}`, 0), potCents);
05Questions
If a result does not match, write to support at @kozyrs_bot with the game, the time, the seed hash and the nonce. We look into every case.