Demostrablemente justo

Ingrese los detalles del juego para verificar sus resultados

Código

Elige un juego de arriba para inspeccionar el código fuente que produce sus resultados.

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-SHA512.
 *
 * @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-SHA512 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.
 *   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('sha512', key);
    hmac.update(`${message}:${step}`);
    const hash = hmac.digest(); // 64 bytes (512 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 getUnbiasedIntFromStream(
  stream: Iterator<number>,
  range: number
) {
  if (!Number.isInteger(range) || range <= 0) {
    throw new Error('Invalid range');
  }

  const maxAcceptable = UINT32_MAX - (UINT32_MAX % range);

  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');
}

/**
 * Draws `count` unique items without replacement using a partial Fisher-Yates shuffle.
 *
 * @remarks
 * - This is mathematically equivalent to fully shuffling the array and taking the last `count` items.
 * - Only the final `count` shuffle steps are performed, which avoids unnecessary work.
 * - Because selection is without replacement, the same number cannot be drawn twice.
 */
function drawWithoutReplacement<T>(
  uint32StreamFactory: () => Iterator<number>,
  data: readonly T[],
  count: number
): T[] {
  if (!Number.isInteger(count) || count < 0 || count > data.length) {
    throw new Error('Invalid count');
  }

  const result = [...data];
  const stream = uint32StreamFactory();
  const n = result.length;

  for (let i = n - 1; i >= n - count; i--) {
    const j = getUnbiasedIntFromStream(stream, i + 1);
    [result[i], result[j]!] = [result[j]!, result[i]!];
  }

  return result.slice(n - count);
}

function determineResultDetails(
  options: {
    difficulty: 'classic' | 'low' | 'medium' | 'high' | 'extreme',
    /**
     * Constraints:
     * - Must contain between 1 and 10 numbers (inclusive).
     * - Each number must be an integer in the range [1, 40] and [1, 56] for extreme mode
     * - Numbers must be unique (no duplicates allowed).
     */
    selectedNumbers: number[],
    nonce: number,
    clientSeed: string,
    serverSeed: string,
  }) {
  const drawCount = 10;
  const difficultyBoardNumberCountMap = {
    classic: 40,
    low: 40,
    medium: 40,
    high: 40,
    extreme: 56,
  } as const;

  const boardNumberCount = difficultyBoardNumberCountMap[options.difficulty];
  const orderedNumbers = Array.from({ length: boardNumberCount }, (_, i) => i + 1);

  const uint32Generator = getUint32Stream(options.serverSeed, `${options.clientSeed}:${options.nonce}:keno`);
  const drawnNumbers = drawWithoutReplacement(() => uint32Generator, [...orderedNumbers], drawCount);

  const matches = options.selectedNumbers.filter((kenoNumber) =>
    drawnNumbers.includes(kenoNumber),
  );

  return {
    drawnNumbers,
    matches,
    boardNumberCount,
  };
}


console.log(
  determineResultDetails({
    difficulty: 'low',
    clientSeed: 'keno-test',
    serverSeed: 'keno-test',
    nonce: 0,
    selectedNumbers: [1, 2, 3, 4, 5],
  })
);