Как работает provably fair в Mines

Вот как играть в Mines на Cloudbet и как проверить, что расположение мин в каждом раунде честно (provably fair).

Как играть в Mines

Каждый раунд Mines на Cloudbet использует одну и ту же основу provably fair — сид сервера, сид клиента, nonce и SHA3-256/SHAKE256 — подробно описанную на нашей главной странице «Как работает provably fair». Эта страница описывает то, что специфично для Mines: как эта последовательность случайных чисел становится реальной сеткой мин, против которой вы играете.

Как случайные числа превращаются в сетку мин

В начале игры вы выбираете количество мин, которые будут размещены, — назовем это число N. Сетку Mines 5×5 можно развернуть в один ряд из 25 позиций, каждая со своим индексом, начиная с 1:

Диаграмма, показывающая, как доказуемо честная сетка Mines от Cloudbet — плитки 5×5, обозначенные от A до Y — разворачивается в один ряд из 25 пронумерованных позиций для честной генерации чисел.

Мы выбираем число из случайной последовательности (сгенерированной так же, как описано на странице Provably Fair), чтобы определить позицию первой мины:

Практический пример доказуемо честного алгоритма Mines от Cloudbet: случайное число выбирает позицию первой мины среди всех 25 плиток.

Если нужно разместить еще мины, мы вытягиваем еще одно случайное число и повторяем процесс — но теперь остается выбирать только из 24 оставшихся позиций:

Практический пример, показывающий, как доказуемо честный алгоритм Mines выбирает следующую мину из 24 оставшихся позиций после того, как первая мина удалена из пула.

Это повторяется, пока не будут размещены все мины. Затем полная сетка восстанавливается и держится в секрете до конца раунда:

Итоговая доказуемо честная сетка Mines, восстановленная из развёрнутого ряда после того, как каждая позиция мины была случайно и проверяемо размещена.

Технические детали: выбор позиции каждой мины

Перед каждым выбором мы знаем P — количество свободных от мин позиций, из которых можно выбирать. P начинается с 25 и уменьшается на 1 каждый раз, когда размещается мина.

Для каждой мины из вывода SHAKE256 вытягивается случайное 4-байтное (32-битное) число в диапазоне от 0 до 4 294 967 295. Чтобы честно выбрать среди P позиций, мы применяем выборку с отбраковкой (общая концепция описана на странице Как работает provably fair), а затем берем результат по модулю P, чтобы получить индекс позиции:

// Pseudo Code
const placementIndex = randomNumber % P;

Блок-схема доказуемо честного расчёта Mines от Cloudbet: случайные байты и rejection sampling определяют индекс позиции каждой мины.

Если остаются еще мины, выбранная позиция удаляется из списка, P уменьшается на 1, и процесс повторяется.

Игровой процесс

Итоговая сетка с расположением мин строится и скрывается на сервере до того, как вы сделаете ход — ее нельзя незаметно изменить впоследствии благодаря уже переданному вам Коммитменту. Каждая выбранная вами клетка просто сверяется с этой скрытой сеткой.

Код для независимой проверки

Эта программа точно соответствует реализации Mines на Cloudbet. Введите свои Сид сервера, Сид клиента, Nonce и количество мин, установите единственную зависимость и запустите ее самостоятельно, чтобы воспроизвести результат своей игры.

Примечание: чтобы раскрыть Сид сервера завершенной игры на Cloudbet, вы должны уже начать новый раунд (что переводит на новую пару сидов).

Зависимости:

npm install js-sha3

Код:

// If you are running this yourself, then you can input your own data here
// You can then compare:
// 1) Cloudbet's provided Commitment to the Commitment outputted here. This proves that the Server Seed was not altered during gameplay.
// 2) Cloudbet's provided game outcome to the game outcome outputted here. This proves that your game's outcome was determined solely by the pre-game seeds and was not altered afterwards.
let myServerSeed      = 'your_server_seed'; // ✍️YOUR_INPUT✍️: Enter your (unhashed) Server Seed here
let myClientSeed      = 'your_client_seed'; // ✍️YOUR_INPUT✍️: Enter your Client Seed here
let myNonce           = 1;                  // ✍️YOUR_INPUT✍️: Enter your Nonce here
let myNumberOfMines   = 3;                  // ✍️YOUR_INPUT✍️: Enter your number of mines here
const usingCustomData = false;              // Change this to true if you are using your own data

// Load libraries
const crypto = require('crypto');
const { sha3_256, shake256 } = require('js-sha3');

// Step 1: Generate a Server Seed and Commitment
// And generate a Client Seed - either by player input or by the player's browser
function generateServerSeed() {
  let seed = crypto.randomBytes(32).toString('hex');
  if (usingCustomData) { seed = myServerSeed; }
  const commitment = sha3_256(seed);
  return { 'serverSeed': seed, 'commitment': commitment };
}
function getClientSeed() {
  let seed = crypto.randomBytes(32).toString('hex');
  if (usingCustomData) { seed = myClientSeed; }
  return seed;
}

// Step 2: Create a Round Signature with SHA3 using a Server Seed, Client Seed, and Nonce
function createRoundSignature(serverSeed, clientSeed, nonce) {
  return sha3_256(`${serverSeed}:${clientSeed}:${nonce}`);
}

// Step 3: Create a SHAKE256 stream generator from a Round Signature.
// To simplify this example program, this function doesn't create a stream, but instead a sequence of custom length.
function* createRandomNumberSequence(numbersToGenerate, roundSignature) {
  const numOfBytes = 4 * numbersToGenerate;           // 4 bytes (32 bits) per random number we generate
  const byteStream = shake256.create(8 * numOfBytes); // shaker256 takes bits as input
  byteStream.update(roundSignature);                  // Use the Round Signature to seed the generator
  const buf = Buffer.from(byteStream.digest());       // The Buffer class lets us package every 4 bytes into one 32-bit integer
  for (let i = 0; i < numbersToGenerate; i++) {
    yield buf.readUInt32BE(i * 4);                    // Read buf in 4-byte steps
  }
  throw new Error("No more numbers in the random number sequence! Fix: make 'numbersToGenerate' larger.");
}

// Step 4: Generate the mine locations
function pickUniquePositions(mineCount, totalTiles, rng) {
  const available = Array.from({ length: totalTiles }, (_, i) => i);
  let result = [];

  for (let i = 0; i < mineCount; ++i) {
    const index = sample(available.length, rng);

    result.push(available[index]);
    available.splice(index, 1);         // Remove chosen position from list of available positions
  }
  result.sort((a, b) => a - b);         // Sort the mine positions from smallest to largest
  result = result.map(num => num + 1);  // Increment positions by one so that they start with 1, 2, 3, ...
  return result;
}
// Rejection sampling for fair results
function sample(P, rng) {
  const maxAcceptable = Math.floor(0x100000000 / P) * P;

  // Rejection sampling
  let rand;
  do {
    rand = rng.next().value;
  } while (rand >= maxAcceptable);
  // Modulo operation
  const sampled_number = rand % P;

  return sampled_number;
}

// Example usage
(function main() {
  const gridSize = 25;
  let mineCount = 3;
  let nonce = 1;
  if (usingCustomData) { mineCount = myNumberOfMines; nonce = myNonce; }

  const { serverSeed, commitment } = generateServerSeed();
  const clientSeed = getClientSeed();

  const roundSignature = createRoundSignature(serverSeed, clientSeed, nonce);
  const numbersToGenerate = 24 * 2; // There are max. 24 mines, however due to rejection sampling we may need more random numbers
  const rng = createRandomNumberSequence(numbersToGenerate, roundSignature);

  const minePositions = pickUniquePositions(mineCount, gridSize, rng);

  console.log('📝 Commitment (shown before the game):', commitment);
  console.log('🔒 Server Seed (revealed after the game):', serverSeed);
  console.log('🎲 Client Seed:', clientSeed);
  console.log('🔢 Nonce:', nonce);
  console.log('🔁 Round Signature (can be verified after the game):', roundSignature);
  console.log('💣 Mine Positions (1 = top-left square, 5 = top-right square, 25 = bottom-right square):', minePositions);
})();

Испытайте удачу прямо сейчас: сыграйте в Mines

Хотите увидеть все вживую? Сыграйте раунд Cloudbet Mines — а затем проверьте свои результаты с помощью калькулятора provably fair. Это займет считаные секунды. Никаких технических знаний не требуется.

Вы увидите весь процесс в действии, сможете изучить свои сиды и понаблюдать, как инструмент восстанавливает ваш результат с нуля.

Разборы provably fair по играм:

  1. Provably fair Pump
  2. Provably fair Plinko
  3. Provably fair Dice
  4. Provably fair Dice: Golden Ticket
  5. Provably fair Limbo
  6. Provably fair Roulette
  7. Provably fair Keno