Guides

Guides · Partie 6 sur 6

Certificats d’inclusion

Vous avez publié une racine de Merkle sous le label 309, ainsi que la liste des feuilles hors chaîne qui la sous-tend. Cette racine est un engagement parfaitement valable — mais ce n’est pas quelque chose que vous pouvez remettre à un collègue, joindre à un contrat ou verser à un dossier judiciaire. C’est précisément cette remise que réalise un certificat d’inclusion : un petit fichier autonome qui rattache une ou plusieurs feuilles à la racine publiée, intègre chaque nœud frère nécessaire pour redériver cette racine et nomme la transaction Cardano dont le temps de bloc atteste l’ensemble. Quiconque peut le vérifier hors ligne, par rapport à n’importe quel explorateur, sans compte et sans rien devoir accorder comme confiance à celui qui l’a produit.

Si vous connaissez OpenTimestamps, c’est la même idée — un reçu portable de preuve d’existence — avec deux différences délibérées. L’autorité d’horodatage est le temps de bloc de la blockchain Cardano, non un serveur calendrier. Et la preuve est générée et vérifiée entièrement côté client : aucune passerelle, aucun serveur émetteur, aucune confiance en nous à quelque étape que ce soit. C’est l’équivalent, pour la preuve d’existence, d’un fichier .ots, ancré sur Cardano et vérifiable de manière autonome.

Ce qu’un certificat prouve — et ce qu’il ne prouve pas

Un certificat formule exactement deux affirmations cryptographiques, chacune vérifiable indépendamment par quiconque :

  1. Inclusion. Une feuille leaf donnée occupe la position index d’un arbre de Merkle SHA-256 RFC 9162 de taille tree_size dont la racine est root. On le prouve en recalculant la racine à partir de la feuille, de son index, de la taille de l’arbre et du chemin de frères intégré. C’est autonome — le seul fichier de certificat suffit.
  2. Ancrage. Cette root apparaît mot pour mot dans le champ merkle[].root de l’enregistrement label 309 porté par la transaction tx_hash. On le prouve en lisant cette transaction sur n’importe quel explorateur Cardano public et en comparant les octets. Il faut le fichier de certificat plus un explorateur — rien d’autre.

Ensemble, ils prouvent que le contenu de la feuille existait à ou avant le temps de bloc de tx_hash.

Ce qu’un certificat ne prouve PAS

Le temps est affirmé par la blockchain publique, non lié cryptographiquement à l’intérieur de la preuve — exactement comme avec OpenTimestamps et Chainpoint, vous faites confiance au temps de bloc de la chaîne, jamais au producteur du certificat. Un certificat n’est pas un horodatage électronique eIDAS « qualifié » (RFC 3161 / une TSA qualifiée) ; c’est un horodatage ancré sur la blockchain — un élément de preuve corroborant fortement une affirmation temporelle, dans la même catégorie que les autres horodatages sur blockchain, et la formulation du fichier le dit exactement. Il ne dit rien sur qui a rédigé le contenu (c’est une signature d’enregistrement facultative — voir Signatures), et il ne prouve pas que le contenu n’était pas connu plus tôt. Il prouve l’existence à une échéance, non la paternité et non la nouveauté.

Pourquoi la preuve n’est pas signée

Un reçu COSE IETF strict est un COSE_Sign1 dont la charge utile est la racine de Merkle, signé par une autorité quelconque. Ici, l’autorité est la blockchain, non une clé que nous détenons — signer la racine avec notre clé réintroduirait donc une confiance dans le serveur et romprait la propriété de vérifiabilité autonome sur laquelle repose tout le standard. À la place, le certificat émet la structure CBOR de preuve d’inclusion IETF exactement telle qu’elle est spécifiée, et porte l’ancrage blockchain à la place de la signature. La mathématique de la preuve est identique octet pour octet à l’encodage IETF ; elle est délibérément non signée et ancrée sur la blockchain.

Le certificat JSON

L’artefact principal est label-309-inclusion-certificate-v1 : un fichier JSON lisible aussi bien par l’humain que par la machine. Un fichier couvre une OU plusieurs feuilles, et chaque élément intègre son chemin de frères complet, de sorte que le fichier se revérifie pour toujours — sans récupération Arweave, sans passerelle, sans avoir besoin du publicateur d’origine.

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"
  }
}

Quelques détails qu’il est bon de connaître :

  • root, leaf et chaque entrée de proof[] sont des valeurs brutes de 32 octets rendues en hex. Les producteurs émettent en minuscules ; un vérificateur accepte l’une ou l’autre casse et rejette tout caractère non hexadécimal ou toute chaîne de longueur impaire.
  • Le "verified": true stocké est le résultat du producteur au moment de la construction. Un vérificateur ne s’y fie jamais — il recalcule la preuve lui-même et rend son propre verdict. Une feuille introuvable dans l’arbre est consignée avec "verified": false et un champ "error", jamais écartée en silence, afin que le fichier reste honnête sur ses échecs.
  • block_time est l’horodatage POSIX, affirmé par l’explorateur, du bloc qui l’englobe. block_time_iso en est le rendu UTC — pour la commodité seulement.

La preuve d’inclusion CBOR (alignée COSE / RFC 9162)

À côté du JSON, chaque élément peut être exporté sous la forme d’un artefact .cbor compact dont la structure de preuve est identique octet pour octet à draft-ietf-cose-merkle-tree-proofs. Cela signifie que n’importe quel vérificateur de structures de données vérifiables RFC 9162 / COSE peut lire directement la mathématique de la preuve — le noyau d’interopérabilité est standard, non maison. Il ne porte aucun temps de bloc absolu ni aucun texte juridique (cela vit dans le JSON) ; il n’est que le noyau portable de la preuve, et il est ancré sur la blockchain et non signé pour la raison ci-dessus.

La preuve d’inclusion IETF nue — bstr .cbor [tree_size, leaf_index, inclusion_path], la valeur 1 de la structure de données vérifiable (vds) pour RFC 9162 SHA-256 — est extractible à elle seule pour un vérificateur COSE pur ; l’ancrage Cardano est porté à côté sous la forme d’une petite map à la place de la signature Sign1.

Construire et vérifier avec l’outillage

Le format de certificat fait partie de l’outillage public et indépendant de toute passerelle : le SDK @cardanowall/sdk-ts (avec ses jumeaux octet pour octet en Python et en Rust), la cardanowall CLI et les surfaces de vérification dans les applications. La mathématique de construction et de vérification est pure et hors ligne — seule la résolution de faits frais de la chaîne touche le réseau.

Avec la CLI

certificate build prend la liste des feuilles, les cibles (hex de feuille brut ou fichiers à hacher) et la transaction dont elle résout les faits d’ancrage. certificate verify rejoue la preuve d’inclusion par élément et affiche l’ancrage qu’il vous reste à confirmer sur la chaîne :

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

Le code de sortie 0 signifie que la preuve de chaque élément se recalcule en la racine ; un code non nul signale un échec d’inclusion, une entrée erronée ou une erreur d’E/S — il s’intègre donc directement dans votre CI. Chaque élément isolé peut aussi être extrait dans la forme canonique { tree_alg, tree_size, index, leaf, proof[] } et contrôlé avec cardanowall merkle verify.

Avec le SDK TypeScript

L’API certificate est pure — vous récupérez les octets de la liste des feuilles avec le fetch propre à la plateforme et les lui passez ; le chemin cryptographique n’atteint jamais le réseau :

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 rend le verdict de la preuve et renvoie l’ancrage affirmé ; confirmer cet ancrage sur la chaîne est votre étape distincte et explicite — l’objet résultat le dit. Le même module est reproduit octet pour octet dans les SDK Python (certificate) et Rust (certificate).

Dans le navigateur, sur une page de transaction

Un enregistrement label 309 portant un engagement merkle[] affiche un panneau d’inclusion sur sa page de transaction. Collez une ou plusieurs empreintes en hex, ou déposez les fichiers d’origine pour les hacher côté client ; la page récupère la liste des feuilles directement depuis le stockage adressé par contenu dans votre navigateur, recalcule chaque preuve, montre un verdict vert/rouge par élément et propose au téléchargement le JSON, le CBOR et un PDF imprimable. Le PDF intègre le JSON complet en pièce jointe, de sorte qu’il est lui-même l’artefact vérifiable par machine — non une simple image de celui-ci. Rien de tout cela ne touche un serveur privé.

L’algorithme de vérification, de bout en bout

Pour vérifier un certificat de manière indépendante — dans votre propre code, sans aucun outil de notre part — faites exactement ceci :

  1. Rejetez les champs mal formés. Chaque entrée root/leaf/proof[] doit être de l’hex de longueur paire se décodant en 32 octets ; tree_size et chaque index doivent être des entiers sûrs avec index < tree_size et 1 ≤ tree_size ≤ 2³² − 1.
  2. Recalculez la racine de chaque élément. Selon RFC 9162 §2.1.3.2, repliez la feuille (leaf = SHA-256(0x00 ‖ leaf_digest)) avec son chemin de frères (node = SHA-256(0x01 ‖ L ‖ R)), en scindant à la plus grande puissance de deux strictement inférieure à la taille courante du sous-arbre, et comparez le résultat à merkle.root octet pour octet. Un arbre à une seule feuille a une preuve vide.
  3. Confirmez l’ancrage sur la chaîne. Récupérez anchor.tx_hash sur n’importe quel explorateur Cardano public, lisez ses métadonnées label 309 et confirmez que merkle.root est égal au merkle[].root de l’enregistrement. Le temps de bloc que vous y lisez est l’horodatage que le certificat affirme.

Les étapes 1 et 2 sont la partie autonome — elles ne quittent jamais votre machine. L’étape 3 est l’unique lecture réseau, et elle se fait vers un explorateur que vous choisissez, jamais vers le producteur du certificat.

Un fichier, vérifiable pour toujours

Parce que chaque frère du chemin est intégré, un certificat continue de se vérifier longtemps après que la liste des feuilles, la passerelle d’origine ou le producteur ont disparu. Les deux contrôles — recalculer la racine à partir du fichier, confirmer la racine sur la chaîne — sont tout ce dont quiconque aura jamais besoin. Voir Traitement par lots avec Merkle pour savoir comment la racine et la liste des feuilles sont construites au départ, et Vérification pour le modèle de vérificateur complet dans lequel s’inscrit le contrôle côté chaîne.