IPFS: канонические байты JSON
Издатель и читатель могут понимать JSON одинаково, но получить разные байты. Порядок свойств, пробелы и представление чисел влияют на отпечаток. В уроке определим два допустимых режима: проверка готового неизменного файла и воспроизводимая каноническая сериализация. Они полезны в разных местах библиотечного выпуска.
Согласованное обязательство CourseCatalog.contentHash — SHA-256 точных сохранённых байтов манифеста. Алгоритм не навязан базовым контрактом; его выбирает наше приложение. Вычисления и подписи в текущей работе не исполняются.
Проверка готового файла
Самый простой маршрут — объявить конкретный файл. Издатель сохраняет release.json, получает его SHA-256 и связывает с доверенной записью. Читатель хеширует скачанные байты до разбора. Такой способ не требует одинаковой сериализации на стороне читателя: он не создаёт документ заново.
Если читатель сначала разбирает JSON, затем вызывает JSON.stringify, он меняет представление. Пробелы исчезают, порядок может зависеть от созданного объекта, а числа могут преобразоваться. Полученный отпечаток относится к новой строке, а не к полученному файлу.
Поэтому порядок операций важен:
получить байты → проверить SHA-256
→ декодировать UTF-8 → разобрать JSON
→ проверить схему → использовать поля
Неверная контрольная сумма останавливает доверенное использование. Неверная схема также останавливает обработку, хотя это другая причина. Корректные байты неизвестной схемы не становятся допустимыми только благодаря совпадению отпечатка.
Создание одного представления
Если несколько издательских компонентов должны получать один отпечаток из одного логического объекта, нужен канонический формат. JCS по RFC 8785 определяет правила JSON-сериализации с ограничениями на данные. Он учитывает порядок свойств, представление примитивов и другие детали.
Нельзя назвать произвольную функцию сортировки верхних ключей реализацией полного JCS. Вложенные объекты, Unicode и числа требуют предусмотренной обработки. Для будущего проекта выбираем совместимую библиотеку и закрепляем её версию после отдельного разрешённого этапа установки. Сейчас пакеты не устанавливаются и реализация не проверяется.
Для учебного манифеста удобно ограничить суммы и большие значения строками с десятичными цифрами. Тогда они не теряют точность при разборе в разных языках. Но строки тоже нуждаются в схеме: "017" и "17" различаются, а отсутствие допустимого формата не исправляется канонизацией.
Порядок массива
Канонизация свойств объекта не означает автоматическую сортировку массива файлов. Если массив имеет установленный порядок, он сохраняет его смысл. Поэтому схема отдельно задаёт: записи сортируются по точному пути до сериализации или представлены в редакционном порядке.
Для библиотеки выберем сортировку по согласованному представлению пути как часть подготовки данных. Это не скрытая функция хеширования, а правило построения самого объекта. Если две записи имеют одинаковый путь, схема отвергает дубликат, а не надеется, что сортировка его уберёт.
Пример логически близких документов:
{"courseId":"17","edition":"1"}
{"edition":"1", "courseId":"17"}
Как готовые файлы они различаются байтами. При совместимой канонической обработке допустимых данных они могут привести к одной канонической форме. Это разные договоры, и приложение должно выбрать нужный заранее.
Unicode и переносы
Текстовые поля могут содержать визуально похожие последовательности Unicode. Канонический JSON не является универсальной нормализацией человеческого языка. Если схема требует конкретную нормальную форму для заголовков или путей, это отдельное правило подготовки.
Переводы строк внутри значения и между свойствами также различаются. Внутри строки они являются частью данных; между элементами обычного форматированного JSON — частью представления. Для проверки готового файла учитываются оба вида байтов. Для канонизации форматирование определяется выбранной спецификацией.
Случайное добавление перевода строки после готового канонического документа создаёт другие сохранённые байты. Поэтому в досье явно пишем, какие байты хешируются. Если файл записывается с завершающим LF, именно эта форма должна быть объявлена либо строго исключена договором.
Издательское состояние
Полезно сохранить канонические байты как отдельный результат подготовки и не пересоздавать их каждый раз перед чтением реестра. Тогда архив, импорт IPFS и вычисление обязательства используют один и тот же объект. Если импортёр добавляет структуру UnixFS, его CID относится к представлению, но SHA-256 файла остаётся отпечатком исходных байтов.
Метаданные вроде текущего времени генерации не вставляются случайно в документ перед подписью. Если дата нужна редакции, она устанавливается один раз как поле выпуска. Воспроизводимая сборка не должна каждый раз получать новые байты только оттого, что часы изменились.
Потеря информации при разборе
Рассмотрим документ с повторённым именем свойства edition. Многие обычные JSON-парсеры сохранят только одно значение, скрыв сам факт повторения. Если приложение затем канонизирует полученный объект, оно уже не видит исходную неоднозначность. Поэтому для принятия внешнего документа недостаточно сказать «разобрали JSON стандартной функцией». Требования к допустимому вводу и обнаружению дубликатов должны выполняться на стадии, где нужная информация ещё доступна.
В библиотеке готовый сохранённый файл сначала сверяется как байты. После этого парсер и валидатор принимают только разрешённую форму схемы; криптографическое совпадение не отменяет этих правил. Для создания канонического объекта редакция также исключает дубли и неподдерживаемые числовые значения до сериализации. Это две стороны одного договора, а не попытка исправить файл читателя после проверки.
Теперь изменим лишь завершающий перевод строки. Первый файл заканчивается последней фигурной скобкой, второй содержит дополнительно LF. Их логическое JSON-содержание может совпасть, но SHA-256 точных файлов должен рассматривать разные последовательности. Если реестр объявил первый вариант, второй нельзя принять по принципу «лишняя строка не важна». Такая уступка означала бы смену алгоритма сравнения без согласования с издателем. Нужно получить объявленные байты либо объявить новый документ.
Межъязыковая реализация дополнительно требует совместимых правил для свойств, строк и чисел. Сортировка, удобная в одном языке, не автоматически соответствует выбранной спецификации. Для подготовки выпуска сохраняем версию канонизатора и ограниченную контрольную структуру как часть досье. Здесь мы описываем ожидаемые равенства и отказы, не выдавая их за результаты исполнения. Основной практический вывод — не терять исходные байты до их сверки и не подменять полноценную канонизацию короткой самодельной сортировкой.
Ожидаемый результат
После будущей реализации две программы должны получить одинаковые канонические байты для допустимой контрольной структуры, а изменение цены или состава должно изменить отпечаток. Неподдерживаемые числа и дубли полей отвергаются по выбранным правилам. Сейчас это критерии, не результаты запуска.
Полная спецификация доступна в RFC 8785. Адресация блоков объяснена в документации IPFS. После урока у нас есть точное значение слова «манифест»: это согласованные сохранённые байты, которые можно независимо получить, проверить и связать с реестром.