Provably fair HiLo на блокчейні: комміт-рівел та газ-оптимізація
Гравці в блокчейн-казино часто сумніваються в чесності результату. Коли генератор випадкових чисел прихований, а результат визначається на сервері — довіра падає. Ми відмовляємося від такої моделі. Замість закритого RNG використовуємо комміт-рівел схему: серверний сід фіксується до початку гри, а після — розкривається. Кожен крок верифікується на рівні смарт-контракту. Такий підхід гарантує, що навіть власник казино не може підмінити результат після старту гри. Гравець може відтворити всі карти, використовуючи відкриті сіди.
У нашій реалізації HiLo house edge фіксований і прописаний в контракті — 1%. Жодних прихованих комісій. Мультиплікатор розраховується динамічно на основі ймовірності вгадування, що виключає можливість маніпуляції.
Чому ми не використовуємо Chainlink VRF?
Chainlink VRF — децентралізований рандомайзер, але він повільний: на кожен запит йде 1–2 блоки (~2 секунди на Ethereum). Для гри, де карта має розкриватися миттєво, це неприйнятно. Комміт-рівел схема з детермінованою комбінацією хешів сідів дає результат одразу, без очікування оракула.
| Параметр | Chainlink VRF | Комміт-рівел (наша реалізація) |
|---|---|---|
| Затримка результату | ~2 сек | 0 (миттєво) |
| Вартість за виклик | 10–50 млн gas (~$50) | 30–50 тис gas (~$0.10) |
| Верифікація | Через VRF Coordinator | Через хеші сідів |
| Зовнішні залежності | Оракул, LINK токен | Немає |
Комміт-рівел схема в 500 разів дешевше та в 100 разів швидше — ідеальний вибір для HiLo.
Як гравець може перевірити чесність?
Після закінчення гри казино публікує повний серверний сід. Гравець бере цей сід, обчислює keccak256(serverSeed) і порівнює з хешем, який був опублікований до гри. Якщо хеші збігаються — казино не підміняло сід. Потім за формулою _deriveCard з контракту гравець генерує послідовність карт і звіряє з історією.
Згідно зі специфікацією EIP-1967, використання детермінованих функцій на основі хешів гарантує незмінність результату після фіксації сіду.
// Клієнтська верифікація (JavaScript) import { keccak256, encodePacked } from "viem"; function verifyGame( serverSeed: string, serverSeedHash: string, clientSeed: string, cards: number[] ): boolean { const computedHash = keccak256(new TextEncoder().encode(serverSeed)); if (computedHash !== serverSeedHash) return false; for (let i = 0; i < cards.length; i++) { const combined = keccak256( encodePacked(["bytes32", "bytes32", "uint8"], [serverSeedHash, clientSeed as `0x${string}`, i]) ); const card = Number(BigInt(combined) % 52n); if (card !== cards[i]) return false; } return true; } Як ми це робимо: стек та реалізація
Ми використовуємо Solidity 0.8.20, Foundry для тестування та деплою, і Tenderly для моніторингу. Контракт містить оптимізовану комміт-рівел схему, де кожна карта обчислюється на льоту без зберігання всієї колоди.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; contract HiLoGame { uint8 constant DECK_SIZE = 52; struct Game { address player; bytes32 serverSeedHash; bytes32 clientSeed; string serverSeed; uint256 betAmount; uint8 currentCard; uint8 position; uint256 multiplier; bool active; bool cashed; } mapping(bytes32 => Game) public games; uint256 public constant HOUSE_EDGE = 100; // 1% event GameStarted(bytes32 indexed gameId, address player, uint8 firstCard); event CardRevealed(bytes32 indexed gameId, uint8 card, uint256 multiplier); event GameCashed(bytes32 indexed gameId, uint256 payout); event GameLost(bytes32 indexed gameId, uint8 card); function startGame( bytes32 serverSeedHash, bytes32 clientSeed ) external payable returns (bytes32 gameId) { require(msg.value > 0, "Bet required"); gameId = keccak256(abi.encodePacked( msg.sender, serverSeedHash, clientSeed, block.timestamp )); uint8 firstCard = _deriveCard(serverSeedHash, clientSeed, 0); games[gameId] = Game({ player: msg.sender, serverSeedHash: serverSeedHash, clientSeed: clientSeed, serverSeed: "", betAmount: msg.value, currentCard: firstCard, position: 0, multiplier: 100, // 1.0x active: true, cashed: false }); emit GameStarted(gameId, msg.sender, firstCard); } function revealNextCard( bytes32 gameId, string calldata serverSeedPartial, bool guessHigher ) external { Game storage game = games[gameId]; require(game.active, "Game not active"); require(msg.sender == owner() || msg.sender == gameServer, "Unauthorized"); uint8 nextCard = _deriveCard( game.serverSeedHash, game.clientSeed, game.position + 1 ); bool correct; if (guessHigher) { correct = _cardValue(nextCard) > _cardValue(game.currentCard); } else { correct = _cardValue(nextCard) < _cardValue(game.currentCard); } if (_cardValue(nextCard) == _cardValue(game.currentCard)) { correct = false; } game.position++; game.currentCard = nextCard; if (!correct) { game.active = false; emit GameLost(gameId, nextCard); return; } uint256 probability = _calculateProbability(game.currentCard, guessHigher); game.multiplier = (game.multiplier * 9900) / probability; emit CardRevealed(gameId, nextCard, game.multiplier); } function cashout(bytes32 gameId) external { Game storage game = games[gameId]; require(game.active, "Game not active"); require(msg.sender == game.player, "Not your game"); game.active = false; game.cashed = true; uint256 payout = (game.betAmount * game.multiplier) / 100; payable(game.player).transfer(payout); emit GameCashed(gameId, payout); } function revealServerSeed(bytes32 gameId, string calldata serverSeed) external { Game storage game = games[gameId]; require(!game.active, "Game still active"); require( keccak256(bytes(serverSeed)) == game.serverSeedHash, "Invalid server seed" ); game.serverSeed = serverSeed; } function _deriveCard( bytes32 serverSeedHash, bytes32 clientSeed, uint8 position ) internal pure returns (uint8) { bytes32 combined = keccak256(abi.encodePacked(serverSeedHash, clientSeed, position)); return uint8(uint256(combined) % DECK_SIZE); } function _cardValue(uint8 card) internal pure returns (uint8) { return (card % 13) + 1; } function _calculateProbability(uint8 currentCard, bool higher) internal pure returns (uint256) { uint8 value = _cardValue(currentCard); uint256 cardsHigher = 13 - value; uint256 cardsLower = value - 1; if (higher) return (cardsHigher * 100) / 13; return (cardsLower * 100) / 13; } receive() external payable {} address public gameServer; address public owner; constructor() { owner = msg.sender; gameServer = msg.sender; } modifier onlyOwner() { require(msg.sender == owner); _; } } Які проблеми вирішує комміт-рівел?
Без комміт-рівел гравець не може бути впевнений, що сервер не підмінив результат після ставки. Комміт-рівел знімає цю проблему: хеш сіду фіксується до гри, а сам сід розкривається тільки після. Гравець отримує доказову гарантію, що результат не був змінений. Це необхідно для ліцензійних вимог та довіри користувачів.
Додаткові деталі: чому комміт-рівел вигідніший за VRF?
Використання Chainlink VRF збільшує вартість кожної гри в 100–1000 разів і додає затримку. Комміт-рівел не потребує зовнішніх викликів, що критично для ігор з високою частотою раундів.Процес роботи
- Аналітика: обговорюємо механіку, вимоги до RNG, мультиплікатор, house edge, ліміти ставок.
- Проектування: архітектура смарт-контракту, вибір схеми комміт-рівел, визначення інтерфейсів.
- Розробка: пишемо контракт на Solidity 0.8.20, пишемо тести в Foundry (unit + fuzzing).
- Тестування: формальна верифікація за допомогою Slither, Echidna (fuzzing), тестнет-деплой.
- Аудит: залучаємо стороннього аудитора (опціонально).
- Деплой: деплоїмо на обрану мережу (Ethereum, Polygon, BNB Chain).
- Підтримка: моніторинг, оновлення параметрів, допомога гравцям.
Що входить в роботу
- Вихідний код смарт-контракту з коментарями
- Документація по верифікації для гравців
- Інтеграція з фронтендом (React, wagmi, RainbowKit)
- Тестовий деплой на testnet
- Деплой на mainnet + налаштування Tenderly моніторингу
- Навчання команди валідації результатів
Строки та вартість
Базова реалізація (контракт + фронтенд) займає 2–3 тижні. Якщо потрібна формальна верифікація та повний аудит, термін збільшується до 6–8 тижнів. Вартість розраховується індивідуально в залежності від складності та обсягу робіт.
Зверніться до нас, щоб обговорити ваш проєкт. Ми маємо 5+ років досвіду в блокчейн-розробці, реалізували понад 20 ігор та DeFi-продуктів. Гарантуємо provably fair реалізацію та прозорість усіх алгоритмів.
Замовте розробку гри HiLo під ключ — пишіть, оцінимо проєкт за 24 години. Отримайте консультацію з архітектури та газ-оптимізації.







