The core idea behind provably fair gaming is that players can verify that game results are truly random and unmanipulated. This is made possible through a combination of cryptographic hashing and a commitment scheme, ensuring transparency and fairness for every game.
The commitment scheme guarantees that players have an influence on the outcome of each game, while cryptographic hashing ensures that Ember cannot alter the results after the fact. These two components work together to create a trustless and verifiable gaming system.
For each provably fair bet, four key inputs are used to generate a random outcome:
Client Seed (your unique input)
Server Seed (Ember’s pre-generated seed)
Nonce (increments with each bet)
Cursor (used for multi-step game results)
These inputs are processed through a cryptographic hash function (HMAC-SHA256), which generates a random byte sequence. This byte sequence serves as the foundation for creating provably fair game results, ensuring that every outcome is transparent, tamper-proof, and verifiable.
// Random number generation based on following inputs: serverSeed, clientSeed, nonce and cursor
function byteGenerator({ serverSeed, clientSeed, nonce, cursor }) {
// Setup cursor variables
let currentRound = Math.floor(cursor / 32);
let currentRoundCursor = cursor;
currentRoundCursor -= currentRound * 32;
// Generate outputs until cursor requirement fulfilled
while (true) {
// HMAC function used to output provided inputs into bytes
const hmac = createHmac('sha256', serverSeed);
hmac.update(`${clientSeed}:${nonce}:${currentRound}`);
const buffer = hmac.digest();
// Update cursor for next iteration of loop
while (currentRoundCursor < 32) {
yield Number(buffer[currentRoundCursor]);
currentRoundCursor += 1;
}
currentRoundCursor = 0;
currentRound += 1;
}
}
The full process can be simplified as follows:
Ember generates a random server seed and provides you with a hashed version before you play.
You have a client seed, which you can set yourself or use the randomly generated one.
A nonce increments with each bet to ensure every result is unique.
After playing, you can reveal the server seed and verify that it matches the original hash.
Now, let’s dive into the key components of this system.
Server Seed
The server seed is a random 64-character hex string generated by Ember. Before you place a bet, we provide you with a hashed version of this seed. This ensures that:
✅ We cannot change the seed after your bet—ensuring fairness.
✅ You can verify the fairness of past bets once the seed is revealed.
To verify past game results, you’ll need to rotate your seed. This reveals the original server seed, allowing you to compare it against the hashed version you were shown before playing. If the two match, you can be 100% sure the results were not manipulated.
Client Seed
The client seed is generated when you first start playing. This is an important part of the fairness system because it ensures you have an influence on the randomness of each game outcome.
By default, a client seed is randomly assigned to you, but you can change it anytime in by visiting your Wallet History page, and tapping on your individual wagers to view the Provably Fair screen. Choosing your own seed ensures an extra level of control over your game’s randomness—similar to cutting the deck in a physical casino.
Nonce
The nonce is a simple incrementing number that starts at 0 and increases by 1 after every bet, ensuring that each game has unique data even if the seeds stay the same.
The nonce is especially important for games that require multiple random outcomes, like blackjack or keno. By keeping track of the nonce, Ember can generate multiple provably fair results without needing to change your seeds.
Cursor (Incremental Number)
In some games, multiple outcomes are needed within a single round (e.g., dealing multiple cards in blackjack). To handle this, we use a cursor that moves through a 32-byte SHA256 hash, generating results as needed.
For example:
• Blackjack requires multiple cards to be dealt, so the cursor continues generating new numbers.
• Mines needs multiple bomb placements, so the cursor ensures unique tile selections.
• Dice or Roulette only need one random number per round, so the cursor stays at its default value.
This ensures that even in games with complex randomness, every action remains fair and verifiable.
Verifying Your Game Results
Every game result can be independently verified using Ember’s Fairness Guarantee system.
Here’s how:
Before you bet – Ember provides you with a hashed server seed.
After betting – You can reveal the original server seed to compare it with the hash.
Verification – Use any SHA256 hashing tool to confirm that the server seed matches the original hash, proving that the result was not manipulated.
For added transparency, you can also change your client seed whenever you want, further ensuring that the randomness remains fair and unique to you.
From Random Numbers to Game Outcomes
The random number generator (RNG) in Ember’s Fairness Guarantee system converts hashed outputs into real game results. Here’s how:
Convert Bytes to Floats – The SHA256 hash produces a 32-byte hexadecimal string, which is then divided into smaller 4-byte groups. Each group of 4 bytes becomes a fraction in [0..1).
Translating Floats to Game Outcomes – These random floats are then mapped to specific game events based on the type of game being played. Example: Multiply by 37 for roulette pockets (0..36), or by 52 for card draws, etc.
Ensuring Fairness – Fisher-Yates Shuffle (When needed): Games that require multiple unique picks (Keno, Mines, Video Poker, etc.) shuffle the entire set of possibilities to avoid duplicates.
This process applies to all games on Ember (except Crash).
How It Works in Different Games
In Ember’s Fairness Guarantee system, randomly generated floats serve as the foundation for determining game outcomes. These floats are transformed into specific in-game events, such as:
The roll of a dice
The draw of a card
The placement of bombs in Mines
The stopping position of a roulette wheel
Below is a breakdown of how we translate random floats into game events for each game type on our platform.
🃏 Blackjack, Hilo & Baccarat
Deck Index: We treat a standard 52-card deck as indices [0..51].
Mapping: Multiply the fraction [0..1) by 52 → an integer index from 0..51.
Multi-Card: For blackjack or HiLo, we may draw up to 52 cards in one sequence. Our code uses the next chunk of bytes for each card drawn.
Cards used in their correct order (match index):
Blackjack:
// Spades
ACE_SPADES("A", "ACE", "♠", 1),
TWO_SPADES("2", "TWO", "♠", 2),
THREE_SPADES("3", "THREE", "♠", 3),
FOUR_SPADES("4", "FOUR", "♠", 4),
FIVE_SPADES("5", "FIVE", "♠", 5),
SIX_SPADES("6", "SIX", "♠", 6),
SEVEN_SPADES("7", "SEVEN", "♠", 7),
EIGHT_SPADES("8", "EIGHT", "♠", 8),
NINE_SPADES("9", "NINE", "♠", 9),
TEN_SPADES("10", "TEN", "♠", 10),
JACK_SPADES("J", "JACK", "♠", 11),
QUEEN_SPADES("Q", "QUEEN", "♠", 12),
KING_SPADES("K", "KING", "♠", 13),
// Hearts
ACE_HEARTS("A", "ACE", "♥", 1),
TWO_HEARTS("2", "TWO", "♥", 2),
THREE_HEARTS("3", "THREE", "♥", 3),
FOUR_HEARTS("4", "FOUR", "♥", 4),
FIVE_HEARTS("5", "FIVE", "♥", 5),
SIX_HEARTS("6", "SIX", "♥", 6),
SEVEN_HEARTS("7", "SEVEN", "♥", 7),
EIGHT_HEARTS("8", "EIGHT", "♥", 8),
NINE_HEARTS("9", "NINE", "♥", 9),
TEN_HEARTS("10", "TEN", "♥", 10),
JACK_HEARTS("J", "JACK", "♥", 11),
QUEEN_HEARTS("Q", "QUEEN", "♥", 12),
KING_HEARTS("K", "KING", "♥", 13),
// Diamonds
ACE_DIAMONDS("A", "ACE", "♦", 1),
TWO_DIAMONDS("2", "TWO", "♦", 2),
THREE_DIAMONDS("3", "THREE", "♦", 3),
FOUR_DIAMONDS("4", "FOUR", "♦", 4),
FIVE_DIAMONDS("5", "FIVE", "♦", 5),
SIX_DIAMONDS("6", "SIX", "♦", 6),
SEVEN_DIAMONDS("7", "SEVEN", "♦", 7),
EIGHT_DIAMONDS("8", "EIGHT", "♦", 8),
NINE_DIAMONDS("9", "NINE", "♦", 9),
TEN_DIAMONDS("10", "TEN", "♦", 10),
JACK_DIAMONDS("J", "JACK", "♦", 11),
QUEEN_DIAMONDS("Q", "QUEEN", "♦", 12),
KING_DIAMONDS("K", "KING", "♦", 13),
// Clubs
ACE_CLUBS("A", "ACE", "♣", 1),
TWO_CLUBS("2", "TWO", "♣", 2),
THREE_CLUBS("3", "THREE", "♣", 3),
FOUR_CLUBS("4", "FOUR", "♣", 4),
FIVE_CLUBS("5", "FIVE", "♣", 5),
SIX_CLUBS("6", "SIX", "♣", 6),
SEVEN_CLUBS("7", "SEVEN", "♣", 7),
EIGHT_CLUBS("8", "EIGHT", "♣", 8),
NINE_CLUBS("9", "NINE", "♣", 9),
TEN_CLUBS("10", "TEN", "♣", 10),
JACK_CLUBS("J", "JACK", "♣", 11),
QUEEN_CLUBS("Q", "QUEEN", "♣", 12),
KING_CLUBS("K", "KING", "♣", 13);
Baccarat:
// Spades
ACE_SPADES("A", "♠", 1),
TWO_SPADES("2", "♠", 2),
THREE_SPADES("3", "♠", 3),
FOUR_SPADES("4", "♠", 4),
FIVE_SPADES("5", "♠", 5),
SIX_SPADES("6", "♠", 6),
SEVEN_SPADES("7", "♠", 7),
EIGHT_SPADES("8", "♠", 8),
NINE_SPADES("9", "♠", 9),
TEN_SPADES("10", "♠", 10),
JACK_SPADES("J", "♠", 11),
QUEEN_SPADES("Q", "♠", 12),
KING_SPADES("K", "♠", 13),
// Hearts
ACE_HEARTS("A", "♥", 1),
TWO_HEARTS("2", "♥", 2),
THREE_HEARTS("3", "♥", 3),
FOUR_HEARTS("4", "♥", 4),
FIVE_HEARTS("5", "♥", 5),
SIX_HEARTS("6", "♥", 6),
SEVEN_HEARTS("7", "♥", 7),
EIGHT_HEARTS("8", "♥", 8),
NINE_HEARTS("9", "♥", 9),
TEN_HEARTS("10", "♥", 10),
JACK_HEARTS("J", "♥", 11),
QUEEN_HEARTS("Q", "♥", 12),
KING_HEARTS("K", "♥", 13),
// Diamonds
ACE_DIAMONDS("A", "♦", 1),
TWO_DIAMONDS("2", "♦", 2),
THREE_DIAMONDS("3", "♦", 3),
FOUR_DIAMONDS("4", "♦", 4),
FIVE_DIAMONDS("5", "♦", 5),
SIX_DIAMONDS("6", "♦", 6),
SEVEN_DIAMONDS("7", "♦", 7),
EIGHT_DIAMONDS("8", "♦", 8),
NINE_DIAMONDS("9", "♦", 9),
TEN_DIAMONDS("10", "♦", 10),
JACK_DIAMONDS("J", "♦", 11),
QUEEN_DIAMONDS("Q", "♦", 12),
KING_DIAMONDS("K", "♦", 13),
// Clubs
ACE_CLUBS("A", "♣", 1),
TWO_CLUBS("2", "♣", 2),
THREE_CLUBS("3", "♣", 3),
FOUR_CLUBS("4", "♣", 4),
FIVE_CLUBS("5", "♣", 5),
SIX_CLUBS("6", "♣", 6),
SEVEN_CLUBS("7", "♣", 7),
EIGHT_CLUBS("8", "♣", 8),
NINE_CLUBS("9", "♣", 9),
TEN_CLUBS("10", "♣", 10),
JACK_CLUBS("J", "♣", 11),
QUEEN_CLUBS("Q", "♣", 12),
KING_CLUBS("K", "♣", 13);
Hilo:
// Spades
ACE_SPADES("A", "ACE", "♠", 1),
TWO_SPADES("2", "TWO", "♠", 2),
THREE_SPADES("3", "THREE", "♠", 3),
FOUR_SPADES("4", "FOUR", "♠", 4),
FIVE_SPADES("5", "FIVE", "♠", 5),
SIX_SPADES("6", "SIX", "♠", 6),
SEVEN_SPADES("7", "SEVEN", "♠", 7),
EIGHT_SPADES("8", "EIGHT", "♠", 8),
NINE_SPADES("9", "NINE", "♠", 9),
TEN_SPADES("10", "TEN", "♠", 10),
JACK_SPADES("J", "JACK", "♠", 11),
QUEEN_SPADES("Q", "QUEEN", "♠", 12),
KING_SPADES("K", "KING", "♠", 13),
// Hearts
ACE_HEARTS("A", "ACE", "♥", 1),
TWO_HEARTS("2", "TWO", "♥", 2),
THREE_HEARTS("3", "THREE", "♥", 3),
FOUR_HEARTS("4", "FOUR", "♥", 4),
FIVE_HEARTS("5", "FIVE", "♥", 5),
SIX_HEARTS("6", "SIX", "♥", 6),
SEVEN_HEARTS("7", "SEVEN", "♥", 7),
EIGHT_HEARTS("8", "EIGHT", "♥", 8),
NINE_HEARTS("9", "NINE", "♥", 9),
TEN_HEARTS("10", "TEN", "♥", 10),
JACK_HEARTS("J", "JACK", "♥", 11),
QUEEN_HEARTS("Q", "QUEEN", "♥", 12),
KING_HEARTS("K", "KING", "♥", 13),
// Diamonds
ACE_DIAMONDS("A", "ACE", "♦", 1),
TWO_DIAMONDS("2", "TWO", "♦", 2),
THREE_DIAMONDS("3", "THREE", "♦", 3),
FOUR_DIAMONDS("4", "FOUR", "♦", 4),
FIVE_DIAMONDS("5", "FIVE", "♦", 5),
SIX_DIAMONDS("6", "SIX", "♦", 6),
SEVEN_DIAMONDS("7", "SEVEN", "♦", 7),
EIGHT_DIAMONDS("8", "EIGHT", "♦", 8),
NINE_DIAMONDS("9", "NINE", "♦", 9),
TEN_DIAMONDS("10", "TEN", "♦", 10),
JACK_DIAMONDS("J", "JACK", "♦", 11),
QUEEN_DIAMONDS("Q", "QUEEN", "♦", 12),
KING_DIAMONDS("K", "KING", "♦", 13),
// Clubs
ACE_CLUBS("A", "ACE", "♣", 1),
TWO_CLUBS("2", "TWO", "♣", 2),
THREE_CLUBS("3", "THREE", "♣", 3),
FOUR_CLUBS("4", "FOUR", "♣", 4),
FIVE_CLUBS("5", "FIVE", "♣", 5),
SIX_CLUBS("6", "SIX", "♣", 6),
SEVEN_CLUBS("7", "SEVEN", "♣", 7),
EIGHT_CLUBS("8", "EIGHT", "♣", 8),
NINE_CLUBS("9", "NINE", "♣", 9),
TEN_CLUBS("10", "TEN", "♣", 10),
JACK_CLUBS("J", "JACK", "♣", 11),
QUEEN_CLUBS("Q", "QUEEN", "♣", 12),
KING_CLUBS("K", "KING", "♣", 13);
// Game event translation
const card = CARDS[Math.floor(float * 52)];
Blackjack use a cursor of 13 to generate up to 52 card outcomes when multiple cards are needed.
Baccarat only requires 6 game events, since only a limited number of cards are dealt.
HiLo draws a new card each action with a “cursor” approach
All rely on the same core HMAC-based RNG.
💎 Coins (Diamonds)
Coins use 7 possible gem outcomes. To determine the result, the float is multiplied by 7, mapping it to a specific coin.
const COINS = [
"Bitcoin", // BTC
"Ethereum", // ETH
"Solana", // SOL
"Sui", // SUI
"Doge", // DOGE
"XRP",
"Pepe"
]
// Game event translation
const gem = GEMS[Math.floor(float * 7)];
🎲 Dice
In Dice, the result is a random roll between 00.00 and 100.00. Since this creates 10,001 possible outcomes, we calculate the result by multiplying the float and adjusting it to fit within the dice range.
// Game event translation
const roll = (float * 10001) / 100;
⚡ Limbo
In Limbo, we use a two-step process to determine the crash point:
Multiply the float by the max multiplier and house edge
Divide the max multiplier by this value to create the final crash point
limboMultiplier
= MAX_VALUE / ((fraction × MAX_VALUE) + 1) × (1 − houseEdge)
🎰 Plinko
In Plinko, the outcome is based on the path of the falling ball. Since there are only two possible directions (left or right) at each level, the float is multiplied by 2 to determine the direction at each step.
// Index of 0 to 1 : left to right
const DIRECTIONS = [ left, right ];
// Game event translation
const direction = DIRECTIONS[Math.floor(float * 2)];
🎡 Roulette
Ember’s Roulette follows the European wheel format, which has 37 possible pockets (0-36). The float is multiplied by 37 to determine the final outcome.
// Index of 0 to 36
const POCKETS = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36
];
// Game event translation
const pocket = POCKETS[Math.floor(float * 37)];
🎱 Keno
Keno requires 10 unique selections from a board of 40 numbers. The float determines each hit, and the pool of available numbers is adjusted after each selection.
// Index of 0 to 39 : 1 to 40
const SQUARES = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40
];
const hit = SQUARES[Math.floor(float * 40)];
The Fisher-Yates shuffle is used to prevent duplicate selections.
💣 Mines
Mines generates up to 24 random bomb placements on a grid. The float determines bomb locations, ensuring no duplicates using the Fisher-Yates shuffle.
🎈 God Candle
God Candle follows a similar structure to Mines, where each float determines when a pop occurs. The Fisher-Yates shuffle prevents duplicate pop locations.
🃏 Video Poker
Each game of Video Poker requires drawing 52 unique cards from a deck. The float is multiplied by the remaining number of cards, ensuring that previously drawn cards cannot be selected again.
// Index of 0 to 51 : ♦2 to ♣A
const CARDS = [ ... ]; // Full deck of cards
// Game event translation
const card = CARDS[Math.floor(float * 52)];
The Fisher-Yates shuffle ensures unique card draws.
🎡 Wheel
In Wheel, the float determines which payout segment the wheel lands on. Each payout tier has different risk levels, which are mapped to possible results.
// Game event translation
const spin = PAYOUTS[segments][risk][float * segments];
Ensuring Fairness Across All Games
Every game event on Ember is generated using:
✅ Cryptographically random floats
✅ Precise game-specific calculations
✅ The Fisher-Yates shuffle (where needed) to prevent duplicates
By following these steps, Ember’s Fairness Guarantee ensures that every bet, spin, and roll is provably fair and completely transparent. 🚀
Why Ember’s Fairness Guarantee Matters
💎 No Hidden Manipulation – Once the server seed is generated, we can’t change your results.
🎰 You Have Control – The client seed ensures you have a say in your game’s randomness.
📜 Fully Transparent & Verifiable – Anyone can independently verify every game result.
With Ember’s Fairness Guarantee, you don’t have to trust us—you can verify it yourself.
FAQs:
Why can’t the platform change the outcome after I play?
Since the hash of the server seed is shown to you before the game starts, the platform cannot change the server seed after you’ve seen the hash. Any change in the server seed would lead to a completely different hash, making it impossible for the platform to manipulate results without detection.
Do I need to understand cryptography to verify fairness?
Not at all! There are many tools online that allow you to verify hashes easily. Simply input the server seed and client seed into an online hash calculator, and you’ll be able to verify the result.
Can I trust the platform to reveal the correct server seed?
Yes, because the server seed is hashed and presented before the game begins. Once the game is over, the server cannot alter this seed without invalidating the hash that was already provided to you.
By following these steps, you can confidently verify the fairness of each game round, ensuring that neither the platform nor any external factors could manipulate the results.