Перейти к содержанию

Безопасность Solidity: псевдослучайность и публичные входы

Временное окно использовало timestamp как условие доступности действия. Теперь рассмотрим неправильный перенос того же поля в выбор случайного результата. Если библиотека хочет распределить учебные задания, привлекательная формула с keccak256 ещё не означает непредсказуемость или честность выбора.

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

Формулируем требуемое свойство

Слово «случайный» может означать несколько задач. Иногда нужно воспроизводимо перемешать учебные карточки для интерфейса. Иногда требуется выбрать результат, который участник не может заранее предсказать или изменить. Эти требования различаются, поэтому сначала определяем последствия знания результата.

Для некритичного распределения упражнений детерминированная функция может быть совершенно полезной. Она даёт одинаковый порядок при том же seed и позволяет продолжить работу после обновления страницы. Но её не следует описывать как независимый честный жребий, если участник способен подбирать вход.

Если выбор определяет право доступа, редкий сертификат или другую ценность, модель угроз расширяется. Нужно учитывать, кто выбирает входы, кто решает включить операцию и кто может отказаться после получения неудобного результата. Хорошее распределение битов хеша не устраняет эти полномочия.

Разбираем неправильный пример

Следующая отдельная модель специально демонстрирует недостаточное основание. Она не используется общим CourseCatalog и не является рекомендуемой процедурой выбора.

pragma solidity 0.8.30;

contract PublicDrawExample {
    function pick(uint256 courseId, uint256 variants)
        external view returns (uint256)
    {
        require(variants > 0, "no variants");
        return uint256(keccak256(abi.encode(
            block.timestamp, msg.sender, courseId
        ))) % variants;
    }
}

Все входы функции доступны в контексте исполнения. Читатель может повторно вычислить результат для заданного набора, а courseId выбирает вызывающий. При изменении допустимых входов появляется возможность искать удобное значение. Хеш делает запись компактной, но не добавляет скрытого независимого источника.

Проверка variants > 0 предотвращает деление по модулю ноль, однако не исправляет непредсказуемость. Это два разных свойства: корректность арифметического исполнения и пригодность механизма выбора. Успешный возврат числа ещё не доказывает честность результата.

Публичность не равна произвольной изменяемости

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

Замена timestamp на block.prevrandao тоже не должна автоматически сопровождаться обещанием полной защиты. Нужно изучить модель целевой сети, момент известности входа и возможное влияние участников исполнения. Документация Solidity описывает поля среды, но бизнес-гарантия выбора требует отдельного анализа.

В локальной лаборатории управляемые параметры блока особенно ясно показывают различие между фикстурой и независимым источником. Полезный демонстрационный seed не становится production-гарантией только потому, что программа выдала разные значения в нескольких условных примерах.

Выбираем честное имя для простого решения

Если нам нужно лишь стабильное распределение упражнения, назовём его assignmentIndex и опишем детерминированность. Вход — публичный id задания, результат — номер варианта. Пользователь заранее знает, что повтор с тем же входом вернёт тот же вариант.

function assignmentIndex(bytes32 publicSeed, uint256 variants)
    pure returns (uint256)
{
    require(variants > 0, "no variants");
    return uint256(publicSeed) % variants;
}

Это свободная функция Solidity для арифметической модели, не готовый контракт выбора прав. Она не обещает непредсказуемость, не определяет происхождение seed и не защищает его от подбора. Такая прозрачность лучше ложного утверждения о случайности, когда приложению достаточно воспроизводимого порядка.

Также остаток от деления может давать неидеальное распределение для ограниченного диапазона входов. Для критичной статистики нужно отдельно анализировать способ получения seed и преобразования диапазона. Наш пример иллюстрирует договор детерминированности, а не доказательство равномерности.

Более сложные механизмы тоже имеют условия

Commit-reveal разделяет объявление обязательства и раскрытие исходного значения. Но последний участник может не раскрыться после оценки результата, если процедура не предусматривает срок и последствия отказа. Скрытый до раскрытия seed не решает автоматически вопрос завершения процесса.

Асинхронный проверяемый источник случайности тоже добавляет доверие, стоимость, ожидание и проверку происхождения ответа. Приложение связывает ответ с request id и назначением, не допускает повторной выдачи и определяет отказ поставщика. В отдельном курсе L2 и оракулов эти механизмы рассматриваются подробнее.

Не вставляем неизвестный адрес сервиса или случайный интерфейс VRF в листинг и не объявляем интеграцию готовой. Сначала фиксируют поддерживаемую сеть, версию поставщика и семантику запроса. Здесь наш результат — корректный выбор класса решения.

Меняем условие: участник может отказаться

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

Требуется сохранённая связь с пользователем и заданием, политика повторов и завершения. Результат первого допустимого запроса может быть единственным, а отказ источника должен иметь отдельный маршрут. Техническая случайность и прикладная однократность остаются разными свойствами.

Почему несколько красивых результатов не являются доказательством

Допустим, интерфейс показал четыре разных варианта для четырёх входов. Это демонстрирует только конкретные вычисления. Из такого набора нельзя заключить, что участник не способен подобрать пятый удобный вход или отказаться от неудобного результата. Свойство безопасности относится к полномочиям и процедуре, а не к визуальному разнообразию ответов.

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

Разбор результата

Мы отделили публичное хеширование, детерминированное распределение и критичную непредсказуемость. Формула с хешем не заменяет модель влияния и отказа. Название функции должно сообщать реальные гарантии, а более сложный механизм — иметь определённые этапы и правила повторов.

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

Оглавление серии