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

bytes и string в Solidity: манифест публикации

Отпечаток курса должен соответствовать определённым данным. Если редактор и читатель понимают слово «тот же текст» по-разному, сравнение будет ненадёжным. Один сохранил файл с другими переводами строк, второй удалил пробелы, третий поменял порядок полей JSON. Для человека заголовок одинаков, а последовательности байтов различаются.

В этом уроке подготовим маленький манифест курса 17 и определим его представление. Мы ещё не вводим новый метод контракта: изучаем входные данные, отпечаток которых позже сохранит существующий publish.

Символ и байт

Строка Solidity и внешняя строка JavaScript могут содержать UTF-8-текст, но операции низкого уровня работают с байтами. Кириллическая буква занимает не обязательно один байт. Поэтому ограничение «не больше ста байтов» не равно «не больше ста видимых символов» и тем более не описывает сложные составные символы.

Возьмём заголовок «Веб». В обычной UTF-8-форме он содержит три русские буквы, каждая из которых кодируется несколькими байтами. Если API рассчитывает размер документа по числу символов интерфейса, а хранилище ограничивает число байтов, результаты могут расходиться. Для отпечатка нужна именно окончательная последовательность байтов.

{"schema":1,"courseId":"17","title":"Веб","release":"1"}

Это конкретное компактное представление нашего учебного манифеста. Его можно сохранить в UTF-8 без добавленного BOM и с заранее оговорённым правилом завершающего перевода строки. Такие решения выглядят мелочами, пока два инструмента не получают разные отпечатки одного логического объекта.

Два равных объекта, два разных файла

Рассмотрим второй JSON:

{
  "title": "Веб",
  "courseId": "17",
  "release": "1",
  "schema": 1
}

После разбора обычным JSON-парсером приложение может получить те же значения полей. Но байты отличаются: есть пробелы и переводы строк, порядок ключей иной. Если вычисляется отпечаток исходного файла, он будет связан с этим новым представлением. Нельзя сначала сказать «проверяем исходный файл», а потом незаметно разобрать и заново сериализовать его.

Можно выбрать другой договор: отпечаток канонического представления объекта. Тогда требуется точный алгоритм канонизации, правила чисел, Unicode и порядок ключей. Самодельное правило «просто сортируем всё» быстро становится неоднозначным для вложенных объектов, массивов и числовых форм. Для первого проекта проще публиковать определённый файл и проверять его байты без преобразования.

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

string, bytes и bytes32

В Solidity string удобно выражает текстовое назначение, bytes — динамический массив байтов, а bytes32 — фиксированное значение. Для сравнения этих вариантов рассмотрим автономный учебный фрагмент:

function digest(bytes calldata releaseBytes)
    external
    pure
    returns (bytes32)
{
    return keccak256(releaseBytes);
}

Это отдельная функция демонстрации хеширования, а не необходимый метод базового реестра. Она не хранит файл и не проверяет личность редактора. Вход calldata даёт ей байты сообщения. Выход — отпечаток выбранного алгоритма. Результат зависит от всех переданных байтов, включая незаметный перевод строки в конце.

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

bytes32 в контракте не содержит название алгоритма. Если одна часть приложения использует SHA-256, а другая Keccak-256, одинаковый размер результата не делает их совместимыми. Алгоритм должен быть частью договора выпуска. Необходимо также помнить, что SHA3-256 и Ethereum Keccak-256 не взаимозаменяемы, несмотря на похожие названия.

Изменение одного условия

Пусть редактор исправил одну букву в заголовке курса. Новое содержимое может по-прежнему относиться к courseId 17, но это другой выпуск и другой отпечаток. Номер курса остаётся стабильным, версия содержимого меняется. Именно это различие позволяет не менять адрес страницы курса при обновлении текста.

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

Изменение Логические поля могут совпасть Байты файла совпадают
Перестановка JSON-ключей Да Нет
Другие отступы Да Нет
Исправление заголовка Нет Нет
Тот же файл по другому URL Да Возможно, нужно сравнить

Теперь ошибка несовпадения стала объяснимой. Она может указывать на другой выпуск, преобразование сериализации или повреждение получения. Сам отпечаток не объясняет, какой из этих случаев произошёл. Поэтому интерфейс должен показывать ожидаемое и полученное значения вместе с выбранным алгоритмом, а редакционный процесс — хранить артефакт выпуска.

Результат урока — точное описание исходных байтов. Мы сохраняем номер 17 и внешний манифест, определяем UTF-8 и правила файла, затем рассматриваем отпечаток как значение, связанное с этим артефактом. Подробности динамических и фиксированных массивов доступны в разделе типов Solidity.

Сохранение артефакта редакцией

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

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

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

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

Оглавление курса. Листинги, расчёты и наблюдения здесь разобраны как ожидаемое поведение; компиляция, учебные программы и проверки при написании не запускались.

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