Guias

Guias · Parte 6 de 6

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_trust_in_cardanowall": 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. Cada item isolado também pode ser extraído no formato canônico { tree_alg, tree_size, index, leaf, proof[] } e conferido com cardanowall merkle verify.

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.