Это ознакомительный перевод. Нормативной является английская версия — при расхождениях верна она. Открыть английскую версию

Запись

Формат записи Label 309 на уровне передачи — где запись располагается под меткой метаданных 309, форма её map-объекта, правила канонического CBOR, транспортное разбиение на фрагменты и схема CDDL.

Запись Label 309 — это единственный CBOR map, размещённый в метаданных транзакции Cardano под меткой 309. Этот map закрепляет в блокчейне один или несколько хешей содержимого; время блока транзакции служит свидетельством того, что соответствующие байты существовали не позднее этого момента. Всё остальное, что может содержать запись, — URI для хранилищ, конверт шифрования, подписи авторства, указатель на замещение — это необязательные метаданные об этом основном утверждении.

На этой странице задаётся форма записи на уровне передачи: где располагается запись, как она кодируется, как передаются значения, превышающие допустимый размер, и какой замкнутой схеме её проверяет структурный валидатор. Криптографические конструкции, упоминаемые здесь (алгоритмы хеширования, запечатанный конверт, подписи), описаны на отдельных страницах; эта страница посвящена формату на уровне передачи.

Где располагается запись

Запись PoE ДОЛЖНА быть помещена под меткой метаданных транзакции 309, зарезервированной как «Proof of Existence record» в реестре меток метаданных CIP-10. Метаданные транзакции представляют собой map от целочисленной метки к значению, поэтому одна транзакция НЕ ДОЛЖНА содержать более одной записи PoE — ровно одна запись на транзакцию.

Транзакция МОЖЕТ содержать дополнительные метаданные под другими метками (например, сообщение CIP-20 под меткой 674). Верификатор, обрабатывающий PoE, ДОЛЖЕН игнорировать все метки, кроме 309.

В эпоху Conway метаданные транзакции представляют собой поле metadata внутри auxiliary_data транзакции. Значения, допустимые под любой меткой, ограничены рекурсивным типом metadatum в реестре: целые числа, байтовые строки, текстовые строки, массивы и map-объекты, причём как байтовые строки, так и текстовые строки ограничены 64 байтами каждая:

CDDL
metadatum =
    { * metadatum => metadatum }
  / [ * metadatum ]
  / int
  / bstr .size (0..64)
  / tstr .size (0..64)

Транзакция, содержащая хотя бы одну bstr или tstr длиннее 64 байт, отклоняется узлами Cardano при отправке — до того как её увидит какой-либо верификатор. Именно это ограничение является причиной того, что Label 309 определяет дисциплину транспортного разбиения на фрагменты (см. ниже): каждое поле записи — базовое или расширенное — должно сводиться к типу metadatum.

Передача: массив фрагментов тела целиком

Сериализованное тело записи обычно превышает 64 байта, поэтому его нельзя хранить под меткой 309 как голое значение. В силу этого тело записи передаётся в виде непрозрачного массива фрагментов тела целиком: единственного CBOR-массива байтовых строк размером ≤ 64 байта (bstr .size (1..64)), последовательная конкатенация которых и есть тело записи. Это транспортное разбиение — единственное, которое выполняет Label 309: единственный шаг формата, который действительно навязан реестром.

Поскольку реестр видит лишь этот транспортный массив и никогда — поля внутри восстановленного тела, эти поля являются обычными значениями CBOR без пофрагментных обёрток для каждого поля и без 64-байтного ограничения на уровне поля: URI хранилища — это единственная текстовая строка, COSE_Sign1 — единственная байтовая строка, а kem_ct для X-Wing — единственная байтовая строка длиной 1120 байт. Поле длиннее 64 байт просто проходит через границы фрагментов массива тела целиком, как и любой другой участок тела.

Производитель ДОЛЖЕН сериализовать тело записи в канонический CBOR однократно, разбить получившуюся байтовую строку на фрагменты от 1 до 64 байт и сохранить получившийся массив определённой длины из байтовых строк определённой длины в качестве значения метки 309. Форма массива обязательна всегда, даже для тела размером 64 байта и меньше: такое тело является массивом из одного элемента, но никак не голым map-объектом или байтовой строкой. Производителям СЛЕДУЕТ использовать минимальное разбиение (каждый фрагмент, кроме последнего, ровно по 64 байта) и НЕ СЛЕДУЕТ выпускать фрагменты нулевой длины; избыточное дробление расходует байты транзакции без какой-либо пользы.

Верификатор ДОЛЖЕН побайтово конкатенировать элементы массива по порядку для восстановления тела записи перед структурной валидацией и ДОЛЖЕН отклонять любое значение метки 309, которое не является таким массивом. Границы фрагментов не несут семантической нагрузки: два транспортных массива, конкатенации которых побайтово идентичны, обозначают одну и ту же запись. Таксономия ошибок передачи закрепляет коды отклонения — фрагмент длиннее 64 байт даёт CHUNK_TOO_LARGE; элемент массива, не являющийся байтовой строкой, массив или элемент неопределённой длины, а также значение метки 309, не являющееся массивом (голый map-объект, голая байтовая строка, целое число), дают MALFORMED_CBOR. Фрагмент нулевой длины не добавляет байтов и допускается — сам по себе он никогда не отклоняется.

Схема описывает восстановленное тело

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

Map записи

Восстановленное тело записи представляет собой CBOR map. Поля с целочисленными значениями имеют старший тип CBOR 0/1; текстовые поля — старший тип 3 и ДОЛЖНЫ быть корректным UTF-8; байтовые поля — старший тип 2; массивы — старший тип 4; вложенные map-объекты — старший тип 5. Необязательное поле, если оно присутствует, НЕ ДОЛЖНО содержать пустое значение.

Структура верхнего уровня:

КлючТипСтатусЗначение
vuintОБЯЗАТЕЛЬНОЕВерсия схемы; настоящий документ определяет v = 1.
itemsмассив item-mapНЕОБЯЗАТЕЛЬНОЕОбязательства по содержимому — см. Содержимое и хеширование.
merkleмассив обязательствНЕОБЯЗАТЕЛЬНОЕОбязательства списка, связывающие внеблокчейновые списки листьев с одним корнем.
supersedesbytes (32)НЕОБЯЗАТЕЛЬНОЕХеш транзакции с предыдущей записью, которую замещает данная.
sigsмассив map-объектов подписейНЕОБЯЗАТЕЛЬНОЕПодписи авторства на уровне записи — см. Подписи.
critмассив текстовых строкНЕОБЯЗАТЕЛЬНОЕКлючи расширений, которые обязательно должны быть поняты.

Соответствующая требованиям запись ДОЛЖНА содержать обязательство хотя бы в одном из полей: items (с ≥ 1 элементом) или merkle (с ≥ 1 элементом). Запись, не содержащая ни того ни другого, либо содержащая одно из них как пустой массив, отклоняется как пустая запись. При этом items и merkle ортогональны: запись может содержать только одно из них или оба одновременно.

Label 309 не накладывает числового ограничения на количество элементов. Единственный потолок — действующий максимальный размер транзакции Cardano, а производители платят побайтовые комиссии, которые естественным образом ограничивают размер записи. Валидатор НЕ ДОЛЖЕН отклонять запись исключительно из-за большого числа элементов, если она укладывается в ограничение реестра на размер транзакции.

Поле версии

v — это целое число без знака в кодировке CBOR, а не строка с семантической версией. Настоящий документ определяет ровно v = 1. Валидатор ДОЛЖЕН отклонять запись, значение v которой выходит за пределы поддерживаемого набора, с типизированной ошибкой; он НЕ ДОЛЖЕН аварийно завершаться, прерываться или молча интерпретировать запись как относящуюся к другой схеме метаданных. Целое число v увеличивается только тогда, когда изменение может привести к тому, что парсер версии v1 неверно интерпретирует запись; аддитивные расширения с пространством имён не увеличивают его.

Items

Каждый элемент в items — это CBOR map с одним обязательным и двумя необязательными полями:

  • hashes — ОБЯЗАТЕЛЬНОЕ, непустой map от идентификатора алгоритма хеширования к 32-байтному дайджесту. Минимум один элемент; дублирующиеся алгоритмы невозможны, поскольку ключи в CBOR map уникальны. См. Содержимое и хеширование.
  • uris — НЕОБЯЗАТЕЛЬНОЕ, список URI для обнаружения содержимого (правила приведены ниже).
  • enc — НЕОБЯЗАТЕЛЬНОЕ, конверт шифрования для запечатанного элемента. См. Sealed PoE.

Отдельного слота подписи для элементов не предусмотрено. Авторство выражается только на уровне записи — через элемент sigs[], охватывающий все элементы единообразно.

Обязательства Merkle

Каждый элемент в merkle связывает запись с упорядоченным списком 32-байтных листьев посредством канонической конструкции хеш-дерева, так что один 32-байтный корень в блокчейне может заменять произвольно большой внеблокчейновый список листьев. Обязательство — это закрытый map:

ПолеТипСтатусЗначение
algtstrОБЯЗАТЕЛЬНОЕЗарегистрированный идентификатор алгоритма обязательства списка.
rootbytes (32)ОБЯЗАТЕЛЬНОЕКанонический корень над упорядоченным списком листьев производителя.
leaf_countuintОБЯЗАТЕЛЬНОЕКоличество зафиксированных листьев; привязывает корень к размеру списка.
urisсписок URIНЕОБЯЗАТЕЛЬНОЕURI с адресацией по содержимому для внеблокчейнового файла со списком листьев.

Корень Merkle фиксирует структуру списка листьев, тогда как элемент hashes фиксирует открытый текст байтов; эти два вида верифицируются по-разному (доказательство включения в сравнении с пересчётом открытого текста), поэтому обязательства списка располагаются на верхнем уровне, а не внутри элемента. Реестр обязательств списка не пересекается с реестром хешей содержимого — см. Реестры алгоритмов.

Supersedes

supersedes — необязательный 32-байтный хеш транзакции Cardano, указывающий на одну более раннюю запись Label 309. Это независимая от сервисов ссылка только для добавления: более поздняя запись может указывать на предыдущую без офлайн-базы данных и идентификаторов записей поставщика.

Замещение не удаляет, не отзывает и не аннулирует предыдущую запись — блокчейн работает только на добавление, и верификаторы ДОЛЖНЫ продолжать считать более раннюю запись существующей и независимо проверяемой. Указатель не содержит полей с причиной или свободным текстом; любой смысл для человека (исправление, замена, отзыв) должен находиться в новом содержимом, а не в метке 309. Верификатор, разрешающий указатель, ДОЛЖЕН искать его в той же сети Cardano, что и содержащая транзакция; поле не содержит дискриминатора сети, поскольку хеш транзакции уникален только в рамках своей сети.

Подписи

sigs — необязательный массив записей подписи на уровне записи. Каждая запись содержит отдельную структуру COSE_Sign1 поверх тела записи — то есть полного map записи с удалённым полем sigs — и, необязательно, открытый ключ подписывающего для пути подписи через кошелёк. Одна подпись удостоверяет всё тело: каждый элемент, каждый URI, каждый конверт, указатель замещения (если присутствует) и любые ключи расширений. Подписи всегда необязательны, и нераспознанный алгоритм подписи никогда не аннулирует утверждение о содержимом. Подписываемая полезная нагрузка, префикс разделения домена, разрешение ключа подписывающего и строгие правила проверки описаны на странице Подписи.

Правила URI

Если поле uris присутствует, оно является непустым списком; каждый элемент — это единственная текстовая строка CBOR, несущая ровно один URI. Ограничения на длину отдельного URI нет, как нет и обрамляющей формы: транспорт тела целиком уже удовлетворяет 64-байтному ограничению реестра на строки, поэтому длинный URI вида ipfs://<CIDv1>/<path> — это одна текстовая строка, как и любая другая. Каждый URI ДОЛЖЕН быть абсолютным, ДОЛЖЕН содержать схему и иерархическую часть и НЕ ДОЛЖЕН содержать идентификатор фрагмента — PoE является утверждением о байтах содержимого, а не о компоненте документа.

В версии v1 набор схем закрыт и ориентирован на адресацию по содержимому:

СхемаПримечания
ar://Идентификатор транзакции Arweave (43 символа в base64url). Форма ar://<txid>.
ipfs://IPFS CID, предпочтителен CIDv1. Форма ipfs://<cid> или ipfs://<cid>/<path>.

Производители НЕ ДОЛЖНЫ использовать другие схемы — https://, http://, file://, data: и прочие — все они отклоняются. Это ограничение намеренное, а не временное: URI с адресацией по содержимому привязывает полученные байты к самому URI через модель целостности слоя хранения (IPFS CID является мультихешем содержимого; идентификатор транзакции Arweave фиксирует данные под консенсусом Arweave), поэтому верификатор может подтвердить «байты, которые я получил, — это байты, зафиксированные производителем», не доверяя DNS, TLS, шлюзам или центрам сертификации. Схема, не входящая в разрешённый набор, делает запись структурно недействительной; такая запись никогда не валидируется как valid.

uris необязательно везде. Запись, содержащая только хеш и не имеющая поля uris, является полным утверждением — факт существования содержимого фиксируется без указания канала получения. Точный профиль CID (допустимые префиксы multibase, кодеки и мультихеши) является частью правил проверки; см. Проверка.

Канонический CBOR

Каждая запись Label 309 ДОЛЖНА быть закодирована в каноническом CBOR в соответствии с RFC 8949 §4.2.1 (Core Deterministic Encoding). Конкретно:

  1. Предпочтительная (кратчайшая) сериализация для каждого целого числа.
  2. Кодирование с определённой длиной для всех байтовых строк, текстовых строк, массивов и map-объектов.
  3. Никаких семантических тегов (настоящий документ не требует ни одного — тег bignum 2/3 НЕ ДОЛЖЕН появляться).
  4. Ключи map-объектов, отсортированные в байтовом лексикографическом порядке их CBOR-кодирования.
  5. Текстовые строки UTF-8 без метки порядка байтов (BOM).
  6. Никаких дублирующихся ключей ни в одном map-объекте.
  7. Никаких значений с плавающей запятой или нетривиальных простых значений — запись содержит только целые числа, байтовые строки, текстовые строки, массивы, map-объекты и (там, где это допускает схема) true/false/null. Числа с плавающей запятой старшего типа 7 (включая целое 1.0), отрицательный ноль и undefined ДОЛЖНЫ быть отклонены, а не приведены к другому типу.

Детерминированность обеспечивает совместимость формата: два производителя, выражающие одну и ту же логическую запись, выводят побайтово идентичные байты, поэтому подпись, вычисленная над телом одной реализацией, проверяется другой. Валидатор ДОЛЖЕН отклонять неканоническое кодирование. Обозреватели и кошельки могут представлять метаданные через JSON-проекцию, однако совместимый верификатор ДОЛЖЕН валидировать исходный CBOR транзакции, а не его CBOR-перекодировку с потерями через JSON.

Прямая совместимость

Label 309 v1 резервирует закрытый набор базовых ключей: v, items, merkle, supersedes, sigs, crit. Запись МОЖЕТ дополнительно содержать ключи расширений, имена которых соответствуют одному из двух зарезервированных пространств имён:

  • ^x-.+ — пространство имён поставщика / экспериментальное.
  • ^[a-z]+-.+ — пространство имён сопутствующей спецификации, где префикс обозначает регистрирующую спецификацию.

Валидатор ДОЛЖЕН декодировать и сохранять ключи расширений, НЕ ДОЛЖЕН отклонять запись только из-за их наличия и ДОЛЖЕН представлять их как информационные, не заявляя о проверке их содержимого. Ключи расширений являются частью подписанного тела, поэтому подпись на уровне записи охватывает и их — ретранслятор не может добавить ключ расширения после того, как подпись была сформирована. Любой неизвестный ключ верхнего уровня, не соответствующий ни одному из шаблонов (опечатка наподобие supersedess или вариант с другим регистром, например Sigs), отклоняется как неизвестное поле. Допуск на основе шаблонов сохраняет обнаружение опечаток в базовом наборе, одновременно обеспечивая стабильный пул для будущих дополнений.

Производитель, требующий от верификатора понимания небазового поля, ДОЛЖЕН перечислить имя этого поля в массиве crit верхнего уровня. Верификатор v1, встретивший элемент crit, который он не реализует, НЕ ДОЛЖЕН сообщать о записи как о действительной. Каждый элемент crit ДОЛЖЕН соответствовать шаблону ключа расширения (базовые ключи в crit запрещены), ДОЛЖЕН указывать поле, фактически присутствующее в записи, и ДОЛЖЕН быть уникальным — так что критическая метка всегда отслеживается до конкретного поля, семантику которого верификатор обязан понимать. Эти правила следуют прецедентам must-understand / must-ignore из RFC 9052 §3.1 (COSE crit) и RFC 7515 §4.1.11 (JWS crit).

Байтовый бюджет

Единственным жёстким ограничением на размер записи является действующий параметр протокола Cardano maxTxSize — 16 384 байта при основной версии протокола 10 в основной сети, с учётом возможных обновлений параметров реестра. Label 309 не вводит ограничения на уровне схемы ниже этого значения. Записи, превышающие ограничение, отклоняются узлами Cardano при отправке, поэтому ни один верификатор их не видит; валидатор НЕ ДОЛЖЕН придумывать специфическое для Label 309 ограничение ниже maxTxSize.

На практике структуры транзакции, не являющиеся метаданными (входы, выходы, свидетели, поля комиссии и срока действия), занимают примерно 245 байт, оставляя порядка 16 КБ для записи label-309. Производителям СЛЕДУЕТ ориентироваться на значение на несколько сотен байт ниже ограничения, чтобы компенсировать разброс комиссий, и СЛЕДУЕТ вычислять размер кандидата записи перед отправкой, завершая работу с ошибкой, если он не поместится. Реалистичные конфигурации, укладывающиеся в одну транзакцию, весьма щедры: более ста элементов с одним хешем, десятки подписей на уровне записи или множество классических слотов получателей — всё это легко помещается в одну транзакцию, а один корень Merkle фиксирует неограниченный внеблокчейновый список листьев при фиксированных ончейн-затратах в 32 байта.

Схема CDDL

Приведённая ниже CDDL является структурной схемой для восстановленного тела записи — байт канонического CBOR, полученных после конкатенации массива фрагментов длиной ≤ 64 байта, хранящегося под меткой 309. Восстановленное тело — это обычный детерминированный CBOR: оно само по себе не является metadatum реестра, и его поля не подпадают под 64-байтное ограничение на строки, которому удовлетворяет уже одна лишь транспортная обёртка тела целиком. Сама обёртка здесь не моделируется.

Блок описывает допустимое надмножество корректно сформированных структур; кросс-полевые инварианты (правило items-или-merkle, взаимоисключение slotspassphrase конверта шифрования, принадлежность идентификаторов алгоритмов реестру, правила формы слота для каждого KEM) применяются типизированным проходом валидации над декодированной структурой, а не самой CDDL.

CDDL
; An extension value is any CBOR value the canonical (deterministic) encoding
; profile admits. Floats and semantic tags are excluded by that profile (they
; are rejected as MALFORMED_CBOR on decode), so the exclusion is not repeated
; here; the reassembled body carries no field-level 64-byte cap.
extension-value =
    { * extension-value => extension-value }
  / [ * extension-value ]
  / int
  / bstr
  / tstr
  / bool
  / null

; A conformant record MUST carry at least one of `items` (>= 1 entry) or
; `merkle` (>= 1 entry); a record with both absent (or both empty) is rejected
; as SCHEMA_EMPTY_RECORD by the typed pass, not at the CDDL layer.
poe-record = {
  poe-common,
  ? "items": [ 1* item-entry ],
  ? "crit":  [ 1* tstr ],
  * extension-key => extension-value
}

poe-common = (
  "v": 1,
  ? "merkle": [ 1* merkle-commit ],
  ? "supersedes": bytes32,
  ? "sigs": [ 1* sig-entry ],
)

extension-key = tstr .regexp "^x-.+"
              / tstr .regexp "^[a-z]+-.+"

item-entry = {
  "hashes": hash-map,
  ? "uris": [ 1* uri ],
  ? "enc": enc,
}

; A non-empty CBOR map keyed by a content-hash algorithm identifier with the
; 32-byte digest as value. Map-key uniqueness makes duplicate algorithms
; structurally impossible.
hash-map = { + content-hash-alg => bytes32 }

; A list commitment binds the record to an ordered leaf list. `leaf_count`
; binds the on-chain commitment to the off-chain list size.
merkle-commit = {
  "alg":        merkle-commit-alg,
  "root":       bytes32,
  "leaf_count": uint32,
  ? "uris":     [ 1* uri ],
}

; `enc` is a choice between the scheme-1 envelope shape and a bounded opaque
; envelope (the degrade-to-opaque rule for an unsupported scheme/kem/aead). The
; typed pass enforces the slots/passphrase exclusivity and the per-KEM
; slot-shape rules over a supported envelope.
enc = enc-scheme-1 / enc-opaque

; `scheme: 1` is not a version counter for the `enc` map alone: it names the
; ENTIRE sealed cryptographic suite — the canonicalEncode rules, the slot
; schema, the HKDF and HMAC hashes, the wrap AEAD, the segmented-STREAM content
; format, the transcript schemas, the in-ciphertext passphrase commitment, the
; pinned X-Wing revision, every domain-separation label, and the Argon2id and
; passphrase-normalization profiles. Changing any one of them requires a new
; `scheme` value; see Sealed PoE for the construction it pins.
enc-scheme-1 = {
  "scheme": 1,
  "aead":   aead-alg,
  "nonce":  bstr,
  ? "kem":        kem-alg,
  ? "slots":      [ 1* slot ],
  ? "slots_mac":  bytes32,
  ? "passphrase": passphrase-block,
}

; The opaque reading of an envelope under an unsupported identifier: `scheme`
; is the only structurally required key, and every other entry is any key/value
; pair the canonical profile admits, subject to the generic decode bounds.
enc-opaque = {
  "scheme": uint,
  * tstr => extension-value
}

slot = classical-slot / hybrid-slot

; enc.kem = "x25519": the per-slot X25519 ephemeral public key + wrapped CEK.
classical-slot = {
  "epk":  bytes32,
  "wrap": bytes48,
}

; enc.kem = "mlkem768x25519": the 1120-byte X-Wing ciphertext plus the wrapped
; CEK. There is NO `epk` — the X25519 ephemeral is the trailing 32 bytes of the
; X-Wing ciphertext inside `kem_ct`.
hybrid-slot = {
  "kem_ct": bstr .size 1120,
  "wrap":   bytes48,
}

passphrase-block = {
  "alg":    kdf-alg,
  "salt":   bstr .size (16..64),
  "params": { "m": uint32, "t": uint32, "p": uint32 },
}

; A signature entry is a closed map. `cose_sign1` is REQUIRED and carries the
; CBOR-encoded COSE_Sign1 as a single byte string; `cose_key` is OPTIONAL and
; carries the CBOR-encoded COSE_Key for the wallet-signing path as a single
; byte string.
sig-entry = {
  "cose_sign1":  bstr,
  ? "cose_key":  bstr,
}

; A uri is one absolute URI in a single text string. The URI shape rules
; (absolute, no fragment, closed scheme set {ar://, ipfs://}) are enforced in
; the typed pass; the rule carries no length cap.
uri = tstr

bytes32 = bstr .size 32
bytes48 = bstr .size 48

; uint32 is the pinned range of every numeric field: an unsigned integer
; representable in 4 bytes (0 .. 2^32-1), handled as an exact integer.
uint32 = uint .size 4

; Algorithm-identifier strings are open `tstr`: the registries are
; authoritative for accepted values, and the typed pass emits the precise
; unsupported-algorithm code for any unrecognised identifier.
content-hash-alg   = tstr  ; e.g. "sha2-256", "blake2b-256"
merkle-commit-alg  = tstr  ; e.g. "rfc9162-sha256"
aead-alg           = tstr
kem-alg            = tstr
kdf-alg            = tstr

Связанные страницы

  • Содержимое и хеширование — map hashes, что фиксирует дайджест и семантика точного совпадения байтов.
  • Реестры алгоритмов — именованные идентификаторы для хешей, обязательств списков, AEAD, KEM и KDF.
  • Подписи — конструкция sigs на уровне записи и её проверка.
  • Sealed PoE — конверт enc и слоты ключей получателей.
  • Проверка — конвейер валидации, профиль CID и каталог ошибок.