Welcome to
bustabit's third provably fair seeding event.
Our provably fair system, based on the
first seeding event, is evolving into an advanced multi-party provably fair scheme. We have integrated
ActuallyFair.com's Vx, a third-party service contributing to the algorithm that converts game hashes to game results. This not only helps us have a more secure system, but also preserves all provably fair guarantees while allowing Actually Fair to verify all games on behalf of our players.
1. Starting with a secret, we have generated a chain of 100,000,000 sha256 hashes by recursively hashing the binary value of the previous hash. Every hash in the chain will be used to generate a game result. The first element in the chain is the hash of game #100,000,000 and the last one is the hash of game #1, aka our terminating hash, which is: 567a98370fb7545137ddb53687723cf0b8a1f5e93b1f76f4a1da29416930fa59.
You can verify if a hash is part of the chain with a function like this:
import { sha256 } from "@noble/hashes/sha256";
import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
const TERMINATING_HASH = "567a98370fb7545137ddb53687723cf0b8a1f5e93b1f76f4a1da29416930fa59";
function verifyInChain(hash: Uint8Array) {
for (let gameId = 1; gameId < 100e6; gameId++) {
hash = sha256(hash);
if (bytesToHex(hash) === TERMINATING_HASH) {
console.log("hash is in the chain. It is game: ", gameId);
return gameId;
}
}
console.error("hash is not in the chain");
}
Which could be used like:
verifyInChain(
hexToBytes("70eed5c29bde5132f4e41ec8b117a31533e5b055c6c21174d932b377a1855a04")
);
2. Vx will use its BLS public key: b40c94495f6e6e73619aeb54ec2fc84c5333f7a88ace82923946fc5b6c8635b08f9130888dd96e1
749a1d5aab00020e4.
3. We will play through that chain of hashes, in reverse order, and use the hashes to determine the crash point in a provably fair manner.
4. To prove that Actually Fair and bustabit have picked a fair hash chain (and public key) we will also mix in the lowercase, hexadecimal representation of the hash of a Bitcoin block that has not been mined yet:
Bitcoin block 831500. This will be known as our game salt.
5. Vx gives us a signature (
vxSignature) by signing the concatenation of the previous game hash along with the game salt. Vx uses BLS signatures so that it is not possible to generate multiple valid signatures for a particular message. We can validate the signature with something like:
import { bls12_381 as bls } from "@noble/curves/bls12-381";
import { concatBytes, utf8ToBytes } from "@noble/hashes/utils";
const VX_PUBKEY = "b40c94495f6e6e73619aeb54ec2fc84c5333f7a88ace82923946fc5b6c8635b08f9130888dd96e1749a1d5aab00020e4";
function validateSignature(
gameSalt: string, // the hash of block 831500
prevGameHash: Uint8Array,
vxSignature: Uint8Array
) {
const message = concatBytes(prevGameHash, utf8ToBytes(gameSalt));
return bls.verify(vxSignature, message, VX_PUBKEY);
}
5. Once we have a valid
vxSignature, we take the next unused hash from the chain and use it to determine game results:
import { hmac } from "@noble/hashes/hmac";
import { sha256 } from "@noble/hashes/sha256";
import { bytesToHex } from "@noble/hashes/utils";
export function gameResult(vxSignature: Uint8Array, gameHash: Uint8Array) {
const nBits = 52; // number of most significant bits to use
// 1. HMAC_SHA256(key=signature, message=hash)
const hash = bytesToHex(hmac(sha256, vxSignature, gameHash));
// 2. r = 52 most significant bits
const seed = hash.slice(0, nBits / 4);
const r = Number.parseInt(seed, 16);
// 3. X = r / 2^52
let X = r / Math.pow(2, nBits); // uniformly distributed in [0; 1)
// 4. X = 99 / (1 - X)
X = 99 / (1 - X); // 1 - X so there's no chance of div-by-zero
// 5. return max(trunc(X), 100)
const result = Math.floor(X);
return Math.max(1, result / 100);
}
For more reference code, take a look at our
open-source verifier and at this
illustrative demo.