Our 'Provably Fair' methods have been independently verified by one of the leading accredited testing laboratories for online gaming!

Recently, we hired iTechLabs to verify the legitimacy of our 'Provably Fair' methods. An accredited laboratory for online gaming certification and quality assurance testing, iTechLabs have tested and certified almost all types of casino games on a wide range of platforms for 14 years.

With this external verification, we're proud to share that the systems we use to determine our games have passed independent authentication!

When you play on CSGORoll, our 'Provably Fair' system generates a random result using multiple separate elements. Together, these elements prove the randomness of each of our games.

Unlike other CS:GO skins sites on the scene, this independent verification proves that we're doing all we should to publicly determine our outcomes, so that you can be certain of our fairness.

All in all, iTechLabs's certification only resulted in a few changes, of which you can explore below.


RNG Technical Changes

— Seed Generation

Games impacted: Unboxing, Box Battles

We have added a fourth value (Game Mode) to the secret seed generation for Unboxing and Box Battles. This was done as a precaution to increase security against potential exploits, as it makes it impossible for RNGs across the different game modes to recycle seeds.

Seed Generation before:

function getCombinedSeed(serverSeed, clientSeed, nonce) {
  // Add main parameters
  const seedParameters = [serverSeed, clientSeed, nonce];

  // Combine parameters to get seed value
  return seedParameters.join('-')
}

Seed Generation after:

function getCombinedSeed(game, serverSeed, clientSeed, nonce) {
  // Add main parameters
  const seedParameters = [serverSeed, clientSeed, nonce];

  // Add game parameter if needed
  if (game) {
    seedParameters.unshift(game);
  }

  // Combine parameters to get seed value
  return seedParameters.join('-')
}

— Random Number Generation

Impacted games: Roll, Unboxing, Plinko, PvP Case Battles, PvP Coin Flip, PvP Dice

We have tweaked our RNG in order to achieve more cryptographic strength and increased security from potential exploits.

Change 1: We moved from using a 32-bit integer (8 character length Hexadecimal hash) to a 52-bit integer (13 character length Hexadecimal hash) - this is simply to increase the cryptographic strength of the generated number.

Change 2: We have amended the method used to get from the raw integer coming from the hash value to the final random result (being a number from 0-X; where X = the max possible result). In our previous version, we used the Modulo % operation with Max possible result (e.g. 1^8 for Unboxing), which would return the final result as a remainder amount in the range of 0 - Max.

We now have tweaked this method, instead of using the Modulo operation, we divide the raw integer generated by the hash by the maximum possible integer (2^52). This gives us a floating point number from 0-0.9999999 recurring.
We then multiply this number by the Max possible result and use floor to round down, giving an integer result in the range of 0 - Max.

Change 3: Some of our games RNGs previously utilised ranges of between 1 - Max, while others generated results between 0 - Max. We have normalised this now, so that all game modes RNG’s start from zero, instead of 1, as was the case in some instances previously.

Random Number Generation before:

function getRandomInt({ max, min, seed }) {
  // Get hash from seed
  log(`Seed value: ${seed}`);
  const hash = crypto.createHmac('sha256', seed).digest('hex');

  // Get value from hash
  const subHash = hash.slice(0, 8);
  const valueFromHash = Number.parseInt(subHash, 16);

  // Get dynamic result for this roll
  const random = Math.abs(valueFromHash) % max;
  return random + min;
}

Random Number Generation after:

function getRandomInt({ max, seed }) {
  // Get hash from seed
  log(`Seed value: ${seed}`);
  const hash = crypto.createHmac('sha256', seed).digest('hex');

  // Get value from hash
  const subHash = hash.slice(0, 13);
  const valueFromHash = Number.parseInt(subHash, 16);

  // Get dynamic result for this roll
  const e = Math.pow(2, 52);
  const result = valueFromHash / e;
  return Math.floor(result * max);
}

Dice / Upgrade RNG

Impacted games: Dice, Item Upgrade

We have added a fourth value to the secret seed (Iteration) used to generate results for our Dice and Item Upgrade game modes. This ensures that results are not exploitable and eradicate the possibility for a skewed result in very rare edge cases.

RNG before:

function getDiceRoll(serverSeed, clientSeed, nonce) {
  const hash = getHash(serverSeed, clientSeed, nonce);
  let index = 0;
  let lucky = getLucky(hash, index);

  while (lucky >= Math.pow(10, 6)) {
    index++;
    lucky = getLucky(hash, index);
    // We have reached the end of the hash and they all must have been FFFFFF hex value
    if (Math.imul(index, 5) + 5 > 129) {
      return 9999;
    }
  }

  lucky %= Math.pow(10, 4);

  return lucky;
}

function getLucky(hash, index) {
  const hashLucky = hash.substr(Math.imul(index, 5), 5);
  return parseInt(hashLucky, 16);
}

function getHash(serverSeed, clientSeed, nonce) {
  const hmac = crypto.createHmac('sha512', serverSeed);
  hmac.update(`${clientSeed}-${nonce}`);
  return hmac.digest('hex');
}

RNG after:

function getDiceRoll({ clientSeed, game, iteration = 0, nonce, serverSeed }) {
  // Prepare seed parameters
  const seedParameters = [clientSeed, nonce];
  if (game) {
      seedParameters.unshift(game);
  }

  // Get hash from seed values
  const hmac = crypto.createHmac('sha512', serverSeed);
  hmac.update(`${seedParameters.join('-')}-${iteration}`);
  const hash = hmac.digest('hex');

  // Initialize variables
  let index = 0;
  let lucky = getLucky(hash, index);

  // Process the hash until left with lucky result
  while (lucky >= Math.pow(10, 6)) {
      index++;
      lucky = getLucky(hash, index);

      // Reaching the end of the hash, run the calculations again
      if (Math.imul(index, 5) + 5 > 128) {
        return getDiceRoll({ clientSeed, game, iteration: iteration + 1, nonce, serverSeed });
      }
  }

  // Return lucky float number
  lucky %= Math.pow(10, 4);

  return lucky;
}

function getLucky(hash, index) {
  const hashLucky = hash.slice(Math.imul(index, 5), Math.imul(index, 5) + 5);
    return Number.parseInt(hashLucky, 16);
}

Got a question about our verification? Visit our dedicated 'Provably Fair' section on CSGORoll, or get in touch.