가이드

가이드 · 전체 6부 중 6부

포함성 증명서

여러분은 label 309 아래에 Merkle 루트를 게시했고, 그것을 뒷받침하는 오프체인 리프 목록도 가지고 있습니다. 그 루트는 더없이 훌륭한 커밋먼트이지만, 동료에게 건네거나, 계약서에 첨부하거나, 법원 제출 서류에 끼워 넣을 수 있는 무언가는 아닙니다. 포함성 증명서가 바로 그 건네줌을 위한 것입니다. 작고 자기완결적인 파일로, 하나 이상의 리프를 게시된 루트에 고정하고, 그 루트를 다시 도출하는 데 필요한 모든 형제 노드를 끼워 넣으며, 그 전부를 자신의 블록 시각으로 증언하는 Cardano 트랜잭션을 지목합니다. 누구든 오프라인으로, 임의의 익스플로러에 대해, 계정 없이, 그리고 그것을 만든 이를 신뢰하지 않고도 검증할 수 있습니다.

OpenTimestamps를 알고 계신다면, 이것은 같은 발상입니다. 들고 다닐 수 있는 존재 증명 영수증이되, 두 가지 의도적인 차이가 있습니다. 타임스탬프 권위는 달력 서버가 아니라 Cardano 블록체인의 블록 시각입니다. 그리고 증명의 생성과 검증은 전적으로 클라이언트 측에서 이루어집니다. 게이트웨이도, 발급자 서버도, 그 어느 단계에서도 우리에 대한 신뢰도 필요 없습니다. 이것은 .ots 파일에 해당하는 존재 증명으로, Cardano 위에 기록되며 단독으로 검증할 수 있습니다.

증명서가 증명하는 것 — 그리고 증명하지 않는 것

증명서는 정확히 두 가지 암호학적 주장을 하며, 그 각각은 누구나 독립적으로 확인할 수 있습니다.

  1. 포함. 주어진 leaf가, 크기가 tree_size이고 루트가 rootRFC 9162 SHA-256 Merkle 트리의 위치 index에 있다는 것. 이는 리프, 그 인덱스, 트리 크기, 그리고 끼워 넣어진 형제 경로로부터 루트를 다시 계산함으로써 증명됩니다. 이것은 자기완결적입니다. 증명서 파일 하나만으로 충분합니다.
  2. 앵커링.root가 트랜잭션 tx_hash가 실어 나르는 label 309 레코드의 merkle[].root 필드에 한 글자도 다르지 않게 나타난다는 것. 이는 그 트랜잭션을 임의의 공개 Cardano 익스플로러에서 읽고 바이트 단위로 비교함으로써 증명됩니다. 필요한 것은 증명서 파일에 익스플로러 하나뿐, 그 밖에는 아무것도 없습니다.

이 둘을 합치면, 그 리프의 내용이 tx_hash의 블록 시각이나 그 이전에 존재했음이 증명됩니다.

증명서가 증명하지 않는 것

시각은 공개 블록체인이 단언하는 것이지, 증명 안에 암호학적으로 묶여 있는 것이 아닙니다. OpenTimestamps나 Chainpoint와 똑같이, 여러분이 신뢰하는 것은 체인의 블록 시각이지 증명서를 만든 이가 결코 아닙니다. 증명서는 eIDAS의 "적격" 전자 타임스탬프(RFC 3161 / 적격 TSA)가 아닙니다. 그것은 블록체인에 기록된 타임스탬프로, 시간에 관한 주장을 뒷받침하는 강력한 증거이며, 다른 블록체인 타임스탬프와 같은 부류에 속합니다. 파일 속 문구도 바로 그렇게 적고 있습니다. 그것은 내용을 누가 썼는지에 대해서는 아무 말도 하지 않으며(그것은 선택적인 레코드 서명입니다 — 서명을 참조하십시오), 그 내용이 그 이전에 알려지지 않았음을 증명하지도 않습니다. 그것이 증명하는 것은 기한까지의 존재이지, 저작자도 새로움도 아닙니다.

이 증명에 서명이 없는 이유

엄밀한 IETF COSE Receipt는 페이로드가 Merkle 루트이고 어떤 권위에 의해 서명된 COSE_Sign1입니다. 여기서의 권위는 우리가 가진 키가 아니라 블록체인입니다. 그러니 우리 키로 루트에 서명하면 서버 신뢰를 다시 끌어들이게 되고, 표준 전체가 딛고 선 단독 검증 가능성이라는 속성을 깨뜨리게 됩니다. 그래서 증명서는 IETF 포함성 증명의 CBOR 구조를 명세 그대로 내보내고, 서명이 들어갈 자리에 블록체인 앵커를 둡니다. 증명의 수학은 IETF 인코딩과 바이트 단위로 동일합니다. 이는 의도적으로 서명이 없으며 블록체인에 기록됩니다.

JSON 증명서

주된 산출물은 label-309-inclusion-certificate-v1입니다. 사람도 기계도 읽을 수 있는 JSON 파일입니다. 파일 하나가 하나 또는 여러 리프를 담으며, 각 항목은 자신의 완전한 형제 경로를 끼워 넣고 있으므로 파일은 영원히 다시 검증됩니다. Arweave에서의 가져오기도, 게이트웨이도, 원래 게시자의 입회도 필요 없습니다.

contract.cert.json
{
  "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는 항목별로 포함성 증명을 다시 실행하고, 여러분이 아직 온체인에서 확인해야 할 앵커를 출력합니다.

terminal
# 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은 모든 항목의 증명이 루트로 다시 계산됨을 뜻합니다. 0이 아닌 코드는 포함성 실패, 잘못된 입력, 또는 IO 오류를 알립니다. 그래서 그대로 CI에 넣을 수 있습니다. 각 단일 항목은 정규화된 { tree_alg, tree_size, index, leaf, proof[] } 형태로 추출하여 cardanowall merkle verify로 확인할 수도 있습니다.

TypeScript SDK로

certificate API는 순수합니다. 리프 목록 바이트는 플랫폼 자체의 fetch로 가져와 넘기십시오. 암호 처리 경로가 네트워크에 닿는 일은 없습니다.

build-and-verify.ts
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 레코드는 자신의 트랜잭션 페이지에 포함성 패널을 보여 줍니다. 16진수 해시를 하나 이상 붙여 넣거나, 원본 파일을 끌어다 놓아 클라이언트 측에서 해시하십시오. 페이지는 여러분의 브라우저 안에서 콘텐츠 주소 지정 스토리지로부터 리프 목록을 곧바로 가져와, 각 증명을 다시 계산하고, 항목별로 녹색/빨간색 판정을 보여 주며, JSON, CBOR, 그리고 인쇄 가능한 PDF를 다운로드용으로 제공합니다. 그 PDF는 완전한 JSON을 파일 첨부로 끼워 넣고 있으므로, 그 자체가 기계로 검증 가능한 산출물이지 단지 그것의 그림이 아닙니다. 이 가운데 어느 것도 사설 서버에 닿지 않습니다.

검증 알고리즘, 처음부터 끝까지

증명서를 독립적으로 검증하려면 — 우리의 어떤 도구도 쓰지 않고 여러분 자신의 코드로 — 정확히 다음과 같이 하십시오.

  1. 잘못된 형식의 필드를 거부하십시오. 모든 root/leaf/proof[] 항목은 32바이트로 디코딩되는 짝수 길이 16진수여야 합니다. tree_size와 각 index는 안전한 정수여야 하며 index < tree_size1 ≤ tree_size ≤ 2³² − 1을 만족해야 합니다.
  2. 각 항목의 루트를 다시 계산하십시오. RFC 9162 §2.1.3.2에 따라, 리프(leaf = SHA-256(0x00 ‖ leaf_digest))를 그 형제 경로(node = SHA-256(0x01 ‖ L ‖ R))와 함께, 현재 부분 트리 크기보다 엄격히 작은 가장 큰 2의 거듭제곱에서 나누며 접어 나가고, 그 결과를 merkle.root와 바이트 단위로 비교하십시오. 리프가 하나뿐인 트리는 빈 증명을 가집니다.
  3. 앵커를 온체인에서 확인하십시오. 임의의 공개 Cardano 익스플로러에서 anchor.tx_hash를 가져와 그 label 309 메타데이터를 읽고, merkle.root가 그 레코드의 merkle[].root와 같음을 확인하십시오. 거기서 읽는 블록 시각이 증명서가 단언하는 타임스탬프입니다.

1~2단계가 자기완결적인 부분으로, 여러분의 머신을 떠나지 않습니다. 3단계가 유일한 네트워크 읽기이며, 그것은 여러분이 고른 익스플로러로 향하지 증명서를 만든 이에게는 결코 향하지 않습니다.

파일 하나, 영원히 검증 가능

경로 위의 모든 형제 노드가 끼워 넣어져 있으므로, 증명서는 리프 목록이나 원래 게이트웨이나 만든 이가 사라진 한참 뒤에도 계속 검증됩니다. 그 두 가지 확인 — 파일로부터 루트를 다시 계산하기, 루트를 온체인에서 확인하기 — 이 누구에게나 언제까지나 필요한 전부입니다. 그 루트와 리프 목록이 애초에 어떻게 구축되는지는 Merkle로 일괄 기록하기를, 체인 측 확인이 들어맞는 검증기 모델 전체는 검증을 참조하십시오.