node-provably-fair
Version:
A provably fair system for generating and verifying random numbers. Includes support for generating random outcomes based on blockchain data, ensuring fair and tamper-proof results.
178 lines (177 loc) • 8.16 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProvablyFair = void 0;
const axios_1 = __importDefault(require("axios"));
const crypto = __importStar(require("crypto"));
const os = __importStar(require("os"));
const uuid_1 = require("uuid");
class ProvablyFair {
constructor() {
this.serverSeed = this.generateSecureRandomString(64);
this.secretSalt = this.generateSecureRandomString(8);
this.nonce = 0;
this.publicHash = this.generatePublicHash(this.serverSeed, this.secretSalt);
}
generateSecureRandomString(length) {
return crypto.randomBytes(length).toString('hex');
}
generatePublicHash(serverSeed, secretSalt) {
return crypto.createHash('sha256').update(serverSeed + secretSalt).digest('hex');
}
generateRollHash(clientSeed, serverSeed, secretSalt, nonce, bitcoinHash, max = 10000000) {
const combinedInput = `${bitcoinHash}:${clientSeed}:${serverSeed}:${nonce}`;
const randomNumber = crypto.createHmac('sha256', secretSalt).update(combinedInput).digest('hex');
const decimalValue = parseInt(randomNumber.slice(0, 8), 16);
return ((decimalValue % max) + 1).toString();
}
verifyRoll(clientSeed, roll, serverSeed, secretSalt, nonce, publicHash, bitcoinHash) {
const generatedPublicHash = this.generatePublicHash(serverSeed, secretSalt);
if (generatedPublicHash !== publicHash) {
return false;
}
const generatedRoll = this.generateRollHash(clientSeed, serverSeed, secretSalt, nonce, bitcoinHash);
return generatedRoll === roll.toString();
}
calculateWinProbability(winRange, totalRange = 10000000) {
if (winRange < 1 || winRange > totalRange) {
throw new Error('The winning range must be between 1 and the total possibilities.');
}
return (winRange / totalRange) * 100;
}
getPublicHash() {
return this.publicHash;
}
getNonce() {
return this.nonce;
}
incrementNonce() {
this.nonce += 1;
}
rotateSeeds() {
this.serverSeed = this.generateSecureRandomString(64);
this.secretSalt = this.generateSecureRandomString(8);
this.publicHash = this.generatePublicHash(this.serverSeed, this.secretSalt);
this.nonce = 0; // Reset nonce on seed rotation
}
/**
* Returns a win interval based on the provided percentage.
* @param percentage - The desired win percentage.
* @param totalRange - The total possibilities (usually 10,000,000).
* @returns The win interval corresponding to the percentage.
*/
getWinInterval(percentage, totalRange = 10000000) {
const winRange = Math.round((percentage / 100) * totalRange); // calculate the size of the winning range based on the percentage
const end = totalRange; // The end will always be the totalRange
const start = totalRange - winRange + 1; // Calculate the start to ensure the range ends at the totalRange
return {
percentage,
interval: [start, end]
};
}
/**
* Returns an array of intervals based on a provided array of percentages.
* @param percentages - An array of percentages.
* @param totalRange - The total possibilities (usually 10,000,000).
* @returns An array of objects containing percentage and corresponding interval.
*/
getWinIntervals(percentages, totalRange = 10000000) {
let currentStart = 1;
return percentages.map((percentage) => {
const winRange = Math.round((percentage / 100) * totalRange);
const end = currentStart + winRange - 1;
const interval = [currentStart, end];
currentStart = end + 1;
return { percentage, interval };
});
}
/**
* Generates a random number based on the hash of the latest Bitcoin blockchain block.
* @param clientSeed - The client seed.
* @param serverSeed - The server seed (optional, generated if not provided).
* @param secretSalt - The secret salt (optional, generated if not provided).
* @param max - The desired maximum range.
* @returns An object containing the generated number, serverSeed, secretSalt, nonce, clientSeed, and publicHash used.
*/
async generateProvably(clientSeed, serverSeed, secretSalt, max = 10000000) {
const bitcoinHash = await this.getLatestBitcoinBlockHash();
const nonce = this.generateRandomNonce(); // Generating random nonce
// Use provided values or generate new ones
const finalServerSeed = serverSeed || this.generateSecureRandomString(64);
const finalSecretSalt = secretSalt || this.generateSecureRandomString(8);
// Generating public hash
const publicHash = this.generatePublicHash(finalServerSeed, finalSecretSalt);
const combinedInput = `${bitcoinHash}:${clientSeed}:${finalServerSeed}:${nonce}`;
const randomNumber = crypto.createHmac('sha256', finalSecretSalt).update(combinedInput).digest('hex');
const decimalValue = parseInt(randomNumber.slice(0, 8), 16);
return {
randomNumber: (decimalValue % max) + 1,
serverSeed: finalServerSeed,
secretSalt: finalSecretSalt,
nonce,
clientSeed,
publicHash,
bitcoinHash
};
}
/**
* Generates a random nonce with extra entropy for maximum randomness.
* @returns A random integer.
*/
generateRandomNonce() {
const timeComponent = Date.now(); // Current time
// Get additional entropy from the system
const systemEntropy = [
process.pid, // Process ID
os.uptime(), // System uptime
os.freemem(), // Free memory
os.loadavg()[0] // System load average
].join('-');
const randomBuffer1 = crypto.randomBytes(8); // Generate 8 bytes of additional entropy
const randomBuffer2 = crypto.randomBytes(8);
const randomValue1 = randomBuffer1.readUInt32BE(0);
const randomValue2 = randomBuffer2.readUInt32BE(0);
// Generate a UUID v4 to add more entropy
const uuid = (0, uuid_1.v4)();
// Combine all entropy sources
const combinedValue = `${timeComponent}-${systemEntropy}-${randomValue1}-${randomValue2}-${uuid}`;
// Apply a final hash to ensure uniformity
const hash = crypto.createHash('sha256').update(combinedValue).digest('hex');
// Convert to a number
return parseInt(hash.slice(0, 8), 16);
}
/**
* Retrieves the latest Bitcoin blockchain block hash using Blockchain.info API.
* @returns The latest block hash.
*/
async getLatestBitcoinBlockHash() {
const response = await axios_1.default.get('https://blockchain.info/latestblock');
return response.data.hash;
}
}
exports.ProvablyFair = ProvablyFair;