ガイド · 全6部中 第6部
包含証明書
あなたは label 309 のもとで Merkle ルートを発行し、それを裏付けるオフチェーンのリーフ一覧も持っています。そのルートは申し分のないコミットメントですが、同僚に手渡したり、契約書に添付したり、裁判の提出書類に入れたりできるものではありません。包含証明書こそが、その受け渡しのためのものです。小さく自己完結したファイルで、1つ以上のリーフを発行済みのルートに固定し、そのルートを導き直すのに必要な兄弟ノードをすべて埋め込み、そのすべてをブロック時刻で証言する Cardano トランザクションを名指しします。だれでもオフラインで、任意のエクスプローラーに対して、アカウントなしで、そしてそれを作った当人を信頼することなく、検証できます。
OpenTimestamps をご存じなら、これは同じ発想です。持ち運び可能な存在証明のレシートであり、意図的な2つの違いがあります。タイムスタンプの権威は、カレンダーサーバーではなく Cardano ブロックチェーンのブロック時刻です。そして証明の生成と検証は、完全にクライアント側で行われます。ゲートウェイも、発行者のサーバーも、どの段階でも私たちへの信頼も要りません。これは .ots ファイルにあたる存在証明であり、Cardano 上に記録され、単独で検証できます。
証明書が証明すること、そして証明しないこと
証明書はちょうど2つの暗号学的な主張を行い、そのいずれもだれでも独立に確かめられます。
- 包含。ある
leafが、大きさtree_size・ルートがrootの RFC 9162 SHA-256 Merkle ツリーの位置indexにあること。これは、リーフ・そのインデックス・ツリーの大きさ・埋め込まれた兄弟パスからルートを再計算することで証明されます。これは自己完結しており、証明書ファイルだけで十分です。 - アンカリング。その
rootが、トランザクションtx_hashの運ぶ label 309 レコードのmerkle[].rootフィールドに一字一句そのまま現れること。これは、そのトランザクションを任意の公開 Cardano エクスプローラーで読み、バイト単位で比較することで証明されます。必要なのは証明書ファイルとエクスプローラーだけで、それ以外には何も要りません。
この2つを合わせると、そのリーフの内容が tx_hash のブロック時刻かそれ以前に存在していたことが証明されます。
証明書が証明しないこと
時刻は公開ブロックチェーンによって主張されるものであって、証明の中に暗号学的に結び付けられているわけではありません。OpenTimestamps や Chainpoint とまったく同じく、あなたが信頼するのはチェーンのブロック時刻であって、証明書を作った者では決してありません。証明書は eIDAS の「適格」電子タイムスタンプ(RFC 3161 / 適格 TSA)ではありません。ブロックチェーンに記録されたタイムスタンプであり、時刻に関する主張を裏付ける強力な証拠で、他のブロックチェーンタイムスタンプと同じ部類に属します。ファイル中の文言もまさにそう述べています。それは内容をだれが書いたかについては何も語りません(それは任意のレコード署名であり、署名を参照してください)。また、その内容がそれ以前に知られていなかったことを証明するものでもありません。証明するのは期限までの存在であって、著作者でも新規性でもありません。
なぜこの証明には署名がないのか
厳密な IETF COSE Receipt は、ペイロードが Merkle ルートであり、何らかの権威によって署名された COSE_Sign1 です。ここでの権威はブロックチェーンであって、私たちが持つ鍵ではありません。ですからルートを私たちの鍵で署名すれば、サーバーへの信頼を再び持ち込むことになり、標準全体が拠って立つ「単独で検証できる」という性質を壊してしまいます。そこで証明書は、IETF の包含証明の CBOR 構造を仕様どおりそのまま出力し、署名の位置にブロックチェーンのアンカーを据えます。証明の数学は IETF の符号化とバイト単位で同一です。これは意図的に署名なしとし、ブロックチェーンに記録するものです。
JSON 証明書
主たる成果物は label-309-inclusion-certificate-v1 です。人にも機械にも読める JSON ファイルです。1つのファイルが1つでも多数でもリーフをカバーし、各項目は完全な兄弟パスを埋め込んでいるので、ファイルは永久に再検証できます。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[]の各要素は、16進で表記された生の32バイト値です。生成側は小文字で出力します。検証側は大文字・小文字のどちらも受け入れ、16進でない文字や長さが奇数の文字列は拒否します。- 保存された
"verified": trueは、生成側がビルド時に得た結果です。検証側がこれを信頼することはありません。検証側は証明を自分で再計算し、自分自身の判定を報告します。ツリーの中に見つからなかったリーフは"verified": falseと"error"フィールドとともに記録され、黙って捨てられることはありません。こうしてファイルは取りこぼしについて正直であり続けます。 block_timeは、これを含むブロックの、エクスプローラーが主張する POSIX タイムスタンプです。block_time_isoはその UTC 表記で、便宜上のものにすぎません。
CBOR 包含証明(COSE / RFC 9162 準拠)
JSON とは別に、各項目を、証明構造が draft-ietf-cose-merkle-tree-proofs とバイト単位で同一になる、コンパクトな .cbor 成果物として書き出すこともできます。これは、RFC 9162 / COSE の検証可能データ構造に対応するどの検証器でも、証明の数学をそのまま読めることを意味します。相互運用の核は独自仕様ではなく標準です。これは絶対ブロック時刻も法的な文言も持ちません(それらは JSON にあります)。これは持ち運び可能な証明の核そのものであり、上に述べた理由からブロックチェーンに記録され署名はありません。
素の IETF 包含証明、すなわち bstr .cbor [tree_size, leaf_index, inclusion_path](RFC 9162 SHA-256 に対する検証可能データ構造(vds)の値は 1)は、純粋な COSE 検証器のためにそれ単体で取り出せます。Cardano のアンカーは、Sign1 署名の位置に置かれる小さなマップとして、そのかたわらに添えられます。
ツールを使った構築と検証
証明書フォーマットは、公開された、ゲートウェイに依存しないツール群の一部です。@cardanowall/sdk-ts SDK(Python と Rust のバイト単位で一致する双子を伴います)、cardanowall CLI、そして各アプリの検証用インターフェースです。構築と検証の数学は純粋でオフラインです。ネットワークに触れるのは、新しいチェーンの事実を解決するときだけです。
CLI から
certificate build は、リーフ一覧、対象(生のリーフ16進、またはハッシュ化するファイル)、そしてアンカーの事実を解決する元になるトランザクションを受け取ります。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 は、すべての項目の証明がルートに再計算されることを意味します。ゼロでないコードは、包含の失敗、不正な入力、あるいは IO エラーを示します。ですからそのまま CI に組み込めます。各単一項目は、正規化された { tree_alg, tree_size, index, leaf, proof[] } の形に取り出して cardanowall merkle verify で確認することもできます。
TypeScript SDK から
certificate API は純粋です。リーフ一覧のバイト列はプラットフォーム自身の 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 は証明の判定を報告し、主張されているアンカーをそのまま返します。そのアンカーをチェーン上で確認するのは、あなたが別途、明示的に行うステップです。結果オブジェクト自身がそう述べています。同じモジュールは Python(certificate)と Rust(certificate)の SDK にもバイト単位でそのまま映されています。
ブラウザで、トランザクションのページ上で
merkle[] コミットメントを運ぶ label 309 レコードは、そのトランザクションのページに包含パネルを表示します。1つ以上の16進ハッシュを貼り付けるか、元のファイルをドロップしてクライアント側でハッシュ化します。ページはあなたのブラウザの中で、コンテンツアドレス指定のストレージからリーフ一覧を直接取得し、各証明を再計算し、項目ごとに緑/赤の判定を示し、JSON・CBOR・印刷可能な PDF をダウンロード用に提供します。その PDF は完全な JSON をファイル添付として埋め込んでいるので、それ自体が機械で検証できる成果物であり、ただの画像ではありません。これらのどれも、私設のサーバーには触れません。
検証アルゴリズム、端から端まで
証明書を独立に検証するには、つまり私たちのツールを一切使わずに自分のコードで検証するには、ちょうど次のようにします。
- 不正な形式のフィールドを拒否する。
root/leaf/proof[]の各要素は、32バイトに復号される偶数長の16進でなければなりません。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))とともに、その時点の部分木の大きさを厳密に下回る最大の2のべき乗で分割しながら畳み込み、その結果をmerkle.rootとバイト単位で比較します。リーフが1つだけのツリーは空の証明を持ちます。 - **アンカーをチェーン上で確認する。**任意の公開 Cardano エクスプローラーで
anchor.tx_hashを取得し、その label 309 メタデータを読み、merkle.rootがそのレコードのmerkle[].rootと等しいことを確認します。そこで読み取るブロック時刻が、証明書の主張するタイムスタンプです。
ステップ 1〜2 が自己完結した部分で、あなたのマシンを離れることはありません。ステップ 3 が唯一のネットワーク読み取りで、それはあなたが選んだエクスプローラーに向かい、証明書を作った者に向かうことは決してありません。
1つのファイル、永久に検証可能
パス上のすべての兄弟ノードが埋め込まれているので、証明書は、リーフ一覧や、元のゲートウェイや、作った者がいなくなったずっと後でも、検証され続けます。その2つの確認、すなわちファイルからルートを再計算すること、ルートをチェーン上で確認すること、これがだれにとっても必要なものすべてです。そのルートとリーフ一覧がそもそもどう構築されるかについてはMerkle でまとめて記録するを、チェーン側の確認が収まる検証器モデルの全体については検証を参照してください。