Fairness
Every shuffle and spin on JustPoker is committed to before you play and can be reproduced by you afterwards, and every Crash point and every Live Roulette and Live Baccarat round is fixed by a published hash chain. This page explains the mechanism, publishes the exact algorithms and lets you verify any hand or game in your browser.
In a conventional online card room you have to trust that the shuffle was random and that the operator did not look at your cards or the dealer's before deciding anything. A provably fair shuffle replaces that trust with arithmetic you can check. It works in three moves, known as commit–reveal.
We generate a secret server seed and publish only its SHA-256 hash. The hash pins the seed down without disclosing it — like sealing a prediction in an envelope and handing you the envelope.
Each deck is derived from our secret seed plus a client seed you control and a hand counter. Because your input arrives after the commitment, neither side can steer the result alone.
When the seed is retired we reveal it. You hash it, confirm it matches the commitment you saw, and recompute every deck it produced. If any card differs, the proof fails.
The algorithm below is frozen: the code that deals your hands, the verifier on this page and the reference vectors all implement it byte for byte. Any change would invalidate every previously published hand.
Before a server seed can be used, we generate 32 random bytes, write them as a 64-character hex string and publish SHA-256 of that string on your Fairness & Seeds page. The seed itself stays secret. Because SHA-256 is one-way and collision-resistant, we cannot later swap in a different seed that produces the same hash.
Your client seed is combined with our server seed on every hand. You can change it whenever no hand is in progress, and the seed you choose is applied to a server seed whose hash was already published — so we cannot pick a server seed with your client seed in mind.
The nonce is a counter that starts at 0 for a seed pair and increases by one per hand. The HMAC message is clientSeed:nonce:round, so every hand with the same pair gets a different, fully determined deck. Your hand history records the hash, client seed and nonce for each hand.
HMAC-SHA256 with the server seed as key produces 7 blocks of 32 bytes. Every four bytes form one float in [0, 1). A Fisher–Yates shuffle walks the deck from position 51 down to 1, swapping each position with floor(float × (i + 1)). Exactly 51 floats (204 bytes) are consumed, in order.
Positions 0 and 1 are your hole cards, 2 and 3 the dealer's, 4 to 6 the flop, 7 the turn and 8 the river. There are no burn cards, so the deck alone determines the whole hand. The dealer's cards are held server-side until showdown.
When you rotate your seed pair, the old server seed is revealed and the pre-committed next seed becomes active. Paste the revealed seed, your client seed, the nonce and the published hash into the verifier below: it recomputes the hash and the entire deck in your browser and shows the cards that must have been dealt.
// 1. Commitment — published before the seed is ever used
serverSeedHash = SHA256( utf8(serverSeed) ) // serverSeed: 64 hex chars
// 2. Byte stream — 7 HMAC rounds of 32 bytes each
for i in 0 .. 6:
block[i] = HMAC_SHA256( key = utf8(serverSeed),
message = utf8(`${clientSeed}:${nonce}:${i}`) )
bytes = block[0] ‖ block[1] ‖ … ‖ block[6] // first 204 bytes are used
// 3. Floats — 4 bytes each, 51 floats in [0, 1)
f[k] = bytes[4k] / 256 + bytes[4k+1] / 256² + bytes[4k+2] / 256³ + bytes[4k+3] / 256⁴
// 4. Fisher–Yates shuffle
deck = [0, 1, …, 51]
for i = 51 down to 1: // k = 51 − i
j = floor( f[k] × (i + 1) )
swap deck[i], deck[j]
// 5. Card encoding
rank = card mod 13 // 0 = Two … 8 = Ten, 9 = Jack, 10 = Queen, 11 = King, 12 = Ace
suit = floor(card / 13) // 0 = ♠ spades, 1 = ♥ hearts, 2 = ♦ diamonds, 3 = ♣ clubs
// 6. Deal order — no burn cards
player = deck[0], deck[1]
dealer = deck[2], deck[3]
flop = deck[4], deck[5], deck[6]
turn = deck[7]
river = deck[8]Every card and wheel game on the site is driven by the same commit–reveal scheme and the same HMAC-SHA256 byte stream; Crash, a round shared by every player, uses a hash chain instead (below). Two games extend the byte stream without changing it.
Blackjack uses your own seed pair exactly like Ultimate Texas Hold'em, but the Fisher–Yates shuffle runs over 312 positions instead of 52, consuming 311 floats (39 HMAC rounds). Each position is a card from one of the 6 physical decks. The shoe is derived anew for every round with the next nonce and never persists, so there is no card-counting edge for anyone — including us. The dealer's hole card is position 3 of the shoe; it stays on our server until the dealer's turn.
At a shared table up to five players receive cards from one deck, so the deck cannot depend on a single player's seed. Each table has its own committed server seed, published as a hash before it is used and rotated every 200 rounds. The client seed for a round is the SHA-256 of the round id and the client seeds of every seat that was dealt in, in seat order. The round id itself is the table id, the table seed hash and the round counter — all public before the deal — so nothing about the shuffle is left for the operator to choose once the players are known. Your hand history records the seed inputs and the combined seed for every live round you played.
// Blackjack — a 6-deck shoe from the SAME byte stream, only the shuffle is longer
n = 52 × 6 = 312 // 311 floats = 1244 bytes = 39 HMAC rounds
shoe = [0, 1, …, 311]
for i = 311 down to 1: j = floor( f[k] × (i + 1) ); swap shoe[i], shoe[j]
card(v) = v mod 52, physicalDeck(v) = floor(v / 52)
deal: shoe[0] player, shoe[1] dealer up, shoe[2] player, shoe[3] dealer hole,
then every hit, double, split card and dealer draw from shoe[4] onwards
A fresh shoe is derived for EVERY round (nonce + 1), so nothing carries over.
// Live table — one deck for up to 5 seats, from the TABLE's committed seed
roundId = tableId + ":" + tableSeedHash + ":" + nonce // derived, never chosen
combinedClientSeed = SHA256( roundId + ":" + seatClientSeeds.join(":") ) // seats dealt in, ascending
deck = deriveDeck( tableServerSeed, combinedClientSeed, nonce )
seat k : deck[2k], deck[2k+1] dealer : deck[2n], deck[2n+1]
flop : deck[2n+2 .. 2n+4] turn : deck[2n+5] river : deck[2n+6]
The table seed rotates every 200 rounds: the old seed is revealed, the next hash was already shown.Both use your own seed pair and the next nonce, exactly like the card games. A spin or a coup is decided by the request that places your bets: the result is derived, settled and stored at once, and the animation at the table only presents it.
The winning number is the first float of the same HMAC stream (round 0, the first four bytes) multiplied by 37 and rounded down. The float has 2³² equally likely values, so every pocket from 0 to 36 comes up with a probability within 10⁻⁸ of 1/37. Your bets never enter the calculation: the number is fixed by the seeds and the nonce before the ball is shown.
Baccarat shuffles 416 positions with the same Fisher–Yates procedure as the blackjack shoe and deals from the front: Player, Banker, Player, Banker, then any third cards the fixed tableau calls for, the Player's first. Nobody makes a decision during a coup, so the first four to six cards of the shoe decide the whole round, and the verifier shows which of them were used.
// Roulette — ONE float from round 0 of the same byte stream
bytes = HMAC_SHA256( utf8(serverSeed), utf8(`${clientSeed}:${nonce}:0`) )[0..3]
f = b0/256 + b1/256² + b2/256³ + b3/256⁴ // in [0, 1)
number = floor( f × 37 ) // 0–36, single-zero wheel
Bias: each number is hit by 2³² ÷ 37 of the 2³² floats, ±1 → below 10⁻⁸.
// Baccarat — a fresh 8-deck shoe every round, dealt from the front
shoe = the blackjack shuffle over 416 positions // 415 floats = 52 HMAC rounds
card(v) = v mod 52; points: A = 1, 2–9 face value, 10/J/Q/K = 0; total = sum mod 10
Player shoe[0], shoe[2] Banker shoe[1], shoe[3]
then the fixed tableau: Player third card (if drawn), then Banker third card (if drawn)Crash is one round shared by every player, so it cannot use your seed pair. Instead, the crash points of a whole chain of games are fixed before the first of them is played, and a public value nobody controls is mixed in afterwards. Your bets and cash-outs never enter the calculation.
We draw a secret random seed and hash it with SHA-256 over and over, once per game in the chain, and publish only the last hash: the terminating hash. The games use the chain backwards, so game 1 uses the hash just before the terminating hash, game 2 the one before that, and so on. SHA-256 cannot be reversed, so nobody can work out the next game from the ones already revealed, and every revealed game hash leads forward to the terminating hash in exactly its game number of steps.
When a chain is committed we announce a future Bitcoin block, and its terminating hash and that block are published below at once, even while an earlier chain is still being played. The block's hash, which nobody can know until the block is mined, becomes the chain's salt. The salt did not exist when the chain was fixed, so we could not have searched for a chain that favours the house. A chain is never played before its salt is recorded.
Each game's crash point is derived from HMAC-SHA256 of the salt, keyed with the game hash: the first 52 bits give a number X between 0 and 1, and the multiplier is (100 − edge) / (1 − X) hundredths, rounded down and never below 1.00x. The house edge is stored with the chain. A cash-out target x is reached with probability (1 − edge) / x, so every auto cash-out target returns the same share of the stakes on average, 1 − edge. A cash-out by hand returns slightly less, because the server pays it only while the round is still running. A game hash is revealed only after its round has crashed.
46696ec2bd158b3e8deb23481f4bf08b705faa7840aa53e89e5976eb3e2c84f700000000000000000001fafa061ec8eae19be6bea9f93c03910c7cba5ae83045Round numbers at the table are this chain's game numbers, the number the verifier asks for.
// Chain — committed before its first game
h[0] = 32 random bytes as 64 hex chars // the secret seed
h[i+1] = SHA256( utf8(h[i]) ) // hex string in, hex string out
terminatingHash = h[L] // published first; L = chain length
gameHash(n) = h[L − n] // game n = 1 … L: the chain backwards
check: SHA256 applied n times to gameHash(n) = terminatingHash
// Crash point — salt = the hash of a Bitcoin block announced at commitment
h = HMAC_SHA256( key = utf8(gameHash), message = utf8(salt) )
r = parseInt( h[0..12], 16 ) // first 13 hex = 52 bits
X = r / 2^52 // in [0, 1)
crashX100 = max( 100, floor( (100 − edge) / (1 − X) ) ) // edge in percent, stored with the chain
multiplier = crashX100 / 100
P(multiplier ≥ x) = (1 − edge / 100) / x // for every cash-out target xAt Live Roulette and Live Baccarat everyone bets on the same round, so no player's seed can decide it. Each table plays its own pre-committed hash chain, salted with a future Bitcoin block exactly like Crash, and the round's game hash goes into the same frozen derivation as the single-player wheel and shoe. Your bets never enter the calculation.
Each table has an independent chain: a secret random seed hashed with SHA-256 once per game, of which only the last hash, the terminating hash, is published, together with the Bitcoin block whose hash will become the salt. Games use the chain backwards, so every revealed game hash leads forward to the terminating hash in exactly its game number of steps, and nobody can work out the next one. Round numbers continue from one chain to the next; the game number within the chain is shown with every round.
The game hash is used as the server seed, the salt as the client seed and the nonce is always 0. A Live Roulette number is the first float of that HMAC stream times 37, rounded down, exactly like a single-player spin; a Live Baccarat coup is dealt from the front of a fresh 8-deck shoe, exactly like a single-player coup. The outcome is computed at "No more bets", when no stake can change any more, and the game hash is published with the result.
Chain
SHA-256 applied to the round's game hash as many times as its game number within the chain gives the published terminating hash. The game was therefore fixed before any game of the chain was played, and before the salt existed.
Salt
The chain's salt is the hash of the Bitcoin block announced when the chain was committed: compare it on any block explorer. A development salt, used only outside production, is labelled as such.
Outcome
The winning number is floor(f × 37) for the first float of HMAC-SHA256(gameHash, salt:0:0); the coup is dealt from the front of the 8-deck shoe shuffled with the floats of HMAC-SHA256(gameHash, salt:0:i). These are the single-player derivations, unchanged.
Settlement
Every ticket is settled from that outcome by the single-player engine with the same payouts, and your hand history re-settles your bets from the stored round each time it is read.
We hold each chain's seed, so we know every future outcome; the commitment means we cannot choose them. The salt did not exist when the chain was fixed, so we could not search for a chain that favours the house, and the order of the games is fixed. The one lever left would be cancelling rounds by restarting the server while bets are open. A cancelled round debits nothing, and every voided round's game hash and would-be result is published below, so a pattern would be visible to anyone.
// Chain — one per table, committed before its first game (the Crash scheme)
h[0] = 32 random bytes as 64 hex chars // the secret seed
h[i+1] = SHA256( utf8(h[i]) ) // hex string in, hex string out
terminatingHash = h[L] // published first; L = chain length
gameHash(g) = h[L − g] // game g = 1 … L of the chain
roundNumber = baseGame + g // continues across a table's chains
salt = hash of the Bitcoin block announced at commitment
check: SHA256 applied g times to gameHash(g) = terminatingHash
// Live Roulette — the single-player spin, serverSeed = gameHash, clientSeed = salt, nonce 0
bytes = HMAC_SHA256( key = utf8(gameHash), message = utf8(salt + ":0:0") )[0..3]
f = b0/256 + b1/256² + b2/256³ + b3/256⁴ // in [0, 1)
number = floor( f × 37 ) // 0–36
// Live Baccarat — a fresh 8-deck shoe every coup, the same three inputs
f[k] = floats of HMAC_SHA256( utf8(gameHash), utf8(salt + ":0:" + i) ) // 415 floats
shoe = Fisher–Yates over 0 … 415; card(v) = v mod 52
coup = Player shoe[0], shoe[2] · Banker shoe[1], shoe[3] · then the fixed tableauconst { createHash, createHmac } = require("node:crypto");
let h = gameHash; for (let i = 0; i < chainGame; i++) h = createHash("sha256").update(h).digest("hex");
console.log(h === terminatingHash);
const k = createHmac("sha256", gameHash).update(`${salt}:0:0`).digest().readUInt32BE(0);
console.log(Math.floor((k / 2 ** 32) * 37));2552424f3afa694e0b850df536b43511539a4c8d483cf47ae5378e0bd4431cda00000000000000000000b98b89d38a790b7dc4a952b97384ece9b477f777f7bdRound numbers at the table are this chain's game numbers, the number the verifier asks for.
1 round has been voided by a restart while bets were open: nothing was debited. It is listed with the result each would have had.
966cb402…373be62d244baee6ad59ce2322c0c3da92c64698422f602cfb0f3208936da3d2cea9a500000000000000000000b98b89d38a790b7dc4a952b97384ece9b477f777f7bdRound numbers at the table are this chain's game numbers, the number the verifier asks for.
1 round has been voided by a restart while bets were open: nothing was debited. It is listed with the result each would have had.
756798e6…800f78Games of the published test chain of the Crash vectors below (100 hashes, its seed public because it is never played), salted with the Bitcoin genesis block hash. Each game hash hashes forward to the terminating hash 79c72c9b3706a559536454b9e12185a390fb5f63b2b7d30150527ef02e03bfab in exactly its game number of steps. Game 19 lands on zero.
| # | Game | Game hash | HMAC, first 8 hex | Float | Number | Action |
|---|---|---|---|---|---|---|
| 1 | 1 | 8509059441…521b78 | 15329830 | 0.082803260535 | 3 Red | Verify |
| 2 | 2 | 51bc667c67…48d513 | c370c85e | 0.763439677190 | 28 Black | Verify |
| 3 | 19 | d90f2fb65d…9997e0 | 05403637 | 0.020511043957 | 0 Green | Verify |
| 4 | 38 | 5b49ee55cc…641fc9 | de7d31db | 0.869097820250 | 32 Red | Verify |
| 5 | 99 | 0fdef169ef…b1f857 | bbc98a75 | 0.733544019284 | 27 Red | Verify |
Games of the same test chain: every coup size, a natural, a coup where both sides draw, and a Tie. The shoe hash covers all 416 raw values of the game's fresh shoe.
| # | Game | Game hash | First six cards | Player · Banker | Shoe hash | Action |
|---|---|---|---|---|---|---|
| 1 | 1 | 8509059441…521b78 | 3h Ac 9d 3h Jh 3d | 3h 9d Jh (2) · Ac 3h (4)Banker wins · 5 cards | 76b60ac8cc…874371 | Verify |
| 2 | 2 | 51bc667c67…48d513 | Jd 4h 8c 5s Kc Jh | Jd 8c (8) · 4h 5s (9)Banker wins · 4 cards | 55a3d3bd4b…91fb5c | Verify |
| 3 | 3 | db182f415a…d37ab8 | Qc 8c 7h 9c 8s 8d | Qc 7h (7) · 8c 9c (7)Tie · 4 cards | aaee00b430…c3159a | Verify |
| 4 | 6 | 29ed2dc830…1eeec9 | 5s 2d 6d 2s 6d 7h | 5s 6d 6d (7) · 2d 2s 7h (1)Player wins · 6 cards | 2aeb10aeb8…b26283 | Verify |
In Coinflip and Jackpot you play other players. The house is never a party: it takes 5% of the pot and nothing else. Each result comes from a server seed committed when the game opens and a round of drand, a public randomness beacon, that is fixed when the entries close and published a few seconds later. Nobody, us included, can know a result while a stake can still change.
Every flip and every jackpot round gets its own secret server seed when it is created. Its SHA-256 hash is shown at once in the lobby or on the round, and the seed itself is revealed only when the game has ended. We cannot change the seed afterwards without the hash giving it away.
The moment a flip is joined, or a jackpot countdown ends, the server fixes a round of drand quicknet, the public randomness beacon of the League of Entropy: 2 rounds after the latest published one, so it appears 3 to 6 seconds later. A drand value is a signature that a threshold of independent nodes produce together; nobody, the operator included, can know it before they do. The game settles from it as soon as it appears.
HMAC-SHA256, keyed with the server seed, over the game's number, the round and its randomness (Jackpot adds the entries hash) gives 48-bit numbers. The first one below a limit that is a multiple of the number of outcomes decides, so every outcome has exactly the same chance. Coinflip takes it modulo 2: 0 is heads, 1 is tails. Jackpot takes it modulo the pot in cents: one ticket per cent, so each player wins with exactly their share of the pot.
Commitment
SHA-256 of the revealed server seed equals the hash published when the game opened, before anyone could join or deposit.
Entries hash
Jackpot only: the entries, hashed in seq order, give the hash published at the close. The ticket table was fixed before the beacon value existed.
Timing
The beacon round was published 3 to 6 seconds after the entries closed (the check allows 1 s of slack), so it did not exist when the last stake was accepted.
Beacon
The randomness is SHA-256 of drand's signature, and the signature verifies against drand quicknet's public key. The verifier checks the signature in your browser; you can also open the round on any drand relay.
Result
HMAC-SHA256 keyed with the server seed over the game number, the round and its randomness (and the entries hash) gives the face or the winning ticket, and with it the winner and the payout.
// Commitment — when the flip is created / the round opens
S = 32 random bytes as 64 hex chars // the server seed, secret until the game ends
commitment = SHA256( utf8(S) ) // published at once
// Beacon round — fixed in the transaction that closes entries (the join; the end of the countdown)
R = beaconRoundAt(closedAt) + 2 // appears 3–6 s after the close
publish(R) = (1692803367 + (R − 1) × 3) × 1000 ms // drand quicknet's schedule
ρ = randomness of drand quicknet round R // = SHA256(signature bytes)
// Jackpot — the ticket table, hashed and published at the close (before ρ exists)
entriesHash = SHA256( "1:alice:500|2:bob:1500|…" ) // seq:username:amountCents, seq order
tickets = [0, a1), [a1, a1 + a2), … // one ticket per cent, seq order
// uniformInt(key = S, message, n) — exactly uniform, no modulo bias
B[i] = HMAC_SHA256( key = S, message + ":" + i ) // i = 0, 1, 2, …
u = the next 48-bit big-endian chunk of B[i] // 5 per block
limit = 2^48 − (2^48 mod n)
result = u mod n, for the first u < limit
coinflip: face = uniformInt(S, "coinflip:" + number + ":" + R + ":" + ρ, 2) // 0 heads, 1 tails
jackpot: ticket = uniformInt(S, "jackpot:" + number + ":" + R + ":" + ρ + ":" + entriesHash, pot)
winner = the entry whose [from, to) contains ticketconst { createHash, createHmac } = require("node:crypto");
const sha = (s) => createHash("sha256").update(s, "utf8").digest("hex");
function uniformInt(key, message, n) {
const N = BigInt(n), R = 2n ** 48n, limit = R - (R % N);
for (let i = 0; ; i++) {
const b = createHmac("sha256", key).update(`${message}:${i}`).digest();
for (let c = 0; c < 5; c++) {
const u = BigInt("0x" + b.subarray(c * 6, c * 6 + 6).toString("hex"));
if (u < limit) return Number(u % N);
}
}
}
// Coinflip vector #1
const S = "3f9a1c8e6b2d4f7a9c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f1a3c5e7b9d0f2a";
const rho = "b22aad4794f7451896f7a371aa46106fd84d919f3f569acd5b2fddf1d1440af3";
console.log(uniformInt(S, `coinflip:1:1000000:${rho}`, 2)); // 0 = heads
// Jackpot vector #1
const J = "3f9a1c8e6b2d4f7a9c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f1a3c5e7b9d0f2a";
const eh = sha("1:alice:500|2:bob:1500");
console.log(uniformInt(J, `jackpot:1:1000000:b22aad4794f7451896f7a371aa46106fd84d919f3f569acd5b2fddf1d1440af3:${eh}`, 2000)); // 238 = aliceIn a game against the house, the house is already the other side. Here it must stay neutral, so the case that matters is an operator playing through an account of its own, knowing every secret on the server.
We pick the server seed after seeing who plays.
Its SHA-256 is published when the game opens. Any other seed would not match it.
An account of ours joins only the flips it would win.
When the last stake is accepted, the value that decides the result does not exist yet: drand publishes it 3–6 seconds later. Knowing the seed tells nobody the result, us included.
A last-second jackpot deposit sized to win.
Same answer: the result does not exist yet. A late deposit buys its share at the same price as any other, and the countdown never extends.
We reorder the jackpot tickets after the draw.
The entries hash is published at the close, before the beacon value exists, and it is part of what is hashed into the result.
We cancel a game, or restart the server, after seeing a bad result.
A game whose beacon round is fixed is never voided, not even by a restart: it waits for its round and settles. The only refund of such a game is an audited admin action after 10 minutes without the round, and it stays on the public list of refunded games with its round number, so anyone can check whether drand had published it.
We claim the entries closed earlier, to reuse a value already published.
The round comes from the moment the close was processed, and the verifier checks that it was published a few seconds after the recorded close.
Someone fakes the beacon value (a hijacked relay, DNS or network).
Every value carries drand's BLS signature, checked against drand quicknet's public key by the server and by this verifier.
A player cancels after someone joined, or two players join one flip.
Every change is one database transaction that only succeeds while the game is still open. A joined flip cannot be cancelled.
That a threshold of the drand nodes, run by independent universities and companies, does not conspire with us. Nothing else: the seed is committed, the round is in the future when entries close, the entries are hashed before the value exists, and the arithmetic is public. The house never plays and takes 5% of the pot; every player's expected return is 95% of what they put in, whatever their share.
If drand is unreachable, joins and deposits pause and games already drawing wait for their round: nothing is lost and no result is chosen by us.
A game still waiting for its round can only be refunded by an audited admin action after 10 minutes. Every such refund stays listed, newest first, with its beacon round to check on drand.
Every refunded flip and roundA new round every 3 seconds since 2023-08-23. Each value is a BLS12-381 signature of the round number (bls-unchained-g1-rfc9380); its randomness is SHA-256 of the signature.
52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e97183cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45aThe creator stakes an amount on heads or tails; a joiner stakes the same amount on the other side. The join fixes the beacon round, and the face decides: the winner takes the pot less the fee.
0fdef169ef…b1f857 is shown in the lobby.b22aad4794…440af3.coinflip:1:1000000:b22aad4794f7451896f7a371aa46106fd84d919f3f569acd5b2fddf1d1440af3.70b9780c8d9c: the first chunk is 123,941,885,349,276. For two outcomes every chunk is accepted.Every finished flip has a Verify link, in the lobby's results, on the flip stage and in your history. It opens the verifier below with every value filled in from the flip: the seed, its commitment, the beacon round with its signature, the creator's side and the time it was joined. You can also type the values yourself, or load any finished flip by its number: flips are numbered in order across the whole lobby, and every finished one can be checked by anyone.
Frozen inputs and outputs, recomputed by an independent implementation in our tests. #1 uses a real drand round; the others use the development beacon (development beacon (predictable; local testing only)) so they cover both faces and both winners.
| # | Flip | Beacon round | Creator | First chunk | Face | Winner | Action |
|---|---|---|---|---|---|---|---|
| 1 | 1 | 1,000,000drand | Heads | 123,941,885,349,276 | Heads | Creator | Verify |
| 2 | 2 | 32,689,921dev | Tails | 198,758,594,205,578 | Heads | Joiner | Verify |
| 3 | 123 | 32,690,000dev | Heads | 208,522,163,183,032 | Heads | Creator | Verify |
| 4 | 4,096 | 33,000,000dev | Tails | 234,010,210,058,384 | Heads | Joiner | Verify |
| 5 | 1,000,000 | 40,000,000dev | Heads | 182,386,310,906,645 | Tails | Joiner | Verify |
Every cent deposited is one ticket, numbered in deposit order. When the countdown ends the entries are hashed and the beacon round is fixed; one ticket wins the pot less the fee, so your chance is exactly your share of the pot.
2e330b18c2…33a5b5 is published, and drand round 1,000,000 is fixed.Every finished round has a Verify link, on the round strip and in your history: it fills in the seed, its commitment, the beacon round and its signature, every entry and the entries hash, and the close time. The verifier draws the ticket line with every entry's range and marks the winning ticket. A round holds at most 25 players and 125 entries.
#1 uses the same real drand round as Coinflip #1. #5 has a pot far beyond any real round (140,737,488,355,329 cents) so that chunks are rejected: it shows the rejection step at work.
Re-derive any round from its seeds and nonce. Pick the game: the single-player deck (commitment, 52-card order, the nine cards dealt and both hands evaluated), the blackjack shoe (commitment, opening deal, the first sixteen cards and the full 312-card shoe with its hash), a roulette spin (commitment, the four bytes, the float and the winning number), a baccarat coup (commitment, both hands dealt from the 8-deck shoe with every third-card rule, and the winner), a live-table round (commitment, combined seed, every seat's cards, the dealer and the board), a Crash game (the crash point and its place in the chain), a Live Roulette or Live Baccarat round (the winning number or the coup, and its place in the table's chain), or a Coinflip or Jackpot game (the commitment, the beacon round with its timing and drand signature, and the face or the winning ticket). Hand history and round "Verify" links open the right mode with the fields filled in.
Choose the game, then copy the values from the hand's detail page or from a revealed seed. Everything is computed in your browser.
Results appear here.
You will see the Player and Banker hands dealt from the front of the 8-deck shoe with every third-card rule, the winner, the full shoe and, if you add the terminating hash and the game number, whether the game hash belongs to the table's published chain. Use Load example to try it with a published test vector.
These inputs and outputs never change. They are checked by our automated tests on every release, and an independent implementation of the algorithm must reproduce them. Each row opens in the verifier.
| # | Server seed | Client seed | Nonce | First nine cards | Deck hash | Action |
|---|---|---|---|---|---|---|
| 1 | 3f9a1c8e6b…9d0f2a | justpoker | 0 | 5c 8c 5h 7s 6s Ts Ks As 3c | b971417169…f39c5e | Verify |
| 2 | 3f9a1c8e6b…9d0f2a | justpoker | 1 | 6h 5h 7h 3h 4h 2s Tc 8d 7d | 486e5b7015…b2d62f | Verify |
| 3 | c0ffee00de…abcdef | player-client-seed_01 | 12345 | Ad As Tc 5h 7h 5s 8d Kc 6s | b65678656a…1df056 | Verify |
| 4 | 0000000000…000000 | a | 0 | 8d Qc 6s 5h 3d Ts 6c 2s 4d | 24a4549278…cede1c | Verify |
The first sixteen cards in deal order (card identity only) and SHA-256 of the whole shoe's raw values.
| # | Server seed | Client seed | Nonce | Decks | First sixteen cards | Shoe hash | Action |
|---|---|---|---|---|---|---|---|
| 1 | 3f9a1c8e6b…9d0f2a | justpoker | 0 | 6 | 8d Jc Tc 4h Ts 9d 3s 3h Ah Th 8d Jc Qs 5s 6d Kc | 3d289d102e…d0f20a | Verify |
| 2 | c0ffee00de…abcdef | player-client-seed_01 | 12345 | 6 | 7d Ah Jh Tc 5h 9h Ah 3s Qh Td 8c 4s 5d 7c 8c 6s | e7195dab0b…b8ee72 | Verify |
| 3 | 0000000000…000000 | a | 7 | 8 | 3s Td 6s Qd 4c 7d Qd Js 5c 8h 4d 9h Ts 6d 2h Qh | 761e669147…f30842 | Verify |
The float from the first four bytes of round 0 and floor(float × 37). The last vector lands on zero.
| # | Server seed | Client seed | Nonce | Float | Number | Action |
|---|---|---|---|---|---|---|
| 1 | 3f9a1c8e6b…9d0f2a | justpoker | 0 | 0.829814557917 | 30 Red | Verify |
| 2 | 3f9a1c8e6b…9d0f2a | justpoker | 1 | 0.252318835817 | 9 Red | Verify |
| 3 | c0ffee00de…abcdef | player-client-seed_01 | 12345 | 0.894200468669 | 33 Black | Verify |
| 4 | 0000000000…000000 | a | 0 | 0.434974659700 | 16 Red | Verify |
| 5 | 0123456789…abcdef | roulette | 2 | 0.025302330265 | 0 Green | Verify |
The first six cards of the shoe, the two hands they deal and the winner: a natural, a coup where both sides draw, and a Tie. The shoe hash covers all 416 raw values.
| # | Server seed | Client seed | Nonce | First six cards | Player · Banker | Shoe hash | Action |
|---|---|---|---|---|---|---|---|
| 1 | 3f9a1c8e6b…9d0f2a | justpoker | 0 | Ks 6d Qs 3c Th Ts | Ks Qs (0) · 6d 3c (9)Banker wins | 1c3f715ca0…c9d81c | Verify |
| 2 | c0ffee00de…abcdef | player-client-seed_01 | 4 | 2s Ah Ts Ad Ts 3c | 2s Ts Ts (2) · Ah Ad 3c (5)Banker wins | ed8065f851…0c8974 | Verify |
| 3 | 0000000000…000000 | a | 6 | 2c 7s Ac Kd 4c 4s | 2c Ac 4c (7) · 7s Kd (7)Tie | 0bee2d259a…344c97 | Verify |
These vectors pin the combined-seed hash and the seat deal order. Their round ids are free-form test values; at a real table the round id is always tableId:tableSeedHash:nonce.
| # | Table seed | Round id · seat seeds | Nonce | Seats | Dealer · board | Deck hash | Action |
|---|---|---|---|---|---|---|---|
| 1 | 3f9a1c8e6b…9d0f2a | round-0001alice, bob, carol | 0 | 4c Qc · 3d Td · Qh 9s | Ts 7s · 6d 3h 8c 9d 6c | 9f3978fdde…8e95d8 | Verify |
| 2 | c0ffee00de…abcdef | 7b1f0c3e-2d4a-4f6b-8e9d-0a1b2c3d4e5fjustpoker, player-client-seed_01, x, seat-4, Zz_- | 199 | Ac 5d · Js 3s · Qh Jd · Kc Kd · 9s 7s | 8h 2d · Kh Ad Ks Qd 9c | d0a3c10485…702b97 | Verify |
Games from a published test chain of 100 hashes, salted with the Bitcoin genesis block hash, at a 2% edge. Its seed is public because it is never played: 3f9a1c8e6b2d4f7a9c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f1a3c5e7b9d0f2a. Every game hash hashes forward to its terminating hash 79c72c9b3706a559536454b9e12185a390fb5f63b2b7d30150527ef02e03bfab in exactly its game number of steps.
| # | Game | Game hash | HMAC, first 13 hex | r | Crash point | Action |
|---|---|---|---|---|---|---|
| 1 | 1 | 8509059441…521b78 | 60f4e053b03bf | 1705677629883327 | 1.57x | Verify |
| 2 | 2 | 51bc667c67…48d513 | bd7e65196d4e4 | 3333608955106532 | 3.77x | Verify |
| 3 | 6 | 29ed2dc830…1eeec9 | f79d2aca1fb8f | 4356070397049743 | 29.91x | Verify |
| 4 | 38 | 5b49ee55cc…641fc9 | 06a88b76f5706 | 117135425623814 | 1.00x | Verify |
| 5 | 99 | 0fdef169ef…b1f857 | 0874b1539e623 | 148756548412963 | 1.01x | Verify |
The deck is a pure function of three inputs: the server seed, your client seed and the nonce. The server seed is fixed the moment its hash is published, which happens before you can influence anything. If we altered the seed afterwards, SHA-256 of the revealed seed would no longer match the hash you were shown, and the verifier would report a mismatch.
We also cannot choose a favourable seed in advance, because your client seed — which we do not know when the server seed is committed — changes every deck. Nothing in the algorithm depends on the bets you place or the decisions you make during the hand.
A revealed server seed lets anyone compute every future deck for that pair, including the dealer's hole cards. That is why a seed is only revealed once it is retired. Rotation retires the active seed, activates the next seed (whose hash you have already seen) and immediately commits a fresh next seed, so there is always a published commitment waiting.
Rotation is refused while a hand is in progress, because revealing the seed mid-hand would expose the dealer's cards.
Your client seed is your contribution to the randomness. Setting your own value after the server seed hash is published proves that the deck could not have been pre-selected: we would have needed to know your seed in advance. The default is random; typing something of your own makes the guarantee personal rather than a matter of trust in our generator.
Changing the client seed never creates a new server seed. The nonce keeps counting and each hand records the client seed it was dealt with.
Yes. Every hand in your hand history stores the server seed hash, the client seed and the nonce it was dealt with. Once the seed pair has been rotated, the hand's detail page links straight to this verifier with the values filled in — in the right mode for a blackjack round, a roulette spin or a baccarat coup. Hands dealt with the currently active pair can be verified as soon as you rotate.
Live-table hands are dealt from the table's own seed rather than yours. Their detail page shows the table seed hash, every seated player's client seed and the combined seed, and links to the live-table verifier once the table seed has dealt its 200 rounds and been revealed.
A Crash bet links to the Crash verifier as soon as its round has crashed, with the game hash, the salt and the chain's terminating hash filled in.
A Live Roulette or Live Baccarat bet links to its table's verifier once the round's result is shown, with the game hash, the salt, the terminating hash and the game number within the chain filled in.
Everyone at the table bets on the same spin or coup, and players join and leave between rounds, so the result cannot depend on one player's seed, and mixing in everyone's seeds would let the last player to join steer it. Each table instead plays a hash chain fixed before its first round and salted afterwards with the hash of a Bitcoin block, as Crash does.
Bets are only reserved while betting is open and are debited at "No more bets", the moment the outcome is computed from the round's game hash. The game hash is published with the result, and the verifier re-derives the number or the coup and checks the hash against the table's published chain.
Every player shares one Crash round, so no single player's seed can decide it, and mixing in the seeds of everyone who bet would let the last player to bet steer the result. Instead, every game of a chain is fixed by the hash chain before the first of them is played, and the salt (the hash of a Bitcoin block nobody could know in advance) is fixed after the chain was published.
Each game hash is revealed when its round crashes. The verifier re-derives the crash point from it and checks that it hashes forward to the published terminating hash, which proves the game was part of the chain all along.
The house does not play: there is no bot, and it only takes its fee. Even an account we controlled could not know a result in advance. We hold each game's server seed, but the result also depends on a drand round that is fixed only when the entries close and published a few seconds later, by a network of independent organisations. When the last stake is accepted, that value does not exist anywhere yet.
The verifier checks that the round really was in the future at the recorded close, and that its value carries drand's signature. Because that close time comes from our server's clock, the server also checks its clock against drand's newest round, and while it finds the clock behind real time, joins and deposits stay closed.
While the beacon cannot be reached, joins and deposits pause. A game whose round is already fixed waits for that round and then settles from it, also after a restart: such a game is never voided automatically.
Only if its round still cannot be fetched after 10 minutes may an administrator refund it. That action is audited, and the game's round number stays public, so anyone can check whether drand had in fact published it. The one other refund after the close is a Jackpot round whose recorded entries no longer match the entries hash published at its close: it can never be drawn, and after the same wait an administrator may refund it, audited with the problem found.
Before the close, while a flip is open or a Jackpot round is open or counting down, an administrator may also cancel it and refund every stake, for example to stop abuse; nobody can know the result then, because the beacon round is not fixed yet.
No. The verifier runs entirely in your browser using the same open code that runs on our servers. Nothing is transmitted or stored. The "Copy verification link" option simply places the values you typed into the page address so you can share or bookmark the check.
Yes. The algorithm above is fully specified and uses only standard SHA-256 and HMAC-SHA256. Any language with those primitives can reproduce a deck in a few lines. The reference vectors on this page are fixed; an independent implementation must reproduce them exactly.
If a verification ever fails and you believe the inputs are correct, email compliance@justpoker.example with the hand reference.
See your active server seed hash, set a client seed of your own, rotate to reveal past seeds and verify every hand you have played.