Небезопасная случайность и Chainlink VRF

В детерминированной машине нет «настоящей» случайности — а псевдослучайность здесь предсказуема.

Ончейн-«рандом» на основе данных блока (block.timestamp, blockhash, block.prevrandao) детерминирован и/или поддаётся влиянию, поэтому небезопасен для розыгрышей и лотерей с деньгами.

Почему блокчейн не умеет в случайность сам

EVM детерминирована: все валидаторы должны получить одинаковый результат, иначе консенсус невозможен. Значит, любая «случайная» величина обязана быть вычислимой из данных блока — а раз так, её может вычислить и кто угодно ещё. Использовать block.timestamp или blockhash как источник случайности для распределения призов — значит дать предсказуемый результат тому, кто эти данные тоже видит.

// УЯЗВИМО: предсказуемая «случайность»
function pickWinner() external view returns (uint) {
    // значения известны до/во время формирования блока
    return uint(keccak256(abi.encodePacked(block.timestamp, blockhash(block.number - 1)))) % players;
}

Две беды: предсказуемость и влияние

Во-первых, предсказуемость: участник может в той же транзакции вычислить «выигрышный» исход и участвовать только тогда, когда выигрывает (через контракт, который откатывает невыгодные попытки). Во-вторых, влияние: тот, кто формирует блок, в некоторых схемах способен слегка подстроить входные данные (например, выбрать, включать ли транзакцию), смещая исход в свою пользу. Оба эффекта — не теоретические: на «самодельном» рандоме регулярно теряют средства лотереи и NFT-минты.

Правильное решение: проверяемая случайность (VRF)

Chainlink VRF (Verifiable Random Function) даёт случайное число вместе с криптографическим доказательством, что оно сгенерировано честно и не подстроено. Схема двухшаговая: контракт запрашивает случайность, а оракул в следующей транзакции возвращает число с доказательством, которое контракт проверяет. Поскольку результат приходит позже и сопровождается доказательством, его нельзя предугадать в момент участия и нельзя подделать.

// БЕЗОПАСНЕЕ: запрос/ответ с доказательством
// 1) requestRandomWords() -- просим случайность
// 2) fulfillRandomWords(requestId, words) -- оракул отдаёт число + доказательство
//    контракт проверяет доказательство и только тогда выбирает победителя

Как работает под капотом

VRF использует пару ключей оракула: по приватному ключу и входу-сидy вычисляется число и доказательство; контракт публичным ключом проверяет, что число соответствует входу и подписи. Никто (включая сам оракул) не может выбрать «удобное» число, не нарушив доказательство. Ключевой принцип безопасности любой случайности: исход не должен быть известен или управляем в момент, когда делаются ставки.

Частые ошибки

  • block.timestamp/blockhash как рандом для денег. Предсказуемо и/или поддаётся влиянию.
  • Решать исход в той же транзакции, где участник может «отменить» проигрыш.
  • Доверять одному непроверяемому источнику случайности.

Итоги

  • EVM детерминирована: ончейн-данные блока непригодны как честный рандом.
  • Опасности — предсказуемость для участников и влияние формирующего блок.
  • Решение — проверяемая случайность (Chainlink VRF) с доказательством честности.
  • Принцип: исход не должен быть известен/управляем в момент ставок.
Проверьте себя
1. Почему block.timestamp/blockhash плохи как источник случайности для денег?
AОни слишком медленные
BEVM детерминирована, и эти данные предсказуемы и/или поддаются влиянию
CОни шифруются
DИх нельзя прочитать в контракте
2. В чём ключевой принцип безопасной случайности?
AИспользовать timestamp
BИсход не должен быть известен или управляем в момент, когда делаются ставки
CЧем больше блоков, тем лучше
DСлучайность не нужна
3. Что даёт Chainlink VRF помимо случайного числа?
AСкидку на газ
BКриптографическое доказательство, что число сгенерировано честно и не подстроено
CГотового победителя
DШифрование контракта