Implementation of Provable Fairness at Gamba
At Gamba, we've taken a unique approach to the implementation of provable fairness in our online gaming platform. Recognizing the complexities and potential intimidation new players might face with traditional methods, we have innovated our process to be more user-friendly and transparent.
Traditional Server Seed Generation
Typically, online casinos generate an unhashed server seed as a 64-character hex string. While this method is secure, it presents a seed that appears as a complex string of characters, which can be difficult for players to interpret and verify.
Example of a Traditional Server Seed
ade416be64b9174243a7cc47f03e7418c165d00fe816bdfae3f301128810064e
Gamba's Innovative Server Seed Generation
To make the server seed more accessible and understandable, we've adopted a 12-word mnemonic seed phrase system. This approach not only simplifies the appearance of the server seed but also makes it more familiar to those used to cryptocurrency wallets.
Example of Gamba's Server Seed
powder entry search sport naive hover earth swap broken cupboard man basket
Generating the 12-Word Mnemonic Phrase
Our process for generating the server seed begins with creating a 12-word mnemonic phrase. This involves encoding entropy in multiples of 32 bits and then generating a checksum from the SHA256 hash of this entropy. The combined entropy and checksum bits are then used to form the mnemonic phrase.
Entropy and Checksum Calculation
CS = ENT / 32
MS = (ENT + CS) / 11
| ENT | CS | ENT+CS | MS |
+-------+----+--------+------+
| 128 | 4 | 132 | 12 |
| 160 | 5 | 165 | 15 |
| 192 | 6 | 198 | 18 |
| 224 | 7 | 231 | 21 |
| 256 | 8 | 264 | 24 |
JS Fairness Model for Float and Integer Generation
const byteGenerator = async function* (
cursor = 0,
clientSeed: string | null = null,
serverSeed: string | null = null,
nonce: number | null = null
) {
// Setup cursor variables
let currentRound = Math.floor(cursor / 32);
let currentRoundCursor = cursor;
currentRoundCursor -= currentRound * 32;
// Generate outputs until cursor requirement is fulfilled
while (true) {
// HMAC function used to output provided inputs into bytes
const hmac = createHmac(
'SHA-256',
clientSeed + ':' + nonce + ':' + currentRound,
serverSeed
);
const buffer = new Uint8Array(str2ab(hmac));
// Update cursor for next iteration of loop
while (currentRoundCursor < 32) {
// The yield keyword is used in a Generator function, similar to JavaScript's function* syntax
yield buffer[currentRoundCursor];
currentRoundCursor++;
}
currentRoundCursor = 0;
currentRound++;
}
};
const createHmac = (algorithm: string, text: string, key: string | null) => {
// eslint-disable-next-line new-cap
const shaObj = new jsSHA(algorithm, 'TEXT');
shaObj.setHMACKey(key, 'TEXT');
shaObj.update(text);
return shaObj.getHMAC('BYTES');
};
const generateIntegers = async ({
cursor = 0,
count = 1,
target = 100,
clientSeed,
serverSeed,
nonce = 0,
floats = [],
}: IntegerProps) => {
// Step 1: Generate a series of floats
floats =
floats.length > 0
? floats
: await generateFloats({
cursor,
count,
clientSeed,
serverSeed,
nonce,
});
// Step 2: Positions
const positions = Array.from({ length: target }, (_, i) => i + 1);
// Step 3: Get hits
const hits = floats.map((float, index) => {
return positions.splice(Math.floor(float * (target - index)), 1)[0];
});
return hits;
};
const generateFloats = async ({
cursor = 0,
count = 1,
range = [0, 1],
clientSeed,
serverSeed,
nonce = 0,
intOnly = false,
checkScaledResult = true,
}: Props) => {
// Random number generator function
const rng = byteGenerator(cursor, clientSeed, serverSeed, nonce);
// Declare bytes as empty array
const bytes = [];
// Populate bytes array with sets of 4 from RNG output
while (bytes.length < count * 4) {
const item = await rng.next();
bytes.push(item?.value);
}
// Return bytes as floats
const floats: number[] = [];
const byteChunks = chunkArray(bytes, 4);
for (const bytesChunk of byteChunks) {
let result = 0;
for (let i = 0; i < bytesChunk.length; i++) {
const divider = Math.pow(256, i + 1);
const partialResult = bytesChunk[i] / divider;
result += partialResult;
}
if (checkScaledResult) {
// Scale the result to the desired range
const scaledResult = result * (range[1] - range[0]) + range[0];
let validResult = intOnly ? Math.floor(scaledResult) : scaledResult;
while (floats.includes(validResult)) {
validResult++;
if (validResult > range[1]) {
validResult = range[0];
}
}
floats.push(validResult);
} else {
floats.push(result);
}
}
return floats;
};
Generate Binary Seed from Mnemonic Function
In our system, we use the binary seed of both the client and server to generate floats. This function is crucial for generating the binary seeds needed to verify the fairness of our algorithms. By ensuring that the mnemonic seed is correctly transformed into a binary seed, we can maintain the integrity and transparency of our processes.
This function can be used to verify if the revealed mnemonic seed matches the binary server seed, demonstrating that our systems and algorithms are FAIR. By generating a binary seed from the mnemonic, you can ensure the integrity and fairness of our processes.
import jsSHA from 'jssha';
import CryptoJS from 'crypto-js';
export const generateBinarySeed = (mnemonic: string): string => {
const salt = 'mnemonic';
const iterationCount = 2048;
const derivedKeyLength = 64;
const seed = CryptoJS.PBKDF2(mnemonic, salt, {
iterations: iterationCount,
keySize: derivedKeyLength / 4, // Key size is specified in words, so divide by 4
hasher: CryptoJS.algo.SHA512,
});
const binarySeed = CryptoJS.enc.Hex.parse(seed.toString());
const hexStr = binarySeed.toString(CryptoJS.enc.Hex);
const bin2hex = (s: string) => {
let i;
let l;
let o = '';
let n;
s += '';
for (i = 0, l = s.length; i < l; i++) {
n = s.charCodeAt(i).toString(16);
o += n.length < 2 ? '0' + n : n;
}
return o;
};
const binStr = bin2hex(hexStr);
const shortBinStr = binStr.slice(0, 64);
return shortBinStr;
};
Converting the Mnemonic Phrase to a Binary Seed
To generate the binary seed from the mnemonic phrase, we use the PBKDF2 function with HMAC-SHA512. The phrase is used as a password, and the salt is the string "mnemonic".
Final Conversion to a 64-Character Hex String
We further process the 128-character string to conform to the traditional 64-character hex string format used in result generation.
Rotating Your Seed Pair
When a player sets a new client seed, our system automatically rotates the server seed as well. This process, referred to as "rotating your seed pair," ensures that both the server and client seeds remain synchronized and fresh for each game, enhancing the integrity of our randomness generation.
Nonce
The nonce, incrementing with each bet, works in tandem with the client and server seeds to generate unique outcomes for each game. This ensures the fairness and unpredictability of each bet.
Conclusion
This innovative approach to server seed generation enhances the user experience by making the process of result verification more approachable and understandable. It maintains high entropy and cryptographic security, mirroring the methods used in cryptocurrency wallet generation. This not only ensures the integrity of our games but also strengthens player confidence in the fairness and transparency of our gaming platform.