Help & Support

Help Topics

Provably Fair System

At Rain.gg, fairness and transparency are our top priorities. Our provably fair system ensures that all game outcomes are determined by verifiable random algorithms that cannot be manipulated. This page explains how our system works and how you can verify the fairness yourself.

ON THIS PAGE

How Fair is Rain.gg?

The outcome of each game is calculated in a deterministic way that allows us to recreate the same result every time after the round is finished. At the start of every game, you can access the hashed server seed of the round that is about to be played, which guarantees that we couldn't tamper with the results in any way.

For games that allow it, we can also use additional factors such as your client seed, EOS block hash, and round number. This ensures that it is not possible for us to deliberately choose unfavorable server seeds before the round starts due to the external influence.

Our system uses cryptographic techniques to generate random results that are:

  • Verifiable - You can check the outcome yourself
  • Transparent - All algorithms are publicly available
  • Unbiased - Results cannot be manipulated
  • Fair - Every player has the same odds

How Results Are Generated

We generate random 64 characters long strings as our server seeds (using the SHA256 hash algorithm to create the hashed version) and rely on the EOS block hash when needed for certain multiplayer games. The code below shows how we transform available hashes into float values that are later converted into usable game actions.

import crypto from 'crypto';

function* getBytesChunks(
  serverSeed: string,
  clientSeed: string,
  nonce: number | string
): Generator<number[]> {
  let step = 0;
  let counter = 0;

  while (true) {
    const hmac = crypto.createHmac('sha512', serverSeed);
    hmac.update(`${clientSeed}:${nonce}:${step}`);
    const hashValue = hmac.digest('hex');

    while (counter < 16) {
      const hashBytes: number[] = [];

      for (let i = 0; i < 4; i++) {
        hashBytes.push(parseInt(hashValue.substring(i * 2 + counter * 8, i * 2 + 2 + counter * 8), 16));
      }

      yield hashBytes;
      counter += 1;
    }

    counter = 0;
    step += 1;
  }
}

function getFloats(
  serverSeed: string,
  clientSeed: string,
  nonce: number | string,
  count: number
): number[] {
  const bytes = getBytesChunks(serverSeed, clientSeed, nonce);
  const ints: number[][] = [];

  while (ints.length < count) {
    ints.push(bytes.next().value);
  }

  return ints.map((bytes) =>
    bytes.reduce((acc, value, i) => acc + value / Math.pow(256, i + 1), 0)
  );
}

These core functions provide the foundation for generating random outcomes in all our games. Let's explore how they're applied to each specific game type.

Case Opening

When opening cases, hashes of the concatenated server seed, client seed, and nonce are used to generate an integer number between 1 and 10,000,000. The generated number is then compared with every item inside the case to find one which tickets range matches the generated value.

const maxTicket = 10000000;
const serverSeed = '';
const clientSeed = '';
const nonce = 1;

function roll() {
  const float = getFloats(serverSeed, clientSeed, nonce, 1)[0];
  
  // from 1 to 10000000
  return Math.floor(float * 10000000 + 1);
}

Case Battles

In case battles, hashes of the concatenated server seed, EOS block hash, and round number with player's slot are used to generate an integer number between 1 and 10,000,000. The generated number is then compared with every item inside the case to find one which tickets range matches the generated value.

const serverSeed = '';
// we use mined eos block hash as our client seed
const eosBlockHash = '';
// first round is 1
const roundNum = 1;

// first slot is 0
function roll(slot: number) {
  const float = getFloats(serverSeed, eosBlockHash, `${roundNum}:${slot}`, 1)[0];
  
  // from 1 to 10000000
  return Math.floor(float * 10000000 + 1);
}

If two or more winning players at the end of the game have the same amount of items value, a tie happens and each gets assigned a unique number. The player with the highest tiebreaker number wins the battle, with the exception of crazy mode where the lowest number is the winning one. In case of a team battle, a sum of individual player rolls is compared to determine the winning team.

const serverSeed = '';
// we use mined eos block hash as our client seed
const eosBlockHash = '';
// first round is 1, we use the last round number to generate the roll
const lastRoundNum = 1;

// first slot is 0
function roll(slot: number) {
  const float = getFloats(serverSeed, eosBlockHash, `${lastRoundNum}:${slot}:tie`, 1)[0];
  
  // from 1000 to 9999
  return Math.floor(float * (9999 - 1000 + 1) + 1000);
}

When playing jackpot mode, an additional float number between 0 and 1 (non-inclusive) is generated at the end of the round to determine the winner. Each player gets odds proportional to the percentage of the total value of items opened in a battle. For crazy mode, the odds are inversed then normalized into the 0 to 1 range.

const serverSeed = '';
// we use mined eos block hash as our client seed
const eosBlockHash = '';
// first round is 1, we use the last round number to generate the roll
const lastRoundNum = 1;

// first slot is 0
function roll(slot: number) {
  const float = getFloats(serverSeed, eosBlockHash, `${lastRoundNum}:jackpot`, 1)[0];
  
  // from to 0 to 0.(9)
  return float;
}

Upgrader

In the Upgrader, hashes of the concatenated server seed, client seed, and nonce are used to generate an integer number between 1 and 10,000. The generated number is then used as the winning ticket.

const maxTicket = 10000;
const serverSeed = '';
const clientSeed = '';
const nonce = 1;

function roll() {
  const float = getFloats(serverSeed, clientSeed, nonce, 1)[0];
  
  // from 1 to 10000
  return Math.floor(float * 10000 + 1);
}

Double

In Double, hashes of the concatenated server seed, public seed, and round number are used to generate an integer number between 0 and 14. The generated number is then converted into the appropriate double symbol.

const betTypeRanges = {
  [DoubleBetTypeEnum.GREEN]: [0, 0],
  [DoubleBetTypeEnum.RED]: [1, 7],
  [DoubleBetTypeEnum.BLACK]: [8, 14],
  [DoubleBetTypeEnum.BAITBET_RED]: [3, 3],
  [DoubleBetTypeEnum.BAITBET_BLACK]: [12, 12],
};
const serverSeed = '';
const publicSeed = '';
const roundNumber = 1;

function roll() {
  const float = getFloats(serverSeed, publicSeed, roundNumber, 1)[0];
  
  // from 0 to 14
  return Math.floor(float * 15);
}

Verification

Rain.gg provides tools for you to verify that the outcomes of games are fair and have not been manipulated. You can check the fairness of your game outcomes by following these steps:

  1. Get the server seed, client seed, and nonce

    Before playing, you can see the hashed server seed. After the game, the unhashed server seed is revealed along with the client seed and nonce used.

  2. Verify the server seed hash

    Hash the revealed server seed with SHA256 to confirm it matches the hash shown before the game.

  3. Recalculate the outcome

    Use the code examples provided above to recreate the game outcome based on the seeds and nonce.

Visit Rain.gg to Verify

Rain.gg provides a dedicated verification page where you can check the fairness of your game outcomes:

Rain.gg Provably Fair

Last updated: April 7, 2025