Provably Fair — How do I apply it?

Jadranko Dragoje
NSoft
Published in
4 min readJan 10, 2023

--

There has been a lot of discussion about the fairness of pseudo-random number generator (PRNG) algorithms in gambling and about fairness in general. While we are confident in the fairness of our algorithms, some providers have gone a step further by providing a way for players to verify their fairness using simple cryptographic calculations. How does this process work?

Provably Fair uses a simple process where the server announces a hashed seed for the upcoming round, which is publicly available to all players. The server also assigns a default player seed that the player can choose to change at any time before the round starts. The player creates a bet, and before the round is generated, the server combines the server seed and the player seed to create a game hash that contains the result. After the round is resolved, the server reveals the unhashed server seed, which is used to obtain the result from the game hash.

The same process is used for round-based games in which multiple players are playing the same round. In this case, the player seeds are typically taken from the first three players who make a bet. If there are less than three players in the round, their seeds are used instead.

Here is the example from one crash cash game:

  • Server announces hashed server seed for the next round:
    64a436bfafb564dc9831530c975c73ca5d43cb29a34fc3bc93e0b1dd4d69274e
  • Player sets seed to some value:
    x8QBX5DPsyIHf066TRht
  • Player creates a bet and starts round
  • Algorithm uses combined server and player seed to generate result and announces game hash at the end of a round:
    50aa38ce0e390e4f7ece4138878e46132e99fbe3887fca2a6c3f6c43ca2dc5611a15e5dc5de7b9ea9db79c9bfc48763e8d6df138ff24c377f4704c23d41bf911
  • Player can use online tool to check game hash and view result (in this case result is decimal number):
    11.97

Generating server seed

To generate server seed we need to use SHA256 algorithm.

const crypto = require('crypto');

function generateServerSeed() {
const seed = crypto.randomBytes(32).toString('hex');
const seedHashRaw = crypto.createHash('sha256').update(seed);
const seedHash = seedHashRaw.digest('hex');

return {
seed,
seedHash,
};
}

Server seed hash needs to be publicly available, usually in game rules or special provably fair section of the game settings. It can also be stored on blockchain. Do not show server seed, only server seed hash. New server seed is generated before each round and announced to players.

Generating result

To generate result we need to use server seed combined with player seed that player provided during bet placement. For example, if we wish to generate result for Roulette game we can use this example:

function generateResult(serverSeed, playerSeed) {
const gameSeed = `${serverSeed}${playerSeed}`;
const gameHash = crypto.createHash('sha512').update(gameSeed).digest('hex');
const gameHashHmac = crypto.createHmac('sha256', gameHash).update(gameSeed).digest('hex');

const resultNumber = parseInt(gameHashHmac.substring(0, 13), 16);
const result = resultNumber % 37;

return {
result,
serverSeed,
playerSeed,
gameHash,
gameHashHmac,
};
}

The generation of the result in Roulette is determined by a game logic. To generate a random number between 0 and 36, we use the modulo operator. The result number, which is used to determine the outcome of the game, is derived from a game hash created using a combination of the server seed and the player seed, and is then converted to an integer.

For other games, like crash games, we would use different formula to get result. Here is the example:

function generateResult(serverSeed, playerSeed) {
const gameHashSeed = `${serverSeed}${playerSeed}`;
const gameHash = crypto.createHash('sha512').update(gameHashSeed).digest('hex');
const gameHashHmac = crypto.createHmac('sha256', gameHash).update(gameHashSeed).digest('hex');

const resultMaximum = 2 ** 52;
const resultNumber = parseInt(gameHashHmac.substring(0, 13), 16);
const resultRandom = resultNumber % 33 === 0 ? 100 : ((100 * resultMaximum) - resultNumber) / (resultMaximum - resultNumber);
const result = Math.floor(resultRandom) / 100;

return {
result,
serverSeed,
playerSeed,
gameHash,
gameHashHmac,
};
}

In this example, the result is a decimal number, unlike in Roulette where the result is an integer.

Validating

Upon the completion of a round, the player receives the game hash. This hash can be used to verify the validity of the seed that was made public before the start of the round. To confirm the game hash, a validation function can be employed:

function isValidHash(serverSeed, playerSeed, gameHash) {
const gameHashSeed = `${serverSeed}${playerSeed}`;
const gameHashCheck = crypto.createHash('sha512').update(gameHashSeed).digest('hex');

return gameHash === gameHashCheck;
}

A player can also utilise the same process to determine the result from the game hash, thereby validating the entire process. It is uncertain whether game providers make public the logic for determining the result from the game hash. From my research, I have not been able to locate this information on official websites. Perhaps this could be something that could be added to each game, allowing players to independently verify this part of the process also.

Conclusion

The implementation of a provably fair algorithm is certainly noteworthy from a technical and certification standpoint, however, I am uncertain about its actual utilisation by players. This approach requires a certain level of technical understanding which the typical player may lack. Furthermore, for games like slots, the algorithm can be even more complicated as the outcome of a spin is not a singular number but rather a matrix of numbers determining the positioning of symbols on the reels. Even if a player is able to use the formula to confirm the result from the game hash, they would still need to check its validity against the paytable of the slot game, which can be quite complex.

--

--