Overview
Solving the Trust Issue with Online Gambling
The underlying concept of provable fairness is that players have the ability to prove and verify that their results are fair and unmanipulated. This is achieved through the use of a commitment scheme, along with hashing.
The commitment scheme is used to ensure that the player has an influence on all results generated. hashing is used to ensure that the casino also remains honest to this commitment scheme. Both concepts combined creates a trust-less environment when gambling online.
fair result = operators input (hashed) + players input
Implementation
Random Number Generation
For each verifiable bet, a client seed, a server seed, a nonce and a cursor are used as the input parameters for the random number generation function. This function utilises the hash function HMAC_SHA256 to generate bytes which are then used as the foundation for how we generate provably fair random outcomes on our platform.
// Random number generation based on following inputs: serverSeed, clientSeed, nonce and cursor
function byteGenerator({ serverSeed, clientSeed, nonce, cursor }) {
// Setup curser variables
let currentRound = Math.floor(cursor / 32);
let currentRoundCursor = cursor;
currentRoundCursor -= currentRound * 32;
// Generate outputs until cursor requirement fullfilled
while (true) {
// HMAC function used to output provided inputs into bytes
const hmac = createHmac('sha256', serverSeed);
const buffer = hmac.digest();
// Update curser for next iteration of loop
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. You are then provided with an encrypted hash of that generated server seed before you place any bets. The reason we provide you with the encrypted form of the server seed is to ensure that the un-hashed server seed cannot be changed by the casino operator, and that the player cannot calculate the results beforehand.
To reveal the server seed from its hashed version, the seed must be rotated by the player, which triggers the replacement with a newly generated one.
From this point you are able to verify that the hashed server seed matches that of the un-hashed server seed. This process can be verified via our un-hashed server seed function found in the menu above.
Client Seed
The client seed belongs to the player and is used to ensure the player also has influence on the randomness of the outcomes generated. Without this component of the algorithm, the server seed alone would have complete leverage over the outcome of each bet.
All players are free to edit and change their client seed regularly to create a new chain of random upcoming outcomes. This ensures the player has absolute control over the generation of the result, similar to cutting the deck at a brick and mortar casino.
During registration, a client seed is created for you by your browser, to ensure your initial experience with the site goes uninterrupted. Whilst this randomly generated client seed is considered suitable, we highly recommend that you choose your own, so that your influence is included in the randomness.
You can do this via the fairness modal.
Client Seed
The nonce is simply a number that increments as every new bet is made. Due to the nature of the SHA256 function, this creates a completely new result each time, without having to generate a new client seed and server seed.
The implementation of nonce, ensures we remain committed to your client seed and server seed pair, whilst generating new results for each bet placed.
Client Seed
We use 4 bytes of data to generate a single game result, and because SHA256 is limited to 32 bytes, we utilise this implementation of a cursor to give us the ability to create more game events without having to modify our provable fair algorithm.
The cursor is only iterated over when the game being played requires the generation of more than 8 (32 bytes / 4 bytes) possible outcomes. For example: when we need to use more than 8 cards in a game of blackjack.
The cursor starts as 0 and gets increased by 1 every time the 32 bytes are returned by the HMAC_SHA256 function. If we don’t require more than 8 random numbers to be generated for the game events, then the curser does not increment as there is no need to generate any additional possible game outcomes.
Games with more than 1 incremental number:
- Hilo (Unlimited to cover required amount of cards)
- Keno (2 increments for every game due to 10 possible outcomes)
- Mines (3 increments per game for 24 possible bomb locations)
- Plinko (2 increments per game to cover possible 16 decisions)
- Blackjack (Unlimited to cover required amount of cards)
- Video Poker (7 increments to generate 52 possible cards in a full deck)
- Diamond Poker (2 increments to cover 10 diamonds: 5 per player/dealer)
- Slots (The incremental number is only utilised for bonus rounds)
Games with only 1 incremental number (represented as default value 0):
- Dice
- Limbo
- Wheel
- Baccarat
- Roulette
- Diamonds
Conversations
Bytes to Floats
The output of the Random Number Generator (byteGenerator) function is a hexadecimal 32-byte hash. As explained under the cursor implementation, we use 4 bytes of data to generate a single game result. Each set of 4 bytes are used to generate floats between 0 and 1 (4 bytes are used instead of one to ensure a higher level of precision when generating the float.) It is with these generated floats that we derive the formal output of the provable 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 }) {
// Random number generator function
const rng = byteGenerator({ serverSeed, clientSeed, nonce, cursor });
// Declare bytes as empty array
const bytes = [];
// Populate bytes array with sets of 4 from RNG output
while (bytes.length < count * 4) {
bytes.push(rng.next().value);
}
// Return bytes as floats using lodash reduce function
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
Where the process of generating random outputs is universal for all our games, it's at this point in the game outcome generation where a unique procedure is implemented to determine the translation from floats to game events.
The randomly float generated is multiplied by the possible remaining outcomes of the particular game being played. For example: In a game that uses a 52 card deck, this would simply be done by multiplying the float by 52. The result of this equation is then translated into a corresponding game event. For games where multiple game events are required, this process continues through each corresponding 4 bytes in the result chain that was generated using the described byteGenerator function.
Shuffle of Game Events
For games such as Keno, Mines and Video Poker, where outcomes cannot be duplicated, we then utilise the Fisher-Yates shuffle algorithm. This procedure influences the conversion process from floats to game events because each time a game event is translated, the amount of possible remaining game event possibilities has been reduced for any remaining steps in the result chain.
As an example, in video poker, there is at first 52 cards available in the complete deck, and therefore the first game event is translated by multiplying the float by 52. Once this card has been dealt, there is only 51 remaining cards in the deck, and therefore the second card translation is done by multiplying the second float generated by 51. This continues in the same fashion until all the game events required have been generated.
With regards to Mines and Keno, this is simply a matter of implementing the same process as explained with video poker but changing that to tiles or locations on the board or grid, ensuring that each game event generated, hasn’t already been done so beforehand in the chain of results.
Game Events
Game events are translation of the randomly generated floats into a relatable outcome that is game specific. This includes anything from the outcome of a dice roll to the order of the cards in a deck, or even the location of every bomb in a game of mines.
Below is a detailed explanation as to how we translate floats into events for each particular different game on our platform.
Blackjack, Hilo & Baccarat
In a standard deck of cards, there are 52 unique possible outcomes. When it comes to playing Blackjack, Hilo & Baccarat on our platform, we utilise an unlimited amount of decks when generating the game event, and therefore each turn of a card always has the same probability. To calculate this, we multiply each randomly generated float by 52, and then translate that result into a particular card, based on 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 differentiating factor involved with these games is that with Hilo and Blackjack there is a curser of 13 to generate 52 possible game events for cases where a large amount of cards are required to be dealt to the player, whereas when it comes to Baccarat we only ever need 6 game events generated to cover the most amount of playable cards possible.
Diamond Poker
When playing Diamond Poker, there is 7 possible outcomes in the form of gems. To achieve this, we multiply each float generated by 7 before it is translated 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, which means that a complete game of Diamond Poker requires the generation of 10 game events. The first 5 are assigned to the dealer and the second 5 are assigned to the player.