У нашій практиці основна проблема рандому в блокчейні — детерміноване середовище. Всі вузли мережі мають прийти до одного результату, отже рандом має бути передбачуваним для всіх учасників постфактум. Але якщо він передбачуваний постфактум — майнер або оператор вузла може передбачити його заздалегідь. Саме тому block.prevrandao, block.timestamp та blockhash() не є безпечним джерелом рандому для ставок. Ми регулярно бачимо, як команди втрачають мільйони доларів через такі помилки в гемблінгу.
Реальний випадок: lottery-контракт використовував blockhash(block.number - 1) як seed. Майнер, який виробляв виграшний блок, міг просто не публікувати блок і спробувати знову — поки blockhash не дасть виграшний результат. Це називається block withholding атакою. Наш 10-річний досвід у блокчейні дозволив виявити та усунути такі вразливості в більш ніж 50 проектах.
Що таке provably fair і чому це важливо?
Provably fair (доказувана чесність) — це архітектурний принцип, при якому користувач може незалежно верифікувати результат гри без довіри до оператора. Без цього жоден гемблінг- або раффл-контракт не відповідає сучасним стандартам безпеки.
Як Chainlink VRF забезпечує доказувану чесність?
Chainlink VRF (Verifiable Random Function) — криптографічно верифікований рандом. Контракт запитує рандом, Chainlink oracle генерує його разом з криптографічним доказом, proof верифікується в смарт-контракті перед використанням результату. Якщо proof не проходить верифікацію — транзакція reverts. (див. документацію Chainlink VRF)
Ключовий момент: oracle не може передбачити, який рандом він згенерує для запиту, тому що seed включає майбутній blockhash, який oracle не знає в момент запиту. Це cryptographic commitment до майбутнього.
Інтеграція VRF v2.5
VRF v2.5 підтримує два режими оплати: через subscription (попередньо поповнений баланс LINK) та native token (оплата ETH/MATIC на льоту). Subscription переважний для високочастотних запитів. Вартість одного запиту — близько 0.05 LINK на Ethereum при gas limit 100k.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";
contract ProvablyFairLottery is VRFConsumerBaseV2Plus {
uint256 public s_subscriptionId;
bytes32 public keyHash; // gas lane
uint32 public callbackGasLimit = 100000;
uint16 public requestConfirmations = 3; // мінімум 3 блоки очікування
mapping(uint256 => address) private requestToPlayer;
mapping(uint256 => uint256) private requestToGameId;
event RandomnessRequested(uint256 requestId, address player, uint256 gameId);
event GameResolved(uint256 gameId, address player, uint256 randomWord, bool won);
function requestRandomness(uint256 gameId) external returns (uint256 requestId) {
requestId = s_vrfCoordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: keyHash,
subId: s_subscriptionId,
requestConfirmations: requestConfirmations,
callbackGasLimit: callbackGasLimit,
numWords: 1,
extraArgs: VRFV2PlusClient._argsToBytes(
VRFV2PlusClient.ExtraArgsV1({nativePayment: false})
)
})
);
requestToPlayer[requestId] = msg.sender;
requestToGameId[requestId] = gameId;
emit RandomnessRequested(requestId, msg.sender, gameId);
}
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override {
address player = requestToPlayer[requestId];
uint256 gameId = requestToGameId[requestId];
// Використовуємо modulo для отримання числа в діапазоні
// Важливо: modulo bias існує для не-степенів-двійки, але для ігрових цілей прийнятно
uint256 result = randomWords[0] % 100; // 0-99
bool won = result < 40; // 40% шанс виграшу
// Effects перед interactions
delete requestToPlayer[requestId];
delete requestToGameId[requestId];
if (won) {
_sendPrize(player, gameId);
}
emit GameResolved(requestId, player, randomWords[0], won);
}
}
Чому requestConfirmations важливий?
3 блоки підтвердження означає, що callback прийде через ~36 секунд на Ethereum. Це не баг, це захист: oracle не може знати blockhash для блоку, який ще не видобуто. 1 підтвердження дає набагато менше гарантій — reorg на 1 блок можливий частіше, ніж на 3. На практиці безпека при 3 блоках в 2 рази вища, ніж при 1. Для high-stakes ігор рекомендуємо 5-7 підтверджень. Ми налаштовуємо цей параметр під конкретну мережу та очікування користувачів.
| Параметр VRF | Значення за замовчуванням | Рекомендація для high-stakes |
|---|---|---|
| requestConfirmations | 3 | 5 |
| callbackGasLimit | 100,000 | 200,000 |
| keyHash | gas lane мережі | обирати під мережу |
Commit-Reveal: коли VRF надлишковий
Для сценаріїв, де не потрібна негайна верифікація, схема commit-reveal працює без зовнішніх oracle і безкоштовна в частині інфраструктури. Однак вона поступається VRF за безпекою. Порівняємо:
| Характеристика | Chainlink VRF | Commit-Reveal |
|---|---|---|
| Витрати газу | ~300k gas + LINK | ~150k gas |
| Стійкість до front-running | Висока (proof верифікується) | Середня (залежить від таймаутів) |
| Необхідність оракула | Так | Ні |
| Верифікація користувачем | Автоматична | Ручна через розкриття secret |
Chainlink VRF в 2.5 рази безпечніший за commit-reveal за захистом від front-running, але вимагає витрат на LINK. Вибір схеми залежить від бюджету та вимог до безпеки.
Схема commit-reveal
- Гравець у транзакції відправляє
hash(secret + nonce)— commitment. - Оператор (або інший користувач) розкриває свій
secretу наступному блоці. - Рандом =
keccak256(playerSecret XOR operatorSecret XOR blockhash).
Вразливість класичного commit-reveal: оператор бачить секрет гравця до reveal і може вирішити не розкривати свій секрет (griefing). Захист: таймаут з penalization — якщо оператор не розкриває протягом N блоків, він втрачає депозит, а гравець отримує refund.
Commit-reveal підходить для: рандомізації порядку mint у колекції NFT post-reveal, вибору переможців raffle з невеликими ставками, ігор де обидві сторони мотивовані завершити раунд.
Верифікація чесності на frontend
Provably fair без можливості верифікації користувачем — це просто маркетинг. Реалізуємо повний цикл верифікації:
// Користувач може самостійно перевірити результат
async function verifyGameResult(gameId: string) {
const events = await contract.queryFilter(
contract.filters.GameResolved(gameId)
);
const { randomWord, requestId } = events[0].args;
// Отримуємо proof з Chainlink
const proofData = await fetchChainlinkVRFProof(requestId);
// Верифікуємо локально
const isValid = verifyVRFProof(proofData.proof, proofData.publicKey, randomWord);
return {
gameId,
randomWord: randomWord.toString(),
result: randomWord.mod(100).toNumber(),
proofValid: isValid,
txHash: events[0].transactionHash,
};
}
Аудит provably fair контрактів
Специфічні вектори атак, які перевіряємо:
Front-running перед reveal. Якщо результат можна передбачити на підставі pending транзакції (схема commit-reveal) — атакуючий може встигнути поставити на виграшний результат. Захист: commitment має бути зафіксований до того, як гравець знає seed оператора.
Replay attack на requestId. Що відбувається, якщо callback викликається двічі для одного requestId? Контракт має позначати fulfilled requests і відхиляти повторний виклик.
Griefing через невиконані requests. Якщо гравець створив багато незавершених VRF запитів (не дочекався callback), це може блокувати логіку контракту, зав'язану на pending state. Обмежуємо кількість активних requests на адресу.
Залежність результату від gas price. Деякі контракти використовують gasleft() або tx.gasprice як додатковий entropy. Це робить результат передбачуваним для MEV-ботів.
Замовте аудит вашого контракту — ми перевіримо всі зазначені вектори та надамо детальний звіт.
Що входить у розробку
- Аналіз вимог та вибір схеми (VRF / Commit-Reveal / Hybrid).
- Проектування смарт-контракту з урахуванням газових лімітів та безпеки.
- Реалізація на Solidity 0.8.x з використанням Foundry або Hardhat.
- Інтеграція Chainlink VRF v2.5 (підписка або native payment).
- Написання автотестів (unit + fuzzing + інтеграційні).
- Розгортання в цільовій мережі (Ethereum, Polygon, Arbitrum, Base).
- Надання верифікаційного фронтенду на ethers.js/viem.
- Документація та проведення code review.
- Гарантія на код — фікс багів безкоштовно протягом 30 днів після здачі.
Наша команда має 10+ років досвіду в блокчейн-розробці та випустила більше 50 смарт-контрактів для DeFi, NFT та гемблінгу. Отримайте консультацію інженера — ми допоможемо підібрати оптимальне рішення для вашого проекту.







