النزاهة القابلة للإثبات
أدخل تفاصيل اللعبة للتحقق من نتائجك
الكود
اختر لعبة من الأعلى لفحص الكود المصدري الذي ينتج نتائجها.
import { createHmac } from 'crypto';
const UINT32_MAX = 0x100000000; // 2^32
/**
* Generates an infinite deterministic stream of cryptographically secure
* 32-bit unsigned integers derived from HMAC-SHA256.
*
* @param key - Secret cryptographic key (server seed). Must remain private until revealed.
* @param message - Public input string (e.g., client seed, nonce, or domain label).
*
* @remarks
* - Uses HMAC-SHA256 as a pseudorandom function (PRF) to produce a reproducible
* and verifiable sequence of numbers.
* - `step` ensures each 32-bit block is derived from a unique input.
* - Output is split into 32-bit chunks, suitable for unbiased integer or bit-level
* extraction.
* - Bits are consumed starting from the Most Significant Bit (MSB) to preserve
* full numeric precision and maximum entropy.
* - Fully deterministic: anyone with the same key and message can verify the results.
*/
function* getUint32Stream(key: string, message: string): Iterator<number> {
let step = 0;
while (true) {
const hmac = createHmac('sha256', key);
hmac.update(`${message}:${step}`);
const hash = hmac.digest(); // 32 bytes (256 bits)
for (let i = 0; i < hash.length; i += 4) {
yield (
(hash[i] << 24) |
(hash[i + 1] << 16) |
(hash[i + 2] << 8) |
hash[i + 3]
) >>> 0; // force unsigned 32-bit
}
step++;
}
}
/**
* Converts a 32-bit random number into an unbiased integer within [0, range).
*
* @param uint32StreamFactory - Function that returns a stream of 32-bit integers.
* @param range - The desired upper bound (exclusive) for the output.
*
* @remarks
* - Uses rejection sampling to avoid modulo bias.
* - Every integer within the range is equally likely.
* - Critical for fair gameplay and provably fair verification.
*/
function getUnbiasedInt(uint32StreamFactory: () => Iterator<number>, range: number) {
if (!Number.isInteger(range) || range <= 0) {
throw new Error('Invalid range');
}
const maxAcceptable = UINT32_MAX - (UINT32_MAX % range);
const stream = uint32StreamFactory();
let next = stream.next();
while (!next.done) {
const value = next.value;
if (value < maxAcceptable) {
return value % range;
}
next = stream.next();
}
throw new Error('Failed to generate unbiased value: entropy stream exhausted');
}
function determineHouseBet(options: {
difficulty: 'easy' | 'medium' | 'hard' | 'expert' | 'master';
serverSeed: string;
clientSeed: string;
nonce: number;
round: number;
}) {
const towerConfig = {
easy: {
tileCount: 4,
fairValueProduces: 'lose-index',
},
medium: {
tileCount: 3,
fairValueProduces: 'lose-index',
},
hard: {
tileCount: 2,
fairValueProduces: 'win-index',
},
expert: {
tileCount: 3,
fairValueProduces: 'win-index',
},
master: {
tileCount: 4,
fairValueProduces: 'win-index',
},
};
const tileCount = towerConfig[options.difficulty].tileCount;
const fairValueProduces = towerConfig[options.difficulty].fairValueProduces;
const fairSelectedTileIndex = getUnbiasedInt(
() =>
getUint32Stream(
options.serverSeed,
`${options.clientSeed}:${options.nonce}:${options.round}:tower`
),
tileCount
);
const result: string[] = [];
for (let index = 0; index < tileCount; index++) {
switch (fairValueProduces) {
case 'win-index':
result.push(index === fairSelectedTileIndex ? '✅' : '❌');
break;
default:
result.push(index !== fairSelectedTileIndex ? '✅' : '❌');
}
}
return result;
}
for (let i = 1 ; i <= 8; i++) {
console.log(
determineHouseBet({
difficulty: 'master',
clientSeed: 'tower-test',
serverSeed: 'tower-test',
nonce: 0,
round: i,
})
)
}
