**Goal:** Create a long list of random numbers that we can prove:
1. We have not tampered with since it was created.
2. We did not generate a lot of random number lists and then cherry pick the list that was most skewed in our favor.
**Methodology:** We generate a long chain of hashes from an initial seed that we keep private.
```ts
function generateHash(input: string): string {
return createHash("sha256").update(input).digest("hex");
}
function generateHashChain(serverSeed: string, length: number): string[] {
let currentHash = serverSeed;
const chain: string[] = [];
for (let i = 0; i < length; i++) {
currentHash = generateHash(currentHash);
chain.push(currentHash);
}
return chain;
}
```
We publish the last hash of that chain. We then reverse that chain, and each game we shift the chain, take that hash, and salt it with a publicly disclosed "client" seed.
```ts
function reverseHashChain(chain: string[]): string[] {
return chain.reverse();
}
function saltHash(hash: string, clientSeed: string) {
return createHmac("sha256", hash).update(clientSeed).digest("hex");
}
```
We then use that salted hash to generate the random crash point for that game.
```ts
function generateCrashPoint(serverSeed: string, clientSeed: string): number {
const hash = saltHash(serverSeed, clientSeed);
if (shouldInstaCrash(hash, houseEdge)) {
return 1;
}
const h = parseInt(hash.substring(0, 52 / 4), 16);
const e = Math.pow(2, 52);
return Math.floor((100 * e - h) / (e - h)) / 100;
}
```
Each game we tell the user a hash of what the salted hash used to generate the next value will be. After the game, we reveal the actual hash used.Users can then verify that the hash chain has not been tampered with by first verifying that the secondary hash they were told before the game actually corresponds to the hash that was revealed after the game. Then they can put the revealed hash in a validation function:
```ts
function validateFairness(hash: string, clientSeed: string, numGames: number = 10): void {
const chain = generateHashChain(hash, numGames);
const reversedChain = reverseHashChain(chain);
console.log(`Validating fairness for the last ${numGames} games:`);
reversedChain.forEach((gameHash, index) => {
const crashPoint = generateCrashPoint(gameHash, clientSeed);
console.log(`Game ${index + 1}: Hash = ${gameHash}, Crash Point = ${crashPoint.toFixed(2)}`);
});
}
```
This function generates the results of the preceding games leading up to the current one, and the user can verify they indeed match the history. If they generate all the preceding games, the hash of the very first one should be the hash that we publicly disclosed at launch (the last hash of the reversed chain).
So all of the above just proves that we didn't tamper with the list once it was created. But to prove that we didn't cherry pick the list, the publicly disclosed client seed that we use to salt the chain with has to be something that we couldn't have picked.
Typically companies have picked this client seed by publicly committing (normally on Twitter) to use the hash of a future Bitcoin block as the client seed. So this way they can commit to a number that they can't have known in advance.
**Most critical security vulnerability:**
The creation and storage of the pre-salted hash chain. The hash chain that we generate with our initial private seed has to be kept entirely secret, because whoever has access to it can predict the outcome of future games. I'm not sure what the best way to handle this is. We need some secure way of retrieving the next element in the chain without being able to output the entire chain, even internally.
_4.19.2024_