Guias

Guias · Parte 6 de 8

Certificados de inclusão

Você publicou uma raiz Merkle sob a label 309, junto com a lista de folhas fora da cadeia que a sustenta. Essa raiz é um compromisso perfeitamente válido — mas não é algo que você possa entregar a um colega, anexar a um contrato ou juntar a um processo judicial. Um certificado de inclusão é justamente essa entrega: um pequeno arquivo autocontido que prende uma ou mais folhas à raiz publicada, incorpora cada irmão necessário para reconstruir essa raiz e nomeia a transação de Cardano cuja hora de bloco dá fé do conjunto inteiro. Qualquer pessoa pode verificá-lo offline, contra qualquer explorador, sem conta e sem confiar em quem quer que o tenha produzido.

Se você conhece o OpenTimestamps, a ideia é a mesma —um recibo portátil de prova de existência— com duas diferenças propositais. A autoridade de carimbo de tempo é a hora de bloco da blockchain de Cardano, não um servidor de calendário. E a prova é gerada e verificada inteiramente no lado do cliente: sem gateway, sem servidor emissor, sem confiança em nós em nenhum passo. É o equivalente, em chave de prova de existência, de um arquivo .ots, ancorado em Cardano e verificável de forma autônoma.

O que um certificado prova — e o que não prova

Um certificado faz exatamente duas afirmações criptográficas, cada uma verificável de forma independente por qualquer pessoa:

  1. Inclusão. Uma dada leaf ocupa a posição index de uma árvore Merkle SHA-256 conforme a RFC 9162 de tamanho tree_size cuja raiz é root. Isso se prova recalculando a raiz a partir da folha, do seu índice, do tamanho da árvore e do caminho de irmãos incorporado. É autocontido: o próprio arquivo do certificado basta.
  2. Ancoragem. Essa root aparece literalmente no campo merkle[].root do registro da label 309 transportado pela transação tx_hash. Isso se prova lendo essa transação em qualquer explorador público de Cardano e comparando os bytes. Precisa do arquivo do certificado mais um explorador, nada além disso.

Juntas, elas provam que o conteúdo da folha existia na hora de bloco de tx_hash, ou antes dela.

O que um certificado NÃO prova

A hora é afirmada pela blockchain pública, não fica ligada criptograficamente à prova —exatamente como no OpenTimestamps e no Chainpoint, você está confiando na hora de bloco da cadeia, nunca em quem produziu o certificado. Um certificado não é um carimbo de tempo eletrônico «qualificado» eIDAS (RFC 3161 / uma TSA qualificada); é um carimbo de tempo ancorado em blockchain: uma evidência corroborante forte de uma afirmação temporal, na mesma categoria de outros carimbos de tempo baseados em blockchain, e a redação no arquivo diz exatamente isso. Ele nada diz sobre quem é o autor do conteúdo (isso é uma assinatura de registro opcional; veja Assinaturas), nem prova que o conteúdo não era conhecido antes. Ele prova a existência até um prazo, não a autoria e não a novidade.

Por que a prova vai sem assinatura

Um Receipt COSE estrito do IETF é um COSE_Sign1 cujo payload é a raiz Merkle, assinado por alguma autoridade. Aqui a autoridade é a blockchain, não uma chave em nosso poder — então assinar a raiz com a nossa chave reintroduziria a confiança no servidor e quebraria a propriedade de verificabilidade autônoma sobre a qual todo o padrão se assenta. Em vez disso, o certificado emite a estrutura CBOR de prova de inclusão do IETF exatamente como especificada, e leva a ancoragem na blockchain no lugar da assinatura. A matemática da prova é idêntica byte a byte à codificação do IETF; ela vai sem assinatura de propósito e está ancorada na blockchain.

O certificado JSON

O artefato principal é o label-309-inclusion-certificate-v1: um arquivo JSON legível tanto por pessoas quanto por máquinas. Um único arquivo cobre uma ou muitas folhas, e cada item incorpora seu caminho completo de irmãos, de modo que o arquivo se reverifica para sempre — sem busca no Arweave, sem gateway, sem precisar do publicador original.

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_issuer_trust": false,
    "time_asserted_by": "Cardano blockchain (block time), via public explorers",
  },
}

Alguns detalhes que vale a pena conhecer:

  • root, leaf e cada entrada de proof[] são valores brutos de 32 bytes representados em hex. Os produtores os emitem em minúsculas; um verificador aceita qualquer um dos dois casos e rejeita qualquer caractere não hexadecimal ou cadeia de comprimento ímpar.
  • O "verified": true armazenado é o resultado do produtor no momento da construção. Um verificador nunca confia nele: recalcula a prova por conta própria e relata o seu próprio veredito. Uma folha que não foi encontrada na árvore é registrada com "verified": false e um campo "error", nunca descartada em silêncio, de forma que o arquivo seja honesto quanto às falhas.
  • block_time é o carimbo de tempo POSIX —afirmado pelo explorador— do bloco que a inclui. block_time_iso é a sua representação em UTC, apenas por conveniência.

A prova de inclusão em CBOR (alinhada a COSE / RFC 9162)

Ao lado do JSON, cada item pode ser exportado como um artefato .cbor compacto cuja estrutura de prova é idêntica byte a byte ao draft-ietf-cose-merkle-tree-proofs. Isso significa que qualquer verificador de estruturas de dados verificáveis RFC 9162 / COSE pode ler a matemática da prova diretamente: o núcleo de interoperabilidade é padrão, não sob medida. Ele não carrega nenhuma hora de bloco absoluta nem prosa jurídica (isso vive no JSON); é apenas o núcleo portátil da prova, e está ancorado na blockchain e sem assinatura pela razão exposta acima.

A prova de inclusão crua do IETF —bstr .cbor [tree_size, leaf_index, inclusion_path], com o valor vds (verifiable-data-structure) 1 para RFC 9162 SHA-256— pode ser extraída por si só para um verificador COSE puro; a ancoragem em Cardano é transportada ao lado dela como um pequeno mapa no lugar da assinatura Sign1.

Construir e verificar com as ferramentas

O formato de certificado faz parte das ferramentas públicas e independentes do gateway: o SDK @cardanowall/sdk-ts (com gêmeos idênticos byte a byte em Python e Rust), a CLI cardanowall e as superfícies de verificação nas aplicações. A matemática de construção e verificação é pura e offline — só a resolução de dados frescos da cadeia toca a rede.

Com a CLI

certificate build recebe a lista de folhas, os alvos (hex bruto de uma folha, ou arquivos cujo hash será calculado) e a transação cujos dados de ancoragem ela resolve. certificate verify reexecuta a prova de inclusão por item e imprime a ancoragem que ainda cabe a você confirmar na cadeia:

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

O código de saída 0 significa que a prova de cada item se recalcula até a raiz; um código diferente de zero sinaliza uma falha de inclusão, uma entrada inválida ou um erro de E/S — pronto para integrar diretamente ao CI.

Um item isolado também se reverifica pelo merkle verify de nível mais baixo, que recebe uma prova de inclusão crua em vez de um certificado inteiro. Extraia-a do JSON do certificado com jq (tree_alg e tree_size vêm do nível merkle do certificado; index, leaf e proof, do item) e, então, passe a raiz junto com ela:

terminal
ROOT=$(jq -r .merkle.root contract.cert.json)
jq '{ tree_alg: .merkle.tree_alg, tree_size: .merkle.tree_size,
      index: .items[0].index, leaf: .items[0].leaf, proof: .items[0].proof }' \
  contract.cert.json > item-0.proof.json

cardanowall merkle verify --root "$ROOT" --proof item-0.proof.json

Para um verificador COSE puro, certificate build --cbor-dir <dir> grava as mesmas provas de inclusão por item como CBOR RFC 9162 ao lado do certificado JSON.

Com o SDK TypeScript

A API certificate é pura: você busca os bytes da lista de folhas com o fetch próprio da plataforma e os entrega; o caminho criptográfico nunca chega à rede:

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,
);

O verifyInclusionCertificate relata o veredito da prova e devolve em eco a ancoragem afirmada; confirmar essa ancoragem na cadeia é o seu passo à parte e explícito — o objeto de resultado deixa isso claro. O mesmo módulo é espelhado byte a byte nos SDKs Python (certificate) e Rust (certificate).

No navegador, em uma página de transação

Um registro da label 309 que carrega um compromisso merkle[] exibe um painel de inclusão em sua página de transação. Cole um ou mais hashes em hex, ou solte os arquivos originais para calcular o hash deles no lado do cliente; a página busca a lista de folhas direto do armazenamento endereçado por conteúdo no seu navegador, recalcula cada prova, mostra um veredito verde/vermelho por item e oferece o JSON, o CBOR e um PDF imprimível para download. O PDF incorpora o JSON completo como arquivo anexado, então ele próprio é o artefato verificável por máquina — não apenas uma imagem de um. Nada disso toca um servidor privado.

O algoritmo de verificação, de ponta a ponta

Para verificar um certificado de forma independente —no seu próprio código, sem nenhuma ferramenta nossa— faça exatamente isto:

  1. Rejeite os campos malformados. Cada entrada de root/leaf/proof[] deve ser hex de comprimento par que decodifique para 32 bytes; tree_size e cada index devem ser inteiros seguros, com index < tree_size e 1 ≤ tree_size ≤ 2³² − 1.
  2. Recalcule a raiz de cada item. Usando a RFC 9162 §2.1.3.2, combine a folha (leaf = SHA-256(0x00 ‖ leaf_digest)) com o seu caminho de irmãos (node = SHA-256(0x01 ‖ L ‖ R)), dividindo na maior potência de dois estritamente abaixo do tamanho da subárvore corrente, e compare o resultado com merkle.root byte a byte. Uma árvore de folha única tem prova vazia.
  3. Confirme a ancoragem na cadeia. Busque anchor.tx_hash em qualquer explorador público de Cardano, leia seus metadados da label 309 e confirme que merkle.root é igual ao merkle[].root do registro. A hora de bloco que você lê ali é o carimbo de tempo que o certificado afirma.

Os passos 1 e 2 são a parte autocontida: eles nunca saem da sua máquina. O passo 3 é a única leitura de rede, e ela vai para um explorador escolhido por você, nunca para quem produziu o certificado.

Um arquivo, verificável para sempre

Como cada irmão do caminho está incorporado, um certificado continua se verificando muito depois de a lista de folhas, o gateway original ou o produtor terem desaparecido. As duas conferências —recalcular a raiz a partir do arquivo, confirmar a raiz na cadeia— são tudo de que qualquer pessoa algum dia precisará. Consulte Agrupar em lote com Merkle para ver como a raiz e a lista de folhas são construídas, antes de mais nada, e Verificação para o modelo completo de verificador no qual a conferência do lado da cadeia se encaixa.