Ive been assigned research involving online gaming companies and their use of cryptographic algorithms to produce 'provably fair' results. The largest player in the industry uses a method involving a client provided seed, which is then combined with a server seed and an incremental nonce. The server seed is shown to the client in hashed form before it can be changed and and then results can be verified. From what Ive been able to deduce, there are some flaws from the get-go which would prevent this particular system from being 'provably fair'.
A. There is no input that is not known to the server side. In order for this to implementation to be fair, a client seed would be combined with a server seed and the client could provide the nonce for each game result (obviously for any seed pair each nonce can only be used once). This way, one piece of information is not known to both sides going into each round of a game.
B. Results for a game begin at nonce 1, the only nonce that is completely resistant to any potential manipulation from what Ive read is nonce 0, which is conveniently not used.**
Ive consulted with several statisticians and done a few personal dives into data-analysis, and found it only requires some basic regression analysis and binomial distributions to decipher that the output of these RNG algorithms is clearly deviated from randomness, but rather highly dependent on user variables and settings.
My question is how are these websites and gaming operators able to change results in real time? Is there another layer of code going on underneath? I apologize if this delves more into computer science than pure cryptography, but it seemed appropriate to ask here. I did discover a comment from Moderator Maarten Bodewes on a related post that a server could change the source of a page, but I'm ignorant to how that could alter results, and also how afterwards clients can still verify results. Large accusations require large evidence bases, but so far IMO whats being perpetrated today spanning the globe amounts to one of, if not the largest 'out in the open' frauds being carried out in history. Please help me understand what could potentially be going on under the hood, surely sha256 isnt broken is it?
Here is an example of implementation:
// 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);
hmac.update(${clientSeed}:${nonce}:${currentRound}
);
const buffer = hmac.digest();
// Update curser for next iteration of loop while (currentRoundCursor < 32) { yield Number(buffer[currentRoundCursor]); currentRoundCursor += 1; } currentRoundCursor = 0; currentRound += 1; } }***
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.