Concepts

Ancrage temporel public

Prouver à un tiers (ou à toi-même dans 5 ans) que la chaîne d'audit existait à une date T précise, sans dépendre de l'horloge système ni d'une autorité de confiance unique.

Deux mécanismes en parallèle

🟠 OpenTimestamps (Bitcoin)

Gratuit, irrévocable. Le hash d'un batch d'entrées est inclus dans une transaction Bitcoin via le protocole OTS. Une fois confirmé (~1-2h), prouvable indépendamment via n'importe quel nœud Bitcoin.

Pro : irrévocable, pas d'autorité centrale, vérifiable à perpétuité.
Con : pas qualifié eIDAS au sens strict (à compléter avec une TSA qualifiée pour le formalisme).

🔵 TSP qualifiés eIDAS (RFC 3161)

Universign, Lex Persona, ChamberSign, Certigna, GlobalSign, D-Trust. Token signé par une autorité qualifiée européenne, format RFC 3161 standard.

Pro : qualifié eIDAS, recevable directement en justice avec preuve QTSA.
Con : payant à l'usage, dépend de la TSA partenaire.

Stratégie : best-of-N

Tous les TSP enabled sont sollicités en parallèle pour chaque batch. Le bundle ASiC-E embarque tous les receipts qui ont répondu, dans anchors/{anchor_id}/{provider}.{ots|tsr}. Un seul provider suffit à prouver la date, mais avoir plusieurs souches d'ancrage rend la preuve robuste à la disparition d'un opérateur.

            anchors/
            ├── 01900000-aaaa/
            │   ├── opentimestamps.ots          ← Bitcoin OTS
            │   ├── universign.tsr              ← RFC 3161 Universign
            │   └── lex_persona.tsr             ← RFC 3161 Lex Persona
            └── 01900000-bbbb/
                ├── opentimestamps.ots
                └── universign.tsr
        

Cycle d'ancrage

1. Cron app:anchor:batch (toutes les 5 min ou ad hoc) :
   → trouve les audit_entry non-ancrées
   → calcule batch_hash = hash agrégé des entries du batch
   → dispatch un BatchAndAnchorMessage Symfony Messenger

2. messenger:consume async consomme :
   → pour chaque TimestampingProvider tagged service "app.timestamp_provider"
     enabled (OTS + TSP eIDAS configurés) :
       → soumet batch_hash, reçoit un timestamp token
       → stocke dans anchor_token (status SUBMITTED)
   → si au moins UN provider a répondu : status anchor = SUBMITTED

3. Cron app:anchor:upgrade (toutes les 15 min, Bitcoin pour OTS) :
   → re-tente la confirmation des OTS submittted depuis > 30 min
   → status passe à CONFIRMED quand le bloc Bitcoin est inclus dans la chaîne
        

Canary : un troisième mécanisme

Indépendant des ancrages, le canary tourne en boucle (5min en prod) et signe avec la master key un snapshot de l'état de la chaîne. Embarqué dans chaque bundle comme CANARY.json. Sert de point d'ancrage temporel continu à grain fin.

Combiné avec les ancrages OTS/TSP du batch, on a une triangulation forte :

  • • OTS Bitcoin → preuve à ~1h près (granularité bloc)
  • • TSP qualifié → preuve à la seconde, signée par autorité QTSA
  • • Canary → preuve à 5 min près, signée master, en continu

Ancrage par session & couverture exigible

  • La clôture (ou le refus) d'une session déclenche un ancrage immédiat — le scheduler 5 min n'est que le filet. Avec une TSA active (confirmée à l'émission), le bundle final arrive en secondes via bundle.generated.
  • Le manifest signé de chaque bundle déclare sa couverture d'ancrage (anchor_coverage: complete|partial|none) — les vérificateurs (CLI, viewer) refusent un bundle incomplet.
  • POST /v1/proofs accepte require_anchored: true → 422 ANCHORS_PENDING tant que chaque entrée n'a pas une ancre CONFIRMED. C'est le mode à utiliser pour toute remise de preuve.