Provably Fair
Common Functions
const generateBytes = (seed, cursor) => { return crypto .createHmac("sha256", `${seed.server_seed}`) .update(`${seed.client_seed}:${seed.nonce}:${cursor}`) .digest(); }; const generateFloats = (seed, count) => { const numberOfSHARoundsRequired = Math.ceil(count / 8); const buffer = Buffer.alloc(numberOfSHARoundsRequired * 32); for (let i = 0; i < numberOfSHARoundsRequired; i++) { generateBytes(seed, i).copy(buffer, i * 32); } const floats: number[] = []; for (let i = 0; i < count; i++) { let curr = new BigNumber(0); const start = i * 4; for (let j = 0; j < 4; j++) { curr = curr.plus( new BigNumber(buffer[start + j]).div(new BigNumber(256).pow(j + 1)) ); } floats.push(curr.toNumber()); } return floats; };
Game Specific Implementations
Dice
// Implementation during 1% edge const diceRollFromSeed = (seed) => { const randomFloat = generateFloats(seed, 1)[0]; return Math.floor(randomFloat * 10001) / 100; }; // Implementation during 0.95% edge const diceRollFromSeed = (seed) => { const randomFloat = generateFloats(seed, 1)[0]; return Math.round(randomFloat * 10000) / 100; };Plinko
const plinkoPathFromSeed = (seed, numberOfRows) => { const floats = generateFloats(seed, numberOfRows); return [...floats].map((float) => (float < 0.5 ? "L" : "R")); };Keno
const kenoSelectionFromSeed = (seed) => { const startingSet = [...Array.from(Array(40).keys())]; const floats = generateFloats(seed, 10); return floats.map((float) => { const index = Math.floor(float * startingSet.length); return startingSet.splice(index, 1)[0]; }); };Wheel
const wheelSpinFromSeed = (seed, segments) => { const floats = generateFloats(seed, 1); return Math.floor(floats[0] * segments); };Diamonds
const diamondSlotsFromSeed = (seed) => { const floats = generateFloats(seed, 5); return floats.map((float) => Math.floor(float * 7)); };Roulette
const rouletteRollFromSeed = (seed) => { const randomFloat = generateFloats(seed, 1)[0]; return Math.floor(randomFloat * 37); };Limbo
const limboMultiplierFromSeed = (seed, gameEdge) => { const float = generateFloats(seed, 1)[0]; const largeNum = new BigNumber(16777216); const numerator = largeNum; const denominator = new BigNumber(float).multipliedBy(largeNum) .plus(1).integerValue(BigNumber.ROUND_FLOOR); const floatWithHouseEdge = numerator .dividedBy(denominator) .multipliedBy(1 - gameEdge / 100); return BigNumber.maximum(1, floatWithHouseEdge.multipliedBy(100) .integerValue(BigNumber.ROUND_FLOOR).dividedBy(100)) .toNumber(); };Blackjack, HiLo and Baccarat
const cards = [ "2D", "2H", "2S", "2C", "3D", "3H", "3S", "3C", "4D", "4H", "4S", "4C", "5D", "5H", "5S", "5C", "6D", "6H", "6S", "6C", "7D", "7H", "7S", "7C", "8D", "8H", "8S", "8C", "9D", "9H", "9S", "9C", "10D", "10H", "10S", "10C", "JD", "JH", "JS", "JC", "QD", "QH", "QS", "QC", "KD", "KH", "KS", "KC", "AD", "AH", "AS", "AC", ]; const shuffledDeckFromSeed = (seed, numCards = 52) => { const floats = generateFloats(seed, numCards); return floats.map((float) => { const cardIdx = Math.floor(float * cards.length); return cards[cardIdx]; }); };Tower
const towerSafePositionsFromSeed = (seed, risk) => { const safePositionsPerRow = TowerConfig[risk].num_safe_positions; const floats = generateFloats(seed, 9 * safePositionsPerRow); const ret = []; let floatIdx = 0; for (let rowIdx = 0; rowIdx < 9; rowIdx++) { const safePositionsInCurrentRow = []; const arr = Array(TowerConfig[risk].num_positions).fill(0).map((_, j) => j); for (let j = 0; j < safePositionsPerRow; j++) { const index = Math.floor(floats[floatIdx] * arr.length); safePositionsInCurrentRow.push(arr.splice(index, 1)[0]); floatIdx += 1; } ret.push(safePositionsInCurrentRow); } return ret; };Mines
const mineLocationsFromSeed = (seed, numberOfMines) => { const gridCellsRasterOrder = [...Array(25).keys()]; const floats = generateFloats(seed, numberOfMines); return floats.map((float) => { const index = Math.floor(float * gridCellsRasterOrder.length); return gridCellsRasterOrder.splice(index, 1)[0]; }); };Goat Run
const goatRunAccidentalLanesFromSeed = (seed, numAccidentalLanes) => { const laneNumbers = Array.from({length: 25}, (_, i) => i + 1); const floats = generateFloats(seed, numAccidentalLanes); return floats.map((float) => { const index = Math.floor(float * laneNumbers.length); return laneNumbers.splice(index, 1)[0]; }); };G3 Cards
const cards = [ "2D", "2H", "2S", "2C", "3D", "3H", "3S", "3C", "4D", "4H", "4S", "4C", "5D", "5H", "5S", "5C", "6D", "6H", "6S", "6C", "7D", "7H", "7S", "7C", "8D", "8H", "8S", "8C", "9D", "9H", "9S", "9C", "10D", "10H", "10S", "10C", "JD", "JH", "JS", "JC", "QD", "QH", "QS", "QC", "KD", "KH", "KS", "KC", "AD", "AH", "AS", "AC", ]; const deckFromSeed = (seed) => { const deck = [...cards]; const floats = generateFloats(seed, 52); const shuffledDeck = []; for (const float of floats) { const cardIdx = Math.floor(float * deck.length); shuffledDeck.push(deck.splice(cardIdx, 1)[0]); } return shuffledDeck; };Crash
// Crash rounds are shared by every player in them, // so an outcome cannot depend on a single player's seed pair and nonce. // Instead, each round's server seed comes from a pre-committed chain of hashes: // the terminal seed is hashed once for every round still left to play. // Because hashing any round's seed once yields the previous round's seed, // the whole history can be walked backwards and checked against the committed terminal seed const serverSeedFromRoundNumber = (terminalSeed, roundNumber) => { let hash = terminalSeed; for (let i = 0; i < 10000000 - roundNumber; i++) { hash = crypto.createHash("sha256").update(hash).digest("hex"); } return hash; }; const crashPointFromSeed = (serverSeed, clientSeed, gameEdge) => { const hmac = crypto.createHmac("sha256", serverSeed).update(clientSeed); const int = parseInt(hmac.digest("hex").slice(0, 8), 16); return Math.max(1, (2 ** 32 / (int + 1)) * (1 - gameEdge)); };Punch
const punchSuccessFromSeed = (seed, turn, action) => { const PUNCH_PAYOUT_HUNDREDTHS = { ["SLAP"]: 110, ["PUNCH"]: 150, ["UPPERCUT"]: 200, ["SPIN"]: 400, }; const PUNCH_ACTIONS = Object.fromEntries( Object.entries(PUNCH_PAYOUT_HUNDREDTHS).map(([action, payout]) => [ action, { multiplier: payout / 100, success_probability: 100 / payout, }, ]) ); // validates seed, turn ∈ [0, PUNCH_MAX_TURNS), action const roll = generateFloats(seed, turn + 1)[turn]; return roll < PUNCH_ACTIONS[action].success_probability; };