Solving the Trust Issue with Online Gambling
The core idea behind provable fairness is to empower players to verify and confirm that their game results are fair and unmanipulated. This is achieved using a commitment scheme along with cryptographic hashing.
The commitment scheme ensures that players have a say in the results generated, while cryptographic hashing ensures that the casino remains honest to this commitment. Together, these elements create a trust-less environment in online gambling.
This concept is simplified in the following representation:
fair result = operator’s input (hashed) + player’s input
Third-Party Verification
All original games played on Casinostake can be verified both on the Casinostake platform and via third-party websites that have also open-sourced the verification process. You can find them through a Google search or by checking out some of these community resources:
Crypto Gambling Foundation
Casinostake is a verified operator on the Crypto Gambling Foundation network, which upholds the highest standards of provably fair gambling. We are proud to be part of this network. For more information about provable fairness and its impact on the industry, visit the Crypto Gambling Foundation.
Random Number Generation
For each verifiable bet, several parameters are used as input for the random number generation function: a client seed, a server seed, a nonce, and a cursor. This function uses the cryptographic hash function HMAC_SHA256 to generate bytes, forming the basis for how provably fair random outcomes are generated on our platform.
// Random number generation based on the following inputs: serverSeed, clientSeed, nonce, and cursor
function byteGenerator({ serverSeed, clientSeed, nonce, cursor }) {
let currentRound = Math.floor(cursor / 32);
let currentRoundCursor = cursor;
currentRoundCursor -= currentRound * 32;
while (true) {
const hmac = createHmac('sha256', serverSeed);
hmac.update(`${clientSeed}:${nonce}:${currentRound}`);
const buffer = hmac.digest();
while (currentRoundCursor < 32) {
yield Number(buffer[currentRoundCursor]);
currentRoundCursor += 1;
}
currentRoundCursor = 0;
currentRound += 1;
}
}
Server Seed
The server seed is generated by our system as a random 64-character hex string. Before you place any bets, you are provided with an encrypted hash of the server seed. This ensures that the casino cannot change the server seed, and the player cannot predict the results beforehand.
To reveal the server seed from its hashed version, the player must rotate the seed, triggering the generation of a new one. You can verify that the hashed server seed matches the un-hashed version via the verification function in the menu.
Client Seed
The client seed is controlled by the player and ensures they have influence over the randomness of the outcomes. Without the client seed, the server seed alone would determine the outcome of each bet.
Players can edit and change their client seed regularly to create a new chain of random outcomes. This allows players to have control over the generation of results, similar to cutting the deck in a physical casino.
During registration, a client seed is created by your browser to ensure uninterrupted initial gameplay. Although the randomly generated client seed is sufficient, we recommend that you choose your own to ensure your influence is included in the randomness.
You can do this via the fairness modal.
Nonce
The nonce is a number that increments with every new bet. Given the nature of the SHA256 cryptographic function, this generates a new result each time without needing to change the client seed or server seed.
The nonce ensures that the same client seed and server seed pair generates new results for each bet placed.
Cursor (Incremental Number)
We use 4 bytes of data to generate a single game result. Since SHA256 is limited to 32 bytes, we use a cursor to generate additional game events without modifying our provably fair algorithm.
The cursor is incremented only when a game requires the generation of more than 8 outcomes. For example, more than 8 cards in a game of blackjack.
If a game requires more than 8 random numbers, the cursor increments by 1 each time 32 bytes are returned by the HMAC_SHA256 function. Otherwise, it remains unchanged.
Bytes to Floats
The output of the random number generator function (byteGenerator) is a hexadecimal 32-byte hash. We use 4 bytes of data to generate a single game result, and these 4 bytes are used to generate floats between 0 and 1. This ensures a higher level of precision when generating the float, which forms the foundation of our provably fair algorithm before it is translated into game events.
// Convert the hash output from the rng byteGenerator to floats
function generateFloats ({ serverSeed, clientSeed, nonce, cursor, count }) {
const rng = byteGenerator({ serverSeed, clientSeed, nonce, cursor });
const bytes = [];
while (bytes.length < count * 4) {
bytes.push(rng.next().value);
}
return _.chunk(bytes, 4).map(bytesChunk =>
bytesChunk.reduce((result, value, i) => {
const divider = 256 ** (i + 1);
const partialResult = value / divider;
return result + partialResult;
}, 0)
);
};
Floats to Game Events
The process of generating random outputs is universal across our games. However, the translation from floats to game events is unique to each game.
The random float is multiplied by the possible remaining outcomes of the game being played. For instance, in a game using a 52-card deck, the float is multiplied by 52. The result is then translated into a corresponding game event.
For games requiring multiple events, this process continues through each corresponding 4-byte result generated by the byteGenerator function.
Shuffle of Game Events
For games such as Keno, Mines, and Video Poker, where outcomes cannot be duplicated, we use the Fisher-Yates shuffle algorithm. This influences the conversion from floats to game events because each time an event is translated, the number of possible remaining outcomes decreases.
For example, in video poker, there are initially 52 cards in the deck, so the first game event is translated by multiplying the float by 52. Once this card is dealt, there are only 51 remaining, so the next event uses 51 as the multiplier. This continues until all game events are generated.
For Mines and Keno, this process is applied to tiles or locations on the board, ensuring each event generated hasn’t already occurred in the result chain.
Game events are the translation of randomly generated floats into relatable outcomes specific to each game. This includes anything from the outcome of a dice roll to the order of cards in a deck or even the location of bombs in a game of Mines.
Below is a detailed explanation of how floats are translated into events for different games on our platform:
Blackjack, Hilo & Baccarat
In a standard deck, there are 52 unique outcomes. For Blackjack, Hilo, and Baccarat on our platform, an unlimited number of decks are used, meaning each card turn has the same probability. We multiply each float by 52 and then translate the result into a specific card using the following index:
// Index of 0 to 51 : ♦2 to ♣A
const CARDS = [
♦2, ♥2, ♠2, ♣2, ♦3, ♥3, ♠3, ♣3, ♦4, ♥4,
♠4, ♣4, ♦5, ♥5, ♠5, ♣5, ♦6, ♥6, ♠6, ♣6,
♦7, ♥7, ♠7, ♣7, ♦8, ♥8, ♠8, ♣8, ♦9, ♥9,
♠9, ♣9, ♦10, ♥10, ♠10, ♣10, ♦J, ♥J, ♠J,
♣J, ♦Q, ♥Q, ♠Q, ♣Q, ♦K, ♥K, ♠K, ♣K, ♦A,
♥A, ♠A, ♣A
];
// Game event translation
const card = CARDS[Math.floor(float * 52)];
The only difference between these games is that in Hilo and Blackjack, a cursor of 13 is used to generate 52 possible events for cases requiring a large number of cards, while Baccarat only needs 6 events to cover all playable cards.
Diamond Poker
In Diamond Poker, there are 7 possible outcomes in the form of gems. We multiply each float by 7 before translating it into a corresponding gem using the following index:
// Index of 0 to 6 : green to blue
const GEMS = [ green, purple, yellow, red, cyan, orange, blue ];
// Game event translation
const gem = GEMS[Math.floor(float * 7)];
Both the dealer and the player are dealt 5 gems each, requiring 10 game events per game.
Diamonds
When playing Diamonds, there are 7 possible outcomes in the form of gems. We multiply each float by 7 before translating it into a corresponding gem using the following index:
// Index of 0 to 6 : green to blue
const GEMS = [ green, purple, yellow, red, cyan, orange, blue ];
// Game event translation
const gem = GEMS[Math.floor(float *
7)];
The player is dealt 5 gems.
Dice Roll
In our dice game, we cover a roll spread of 00.00 to 100.00, with 10,001 possible outcomes. The game event translation is done by multiplying the float by the number of possible outcomes and dividing by 100 to fit within the dice range.
// Game event translation
const roll = (float * 10001) / 100;
Limbo
Limbo uses a two-step process. First, the float is multiplied by both the maximum possible multiplier and the house edge. Then, to generate a game event with a probability distribution, the maximum multiplier is divided by the result of the first step, creating a crash point.
// Game event translation with houseEdge of 0.99 (1%)
const floatPoint = 1e8 / (float * 1e8) * houseEdge;
// Crash point rounded down to the required denominator
const crashPoint = Math.floor(floatPoint * 100) / 100;
// Consolidate all crash points below 1
const result = Math.max(crashPoint, 1);
Plinko
In Plinko, the generated outcome is based on the path of the falling ball. The game event determines the direction of the ball for each level. Players can choose between 8 and 16 pins, which determines the number of events needed to complete the path. Each float is multiplied by 2, mapping to the following index:
// Index of 0 to 1 : left to right
const DIRECTIONS = [ left, right ];
// Game event translation
const direction = DIRECTIONS[Math.floor(float * 2)];
Roulette Roll
Our Roulette is based on the European version with 37 possible pockets. The game event is calculated by multiplying the float by 37 and then translating it into a corresponding pocket using the following index:
// 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 the selection of 10 possible game events in the form of hits on a board. Each float is multiplied by the number of possible unique squares. Once a hit is placed, it cannot be chosen again, reducing the pool of possible outcomes. This is done using the following index:
// 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 hits.
Mines
Mines generates 24 separate game events in the form of mines on the board. Each float is multiplied by the number of possible tiles remaining. The location of the mine is plotted using a grid position from left to right, top to bottom.
The Fisher-Yates shuffle prevents duplicate hits. The number of events used depends on the settings chosen.
Video Poker
Video Poker involves 52 separate game events, in the form of cards in a deck. Each float is multiplied by the number of possible cards remaining. The result is then translated into a specific card using the following index:
// Index of 0 to 51 : ♦2 to ♣A
const CARDS = [
♦2, ♥2, ♠2, ♣2, ♦3, ♥3, ♠3, ♣3, ♦4, ♥4,
♠4, ♣4, ♦5, ♥5, ♠5, ♣5, ♦6, ♥6, ♠6, ♣6,
♦7, ♥7, ♠7, ♣7, ♦8, ♥8, ♠8, ♣8, ♦9, ♥9,
♠9, ♣9, ♦10, ♥10, ♠10, ♣10, ♦J, ♥J, ♠J,
♣J, ♦Q, ♥Q, ♠Q, ♣Q, ♦K, ♥K, ♠K, ♣K, ♦A,
♥A, ♠A, ♣A
];
// Game event translation
const card = CARDS[Math.floor(float * 52)];
The Fisher-Yates shuffle prevents duplicate cards.
Wheel
The game event number is calculated by multiplying the float by the possible outcomes in the segment. It is then used to determine the game event result as a multiplier, using the following index:
// Index per payout configuration
const PAYOUTS = {
'10': {
low: [ 1.5, 1.2, 1.2, 1.2, 0, 1.2, 1.2, 1.2, 1.2, 0 ],
medium: [ 0, 1.9, 0, 1.5, 0, 2, 0, 1.5, 0, 3 ],
high: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 9.9 ]
},
'20': {
low: [
1.5, 1.2, 1.2, 1.2, 0, 1.2, 1.2, 1.2, 1.2, 0,
1.5, 1.2, 1.2, 1.2, 0, 1.2, 1.2, 1.2, 1.2, 0
],
medium: [
1.5, 0, 2, 0, 2, 0, 2, 0, 1.5, 0,
3, 0, 1.8, 0, 2, 0, 2, 0, 2, 0
],
high: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 19.8
]
},
'30': {
low: [
1.5, 1.2, 1.2, 1.2, 0, 1.2, 1.2, 1.2, 1.2, 0,
1.5, 1.2, 1.2, 1.2, 0, 1.2, 1.2, 1.2, 1.2, 0,
1.5, 1.2, 1.2, 1.2, 0, 1.2, 1.2, 1.2, 1.2, 0
],
medium: [
1.5, 0, 1.5, 0, 2, 0, 1.5, 0, 2, 0,
2, 0, 1.5, 0, 3, 0, 1.5, 0, 2, 0,
2, 0, 1.7, 0, 4, 0, 1.5, 0, 2, 0
],
high: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 29.7
]
},
'40': {
low: [
1.5, 1.2, 1.2, 1.2, 0, 1.2, 1.2, 1.2, 1.2, 0,
1.5, 1.2
, 1.2, 1.2, 0, 1.2, 1.2, 1.2, 1.2, 0,
1.5, 1.2, 1.2, 1.2, 0, 1.2, 1.2, 1.2, 1.2, 0,
1.5, 1.2, 1.2, 1.2, 0, 1.2, 1.2, 1.2, 1.2, 0
],
medium: [
2, 0, 3, 0, 2, 0, 1.5, 0, 3, 0,
1.5, 0, 1.5, 0, 2, 0, 1.5, 0, 3, 0,
1.5, 0, 2, 0, 2, 0, 1.6, 0, 2, 0,
1.5, 0, 3, 0, 1.5, 0, 2, 0, 1.5, 0
],
high: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 39.6
]
},
'50': {
low: [
1.5, 1.2, 1.2, 1.2, 0, 1.2, 1.2, 1.2, 1.2, 0,
1.5, 1.2, 1.2, 1.2, 0, 1.2, 1.2, 1.2, 1.2, 0,
1.5, 1.2, 1.2, 1.2, 0, 1.2, 1.2, 1.2, 1.2, 0,
1.5, 1.2, 1.2, 1.2, 0, 1.2, 1.2, 1.2, 1.2, 0,
1.5, 1.2, 1.2, 1.2, 0, 1.2, 1.2, 1.2, 1.2, 0
],
medium: [
2, 0, 1.5, 0, 2, 0, 1.5, 0, 3, 0,
1.5, 0, 1.5, 0, 2, 0, 1.5, 0, 3, 0,
1.5, 0, 2, 0, 1.5, 0, 2, 0, 2, 0,
1.5, 0, 3, 0, 1.5, 0, 2, 0, 1.5, 0,
1.5, 0, 5, 0, 1.5, 0, 2, 0, 1.5, 0
],
high: [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 49.5
]
}
};
// Game event translation
const spin = PAYOUTS[segments][risk][float * segments];
Crash
For the crash game, see the BitcoinTalk seeding thread to learn about how we use the salt hash-based provable fairness model.
Slide
For the slide game, see the BitcoinTalk seeding thread to learn about how we use the salt hash-based provable fairness model.
Scarab Spin / Tome of Life
In this game, the event number is calculated by multiplying the float by the possible outcomes on the reel. The first 4 reels have 30 possible outcomes, while the last reel has 41. The event determines the central stop position for each reel. This game consists of 5 event numbers unless there’s a bonus round, in which case more are generated.
Blue Samurai
Blue Samurai slots have three different types of spins: regular, bonus, and special.
For regular and bonus spins, 18 floats are generated from your hash. Unlike Scarab Spin slots, which have fixed reels, Samurai slots have dynamic reels, meaning each symbol is generated from the corresponding float.
We use weighted random sampling to assign each float to its corresponding tile, moving down the reels from left to right. Each symbol has its own fixed probability of appearing in any tile. For more information on how symbols are selected, see the fitness proportionate selection algorithm.
Special spins are slightly different. Only 12 floats are taken from your hash, as the outer reels are disabled. Any samurai symbols stay in place for the remainder of the game, with the result being the final count of samurais. Although we generate 12 floats each time for simplicity, if a float corresponds to a tile with a stuck samurai, that float is not used.
Dragon Tower
A Dragon Tower game is generated with 9 separate game events, representing levels up the tower. The game generates a number of eggs depending on the difficulty level, and each level has a range of tiles where the egg can be placed.
Each float is converted to an integer to determine the egg location on each row. For example, a level on easy difficulty would be represented like this: [0, 1, 3]—eggs would be present at tiles 1, 2, and 4.
// count represents the number of eggs
// size represents the number of possible squares
const LEVEL_MAP = {
easy: { count: 3, size: 4 },
medium: { count: 2, size: 3 },
hard: { count: 1, size: 2 },
expert: { count: 1, size: 3 },
master: { count: 1, size: 4 },
};
The Fisher-Yates shuffle prevents duplicate eggs on a row.