Использовать block.timestamp, block.prevrandao или хэш предыдущего блока для генерации случайных чисел — рискованно: майнер или валидатор может влиять на эти значения. Несколько лет назад известная лотерея потеряла $4M из-за манипуляции блочным хэшем. Случайные числа на блокчейне — нетривиальная задача, требующая индивидуального подхода. Мы разрабатываем системы RNG под ключ, оцениваем угрозы и предлагаем оптимальное решение. Наш опыт насчитывает более 50 успешных проектов в этой области.
Почему on-chain рандом сложен?
Блокчейн детерминирован. Каждый узел должен прийти к одному результату, выполняя одни и те же операции. Это фундаментально противоречит случайности: если результат предсказуем — он не случаен. Любой источник, который виден в блокчейне до фиксации результата, может быть использован атакующим.
Validator bias — валидатор на Ethereum видит block.prevrandao (RANDAO reveal) до публикации блока. Если результат невыгоден — он может пропустить свой слот (слот пропускается, результат меняется). Стоимость атаки = потерянное вознаграждение за слот (~0.01 ETH). Если ставка в лотерее > 0.01 ETH — атака рациональна.
Chainlink VRF: стандарт для большинства случаев
Chainlink VRF (Verifiable Random Function) — наиболее проверенное решение для NFT-минта, лотерей, игровой механики. Chainlink VRF работает через оракульную сеть:
- Контракт запрашивает случайное число, отправляя LINK.
- Chainlink-нода генерирует случайное число и криптографическое доказательство.
- Доказательство верифицируется on-chain перед использованием числа.
// VRF V2.5 (актуальная версия)
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 Lottery is VRFConsumerBaseV2Plus {
uint256 public s_subscriptionId;
bytes32 public keyHash; // gas lane
uint32 public callbackGasLimit = 200_000;
uint16 public requestConfirmations = 3;
mapping(uint256 => address) public requestToPlayer;
function requestRandomWinner() 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;
}
function fulfillRandomWords(
uint256 requestId,
uint256[] calldata randomWords
) internal override {
address player = requestToPlayer[requestId];
uint256 result = randomWords[0] % totalTickets;
_declareWinner(player, result);
}
}
requestConfirmations: 3 — ждём 3 блока подтверждения перед генерацией. Это усложняет reorg-атаки на request.
Ограничения VRF: latency 1-3 блока (15-45 секунд на mainnet), стоимость LINK на каждый запрос (0.25-2 LINK в зависимости от сети), необходимость subscription менеджмента. Для high-frequency gameplays (каждый ход в игре требует рандома) — слишком дорого и медленно.
Commit-reveal: рандом без оракула
Для случаев без доступа к Chainlink или при необходимости минимизировать затраты — commit-reveal схема:
Слабое место commit-reveal: последний, кто раскрывает secret, видит финальный результат до публикации. Он может выбрать не раскрывать (griefing) или раскрыть только если результат выгоден. Mitigation: штраф за не-reveal (bond при commit, который сгорает при неявке).
Пример реализации с бондами
mapping(address => bytes32) public commits;
mapping(address => uint256) public bonds;
uint256 public bondAmount = 0.1 ether;
function commit(bytes32 commitment) external payable {
require(msg.value == bondAmount);
commits[msg.sender] = commitment;
bonds[msg.sender] = msg.value;
}
function reveal(uint256 secret) external {
require(keccak256(abi.encode(secret, msg.sender)) == commits[msg.sender]);
// process secret
payable(msg.sender).transfer(bonds[msg.sender]); // возврат залога
delete bonds[msg.sender];
}
function claimBond(address participant) external {
require(bonds[participant] > 0);
// проверка, что участник не раскрыл в срок
// перевести залог штрафующему
}
RANDAO: нативный Ethereum рандом после Merge
После перехода на PoS Ethereum предоставляет block.prevrandao — агрегированный RANDAO reveal от валидаторов. Это лучше, чем старый block.difficulty, но имеет описанную выше проблему validator bias.
Для некритичных применений (косметика в игре, порядок в очереди, некрупные лотереи) — block.prevrandao достаточен и бесплатен:
uint256 random = uint256(keccak256(abi.encode(
block.prevrandao,
block.timestamp,
msg.sender,
nonce++
)));
Добавление msg.sender и nonce увеличивает entropy и усложняет предсказание для конкретного пользователя, хотя не устраняет validator bias.
Как выбрать подходящий метод RNG?
| Применение | Ставка / ценность | Рекомендация |
|---|---|---|
| NFT минт (whitelist рандом) | Высокая | Chainlink VRF |
| Лотерея с крупным призом | Высокая | Chainlink VRF + requestConfirmations: 5+ |
| Внутриигровой рандом (предметы) | Средняя | Commit-reveal или Chainlink VRF |
| Порядок в очереди | Низкая | block.prevrandao |
| PvP матчмейкинг | Низкая | block.prevrandao + nonce |
Сравнение методов
| Метод | Безопасность | Скорость | Стоимость | Комплексность |
|---|---|---|---|---|
| Chainlink VRF | Высокая | 1-3 блока | 0.25-2 LINK на запрос | Средняя |
| Commit-reveal | Средняя (зависит от механик) | 2+ раунда | Только газ | Высокая |
| RANDAO | Низкая (validator bias) | 0 блоков | Бесплатно | Низкая |
| Гибрид (off-chain + on-chain) | Высокая | 0 блоков | Комбинация | Высокая |
Гибридные решения
Для gamefi-проектов, где требуется быстрый рандом с высокой throughput, используем off-chain VRF с on-chain commitment:
- Backend генерирует seed через Chainlink VRF заранее.
- Hash seed публикуется on-chain (commitment).
- При каждом игровом событии — используем HMAC(seed, event_id) как рандом.
- После сессии — раскрываем seed, пользователи могут верифицировать все результаты.
Это даёт instant response на каждое действие и полную верифицируемость постфактум. Гибридное решение экономит до 90% газа по сравнению с прямыми VRF запросами, а при высокочастотном использовании — в 5-10 раз дешевле.
Что вы получаете
- Анализ угроз и выбор оптимальной схемы.
- Интегрированный смарт-контракт с покрытием юнит-тестами (более 200 тестов).
- Документацию по развертыванию и использованию.
- Поддержку при деплое и мониторинге.
- Обучение команды (опционально).
Гарантируем прозрачность — все решения верифицируемы на блокчейне. Свяжитесь с нами для оценки вашего проекта.
Процесс работы
Аналитика. Определяем threat model: кто может атаковать? Какова максимальная выгода от манипуляции? Какая latency допустима? Есть ли доступ к Chainlink на целевом чейне.
Разработка и тестирование. VRFConsumer тестируем через VRFCoordinatorV2_5Mock из пакета Chainlink — позволяет симулировать fulfillment в unit-тестах без реальной оракульной сети. Commit-reveal тестируем на сценарии griefing и last-revealer атак.
Деплой. Для Chainlink VRF — создаём subscription, фондируем LINK, добавляем consumer. Настраиваем мониторинг баланса subscription.
Ориентиры по срокам
Интеграция Chainlink VRF в существующий контраст: 1-2 дня. Система RNG с commit-reveal и anti-griefing: 1-2 дня. Гибридный off-chain VRF с on-chain commitment и верификацией: 3-5 дней.
Получите консультацию — оценим ваш проект и предложим оптимальное решение под ключ.







