0
$\begingroup$

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.

$\endgroup$
2
  • $\begingroup$ I'm pretty sure that this falls under one of our close reasons: trying to evaluate an entire system. As for my comment (which you forgot to link to); the problem is that server code can change and if you have a page generated by the same server then you'll have to trust that server to trust the code running in the browser. $\endgroup$
    – Maarten Bodewes
    Dec 4, 2023 at 22:10
  • $\begingroup$ I'm not getting the meaning of the word "deviated" in "d 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". $\endgroup$
    – Maarten Bodewes
    Dec 4, 2023 at 22:15

1 Answer 1

1
$\begingroup$

Certainly it's possible for the code to change, and discussion of that is off topic here. As for the approach where both the client and the server specify large (e.g., 256-bit) random numbers to seed a CSPRNG, that can be secure if the two sides use a commitment scheme, even if the server knows both sides.

With a commitment scheme, each side chooses a value (in this case, a random value) and then commits to its value in such a way that it's computationally infeasible to change the result. Often this is a Pedersen commitment, but it can be done using HMAC with a secure hash function and a suitably large random key. In this case, it appears they're using SHA-256, which is not a good choice but seems to serve the same purpose. Once the commitment is sent, the sender can reveal the commitment later by showing the value and whatever else went into the commitment.

So the scheme where the server picks a random number and reveals the hash before seeing client input, even with an incrementing nonce, is secure. The server cannot change the results without being detected because it cannot invert the hash, and the client cannot change the results because it cannot guess the server input value since it, too, cannot invert the hash.

If, however, the server accepts the client value before revealing its hash, then it's of course possible to cheat, since it can pick a value that gives it an advantage.

This approach can be extended to arbitrary numbers of players where each client commits to a value, along with the server, and then all the clients reveal their commitments to the server. The server then shuffles the cards, and, at the end of the round, reveals its secret to allow everyone to verify that the game was played fairly.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.