Aller au contenu

PD-237 — Persistance backend des arbres de Merkle (consommation PD-54)

1. Objectif

La presente specification definit, de maniere canonique, contractuelle et testable, le comportement attendu du backend en tant que consommateur de la primitive cryptographique definie dans PD-54.

Cette User Story couvre la persistance, l'indexation et la recuperation server-side des racines de Merkle, des preuves d'inclusion et des metadonnees de batch, conformement a l'exigence R17 de PD-54.

Le backend agit comme un systeme de stockage opaque vis-a-vis du contenu des evenements et se limite a manipuler des hashes, racines et metadonnees conformes a PD-54.

La conformite du backend est conditionnee a l'existence de definitions normatives, versionnees et testables des artefacts PD-54 consommes (formats de hash, structure des preuves, stabilite inter-version). Toute ambiguite ou lacune dans PD-54 est hors responsabilite du backend.

Aucune implementation logicielle n'est decrite dans ce document.


2. Perimetre / Hors perimetre

2.1 Inclus

  • Reception et validation des arbres de Merkle clos conformes a PD-54.
  • Persistance des racines (merkle_root) avec leurs metadonnees.
  • Persistance des preuves d'inclusion (inclusion_proofs) associees aux feuilles.
  • Persistance des metadonnees de batch (fenetre temporelle, algorithme, version).
  • Recuperation des preuves d'inclusion par feuille ou par racine.
  • Verification de la coherence interne des donnees persistees (presence, formats, cardinalites, preuves coherentes avec merkle_root).
  • Observabilite et auditabilite des operations.

2.2 Hors perimetre

  • Construction des arbres de Merkle (reserve a PD-54).
  • Construction des arbres de Merkle et calcul des hashes initiaux (reserve a PD-54).
  • Horodatage eIDAS, ancrage blockchain, signature des racines.
  • Replication, backup ou archivage long terme.
  • Compression ou optimisation du stockage des preuves.

Les elements hors perimetre sont explicitement non testables dans le cadre de la presente specification.


3. Definitions

  • MerkleRoot : hash racine d'un arbre de Merkle clos, produit conformement a PD-54.
  • InclusionProof : liste ordonnee de hashes (encodage hex lowercase conforme a hash_algorithm_id) permettant de verifier l'appartenance d'une feuille a une racine, ordonnee du niveau feuille vers la racine.
  • Leaf : hash d'un evenement canonique (feuille de l'arbre).
  • BatchMetadata : ensemble {window_start, window_end, window_timezone, hash_algorithm_id, hash_algorithm_version, canonicalization_id, leaf_ordering_id}.
  • tree_id : identifiant unique genere par le backend pour chaque persistance, opaque et non determinant; deux arbres identiques peuvent avoir des tree_id differents. tree_id n'est jamais accepte en entree lors d'une persistance.
  • Arbre clos : arbre dont la racine a ete calculee et dont aucun ajout n'est autorise (INV-03 de PD-54).

4. Invariants (non negociables)

ID Invariant Justification
INV-BE-01 Le backend ne recoit ni ne persiste d'evenement en clair Cloisonnement et conformite PD-54
INV-BE-02 Toute recuperation de preuve retourne exactement la preuve persistee, sans modification Integrite probatoire
INV-BE-03 Pour un tree_id donne, les metadonnees de batch sont immuables apres creation Prevention des incoherences
INV-BE-04 Un mismatch de metadonnees entre requete et donnees persistees entraine un rejet explicite Securite et auditabilite
INV-BE-05 La suppression d'un arbre persiste est interdite (append-only) Immutabilite probatoire

5. Flux nominaux

5.0 Cycle de vie d'un arbre persiste

  1. PD-54 construit l'arbre et calcule la racine (cote client/service appelant).
  2. Le service appelant soumet l'arbre clos au backend via l'API de persistance.
  3. Le backend valide la conformite des metadonnees.
  4. Le backend persiste la racine, les preuves d'inclusion et les metadonnees.
  5. Le backend retourne le tree_id attribue.

5.1 Persistance d'un arbre clos

  • Le backend recoit un payload contenant :
  • merkle_root : hash racine (format conforme a PD-54 hash_algorithm_id).
  • leaves[] : liste des feuilles (hashes) dans l'ordre canonique.
  • inclusion_proofs[] : liste des preuves d'inclusion, une par feuille.
  • batch_metadata : metadonnees de batch.
  • Le payload ne contient pas tree_id. Toute presence d'un tree_id fourni par l'appelant est rejetee (ERR-MK-07).
  • Le backend verifie la presence et la conformite de toutes les metadonnees.
  • Le backend verifie le format des hashes selon hash_algorithm_id.
  • Le backend verifie que chaque inclusion_proof permet de reconstruire merkle_root a partir de la leaf correspondante, selon hash_algorithm_id.
  • Le backend persiste l'ensemble de maniere atomique.
  • Le backend retourne tree_id et created_at.

5.2 Recuperation d'une preuve d'inclusion

  • Le backend recoit une requete contenant :
  • leaf : hash de la feuille recherchee.
  • tree_id (optionnel) : si fourni, recherche dans un arbre specifique.
  • Si tree_id est absent, le backend recherche dans tous les arbres et retourne tous les resultats correspondants, ordonnes par tree_id croissant.
  • Le backend retourne un ou plusieurs resultats, chacun contenant :
  • tree_id : identifiant de l'arbre.
  • merkle_root : racine de l'arbre contenant la feuille.
  • inclusion_proof : preuve d'inclusion ordonnee (format §3).
  • batch_metadata : metadonnees pour verification independante.
  • leaf_index : position de la feuille dans l'arbre.

5.3 Recuperation d'un arbre complet

  • Le backend recoit une requete contenant tree_id.
  • Le backend retourne l'ensemble des donnees persistees pour cet arbre.

5bis. Diagrammes

5bis.1 Diagramme de sequence — Persistance d'un arbre clos (§5.1)

sequenceDiagram
    participant Caller as Service appelant (PD-54)
    participant API as Backend API
    participant Valid as Validation
    participant Crypto as Verification crypto
    participant DB as PostgreSQL

    Caller->>API: POST /merkle-trees {merkle_root, leaves[], inclusion_proofs[], batch_metadata}
    Note right of API: tree_id absent du payload (INV-BE-05, ERR-MK-07)

    API->>Valid: Verifier presence et conformite metadonnees
    alt Metadonnees absentes ou incompletes
        Valid-->>API: ERR-MK-03
        API-->>Caller: 400 {code: ERR-MK-03}
    end

    Valid->>Valid: Verifier format hashes selon hash_algorithm_id
    alt Format non conforme (majuscules, longueur)
        Valid-->>API: ERR-MK-02
        API-->>Caller: 400 {code: ERR-MK-02}
    end

    Valid->>Valid: Verifier cardinalite leaves[] == inclusion_proofs[]

    loop Pour chaque feuille i
        Valid->>Crypto: Reconstruire racine depuis leaves[i] + inclusion_proofs[i]
        Crypto-->>Valid: racine calculee
        Valid->>Valid: Comparer racine calculee vs merkle_root (INV-BE-02)
    end
    alt Incoherence preuve / racine
        Valid-->>API: ERR-MK-04
        API-->>Caller: 400 {code: ERR-MK-04}
    end

    API->>DB: BEGIN TRANSACTION
    DB->>DB: INSERT merkle_trees (INV-BE-03, INV-BE-05 — append-only)
    DB->>DB: INSERT merkle_leaves (avec inclusion_proof JSONB)
    DB->>DB: COMMIT (atomique)
    DB-->>API: tree_id, created_at
    API-->>Caller: 201 {tree_id, created_at}

5bis.2 Diagramme de sequence — Recuperation d'une preuve d'inclusion (§5.2)

sequenceDiagram
    participant Caller as Service appelant
    participant API as Backend API
    participant DB as PostgreSQL

    Caller->>API: GET /merkle-proofs {leaf, tree_id?}

    alt tree_id fourni
        API->>DB: SELECT leaves + tree WHERE tree_id = ? AND leaf_hash = ?
        alt tree_id inexistant
            DB-->>API: 0 rows
            API-->>Caller: 404 {code: ERR-MK-05}
        end
        alt Feuille non trouvee
            DB-->>API: 0 rows
            API-->>Caller: 404 {code: ERR-MK-06}
        end
    else tree_id absent
        API->>DB: SELECT leaves + trees WHERE leaf_hash = ? ORDER BY tree_id ASC
        alt Aucun resultat
            DB-->>API: 0 rows
            API-->>Caller: 404 {code: ERR-MK-06}
        end
    end

    DB-->>API: {tree_id, merkle_root, inclusion_proof, batch_metadata, leaf_index}
    Note right of API: Retour byte-identical (INV-BE-02)
    API-->>Caller: 200 [{tree_id, merkle_root, inclusion_proof, batch_metadata, leaf_index}]

5bis.3 Diagramme d'etats — Cycle de vie d'un arbre dans le backend (§5.0)

stateDiagram-v2
    [*] --> Soumis: POST payload (§5.1)

    Soumis --> Valide: Metadonnees + hashes + preuves OK
    Soumis --> Rejete: ERR-MK-01/02/03/04/07

    Valide --> Persiste: Transaction atomique COMMIT

    Rejete --> [*]: Aucun effet de bord (INV-BE-04)

    Persiste --> Persiste: Lecture seule (INV-BE-05 — append-only, INV-BE-03 — immuable)

    note right of Persiste
        Etat terminal.
        DELETE/UPDATE interdits.
        Donnees immuables apres creation.
    end note

6. Cas d'erreur

ID Situation Resultat attendu
ERR-MK-01 Absence de merkle_root Rejet explicite
ERR-MK-02 Format de hash non conforme a hash_algorithm_id (incluant toute lettre hex en majuscule) Rejet explicite
ERR-MK-03 Metadonnees de batch absentes ou incompletes Rejet explicite
ERR-MK-04 Incoherence nombre de feuilles / nombre de preuves, ou preuve d'inclusion invalide Rejet explicite
ERR-MK-05 tree_id inexistant lors d'une recuperation Rejet explicite (404)
ERR-MK-06 Feuille non trouvee dans les arbres persistes Rejet explicite (404)
ERR-MK-07 Tentative de modification d'un arbre existant (ex: tree_id fourni par l'appelant lors d'une persistance) Rejet explicite + audit

Tout rejet explicite : - inclut un code d'erreur ERR-MK-xx et une raison contractuelle, - n'entraine aucun effet de bord (aucune persistance/modification d'etat), - n'entraine aucune recuperabilite via l'API des racines, feuilles, preuves ou metadonnees associees au payload, - pour ERR-MK-07, declenche un evenement d'audit.

6.1 Mapping protocolaire HTTP (normatif)

Le mapping suivant est contractuel pour toute API HTTP exposee par le backend :

Code HTTP status Response body minimal
ERR-MK-01 400 { "code": "ERR-MK-01", "message": "..." }
ERR-MK-02 400 { "code": "ERR-MK-02", "message": "..." }
ERR-MK-03 400 { "code": "ERR-MK-03", "message": "..." }
ERR-MK-04 400 { "code": "ERR-MK-04", "message": "..." }
ERR-MK-05 404 { "code": "ERR-MK-05", "message": "..." }
ERR-MK-06 404 { "code": "ERR-MK-06", "message": "..." }
ERR-MK-07 403 { "code": "ERR-MK-07", "message": "..." }

Le champ message DOIT contenir la raison contractuelle et DOIT etre non vide. Des champs supplementaires peuvent etre ajoutes sans modifier le champ code.


7. Criteres d'acceptation (testables)

ID Critere Observable
CA-01 Persistance possible uniquement avec payload complet et conforme; a l'issue d'un rejet, aucune donnee associee au payload n'est recuperable via l'API Persistance controlee
CA-02 Recuperation de preuve retourne exactement les donnees persistees Comparaison byte-to-byte
CA-03 Aucun evenement en clair n'est stocke Inspection backend
CA-04 Toute incoherence de metadonnees est rejetee Codes d'erreur
CA-05 Les arbres persistes sont immuables (append-only) Tentative de modification rejetee
CA-06 Recherche par feuille retourne tous les arbres contenant cette feuille Multi-arbre (R21 PD-54)

8. Scenarios de test (Given / When / Then)

Les scenarios de test sont definis dans PD-237-tests.md et couvrent l'ensemble des invariants et criteres d'acceptation testables.


9. Schema de persistance

9.1 Table merkle_trees

CREATE TABLE merkle_trees (
  tree_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  merkle_root VARCHAR(64) NOT NULL,           -- format conforme a hash_algorithm_id (ex: SHA-256 hex lowercase)
  leaf_count INTEGER NOT NULL,
  window_start TIMESTAMPTZ NOT NULL,
  window_end TIMESTAMPTZ NOT NULL,
  window_timezone VARCHAR(50) NOT NULL,       -- IANA TZ
  hash_algorithm_id VARCHAR(50) NOT NULL,
  hash_algorithm_version VARCHAR(20) NOT NULL,
  canonicalization_id VARCHAR(50) NOT NULL,
  leaf_ordering_id VARCHAR(50) NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

  CONSTRAINT chk_window_order CHECK (window_end > window_start),
  CONSTRAINT chk_leaf_count CHECK (leaf_count > 0),
  CONSTRAINT chk_root_format CHECK (merkle_root ~ '^[a-f0-9]{64}$')
);

CREATE INDEX idx_merkle_trees_window ON merkle_trees(window_start, window_end);
CREATE INDEX idx_merkle_trees_root ON merkle_trees(merkle_root);

9.2 Table merkle_leaves

CREATE TABLE merkle_leaves (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tree_id UUID NOT NULL REFERENCES merkle_trees(tree_id),
  leaf_hash VARCHAR(64) NOT NULL,             -- format conforme a hash_algorithm_id (ex: SHA-256 hex lowercase)
  leaf_index INTEGER NOT NULL,
  inclusion_proof JSONB NOT NULL,             -- Array ordonne de hashes (hex lowercase) du niveau feuille vers racine
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

  CONSTRAINT chk_leaf_format CHECK (leaf_hash ~ '^[a-f0-9]{64}$'),
  CONSTRAINT chk_leaf_index CHECK (leaf_index >= 0),
  UNIQUE (tree_id, leaf_index)
);

CREATE INDEX idx_merkle_leaves_hash ON merkle_leaves(leaf_hash);
CREATE INDEX idx_merkle_leaves_tree ON merkle_leaves(tree_id);

Note : Les contraintes chk_root_format/chk_leaf_format sont definies pour hash_algorithm_id = SHA-256. Si un autre algorithme est utilise, une contrainte equivalente DOIT etre definie.

Append-only : aucune operation de suppression/modification n'est autorisee sur les arbres persistes. La couche de stockage DOIT empecher les operations DELETE/UPDATE sur merkle_trees/merkle_leaves.


10. Hypotheses explicites

ID Hypothese Impact si faux
H-01 Les arbres soumis sont clos (INV-03 PD-54) et leur cloture n'est pas verifiable a posteriori Donnees incoherentes
H-02 Le backend dispose d'une implementation de hash conforme a hash_algorithm_id pour verifier les preuves d'inclusion Verification impossible
H-03 Le stockage garantit l'atomicite des transactions Donnees partielles
H-04 Les hash_algorithm_id soumis appartiennent a la liste supportee (section 10.1) Rejet ERR-MK-02

10.1 Liste des hash_algorithm_id supportes (contractuel)

  • hash_algorithm_id : SHA-256
  • hash_algorithm_version : 1.0

Toute valeur hors liste est non supportee.

10.2 Contraintes d'implementation (informatives)

  • C-IMPL-01 : leaf_count <= 10 000
  • C-IMPL-02 : inclusion_proof JSONB < 1MB

Ces contraintes sont operationnelles et n'ajoutent pas d'exigence contractuelle.


11. Points a clarifier

  • Politique de retention des arbres (archivage vs suppression logique).
  • Strategie de pagination pour la recuperation de gros arbres.

References

  • Epic : PD-186 BACKEND CORE
  • Dependance normative : PD-54 — Construction d'Arbres de Merkle Periodiques
  • Pattern de reference : PD-236 — Recherche backend par tokens deterministes
  • Repos concernes : backend