Доказуемо честно

Implementation

Implementation of Provable Fairness at Gamba

At Gamba, we've taken a unique approach to the implementation of provable fairness in our online gaming platform. Recognizing the complexities and potential intimidation new players might face with traditional methods, we have innovated our process to be more user-friendly and transparent.

Traditional Server Seed Generation

Typically, online casinos generate an unhashed server seed as a 64-character hex string. While this method is secure, it presents a seed that appears as a complex string of characters, which can be difficult for players to interpret and verify.

Example of a Traditional Server Seed

ade416be64b9174243a7cc47f03e7418c165d00fe816bdfae3f301128810064e

Gamba's Innovative Server Seed Generation

To make the server seed more accessible and understandable, we've adopted a 12-word mnemonic seed phrase system. This approach not only simplifies the appearance of the server seed but also makes it more familiar to those used to cryptocurrency wallets.

Example of Gamba's Server Seed

powder entry search sport naive hover earth swap broken cupboard man basket

Generating the 12-Word Mnemonic Phrase

Our process for generating the server seed begins with creating a 12-word mnemonic phrase. This involves encoding entropy in multiples of 32 bits and then generating a checksum from the SHA256 hash of this entropy. The combined entropy and checksum bits are then used to form the mnemonic phrase.

Entropy and Checksum Calculation

CS = ENT / 32
MS = (ENT + CS) / 11

| ENT | CS | ENT+CS | MS |
+-------+----+--------+------+
| 128 | 4 | 132 | 12 |
| 160 | 5 | 165 | 15 |
| 192 | 6 | 198 | 18 |
| 224 | 7 | 231 | 21 |
| 256 | 8 | 264 | 24 |

JS Fairness Model for Float and Integer Generation
const byteGenerator = async function*(cursor=0, clientSeed:string|null=null, serverSeed:string|null=null, nonce:number|null=null) {
  let currentRound=Math.floor(cursor/32);
  let currentRoundCursor=cursor-currentRound*32;
  while(true){
    const hmac=createHmac('SHA-256',`${clientSeed}:${nonce}:${currentRound}`,serverSeed);
    const buffer=new Uint8Array(str2ab(hmac));
    while(currentRoundCursor<32){
      yield buffer[currentRoundCursor];
      currentRoundCursor++;
    }
    currentRoundCursor=0;
    currentRound++;
  }
};

const createHmac=(algorithm:string,text:string,key:string|null)=>{
  const shaObj=new jsSHA(algorithm,'TEXT');
  shaObj.setHMACKey(key,'TEXT');
  shaObj.update(text);
  return shaObj.getHMAC('BYTES');
};

const generateIntegers=async({cursor=0,count=1,target=100,clientSeed,serverSeed,nonce=0,floats=[]}:IntegerProps)=>{
  const resolvedFloats=floats.length>0?floats:await generateFloats({cursor,count,clientSeed,serverSeed,nonce});
  const positions=Array.from({length:target},(_,i)=>i+1);
  return resolvedFloats.map((float,index)=>positions.splice(Math.floor(float*(target-index)),1)[0]);
};

const generateFloats=async({cursor=0,count=1,range=[0,1],clientSeed,serverSeed,nonce=0,intOnly=false,checkScaledResult=true}:Props)=>{
  const rng=byteGenerator(cursor,clientSeed,serverSeed,nonce);
  const bytes:number[]=[];
  while(bytes.length<count*4){
    const item=await rng.next();
    bytes.push(item?.value);
  }
  const floats:number[]=[];
  const byteChunks=chunkArray(bytes,4);
  for(const bytesChunk of byteChunks){
    let result=0;
    for(let i=0;i<bytesChunk.length;i++){
      result+=bytesChunk[i]/Math.pow(256,i+1);
    }
    if(checkScaledResult){
      const scaledResult=result*(range[1]-range[0])+range[0];
      let validResult=intOnly?Math.floor(scaledResult):scaledResult;
      while(floats.includes(validResult)){
        validResult++;
        if(validResult>range[1]) validResult=range[0];
      }
      floats.push(validResult);
    }else{
      floats.push(result);
    }
  }
  return floats;
};

Generate Binary Seed from Mnemonic Function

In our system, we use the binary seed of both the client and server to generate floats. This function is crucial for generating the binary seeds needed to verify the fairness of our algorithms. By ensuring that the mnemonic seed is correctly transformed into a binary seed, we can maintain the integrity and transparency of our processes.

This function can be used to verify if the revealed mnemonic seed matches the binary server seed, demonstrating that our systems and algorithms are FAIR. By generating a binary seed from the mnemonic, you can ensure the integrity and fairness of our processes.

import jsSHA from 'jssha';
import CryptoJS from 'crypto-js';

export const generateBinarySeed = (mnemonic: string): string => {
  const salt = 'mnemonic';
  const iterationCount = 2048;
  const derivedKeyLength = 64;

  const seed = CryptoJS.PBKDF2(mnemonic, salt, {
    iterations: iterationCount,
    keySize: derivedKeyLength / 4, // Key size is specified in words, so divide by 4
    hasher: CryptoJS.algo.SHA512,
  });

  const binarySeed = CryptoJS.enc.Hex.parse(seed.toString());
  const hexStr = binarySeed.toString(CryptoJS.enc.Hex);
  const bin2hex = (s: string) => {
    let i;
    let l;
    let o = '';
    let n;
    s += '';
    for (i = 0, l = s.length; i < l; i++) {
      n = s.charCodeAt(i).toString(16);
      o += n.length < 2 ? '0' + n : n;
    }
    return o;
  };
  const binStr = bin2hex(hexStr);
  const shortBinStr = binStr.slice(0, 64);
  return shortBinStr;
};

Converting the Mnemonic Phrase to a Binary Seed

To generate the binary seed from the mnemonic phrase, we use the PBKDF2 function with HMAC-SHA512. The phrase is used as a password, and the salt is the string "mnemonic".

Final Conversion to a 64-Character Hex String

We further process the 128-character string to conform to the traditional 64-character hex string format used in result generation.

Rotating Your Seed Pair

When a player sets a new client seed, our system automatically rotates the server seed as well. This process, referred to as "rotating your seed pair," ensures that both the server and client seeds remain synchronized and fresh for each game, enhancing the integrity of our randomness generation.

Nonce

The nonce, incrementing with each bet, works in tandem with the client and server seeds to generate unique outcomes for each game. This ensures the fairness and unpredictability of each bet.

Conclusion

This innovative approach to server seed generation enhances the user experience by making the process of result verification more approachable and understandable. It maintains high entropy and cryptographic security, mirroring the methods used in cryptocurrency wallet generation. This not only ensures the integrity of our games but also strengthens player confidence in the fairness and transparency of our gaming platform.

Provable Fairness for Crash

Crash uses a provably fair system designed to ensure that each crash point is determined before the round begins and cannot be altered. The system combines server-side cryptography with blockchain data to guarantee transparency, determinism, and resistance to manipulation.

Pre-Generated Hash Chain

Before any Crash game is played, we generate a hash chain of 100,000 hashes starting from a secret server seed. This is done by repeatedly applying the SHA256 hash function. The entire chain is generated in advance and stored securely.

function generateHashChain(serverSeed, chainLength) {
 let currentHash = crypto.createHash('sha256').update(serverSeed).digest('hex');
 for (let i = chainLength; i > 0; i--) {
currentHash = crypto.createHash('sha256').update(currentHash).digest('hex');
}
 return currentHash;
}

The Crash game operates on this hash chain using the following rules:

  1. Games are played in reverse order, starting from the end of the chain

  2. Each round uses a unique Round Seed derived from the chain

  3. The Round Seed is revealed immediately after the round ends

  4. Each hash is cryptographically linked to the next, making post-generation manipulation impossible

To verify a round’s integrity, you can hash the revealed Round Seed and confirm that it matches the previously published seed:

const nextRoundSeed = crypto.createHash('sha256').update(currentRoundSeed).digest('hex');

If the result matches, the round is proven to be valid.

Seed Components Used Per Round

Each Crash round combines three independent components:

  • Round Seed

    A unique server-generated hash from the pre-generated hash chain.

  • Hash Seed (Bitcoin Block Hash)

    A Bitcoin block hash that did not exist at the time the hash chain was generated, ensuring the house cannot predict or influence outcomes.

  • Nonce

    The sequential round number, ensuring uniqueness for each round.

These inputs are combined using HMAC-SHA256 to produce a deterministic random value.

Crash Point Calculation

The crash point is calculated before the round starts using the following process. A 1% house edge is applied directly in the formula.

function getCrashPoint(roundSeed, hashSeed, nonce) {
 const message = `${hashSeed}:${nonce}:0`;
 const hmac = crypto.createHmac('sha256', roundSeed);
hmac.update(message);

 const hash = hmac.digest();
 const divisors = [256, 65536, 16777216, 4294967296];

 let e = 0;
 for (let i = 0; i < 4; i++) {
e += hash[i] / divisors[i];
}

 if (e === 0) return 1.00;

 const crashPoint = Math.floor((99 / (e * 100)) * 100) / 100;
 return Math.max(1.00, crashPoint);
}

Example Crash Point Verification

Given the following round data:

Round Seed: 77b271fe12fc90f633dcd1f882021d8653b9b5c9b1ef7c8e2a42a6e9e38a8d3f Hash Seed: 0000000000000000000232f0e003c1e7dcce7c2eafae0c7aa770787c400193c5 Nonce: 1 

Step 1 — Build the message

message = "0000000000000000000232f0e003c1e7dcce7c2eafae0c7aa770787c400193c5:1:0" 

Step 2 — Generate HMAC-SHA256

hmac = 0a4262003fd4aa7cf137ca8a129bc90a61ebf59e1d0810fea3706f9bac9f1488

Step 3 — Convert the first 4 bytes to a float

bytes = [10, 66, 98, 0]
e = 10/256 + 66/65536 + 98/16777216 e = 0.04007542 

Step 4 — Apply the crash formula

crashPoint = 99 / (e × 100)
crashPoint = 24.70x

You can verify this result yourself by running:

const result = getCrashPoint(
 '77b271fe12fc90f633dcd1f882021d8653b9b5c9b1ef7c8e2a42a6e9e38a8d3f',
 '0000000000000000000232f0e003c1e7dcce7c2eafae0c7aa770787c400193c5',
 1
);
console.log(`Crash Point: ${result}x`);

All inputs required to verify a Crash round are revealed immediately after each game, allowing any player to independently reproduce and confirm the crash point.