1

I've 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 I've read is nonce 0, which is conveniently not used.

I've 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 isn't broken, is it?


Here is an example of the 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.

Glorfindel
  • 506
  • 1
  • 11
  • 22
Joey Jolly
  • 21
  • 3

2 Answers2

2

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.

bk2204
  • 3,564
  • 7
  • 12
2

I've been assigned research involving online gaming companies and their use of cryptographic algorithms to produce 'provably fair' results.

TL;DR: There's no practical way to create truly unpredictable & unbiasable randomness without at least one trusted party. That being said...

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.

Stop! Any scheme where there is no commitment beforehand would allow an attacker to scheme about the "random" values it uses. If the casino sees that it's original server seed combined with the client seed would lead to a jackpot, it can change it's server seed opaquely. This is called a "predictable" scheme: One party can predict the output, and then change that output for the other party. In other words, one party can predict the impact a number will have on the result.

First off, let's describe our desired properties:

  • Unpredictable: One party cannot provide a value $r$ to produce a desired result
  • Unbiasable: One party cannot prevent another party from discovering an undesirable result

Unbiasable $\neq$ Unpredictable

Distributed randomness is a problem that generally doesn't provide adequate security against organized adversaries. No matter how unpredictable your randomness is, there will always be at least one party who can bias the results. Let's look at why that is with a simple commit-then-reveal scheme:

  1. All parties generate $r_n$ and commitment $c_n$.
  2. All parties share their commitment $c_n$
  3. All parties then reveal their randomness $r_n$
  4. All parties' $r_n$ is combined to form $r$, the randomly drawn value

In a vacuum, this scheme is unpredictable, a desirable quality for casinos and the like. Until every party reveals their $r_n$, there's no way for any one person to have predicted the final $r$ unless all parties colluded.

However, let's look at a simple attack on this commit-then-reveal scheme. Let's say Alice runs the casino and Bob just put an awful lot of money on his latest bet. Alice and Bob both generate their commitments and exchange them. Now, Alice sends Bob her $r_n$. Bob combines this with his own $r_n$ to form $r$, and sees that the result is bad for him: he lost all his money. Now that he knows that, is he likely to send Alice his $r_n$? He has an opportunity to hide the results from Alice, making this randomness scheme biasable--an undesirable result can be omitted, despite the fact that it couldn't be predicted.

In an online casino, this doesn't have much significance. If a player hides his result, then the casino can simply withhold the funds for the play indefinitely. However, let's look at this the other way around: Alice is an evil casino dealer, and Bob is a good upstanding citizen. If Bob reveals his $r_n$ and Alice learns that she just lost the casino a trillion dollars, she could also choose to withhold her $r_n$. Both Alice or Bob could feign ignorance or incompetence when they fail to reveal their values, but every time it has real & measurable consequences for the other party.

Byzantine Adversaries

The Byzantine Generals' Problem is about reaching an agreement in the face of malicious actors. The general understanding is that you need $2a+1$ good guys for every $a$ bad guys to resist every attack that the bad guys could mount on the system.

Distributed randomness with a byzantine adversary requires two properties:

  • Unpredictability: Until a threshold $t$ is reached, no party can predict the results
  • Unbiasability: Once threshold $t$ is reached, the results cannot be "hidden" or modified by any party so long as there are $2a+1$ good guys.

RandShare is an example of one such protocol: it provides both unpredictability and unbiasability in the face of a byzantine adversary. It can absolutely be used in online gambling settings, except...

However what happens if we don't have $2a+1$ good guys? What if we're playing poker and there are three bad guys and one good guy? In this scenario, we would need seven good guys, so the three bad guys can bias the results all they'd like. Instead of revealing their hands, the three bad guys could just walk up and leave, taking their chips with them. While they could never predict the results, they can still influence the fairness of the game.

The Need For Trust

Somewhere, we need to have some degree of trust to act as a "threshold" to prevent results from being biased. If we are using a distributed protocol like RandShare, we need to trust that there are $2a+1$ good guys for every attacker. If we are using a simple commit-then-reveal scheme, then we need to trust that the last person to reveal their $r_n$ will always do so.

If either of these "trusts" are broken, then the results can be manipulated and there's no way for anyone besides the attackers to tell for sure.

...their use of cryptographic algorithms to produce 'provably fair' results

It's not provably fair. No matter how much they toot their horn about unpredictability, you cannot have unbiasability without some form of trust. Gaming companies can put the trust of unbiasability into the user's hands, and withhold the pot if the user doesn't oblige, but then the casino wouldn't be able to tell the difference between an act of sabotage or a transient error.

I've 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.

With the extremely poor quality of the code you've presented, I'd believe it in a heartbeat. I wouldn't bet a dime on that site.

Please help me understand what could potentially be going on under the hood, surely SHA256 isn't broken, is it?

It's not, but SHA256 is fast enough that an attacker can quickly brute-force a desirable outcome. If there are $2^{16}$ possible outcomes, an attacker only has to do $2^{16}$ hashes, something they could do in the blink of an eye (literally, it's a matter of milliseconds). I would use a memory-hard algorithm like scrypt to make this harder.

Mooshua
  • 270
  • 2