Руководства · Часть 6 из 6
Сертификаты включения
Вы опубликовали корень Merkle под Label 309 и стоящий за ним офлайн-список листьев. Этот корень — вполне надёжное обязательство, но это не то, что можно вручить коллеге, приложить к договору или подшить к судебному заявлению. Именно такую передачу и обеспечивает сертификат включения: небольшой самодостаточный файл, который привязывает один или несколько листьев к опубликованному корню, встраивает каждый соседний узел, необходимый для повторного вывода этого корня, и называет транзакцию Cardano, время блока которой свидетельствует обо всём этом. Любой может проверить его офлайн, относительно любого эксплорера, без аккаунта и без какого-либо доверия к тому, кто его создал.
Если вам знаком OpenTimestamps — это та же идея, переносимая квитанция о
подтверждении существования, с двумя сознательными отличиями.
Авторитет-источник времени — это время блока блокчейна Cardano, а не
сервер-календарь. И доказательство создаётся и проверяется полностью на стороне
клиента: ни шлюза, ни сервера издателя, ни на одном шаге доверия к нам. Это
аналог файла .ots для подтверждения существования, закреплённый в Cardano и
проверяемый самостоятельно.
Что сертификат доказывает — и чего не доказывает
Сертификат делает ровно два криптографических утверждения, каждое из которых любой может проверить независимо:
- Включение. Заданный
leafзанимает позициюindexв SHA-256-дереве Merkle по RFC 9162 размераtree_size, корнем которого являетсяroot. Это доказывается повторным вычислением корня из листа, его индекса, размера дерева и встроенного пути из соседних узлов. Это самодостаточно — достаточно одного лишь файла сертификата. - Закрепление. Этот
rootдословно присутствует в полеmerkle[].rootзаписи Label 309, которую несёт транзакцияtx_hash. Это доказывается чтением этой транзакции в любом публичном эксплорере Cardano и сравнением байтов. Для этого нужен файл сертификата плюс эксплорер — и больше ничего.
Вместе они доказывают, что содержимое листа существовало в момент или до
времени блока tx_hash.
Чего сертификат НЕ доказывает
Время утверждается публичным блокчейном, а не привязано криптографически к самому доказательству — ровно как в OpenTimestamps и Chainpoint, вы доверяете времени блока цепочки, а не тому, кто создал сертификат. Сертификат — это не eIDAS-«квалифицированная» электронная метка времени (RFC 3161 / квалифицированный TSA); это метка времени, закреплённая в блокчейне, — весомое подкрепляющее свидетельство временного утверждения, в той же категории, что и другие метки времени на блокчейне, и формулировка в файле говорит именно это. Он ничего не сообщает о том, кто создал содержимое (это необязательная подпись записи — см. Подписи), и не доказывает, что содержимое не было известно раньше. Он доказывает существование к определённому сроку, а не авторство и не новизну.
Почему доказательство не подписано
Строгая квитанция COSE по IETF — это COSE_Sign1, полезной нагрузкой которого
служит корень Merkle, подписанный некоторым авторитетом. Здесь авторитет — это
блокчейн, а не ключ, которым владеем мы, — поэтому подписание корня нашим
ключом снова ввело бы доверие к серверу и сломало бы свойство самостоятельной
проверяемости, на котором держится весь стандарт. Вместо этого сертификат
выдаёт CBOR-структуру доказательства включения по IETF ровно так, как
специфицировано, и несёт якорь блокчейна вместо подписи. Математика
доказательства побайтно совпадает с кодировкой IETF; оно намеренно не подписано
и закреплено в блокчейне.
JSON-сертификат
Основной артефакт — это label-309-inclusion-certificate-v1: JSON-файл,
читаемый и человеком, и машиной. Один файл охватывает один ИЛИ много листьев, и
каждый элемент встраивает свой полный путь из соседних узлов, так что файл
проверяется заново навсегда — без обращения к Arweave, без шлюза, без
первоначального публикатора.
{
"format": "label-309-inclusion-certificate-v1",
"generated_at": "2026-06-16T12:00:00.000Z", // informational only, never trusted
"anchor": {
"chain": "cardano",
"network": "mainnet",
"tx_hash": "…64hex…",
"metadata_label": 309,
"block_time": 1781611200, // POSIX seconds — explorer-asserted
"block_time_iso": "2026-06-16T12:00:00.000Z",
"block_height": 12345678, // optional; explorer-asserted
"explorer_urls": [
"https://cardanoscan.io/transaction/…",
"https://adastat.net/transactions/…"
]
},
"merkle": {
"tree_alg": "rfc9162-sha256",
"root": "…64hex…",
"tree_size": 1024, // === the on-chain leaf_count
"leaves_list_uri": "ar://<txid>" // optional source reference
},
"items": [
{
"leaf": "…64hex…", // the content hash committed as a leaf
"leaf_alg": "sha2-256", // how to hash a file to reproduce `leaf`
"index": 42,
"proof": ["…64hex…", "…64hex…"], // siblings, leaf→root; [] for a single-leaf tree
"verified": true, // proof recomputes to merkle.root at build time
"label": "contract.pdf" // optional note/filename
}
],
"claim": "Each listed hash was included in a Merkle tree whose root was published on the Cardano blockchain in the referenced transaction under metadata label 309; therefore each hash provably existed on or before the stated block time.",
"verification": {
"method": "RFC 9162 (Certificate Transparency) SHA-256 inclusion proof. For each item, recompute the Merkle root from leaf+index+tree_size+proof and compare to merkle.root; then confirm merkle.root equals the merkle[].root in the label 309 record of anchor.tx_hash on any public Cardano explorer.",
"requires_trust_in_cardanowall": false,
"time_asserted_by": "Cardano blockchain (block time), via public explorers"
}
}Несколько деталей, которые стоит знать:
root,leafи каждый элементproof[]— это сырые 32-байтовые значения, представленные в hex. Создатели выдают строчные буквы; верификатор принимает любой регистр и отвергает любой не-hex-символ или строку нечётной длины.- Сохранённое
"verified": true— это результат создателя на момент сборки. Верификатор никогда ему не доверяет — он пересчитывает доказательство сам и выносит собственный вердикт. Лист, который не был найден в дереве, записывается с"verified": falseи полем"error", а не тихо отбрасывается, — так файл остаётся честным насчёт промахов. block_time— это POSIX-метка времени включающего блока, утверждаемая эксплорером.block_time_iso— её представление в UTC, только для удобства.
CBOR-доказательство включения (согласованное с COSE / RFC 9162)
Помимо JSON, каждый элемент можно экспортировать как компактный артефакт
.cbor, структура доказательства которого побайтно идентична
draft-ietf-cose-merkle-tree-proofs.
Это означает, что любой верификатор проверяемых структур данных RFC 9162 /
COSE может прочитать математику доказательства напрямую — ядро
совместимости стандартное, а не самописное. Он не несёт ни абсолютного времени
блока, ни юридического текста (это живёт в JSON); это лишь переносимое ядро
доказательства, и оно закреплено в блокчейне и не подписано по причине, указанной
выше.
Голое доказательство включения по IETF — bstr .cbor [tree_size, leaf_index, inclusion_path], значение 1 проверяемой структуры данных (vds) для RFC 9162
SHA-256 — извлекается само по себе для чистого COSE-верификатора; якорь Cardano
несётся рядом как небольшая map вместо подписи Sign1.
Сборка и проверка с помощью инструментов
Формат сертификата — часть публичного, не привязанного к шлюзу инструментария:
SDK @cardanowall/sdk-ts (с побайтными близнецами на Python и Rust),
cardanowall CLI и поверхности верификации в приложениях. Математика сборки и
проверки чистая и офлайн — сеть затрагивает лишь получение свежих фактов из
блокчейна.
Через CLI
certificate build принимает список листьев, цели (сырой hex листа или файлы
для хеширования) и транзакцию, факты-якорь которой он разрешает.
certificate verify заново выполняет доказательство включения для каждого
элемента и печатает якорь, который вам ещё предстоит подтвердить в блокчейне:
# Build a certificate for two files against a published root.
cardanowall certificate build \
--leaves-list leaves.cbor \
--tx <tx-hash> \
--file contract.pdf --file exhibit-a.png \
--out contract.cert.json
# Re-verify the proofs offline — no network, no trust in the producer.
cardanowall certificate verify contract.cert.jsonКод завершения 0 означает, что доказательство каждого элемента пересчитывается
в корень; ненулевой код сигнализирует о провале включения, неверном вводе или
ошибке ввода-вывода — так что он сразу встраивается в CI. Каждый отдельный
элемент можно также извлечь в каноническую форму
{ tree_alg, tree_size, index, leaf, proof[] } и проверить через
cardanowall merkle verify.
Через TypeScript SDK
API certificate чистый — вы получаете байты списка листьев собственным
fetch платформы и передаёте их внутрь; криптографический путь никогда не
обращается к сети:
import { certificate, merkle } from '@cardanowall/sdk-ts';
// `leaves` comes from decodeLeavesList(...) over the fetched leaves-list bytes.
const cert = certificate.buildInclusionCertificate({
anchor, // chain facts resolved from the tx
merkle: { treeAlg: 'rfc9162-sha256', root, treeSize: leaves.length },
leaves,
targets: [{ leaf, leafAlg: 'sha2-256', label: 'contract.pdf' }],
});
// Pure re-verification from the certificate alone — no Arweave, no chain.
const result = certificate.verifyInclusionCertificate(cert);
console.log(result.ok); // true when every item's proof recomputes to root
console.log(result.anchorClaim); // the anchor you confirm on a public explorer
// Each item also re-verifies through the plain Merkle predicate.
const item = cert.items[0];
const ok = merkle.merkleSha2256VerifyInclusion(
leafBytes, item.index, cert.merkle.tree_size, proofBytes, rootBytes,
);verifyInclusionCertificate сообщает вердикт по доказательству и возвращает
заявленный якорь; подтвердить этот якорь в блокчейне — ваш отдельный, явный
шаг, и объект результата прямо об этом говорит. Тот же модуль побайтно
отражён в SDK на Python (certificate) и Rust (certificate).
В браузере, на странице транзакции
Запись Label 309, несущая обязательство merkle[], показывает панель включения
на своей странице транзакции. Вставьте один или несколько hex-хешей или
перетащите исходные файлы, чтобы захешировать их на стороне клиента; страница
получает список листьев прямо из контентно-адресуемого хранилища в вашем
браузере, пересчитывает каждое доказательство, показывает зелёный/красный
вердикт по каждому элементу и предлагает скачать JSON, CBOR и пригодный для
печати PDF. PDF встраивает полный JSON как вложенный файл, так что он сам и есть
машинно-проверяемый артефакт, а не просто его изображение. Ничто из этого не
затрагивает приватный сервер.
Алгоритм проверки, от начала до конца
Чтобы проверить сертификат независимо — в собственном коде, вообще без инструментов от нас, — сделайте ровно следующее:
- Отвергните искажённые поля. Каждый элемент
root/leaf/proof[]должен быть hex чётной длины, декодирующимся в 32 байта;tree_sizeи каждыйindexдолжны быть безопасными целыми сindex < tree_sizeи1 ≤ tree_size ≤ 2³² − 1. - Пересчитайте корень каждого элемента. По RFC 9162 §2.1.3.2 сверните лист
(
leaf = SHA-256(0x00 ‖ leaf_digest)) с его путём из соседних узлов (node = SHA-256(0x01 ‖ L ‖ R)), разбивая на наибольшей степени двойки строго ниже текущего размера поддерева, и сравните результат сmerkle.rootбайт за байтом. У дерева из одного листа доказательство пустое. - Подтвердите якорь в блокчейне. Получите
anchor.tx_hashв любом публичном эксплорере Cardano, прочитайте его метаданные Label 309 и подтвердите, чтоmerkle.rootравенmerkle[].rootзаписи. Время блока, которое вы там прочитаете, — это та метка времени, которую утверждает сертификат.
Шаги 1–2 — самодостаточная часть, они никогда не покидают вашу машину. Шаг 3 — единственное чтение из сети, и оно идёт к эксплореру по вашему выбору, никогда — к тому, кто создал сертификат.
Один файл, проверяемый навсегда
Поскольку каждый соседний узел на пути встроен, сертификат продолжает проверяться ещё долго после того, как список листьев, исходный шлюз или создатель исчезли. Две проверки — пересчитать корень из файла, подтвердить корень в блокчейне — это всё, что когда-либо кому-то понадобится. О том, как корень и список листьев создаются изначально, читайте в разделе Пакетная обработка через Merkle, а полную модель верификатора, в которую вписывается проверка на стороне блокчейна, — в разделе Проверка.