Безопасность Solidity: границы доверия и источники данных
В модель угроз мы включили контракт и веб-файл. Теперь разложим путь чтения и публикации по компонентам. Браузер, кошелёк, RPC, CourseCatalog и файловое хранилище дают разные сведения и принимают разные решения. Успех одного запроса не доказывает корректность всей цепочки.
Начнём с читателя курса 17. Страница загружает конфигурацию, выбирает сеть и адрес каталога, читает courses(17), затем получает манифест и сравнивает отпечаток точных байтов. В каждом переходе появляется предположение: правильный адрес, подходящий источник состояния, выбранный блок и согласованный алгоритм.
Контракт и его узкая обязанность
CourseCatalog защищает определённые изменения собственного состояния. Он не выбирает адрес RPC браузера, не проверяет JavaScript страницы и не решает, доверяет ли пользователь редакции. Адрес реестра, указанный в конфигурации клиента, тоже не определяется его кодом.
Если сайт читает другую сеть с похожим контрактом, та модель может корректно вернуть H2 и active=true. Но это не состояние согласованного экземпляра. Поэтому понятие «ответ контракта» всегда включает chain ID и полный адрес, а для воспроизводимого наблюдения ещё выбранную точку состояния.
конфигурация сайта → сеть A, адрес C, курс 17
RPC → состояние C на блоке B
courses(17) → H1, true
хранилище → bytes M
клиент → SHA-256(M) == H1
Это схема проверки, а не запущенная программа. Она специально не смешивает результат RPC с данными хранилища. Полученный файл может быть правильным при недоступном RPC, а состояние может быть известно при недоступном файле. Пользовательский экран должен объяснять каждую неопределённость отдельно.
RPC как транспорт состояния
Обычный RPC-провайдер отвечает клиенту по сетевому протоколу. Успешный HTTP и корректная ABI-форма ещё не дают самостоятельного доказательства достоверности всего ответа. У приложения есть выбранная модель доверия к провайдеру и сети, которую нужно обозначить.
Для небольшой учебной демонстрации можно использовать определённый доверенный источник чтения. Для иной задачи могут понадобиться собственный узел, дополнительные источники или проверяемые доказательства. Курс не обещает, что сравнение двух одинаковых ответов автоматически исключает общий неправильный источник.
Необходимо также отличать выбранный блок от неопределённого latest. Чтение нескольких связанных значений в разные моменты может дать несогласованную картину после новой публикации. Для предметного отчёта полезно фиксировать точку, на которой получены hash и active, и указывать её клиенту.
Манифест и байты
Согласованный отпечаток библиотеки — SHA-256 точных сохранённых байтов. Поэтому парсинг JSON и повторная сериализация могут изменить порядок, пробелы или переводы строк, хотя объекты выглядят одинаково. Если договор касается исходного файла, вычисляют digest до таких преобразований.
Нельзя заменить его keccak256 лишь потому, что Solidity часто использует эту функцию. Нельзя также подставить CID вместо прямой SHA-256 файла: CID имеет своё представление данных и организации объекта. Совпадение 32-байтового типа не означает совпадение алгоритма и входа.
Сам hash не говорит, что содержание полезно, правдиво или соответствует названию курса. Он связывает ожидаемые байты с записанным значением по выбранному алгоритму. Оценка качества и редакционное одобрение остаются отдельными действиями команды библиотеки.
Подготовка публикации
Издатель должен увидеть полный courseId, digest, сеть и адрес реестра перед подписью. Если интерфейс показывал H1, а кошелёк получил calldata для H2, выбранный publisher может подписать нежелательное действие при совершенно корректном onlyPublisher. Это нарушение пути формирования намерения.
Кошелёк не обязан понимать предметный смысл bytes32. Он может показать сырые поля либо доступное декодирование ABI. Пользователь и приложение должны связать эти поля с проверенным файлом. Красивое название кнопки «опубликовать хороший урок» не является частью проверяемой авторизации контракта.
После отправки transaction hash обозначает намерение, известное сети, а не гарантированную успешную публикацию. Receipt, статус выполнения и текущая запись читаются отдельно. При сетевом таймауте повторное нажатие может создать ещё одну публикацию, а не подтвердить отсутствие первой.
Сервер и статический сайт
Статическая страница может публично показывать состояние и ссылки. Если добавляется серверный кабинет, он имеет собственную авторизацию и сессии. Адрес, присланный браузером, не доказывает контроль над аккаунтом; подписанный вход и проверка права нужны по отдельному договору.
Наличие private переменной Solidity не скрывает значение от наблюдателя цепочки. Наличие скрытого элемента HTML тоже не защищает доступный без авторизации ресурс. Поэтому секреты, ключи и ограниченный контент не помещают в эти места с надеждой на визуальную недоступность.
При отображении description из внешнего JSON действуют обычные правила обработки веб-данных. Его следует выводить в согласованном безопасном формате. Тот факт, что URI пришёл из on-chain записи, не превращает произвольные строки в безопасный HTML или JavaScript.
Граница ошибки
Пусть RPC недоступен, но кэш показывает вчерашний active=true. Решение продолжить чтение по кэшу означает политику устаревших данных. Оно не доказывает, что курс всё ещё активен. Для каждого действия укажите, допустима ли такая неопределённость, как долго и с каким сообщением пользователю.
Пусть файл доступен, но SHA-256 отличается. Клиент должен показать несоответствие и не объявить его согласованным текущим выпуском. Если интерфейс молча проигнорирует сравнение ради скорости, контрактная запись не компенсирует этот пропуск в браузере.
Пусть файл корректен, но активность false. Это отозванный выпуск с сохранённым hash. Можно дать читателю отдельное историческое представление по правилам сайта, но нельзя назвать его текущим активным материалом. Целостность файла и статус редакционного применения различаются.
В самостоятельном задании нарисуйте путь от формы издателя до отображения читателем. Для каждого входа укажите источник, доверие и проверяемое условие. Затем нарушьте ровно один переход и объясните, какой компонент это обнаружит. Такой разбор подготовит точную авторизацию методов вместо ожидания, что один модификатор исправит все внешние источники.
Для самостоятельного уточнения: security-considerations.html, json-rpc, content-addressing.
Оглавление курса. Листинги, расчёты и наблюдения здесь разобраны как ожидаемое поведение; компиляция, учебные программы и проверки при написании не запускались.