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¶
- PD-54 construit l'arbre et calcule la racine (cote client/service appelant).
- Le service appelant soumet l'arbre clos au backend via l'API de persistance.
- Le backend valide la conformite des metadonnees.
- Le backend persiste la racine, les preuves d'inclusion et les metadonnees.
- 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