Проверка
Три роли верификаторов Label 309, состояния вердикта, глубина финальности и типизированный каталог ошибок — как любой может прийти к одному и тому же ответу, опираясь только на публичную инфраструктуру.
Запись Label 309 проверяется, а не принимается на веру. Издатель закрепляет хеш содержимого в Cardano под меткой 309; с этого момента притязание держится на собственных байтах, и любой, у кого есть ссылка на транзакцию, может его проверить. Эта страница описывает, как выполняется такая проверка: три роли верификаторов, что каждая из них затрагивает, а что нет, какие состояния вердикта они выдают, ниже какой глубины подтверждения вердикт остаётся предварительным и какой типизированный каталог ошибок заставляет две независимые реализации сходиться в одном и том же сбое для одного и того же входа.
Определяющее свойство — независимость от сервиса. Соответствующий стандарту верификатор приходит к своему вердикту, опираясь только на публичную цепочку, выбранный самим верификатором обозреватель или шлюз Cardano и — для притязаний о содержимом и о запечатанных записях — шлюзы хранилища с адресацией по содержимому, которые верификатор также выбирает сам. Он никогда не обращается к издателю. Стандарт не называет конкретного поставщика; шлюз — это вход, который задаёт оператор.
Три роли, каждая — строгое расширение предыдущей
Проверка устроена послойно. Каждая роль делает всё то же, что и роль над ней, а затем добавляет одну возможность. Нижняя роль сама по себе является полноценным, полезным верификатором — она просто доказывает меньше.
| Роль | Добавляет | Затрагивает |
|---|---|---|
| Структурный валидатор | соответствие схеме и доменным правилам на байтах записи | ничего — это чистая функция |
| Публичный верификатор | разрешение цепочки, проверку включения в блокчейн, проверку подписей | обозреватель Cardano и шлюзы содержимого |
| Верификатор получателя | пробную расшифровку запечатанной полезной нагрузки, перерасчёт хеша открытого текста | собственный закрытый ключ верификатора |
Структурный валидатор — чистая функция над байтами
Структурный валидатор — это одна функция, переводящая байтовую строку в результат. Он не выполняет никакого ввода-вывода, никакой проверки криптографических подписей и никакой расшифровки. Он никогда не видит ни сети, ни транзакции, ни ключа. На одном и том же входе он каждый раз возвращает один и тот же выход — всегда и где угодно. Именно это позволяет запускать его до отправки внутри инструментов издателя, внутри стороннего индексатора или внутри архивного средства, подтверждающего корректность формата на долгие годы, — и всё это без сервера.
Его конвейер фиксирован:
1. resource bounds — a local, non-normative guard on input size; never a
Label 309 conformance error.
2. canonical decode — decode with a canonical-CBOR decoder (RFC 8949 §4.2.1):
definite lengths, sorted map keys, no duplicate keys,
valid UTF-8. Any malformed or non-canonical input →
a single MALFORMED_CBOR.
3. schema parse — type, length, and the chunk/length bounds; a strict
object mode that rejects unknown fields.
4. domain rules — cross-field constraints the schema cannot express:
registry membership, the items-or-merkle rule, COSE
structural shape, URI reconstruction, envelope shape.
5. result — { valid, record } with optional warnings/info, or a
sorted list of typed issues.Валидатор не зависит от профиля: он разбирает полную схему v1 независимо от того,
с каким подмножеством намерен работать нижестоящий верификатор. Ошибки делают запись
недействительной; предупреждения и информационные записи отображаются, но запись при
этом остаётся действительной. Что важно, он подтверждает форму COSE_Sign1 — массив
из четырёх элементов, отсоединённую (null) полезную нагрузку, корректно сформированный
защищённый заголовок, — но никогда не проверяет саму подпись и никогда не
отклоняет запись лишь потому, что алгоритм подписи ему незнаком (это помечается как
SIGNATURE_UNSUPPORTED со степенью серьёзности info, и запись остаётся
действительной). Проверка подписи — задача публичного верификатора. Схему, которую
обеспечивает этот этап, см. в разделе Запись.
Публичный верификатор — цепочка, включение и подписи
Публичный верификатор накладывает работу с цепочкой поверх структурного валидатора. Получив ссылку на транзакцию Cardano, он:
- Разрешает выбранный верификатором обозреватель. Цепочка обозревателей — это
вход; верификатор пробует их по порядку, получая сырой ончейн-CBOR транзакции,
а не JSON-проекцию метаданных обозревателя. JSON-представление сводит основные
типы CBOR в единое JSON-объединение и отбрасывает порядок ключей map, разметку с
явной длиной и различие между байтами и текстом, так что верификатор, повторно
кодирующий запись из него, не смог бы воспроизвести байт-точный вход для подписи, и
любая подпись на соответствующей стандарту записи оказалась бы недействительной.
Отрицательный ответ одного поставщика не авторитетен для цепочки — транзакция
может быть в блокчейне и лишь неизвестна этому поставщику, — поэтому верификатор
опрашивает всех остальных поставщиков прежде, чем выдать
TX_NOT_FOUND; если все поставщики недоступны, он выдаётPROVIDER_UNAVAILABLE. - Привязывает полученные байты к ссылке на транзакцию. Прежде чем прочитать
что-либо из полученной транзакции, верификатор пересчитывает
blake2b-256над полученными байтами тела транзакции — по определению реестра это и есть идентификатор транзакции — и отклоняет ответ при любом несовпадении с запрошенным хешем. Затем он пересчитываетblake2b-256над полученными байтами вспомогательных данных и отклоняет ответ при любом несовпадении сauxiliary_data_hashуже проверенного тела. Оба дайджеста вычисляются над байтами ровно в полученном виде, никогда — над перекодировкой. Ответ, проваливший любую из проверок, несёт доказуемо неверные байты и отбрасывается; если ни один поставщик не выдаёт ответа, переживающего эту привязку, отчёт несётTX_INTEGRITY_MISMATCH. После этого шага каждый байт записи и окружающей транзакции криптографически привязан к переданному вызывающим хешу — ни один обозреватель не может подменить, исправить или усечь запись, не породив второй прообраз blake2b-256. - Разворачивает вспомогательные данные. Реестр эпохи Conway допускает три
кодировки вспомогательных данных, все действительные: голый map метаданных без
тега, массив из двух элементов
[ metadata, scripts ]и map с тегом 259, где метаданные находятся под ключом0. Верификатор ДОЛЖЕН принимать все три и ветвиться исключительно по типу и тегу CBOR верхнего уровня — значение с тегом 259 является формой map с ключом, массив без тега — формой из двух элементов, а map без тега — всегда самим map метаданных. Он НЕ ДОЛЖЕН осматривать ключи map, чтобы угадать форму; любая иная форма верхнего уровня или любой тег, кроме 259, — этоMALFORMED_CBOR. Если привязанная транзакция не несёт метаданных под меткой 309, верификатор выдаётMETADATA_NOT_FOUND— исход, относимый к записи, поскольку сама привязанная транзакция доказывает это отсутствие: ни один поставщик не мог бы снять метаданные, не провалив привязку. - Собирает массив фрагментов тела целиком. Значение под меткой 309 — это тело записи в каноническом CBOR, разбитое на массив байтовых строк по ≤ 64 байта. Верификатор побайтово склеивает элементы по порядку — возвращая сырые байты, без прохода повторного кодирования, — чтобы проверка на каноничность CBOR всё ещё могла поймать несоответствующее стандарту ончейн-кодирование.
- Структурно проверяет собранное тело (это делает роль уровнем выше). Отклонение
валидатором замыкает отчёт вердиктом
failed. - Проверяет глубину подтверждения (см. ниже). Транзакция ниже порога
останавливается здесь с вердиктом
pending. - Проверяет каждую подпись на уровне записи по строгим правилам Ed25519. Он не расшифровывает. Разрешение подписи, полезная нагрузка с разделением доменов и строгие правила проверки описаны в разделе Подписи.
- Получает содержимое и сверяет хеши, спрашивая с записи лишь за те байты, которые он может отнести к собственному адресу содержимого URI (см. ниже). Он не расшифровывает.
Почему сырой CBOR, а не JSON
Подпись вычисляется по байт-в-байт каноническому CBOR тела записи. JSON-проекция метаданных по самой своей природе теряет часть данных — её нельзя преобразовать обратно в те же байты. Повторное кодирование из JSON ломает любую подпись на соответствующей стандарту записи. Сырой CBOR транзакции — единственный авторитетный вход для любой криптографической проверки; JSON-представление служит для показа человеку, уже после того как проверка успешно пройдена.
Верификатор получателя — расшифровать и пересчитать
Верификатор получателя — это публичный верификатор, который вдобавок располагает закрытым ключом. Для запечатанной записи, адресованной ему, он выполняет пробную расшифровку ончейн-слотов ключей своим ключом, при успехе восстанавливает ключ содержимого, расшифровывает шифртекст, а затем заново вычисляет хеши открытого текста и сверяет их с ончейн-обязательством — замыкая связь между зашифрованными байтами и притязанием о существовании содержимого. Поскольку каждая запечатанная запись несёт как минимум одну запись с хешем содержимого, такому перерасчёту всегда есть с чем сравнивать. Запечатанный конверт, слоты ключей и схема разворачивания описаны в разделе Sealed PoE.
Прежде чем получатель коснётся хоть одного примитива KEM или AEAD, он заново
применяет те же проверки формы и ресурсов конверта, что уже провёл структурный
валидатор, отклоняя сначала структурно недействительный или превышающий размеры
конверт. Две из этих доисполнительных проверок — это закреплённые на стороне
развёртывания ресурсные пороги, а не проводные поля: контрольные границы — это
MAX_SLOTS = 1024 слотов и 65536 байт для декодированного конверта enc, обе
намного выше потолка метаданных транзакции Cardano в ~16 КиБ, ограничивающего любую
честную запись, так что запись, превышающая любую из них, недоформирована и
отклоняется с кодом ENC_SLOTS_TOO_MANY или ENC_ENVELOPE_TOO_LARGE ещё до запуска
хоть одного примитива. Развёртывания МОГУТ ужесточать их.
Одна проверка формы несёт на себе нагрузку по безопасности: материал инкапсуляции
ДОЛЖЕН быть различным в пределах одного slots[] — все значения epk для
x25519 или все значения kem_ct для mlkem768x25519. Дубликат внутри записи
отклоняется с кодом ENC_SLOTS_DUPLICATE_KEM_MATERIAL ещё до запуска любого
примитива KEM или AEAD, поскольку повторяющийся epk/kem_ct разрушил бы
уникальность ключей по слотам, на которую опирается обёртывание с нулевым nonce.
Повторное использование ключей между записями или между ключами — это обязанность
производителя, и верификатор его обнаружить не может; обнаружим лишь дубликат внутри
одной записи. Все три проверки структурны — валидатор обеспечивает их на каждой
записи (коды дубликата материала, числа слотов и декодированного конверта относятся
к кодам Части A); получатель просто проводит их повторно как защиту в глубину.
Само разворачивание — это два криптографических шага, которые получатель
воспроизводит из конверта, и никогда из какого-либо стороннего входа. Сначала он
один раз, до цикла, заново вычисляет хеш транскрипта слотов slots_hash и
держит его неизменным на протяжении всех слотов:
slots_hash = SHA-256("cardano-poe-slots-transcript-v1" || canonicalEncode(SLOTS_TRANSCRIPT)),
где транскрипт фиксирует поля заголовка, набор слотов в проводном виде (каждое поле
слота — единственная байтовая строка, так что пофрагментной нормализации нет) и
утверждение о хеше элемента через hashes_hash. Привязка hashes_hash — это то, что
позволяет получателю подтвердить, что конверт был запечатан именно под это
утверждение о хеше, из одних лишь ончейн-байтов, ещё до выгрузки шифртекста: конверт,
пришитый к элементу с другой картой hashes, проваливает MAC. Он перебирает все
слоты без досрочного выхода; слот принимается лишь тогда, когда полученный из него ключ
содержимого вдобавок воспроизводит slots_mac, лежащий на проводе, над этим неизменным
slots_hash. В приём вплетается ещё и бит валидности, не зависящий от секрета: на
классическом пути слот, изготовленный так, чтобы свести общий секрет X25519 к нулевому
значению, выставляет этот бит в ложь (сравнением с 0^32 за постоянное время), KEK за
постоянное время выбирается равным фиктивному, выведенному из 0^32, так что цикл
выполняет идентичную работу, а бит управляет приёмом слота — слот с недействительным
ECDH открыться не может, какими бы ни были его обёртка или MAC. И открытие обёртки, и
последующее открытие содержимого атомарны: при отказе тега AEAD они не возвращают
открытого текста, а возвращаемый ими кандидат (обёрнутый ключ или открытый текст
содержимого) — это фиксированная или псевдослучайная заглушка, не зависящая от
провалившегося шифртекста: непроверенный открытый текст никогда не отдаётся наружу.
Ключ получателя МОЖЕТ правомерно совпасть более чем с одним слотом: производитель
может запечатать один и тот же ключ содержимого одному и тому же получателю в нескольких
слотах, каждый со свежим материалом KEM по слоту, чтобы дополнить число получателей —
это правомерный приём приватности, и он отличен от отклонения дубликата материала
инкапсуляции, срабатывающего лишь на идентичном epk/kem_ct. Верификатор выбирает
ключ первого совпадения и НЕ ДОЛЖЕН отклонять лишь потому, что совпало
несколько слотов. Единственная аномалия, которую он ДОЛЖЕН отклонить, — это два
совпавших слота, восстанавливающих разные ключи содержимого (сравнение за
постоянное время): цикл несёт бит cek_conflict и выдаёт единственный общий отказ,
если какое-либо более позднее совпадение даёт ключ, отличный от выбранного. Это защита
в глубину — при обязательстве набора слотов совпадение с другим ключом и так
неосуществимо, так что проверка просто закрывается наглухо.
Затем на восстановленном ключе шифрования содержимого он выводит ключ содержимого
(лист HKDF от этого ключа, посоленный enc.nonce) и открывает шифртекст
сегментированного STREAM фрагмент за фрагментом, проверяя тег каждого фрагмента
до того, как выдать открытый текст этого фрагмента. AAD на фрагмент пуст: весь
контекст заголовка уже привязан к ключу транзитивно через slots_mac, так что
переворот любого поля заголовка меняет то, что выводит получатель, и поток не
открывается. Усечение ловится финальным флагом — отсутствующий финальный фрагмент,
данные после него, финальный флаг на нефинальном фрагменте или короткий нефинальный
фрагмент — всё это проваливается как TAMPERED_CIPHERTEXT. Сегментированный формат не
налагает криптографического потолка на размер (88-битный счётчик фрагментов допускает
2^88 фрагментов); практический максимум — это политика защиты от отказа в
обслуживании в развёртывании, применяемая инкрементально по мере чтения потока. На
пути парольной фразы получатель сначала читает ведущий 32-байтовый
заголовок-обязательство и сравнивает его за постоянное время до открытия любого
фрагмента.
Именно на пути получателя каталог ошибок раскрывает свою точность: он различает случай, когда ни один слот не принял этот ключ (неверный получатель), и случай, когда слот ключ принял, но набор слотов или шифртекст были подменены. Это разные притязания о безопасности, и им соответствуют разные коды (см. ниже). Однако недоверенный вызывающий получает ровно одну общую форму отказа независимо от причины — ни один слот не открылся, набор слотов был подменён или тег содержимого не сошёлся — и ответ никогда не выдаёт, какой именно случай произошёл, ни какой слот совпал; типизированные коды — это внутренняя диагностика лишь для доверенного локального вызывающего.
Время следует одной явной модели. Верификатор МОЖЕТ вернуться на проверке отсутствия совпадения — до расшифровки содержимого, — так что неполучатель и получатель тратят измеримо разное время. Эта разница выдаёт лишь получатель-против-неполучателя, никогда — какой слот совпал, и никакого ключевого материала. Единообразие времени между неполучателем и получателем, чей шифртекст не открывается, НЕ требуется, а фиктивное открытие содержимого НЕ ДОЛЖНО предписываться — оно навязало бы стоимость расшифровки содержимого каждому прохожему. Гарантия постоянного времени, которая всё же держится, — это межслотовая: в пределах одного прохода по закрытому ключу цикл идёт по всем слотам со сравнениями за постоянное время, так что не утекает, какой слот, если вообще какой-либо, этот ключ разворачивает.
Финальность: глубина подтверждения
Расчёт в Cardano носит вероятностный характер. Транзакция глубиной в один блок ещё
может быть отброшена короткой реорганизацией цепочки; транзакция глубиной во много
блоков с подавляющей вероятностью уже учтена окончательно. Верификатор, который
назвал бы запись глубиной в один блок valid, позволил бы злоумышленнику заново
закрепить противоречащую запись на конкурирующей ветке и получить вердикт
«действительна» сразу для обеих — незаметно нарушив допущение о неизменяемости
append-only, на котором держится всё подтверждение существования.
Поэтому, пока запись находится ниже порога глубины подтверждения, верификатор
сообщает о ней как о pending, а не failed. РЕКОМЕНДУЕМЫЙ порог общего
назначения — ≥ 15 блоков (примерно пять минут). Порог — это политика
верификатора, а не константа формата: развёртываниям, работающим с записями высокой
ценности или доказательственного значения, СЛЕДУЕТ поднимать его в сторону
жёсткой финальности, а верификатор ДОЛЖЕН показывать использованный им порог,
чтобы потребители могли наложить поверх более строгую политику. Запись pending
корректно сформирована и находится в блокчейне; она просто ещё не учтена достаточно
глубоко и при повторной попытке позже может перейти в состояние valid.
Глубина подтверждения, высота блока и время блока — это факты, утверждаемые
обозревателем, которые верификатор никогда не измышляет. Привязка к ссылке на
транзакцию делает содержимое транзакции бездоверительным, но факты цепочки о ней
не выводятся из байтов — CBOR транзакции не несёт ни метки времени, ни высоты. Глубина
вычисляется как (высота вершины разрешающего обозревателя) − (высота включающего блока) + 1, так что транзакция во вершинном блоке имеет глубину ровно 1; время блока —
это метка времени POSIX (целые секунды, UTC) слота включающего блока, взятая из поля
времени обозревателя, а не пересчитанная верификатором. Поскольку эти факты держатся
на слове обозревателя, верификатору СЛЕДУЕТ разрешать их как минимум через два
независимых обозревателя и показывать любое расхождение; развёртывания, для которых
время блока несёт нагрузку — юридическое нотариальное заверение, споры о сроках, —
ДОЛЖНЫ перепроверять его. Отчёт несёт разрешённую глубину рядом с порогом, с
которым она сравнивалась, и block_time (а также block_slot, когда он доступен).
Состояния вердикта
Верификатор приходит к одному из четырёх машинных вердиктов, каждый из которых
взаимно-однозначно сопоставлен с кодом выхода процесса, так что вызывающая сторона —
CI-гейт, монитор, скрипт — может отличить сбой, относимый к самой записи, от
временного эксплуатационного сбоя, не разбирая структурированный отчёт. Определяющая
линия — относимость: чтобы осудить запись, нужны доказательства, которые
действительно дают собственные байты записи — или байты, относимо привязанные к её
ссылкам. Никакое недобросовестное поведение поставщика не может сфабриковать failed.
| Вердикт | Выход | Значение |
|---|---|---|
| valid | 0 | каждая выполненная верификатором проверка вернула ok; ни одной проблемы со степенью серьёзности error нет. |
| failed | 1 | относимый к записи сбой: структурный валидатор отклонил байты, подпись не проверилась, относимый хеш не совпал, привязанная транзакция не несёт метаданных под меткой 309 (METADATA_NOT_FOUND) или сработало правило deny-host. |
| unverifiable | 2 | сбоя, относимого к записи, нет, но требуемую проверку не удалось выполнить или отнести: транзакция не разрешилась, ни один ответ поставщика не пережил привязку (TX_INTEGRITY_MISMATCH), либо зафиксированное содержимое/шифртекст не удалось получить или отнести. Та же запись при повторе или у другого шлюза может оказаться valid. |
| pending | 3 | структурно корректна и в блокчейне, но ниже порога глубины подтверждения (INSUFFICIENT_CONFIRMATIONS); может быть учтена окончательно. Никакой результат записи pending не может предъявляться как окончательный. |
Сбои времени выполнения на стороне хоста-верификатора, не относимые к записи, используют коды выхода 4 и выше и не соответствуют ни одному вердикту.
Вердикт valid НЕ ДОЛЖЕН выдаваться при наличии любой проблемы со степенью
серьёзности error; запись МОЖЕТ быть valid при непустом списке warnings
и/или info. Ни один слой не вправе «смягчить» ошибку до предупреждения, чтобы
пропустить запись. Каждый вердикт зарезервирован за своим случаем: pending никогда
не подставляется вместо valid или failed, а сбой, относимый к поставщику, — это
unverifiable, а не failed.
Порог обязательства управляет доступностью: когда проверка содержимого
выполнялась, но сбои доступности не оставили ни одного фактически проверенного
обязательства записи по содержимому, вердикт — unverifiable, а не valid: вердикт
valid означает, что проверено как минимум одно обязательство по содержимому. Исходы
целостности порогом не затрагиваются: относимые байты, провалившие обязательство, дают
failed независимо от того, что ещё было доступно.
Привязка к адресу содержимого и относимость
Шаг 8 (а для верификатора получателя — расшифровка) получает байты из шлюзов хранилища с адресацией по содержимому, выбранных верификатором, — а шлюзы недоверенны. Линия вердикта выше держится на единственном вопросе, на который верификатор ДОЛЖЕН уметь ответить о каждом полученном потоке байтов: можно ли отнести эти байты к самому URI? Обе схемы адресуются по содержимому, поэтому ответ возможен:
ipfs://— пересчитать CID над полученным содержимым (непосредственно мультихеш для CID с сырым кодеком; поблочные дайджесты вдоль разрешённого пути для CID в форме DAG).ar://— проверить подписанную транзакцию Arweave и пересчитать дерево Merkle фрагментов против еёdata_root; для элемента данных ANS-104 — пересчитать deep-hash, проверить подпись владельца и убедиться, что SHA-256 подписи равен идентификатору в URI.
Байты, удовлетворяющие собственным дайджестам записи, в проверке привязки не нуждаются — обязательство записи не слабее модели целостности слоя хранения. Там, где привязка применяется, относимость решает, что означает несовпадение:
- Относимые байты — привязка проверена либо переданы вызывающим вне полосы —
засчитываются против записи, когда они проваливают обязательство:
URI_INTEGRITY_MISMATCHдля содержимого элемента (жёсткий сбой целостности независимо от того, что несут соседние URI), семействоMERKLE_*для списка листьев,TAMPERED_CIPHERTEXTдля относимого блоба шифртекста. Вердиктfailed. - Неотносимые байты — привязка не проверена либо проверена и провалилась —
обвиняют поставщика, но никогда — запись:
URI_PROVIDER_INTEGRITY_MISMATCH(предупреждение). Верификатор продолжает с оставшимися URI и шлюзами; притязание, оставшееся без относимых байтов, заканчивается исходом доступности (CONTENT_UNAVAILABLE,MERKLE_LEAVES_UNAVAILABLE,CIPHERTEXT_UNAVAILABLE), вердиктunverifiable— ровно так, как если бы ничего не было получено.
Это аналог привязки к ссылке на транзакцию на стороне хранения: недобросовестный шлюз
может ухудшить лишь доступность, но никогда — вердикт. Поэтому
URI_INTEGRITY_MISMATCH и URI_PROVIDER_INTEGRITY_MISMATCH — это разные коды: первый
осуждает запись, второй осуждает поставщика, — и верификатор, смешавший их, позволил бы
враждебному шлюзу подделать failed. Пересверка хеша открытого текста после расшифровки
не нуждается в квалификаторе относимости: шифртекст, открывающийся под
аутентифицированным конвертом, относится самим AEAD, так что несовпадение хеша
открытого текста там (URI_INTEGRITY_MISMATCH) всегда относимо к записи и вынуждает
failed.
Типизированный каталог ошибок
Каждый режим сбоя сводится к коду из единого закрытого каталога. Коды записаны
в SCREAMING_SNAKE_CASE, и соответствующая стандарту реализация ДОЛЖНА выдавать
именно эти строки — никогда внутренний код парсера в нижнем регистре и никогда
произвольное сообщение. Две реализации на двух языках, получив один и тот же вход,
выдают один и тот же код; полный нормативный список зафиксирован байт-в-байт набором
тестов на соответствие, а сам каталог заблокирован (коды добавляются только через
поправку).
Модель степеней серьёзности
Каждая проблема несёт одну из трёх степеней серьёзности, и это различие несёт смысловую нагрузку:
- error — делает вердикт недействительным. Результат
validне может сосуществовать ни с однойerror. - warning — нефатальная аномалия времени выполнения (отказал один шлюз, список
листьев был доступен лишь частично), которая не препятствует вердикту
valid. - info — намеренно не выполненная проверка: аспект, который верификатор решил
не оценивать (поле вне его профиля, нераспознанный необязательный алгоритм). Запись
info— это не смягчённая ошибка, и в этом качестве она никогда не используется.
Один код стоит особняком: INSUFFICIENT_CONFIRMATIONS отображается на вердикт
pending, а не на степень серьёзности, потому что запись корректно сформирована и
лишь ожидает окончательного учёта.
Семейства ошибок
Каталог сгруппирован по семействам. Показательный, но не исчерпывающий набор кодов:
| Семейство | Степень серьёзности | Показательные коды |
|---|---|---|
| Некорректный / неканонический CBOR | error | MALFORMED_CBOR, CHUNK_TOO_LARGE |
| Схема | error | SCHEMA_TYPE_MISMATCH, SCHEMA_MISSING_REQUIRED, SCHEMA_UNKNOWN_FIELD, SCHEMA_EMPTY_RECORD |
| Неподдерживаемый алгоритм | error | UNSUPPORTED_HASH_ALG, UNSUPPORTED_AEAD_ALG, UNSUPPORTED_KEM_ALG, UNSUPPORTED_MERKLE_COMMIT_ALG |
| Обозреватель / метаданные | error / pending | TX_NOT_FOUND, PROVIDER_UNAVAILABLE, TX_INTEGRITY_MISMATCH (→ unverifiable), METADATA_NOT_FOUND (→ failed); INSUFFICIENT_CONFIRMATIONS (→ pending) |
| Подпись | error / info | MALFORMED_SIG_COSE_SIGN1, SIGNER_KEY_UNRESOLVED, SIGNATURE_INVALID, WALLET_ADDRESS_MISMATCH; SIGNATURE_UNSUPPORTED (info) |
| Шифрование / KEM / обёртка | error | KEM_EPK_LENGTH_MISMATCH, KEM_CT_LENGTH_MISMATCH, WRAP_LENGTH_MISMATCH, ENC_SLOTS_DUPLICATE_KEM_MATERIAL, ENC_SLOTS_TOO_MANY, ENC_ENVELOPE_TOO_LARGE, ENC_REQUIRES_CONTENT_HASH |
| Результат расшифровки | error | WRONG_DECRYPTION_INPUT_SHAPE, WRONG_RECIPIENT_KEY, TAMPERED_HEADER, TAMPERED_CIPHERTEXT, KDF_DERIVATION_FAILED |
| URI / содержимое | error / warning | INVALID_URI, URI_TARGET_FORBIDDEN, URI_INTEGRITY_MISMATCH, CONTENT_UNAVAILABLE (→ unverifiable), CIPHERTEXT_UNAVAILABLE (→ unverifiable); URI_PROVIDER_INTEGRITY_MISMATCH, URI_FETCH_FAILED (предупреждения) |
| Merkle-обязательство по списку | error / warning / info | MERKLE_ROOT_MISMATCH, SCHEMA_MERKLE_LEAF_COUNT_MISMATCH; MERKLE_LEAVES_UNAVAILABLE (двойная); MERKLE_UNSUPPORTED (двойная) |
| Независимость от сервиса | error | SERVICE_INDEPENDENCE_VIOLATION |
Сетевые/политические коды (TX_NOT_FOUND, PROVIDER_UNAVAILABLE,
CONTENT_UNAVAILABLE, CIPHERTEXT_UNAVAILABLE) — а также TX_INTEGRITY_MISMATCH,
чьё несовпадение доказуемо против поставщиков, а не против записи, — несут степень
серьёзности error в списке проблем (они блокируют вердикт valid), но не
относимы к записи, поэтому отображаются на unverifiable, а не на failed.
URI_PROVIDER_INTEGRITY_MISMATCH — это пофрагментное предупреждение по тому же
принципу. Несколько кодов несут двойную степень серьёзности — ENC_UNSUPPORTED,
MERKLE_UNSUPPORTED, OUT_OF_PROFILE_SKIPPED и MERKLE_LEAVES_UNAVAILABLE — читаясь
по умолчанию как info/warning и повышаясь до error в строгом контексте (роль
получателя, эскалация только по merkle, строгий сквозной режим или порог обязательства).
Путь получателя — самое диагностически точное семейство. Для доверенного
локального вызывающего неудавшаяся расшифровка разрешается в один из трёх
внутренних кодов: WRONG_RECIPIENT_KEY означает, что ни один слот не принял
переданный ключ (ключ содержимого так и не был восстановлен); TAMPERED_HEADER
означает, что ключ был восстановлен, но ключ-кандидат содержимого не воспроизвёл
slots_mac над slots_hash (был изменён слот, поле заголовка или сам slots_mac);
TAMPERED_CIPHERTEXT означает, что набор слотов остался цел, но тег AEAD содержимого
не сошёлся уже после восстановления ключа. Для этого локального вызывающего три
случая структурно различимы, и граница между ними не раскрывает ключевого материала.
Для недоверенного внешнего вызывающего все три схлопываются в единственный общий
отказ, описанный выше, неразличимый по форме ответа. По модели времени допустимый
ранний возврат при отсутствии совпадения отделяет WRONG_RECIPIENT_KEY
(неполучателя) от двух исходов подмены на стороне получателя, между собой более ничем
не различимых, — так что диагностическая точность никогда не превращается в оракул,
избирательный по слоту или по ключу.
Как коды отображаются на четыре вердикта
Код, выданный структурным валидатором, означает, что байты записи не соответствуют
стандарту. Публичный верификатор сразу замыкает отчёт вердиктом failed — выход 1 —
со списком проблем валидатора, не выполняя дальнейшей работы с цепочкой или
криптографией. Код, выдаваемый только после успешной структурной проверки — подпись,
которая не проверилась, относимый хеш, который не совпал, METADATA_NOT_FOUND на
привязанной транзакции, — тоже даёт failed: каждый из них относим к записи, это
доказательства, которые дают собственные байты записи.
Временный или относимый к поставщику сбой — это другой вердикт: содержимое, которое не
удалось получить или отнести, недоступный обозреватель или поставщик, отдавший байты,
проваливающие привязку к ссылке на транзакцию, — всё это отображается на
unverifiable (выход 2), поскольку ни один из этих случаев не является виной записи, и
та же запись при повторе может оказаться valid. Поэтому различие, которое сохраняет
код выхода, троично среди сбойных состояний: относимый к записи failed (1),
эксплуатационный или относимый к поставщику unverifiable (2) и pending ниже порога
(3).
Несколько кодов несут степень серьёзности, зависящую от контекста. Верификатор,
читающий запись богаче своего объявленного профиля (например, верификатор только по
хешу, столкнувшийся с запечатанным элементом), сообщает о лишнем поле как об info
в контексте показа и всё равно проверяет притязание о хеше, либо как об error
в контексте строгого сквозного аудита. Точно так же нераспознанный необязательный
алгоритм подписи — это info: притязание о существовании содержимого от него не
зависит, поэтому публичное доказательство только по хешу остаётся valid даже
тогда, когда вспомогательную подпись проверить невозможно.
Независимость от сервиса не является необязательной
Проверка никогда не обращается обратно к издателю. Каждый исходящий вызов, который
делает соответствующий стандарту верификатор, направлен на инфраструктуру, выбранную
оператором, — обозреватель Cardano для транзакции и шлюзы хранилища с адресацией по
содержимому для любых байтов ar:// или ipfs://. Это обеспечено структурно,
а не обещанием в комментарии к коду:
- Каждый сетевой вызов проходит через единую обёртку исходящего трафика, которая
записывает
url,method,status, число байтов и назначение для каждого вызова — будь то успех, сбой или повтор — в обязательный журнал аудита в отчёте. Верификатор, который не может предъявить такой журнал, не может доказать свою независимость. - Эта обёртка принимает заданный при развёртывании список deny-host и жёстко
обрывает любой вызов к совпадающему хосту с кодом
SERVICE_INDEPENDENCE_VIOLATION. Список — это вход оператора (набор тестов на соответствие наполняет его собственными доменами разработчика реализации), а не константа формата Label 309. - Стенд для проверки соответствия запускает верификатор на замороженных эталонных
транзакциях в сети, где собственные домены оператора не разрешаются ни во что, и
убеждается, что верификатор всё равно возвращает
valid. Утверждение делается на уровне сети ОС, а не путём поиска по исходникам — верификатор, который дотянулся бы до запрещённого хоста по жёстко зашитому IP, прошёл бы проверку исходного кода, но провалил бы этот тест.
Верификатору нужен достижимый обозреватель Cardano и ничего, специфичного для конкретной реализации. Шлюзы содержимого, закрытый ключ получателя и внецепочечный список листьев — это необязательные входы, которые открывают соответственно проверки содержимого, получателя и Merkle. Ни один из них не является сервисом, который называет стандарт, и ни один из них не является издателем.
Связанные страницы
- Запись — формат, который проверяет структурный валидатор: метка 309, форма map, сборка из частей и схема CDDL.
- Подписи — построение COSE_Sign1 на уровне записи, полезная нагрузка с доменным разделением и строгие правила проверки Ed25519.
- Sealed PoE — конверт шифрования, слоты ключей получателей и разворачивание, которое выполняет верификатор получателя.
Sealed PoE
Конверт шифрования Label 309 — как отправитель запечатывает содержимое для одного или нескольких ключей получателей, тогда как в блокчейн попадают только хеш открытого текста и обёрнутые слоты ключей, но никогда — сам открытый текст и никогда — идентичности получателей.
Модель безопасности
Чему доверяет верификатор Label 309 и чему не доверяет — инвариант автономной проверяемости, гарантии приватности запечатанного подтверждения существования, нормативные криптографические правила, которые обязана соблюдать каждая реализация, и известные пределы формата записи.