Aller au contenu

PD-237 — Plan d'implementation

1. Objectif

Implementer la persistance backend des arbres de Merkle conformement a la specification PD-237, en tant que consommateur de la primitive cryptographique PD-54.

Le backend doit : - Recevoir et valider des arbres de Merkle clos - Persister les racines, preuves d'inclusion et metadonnees de maniere atomique - Garantir l'immutabilite (append-only) et l'integrite probatoire - Exposer des API de recuperation par tree_id, par feuille ou par racine


2. Choix techniques retenus

Decision Justification
NestJS + TypeORM Stack backend existante, coherence avec PD-13/PD-14
PostgreSQL Support natif UUID, JSONB, CHECK constraints, transactions ACID
Schema dedie vault_merkle Isolation des donnees probatoires, separation des responsabilites
Triggers SQL pour append-only Enforcement au niveau BDD (defense en profondeur)
crypto.createHash (Node.js) Verification des preuves d'inclusion SHA-256
class-validator + custom pipes Validation stricte des payloads entrants
EventEmitter2 Emission d'evenements d'audit pour ERR-MK-07

3. Architecture ciblee

┌─────────────────────────────────────────────────────────────────┐
│                        API Layer                                 │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐  │
│  │ POST /merkle    │  │ GET /merkle/:id │  │ GET /merkle/    │  │
│  │   /trees        │  │                 │  │   proof/:leaf   │  │
│  └────────┬────────┘  └────────┬────────┘  └────────┬────────┘  │
└───────────┼─────────────────────┼─────────────────────┼─────────┘
            │                     │                     │
            ▼                     ▼                     ▼
┌─────────────────────────────────────────────────────────────────┐
│                     MerkleTreeService                            │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │ - validatePayload()      - persistTree()                    ││
│  │ - verifyInclusionProofs() - getTreeById()                   ││
│  │ - validateHashFormat()    - getProofByLeaf()                ││
│  │                           - getProofsByRoot()               ││
│  └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
            │                     │
            ▼                     ▼
┌─────────────────────────────────────────────────────────────────┐
│                   MerkleProofVerifier                            │
│  ┌─────────────────────────────────────────────────────────────┐│
│  │ - verifyProof(leaf, proof, root, algorithm)                 ││
│  │ - computeHash(data, algorithm)                              ││
│  └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│                     Repository Layer                             │
│  ┌─────────────────────┐  ┌─────────────────────┐               │
│  │ MerkleTreeRepository│  │ MerkleLeafRepository│               │
│  └──────────┬──────────┘  └──────────┬──────────┘               │
└─────────────┼─────────────────────────┼─────────────────────────┘
              │                         │
              ▼                         ▼
┌─────────────────────────────────────────────────────────────────┐
│                   PostgreSQL (schema: vault_merkle)              │
│  ┌─────────────────────┐  ┌─────────────────────┐               │
│  │    merkle_trees     │◄─┤    merkle_leaves    │               │
│  └─────────────────────┘  └─────────────────────┘               │
│  + TRIGGER prevent_update_delete (append-only)                   │
└─────────────────────────────────────────────────────────────────┘

4. Decoupage technique

4.1 Phase 1 : Schema et migrations

Tache Fichier Observable
Creer schema vault_merkle migrations/xxx-create-schema-vault-merkle.ts Schema existe
Creer table merkle_trees migrations/xxx-create-merkle-trees.ts Table avec contraintes CHECK + UNIQUE anti-doublon
Creer table merkle_leaves migrations/xxx-create-merkle-leaves.ts Table avec FK et index
Trigger append-only migrations/xxx-merkle-append-only-trigger.ts DELETE/UPDATE rejetes

Contrainte anti-doublon (multi-instance safe) :

ALTER TABLE merkle_trees
ADD CONSTRAINT uq_merkle_trees_identity
UNIQUE (merkle_root, window_start, window_end);
Cette contrainte garantit qu'un meme arbre (identifie par sa racine et sa fenetre temporelle) ne peut etre persiste qu'une seule fois, meme en deploiement multi-instance.

4.2 Phase 2 : Entites et DTOs

Tache Fichier Observable
Entite MerkleTree src/merkle/entities/merkle-tree.entity.ts Mapping TypeORM
Entite MerkleLeaf src/merkle/entities/merkle-leaf.entity.ts Mapping TypeORM
DTO PersistTreeRequest src/merkle/dto/persist-tree.dto.ts Validation class-validator
DTO GetProofRequest src/merkle/dto/get-proof.dto.ts Validation leaf/merkle_root mutuellement exclusifs
DTO TreeResponse src/merkle/dto/tree-response.dto.ts Format de sortie
DTO ProofResponse src/merkle/dto/proof-response.dto.ts Format de sortie (feuille ou racine)

4.3 Phase 3 : Verification cryptographique

Tache Fichier Observable
MerkleProofVerifier src/merkle/services/merkle-proof-verifier.ts Verification unitaire
Support SHA-256 Idem Hash conforme PD-54
Validation format hex Idem Regex ^[a-f0-9]{64}$

4.4 Phase 4 : Service metier

Tache Fichier Observable
MerkleTreeService src/merkle/services/merkle-tree.service.ts Logique metier
Validation payload Idem Rejet ERR-MK-01..04
Persistance atomique Idem Transaction TypeORM
Recuperation tree Idem getTreeById()
Recuperation proof par feuille Idem getProofByLeaf()
Recuperation proofs par racine Idem getProofsByRoot()

4.5 Phase 5 : Controller et routes

Tache Fichier Observable
MerkleController src/merkle/merkle.controller.ts Endpoints REST
POST /merkle/trees Idem Persistance
GET /merkle/trees/:id Idem Arbre complet
GET /merkle/proofs Idem Preuve par feuille ou par racine

4.6 Phase 6 : Gestion des erreurs et audit

Tache Fichier Observable
MerkleException src/merkle/exceptions/merkle.exception.ts Codes ERR-MK-xx
ExceptionFilter src/merkle/filters/merkle-exception.filter.ts Mapping HTTP
AuditService integration src/merkle/services/merkle-tree.service.ts Event ERR-MK-07

4.7 Phase 7 : Tests

Tache Fichier Observable
Tests unitaires verifier tests/unit/merkle/merkle-proof-verifier.spec.ts TC-INV-02
Tests unitaires service tests/unit/merkle/merkle-tree.service.spec.ts TC-NOM-, TC-ERR-
Tests integration tests/integration/merkle/merkle.e2e.spec.ts TC-INV-, TC-NR-
Tests adversariaux tests/integration/merkle/merkle-adversarial.e2e.spec.ts TC-NEG-*

5. Mapping Invariants → Mecanismes

Invariant Mecanisme technique Observable Test(s)
INV-BE-01 : Pas d'evenement en clair DTO n'accepte que des hashes (VARCHAR 64), pas de champ event_data Inspection schema + payloads API TC-INV-01, TC-NOM-05
INV-BE-02 : Preuve identique Stockage JSONB sans transformation, retour direct du champ inclusion_proof Comparaison byte-to-byte via Buffer.compare() TC-INV-02, TC-NOM-02
INV-BE-03 : Metadonnees immuables Trigger SQL BEFORE UPDATE sur merkle_treesRAISE EXCEPTION Tentative UPDATE rejetee en BDD TC-INV-03
INV-BE-04 : Rejet mismatch Validation DTO + verification avant persistance, pas de PATCH/PUT Code erreur ERR-MK-07 TC-INV-04
INV-BE-05 : Append-only Trigger SQL BEFORE DELETE + BEFORE UPDATERAISE EXCEPTION DELETE/UPDATE rejetes en BDD TC-INV-05, TC-NR-01

6. Mapping Criteres d'acceptation → Mecanismes + Tests

Critere Mecanisme Observable Test(s)
CA-01 : Payload complet requis Validation DTO (class-validator @IsNotEmpty, @IsArray, etc.) + transaction atomique avec rollback Rejet + aucune donnee recuperable TC-NOM-01, TC-ERR-01..04
CA-02 : Preuve exacte Pas de normalisation, stockage/retour brut JSON.stringify(stored) === JSON.stringify(returned) TC-NOM-02, TC-INV-02
CA-03 : Pas de clair Schema sans colonnes texte libre, uniquement hashes Inspection colonnes via \d merkle_* TC-NOM-05, TC-INV-01
CA-04 : Rejet incoherence Validation pre-persistance, codes ERR-MK-xx explicites HTTP 400 + body erreur TC-INV-04, TC-ERR-*
CA-05 : Immutabilite Triggers SQL append-only, pas d'endpoint DELETE/PATCH HTTP 405 ou trigger exception TC-INV-05, TC-NEG-01/02
CA-06 : Recherche multi-arbres Requete SQL WHERE leaf_hash = ? sans restriction tree_id + ORDER BY tree_id ASC Liste complete ordonnee TC-NOM-04, TC-NR-02
Perimetre §2.1 : Recuperation par racine Requete SQL WHERE merkle_root = ? via getProofsByRoot() + ORDER BY leaf_index ASC Toutes preuves d'un arbre -

7. Gestion des erreurs

7.1 Codes d'erreur contractuels

Code Situation HTTP Status Response body
ERR-MK-01 merkle_root absent 400 { "code": "ERR-MK-01", "message": "Missing merkle_root" }
ERR-MK-02 Format hash invalide 400 { "code": "ERR-MK-02", "message": "Invalid hash format for {field}" }
ERR-MK-03 Metadonnees incompletes 400 { "code": "ERR-MK-03", "message": "Missing or incomplete batch_metadata" }
ERR-MK-04 Cardinalite ou preuve invalide 400 { "code": "ERR-MK-04", "message": "Proof validation failed for leaf at index {i}" }
ERR-MK-05 tree_id inexistant 404 { "code": "ERR-MK-05", "message": "Tree not found" }
ERR-MK-06 Feuille non trouvee 404 { "code": "ERR-MK-06", "message": "Leaf not found in any tree" }
ERR-MK-07 Tentative modification 403 { "code": "ERR-MK-07", "message": "Modification of persisted tree forbidden" }

7.2 Audit pour ERR-MK-07

// Emission evenement audit
this.eventEmitter.emit('audit.security', {
  type: 'MERKLE_MODIFICATION_ATTEMPT',
  severity: 'HIGH',
  payload: {
    attempted_tree_id: treeId,
    client_ip: request.ip,
    timestamp: new Date().toISOString(),
  },
});

8. Securite

Mesure Implementation Observable
Append-only (BDD) Triggers prevent_update_merkle_trees, prevent_delete_merkle_trees Exception PostgreSQL si tentative
Append-only (API) Pas d'endpoint DELETE/PATCH/PUT sur /merkle/trees HTTP 405 Method Not Allowed
Anti-doublon (multi-instance) Contrainte UNIQUE (merkle_root, window_start, window_end) Rejet SQL si doublon
Validation hash Regex ^[a-f0-9]{64}$ en DTO et CHECK constraint SQL Rejet payload non conforme
Verification preuves MerkleProofVerifier.verify() avant persistance Rejet si proof invalide
tree_id en entree interdit Validation DTO @IsEmpty() sur tree_id Rejet ERR-MK-07 si present
Audit modifications EventEmitter sur ERR-MK-07 Log audit + alerte
Transactions atomiques @Transaction() ou queryRunner.startTransaction() Rollback complet si echec

9. API Endpoints

9.1 POST /api/v1/merkle/trees

Request:

{
  "merkle_root": "a1b2c3...",
  "leaves": ["hash1", "hash2", ...],
  "inclusion_proofs": [
    ["sibling1", "sibling2", ...],
    ...
  ],
  "batch_metadata": {
    "window_start": "2025-01-01T00:00:00Z",
    "window_end": "2025-01-01T23:59:59Z",
    "window_timezone": "Europe/Paris",
    "hash_algorithm_id": "SHA-256",
    "hash_algorithm_version": "1.0",
    "canonicalization_id": "JSON-CANONICAL-V1",
    "leaf_ordering_id": "TIMESTAMP-ASC"
  }
}

Response (201):

{
  "tree_id": "uuid",
  "created_at": "2025-01-25T10:30:00Z"
}

9.2 GET /api/v1/merkle/trees/:tree_id

Response (200):

{
  "tree_id": "uuid",
  "merkle_root": "a1b2c3...",
  "leaf_count": 42,
  "leaves": [...],
  "inclusion_proofs": [...],
  "batch_metadata": {...},
  "created_at": "..."
}

9.3 GET /api/v1/merkle/proofs

Recuperation de preuves par feuille ou par racine (spec §2.1).

Query parameters : - leaf (optionnel) : hash de la feuille recherchee - merkle_root (optionnel) : hash racine pour recuperer toutes les preuves d'un arbre - tree_id (optionnel) : restreint la recherche a un arbre specifique

Contraintes : - Au moins un parametre leaf ou merkle_root doit etre fourni - leaf et merkle_root sont mutuellement exclusifs (erreur 400 si les deux sont fournis)

Ordre des resultats (spec §5.2) : - Recherche par leaf : resultats ordonnes par tree_id ASC - Recherche par merkle_root : resultats ordonnes par leaf_index ASC

Exemples d'usage :

GET /api/v1/merkle/proofs?leaf=abc123...
GET /api/v1/merkle/proofs?leaf=abc123...&tree_id=uuid
GET /api/v1/merkle/proofs?merkle_root=def456...

Response (200) - Recherche par feuille :

{
  "results": [
    {
      "tree_id": "uuid1",
      "merkle_root": "...",
      "inclusion_proof": [...],
      "leaf_index": 5,
      "batch_metadata": {...}
    },
    ...
  ]
}

Response (200) - Recherche par racine :

{
  "results": [
    {
      "tree_id": "uuid1",
      "merkle_root": "def456...",
      "leaf_hash": "aaa...",
      "inclusion_proof": [...],
      "leaf_index": 0,
      "batch_metadata": {...}
    },
    {
      "tree_id": "uuid1",
      "merkle_root": "def456...",
      "leaf_hash": "bbb...",
      "inclusion_proof": [...],
      "leaf_index": 1,
      "batch_metadata": {...}
    },
    ...
  ]
}

Erreurs : - ERR-MK-06 (404) : Aucune preuve trouvee pour la feuille ou la racine - 400 : Parametres leaf et merkle_root fournis simultanement - 400 : Aucun parametre leaf ou merkle_root fourni


10. Hypotheses d'implementation

ID Hypothese Verification Impact si faux
H-IMPL-01 hash_algorithm_id = "SHA-256" est le seul supporte initialement Config + validation Ajouter factory pour autres algos
H-IMPL-02 Taille max arbre = 10 000 feuilles Stress test Pagination ou streaming
H-IMPL-03 inclusion_proof stocke en JSONB < 1MB Mesure taille moyenne Compression ou stockage externe
H-IMPL-04 Le service appelant fournit des preuves deja calculees Contract PD-54 Rejet ERR-MK-04 si invalide

Compatibilite multi-instance : Le design est compatible avec un deploiement multi-instance grace a : - Contrainte UNIQUE (merkle_root, window_start, window_end) → prevention des doublons - tree_id UUID DEFAULT gen_random_uuid() → pas de collision - Transactions ACID PostgreSQL → atomicite garantie - Contrainte UNIQUE (tree_id, leaf_index) → integrite des feuilles


11. Points de vigilance

11.1 Risques

Risque Probabilite Impact Mitigation
Performance sur gros arbres (10k+ feuilles) Moyenne Moyen Pagination GET, batch INSERT
Taille JSONB inclusion_proofs Faible Moyen Monitoring taille, alerte si > 500KB
Doublon en multi-instance Faible Faible Contrainte UNIQUE (merkle_root, window_start, window_end)

11.2 Dettes potentielles

Dette Raison Remediation future
Un seul hash_algorithm_id supporte Scope initial Factory pattern extensible
Pas de pagination recuperation arbre Arbres < 10k feuilles Cursor-based pagination
Pas de compression preuves Complexite vs gain Evaluer si > 100k arbres

11.3 Hypotheses fragiles

Hypothese Fragilite Signal d'alerte
H-01 (arbres clos) Non verifiable cote backend N/A - confiance en PD-54
H-02 (hash conforme) Depend de l'appelant Monitoring rejets ERR-MK-02
H-03 (atomicite) PostgreSQL garantit Monitoring erreurs transaction

12. Hors perimetre

Element Raison Reference
Construction des arbres Reserve a PD-54 Spec §2.2
Horodatage eIDAS Autre User Story Spec §2.2
Ancrage blockchain Autre User Story Spec §2.2
Replication/backup Responsabilite infra Spec §2.2
Compression preuves Optimisation future Spec §2.2
Pagination gros arbres A clarifier (§11) Spec §11
Mapping GRPC A clarifier (§11) Spec §11

13. Estimation effort

Phase Complexite Dependances
Phase 1 : Schema Faible PD-14 (TypeORM)
Phase 2 : Entites/DTOs Faible Phase 1
Phase 3 : Verifier crypto Moyenne PD-54 (spec hashes)
Phase 4 : Service Moyenne Phase 2, 3
Phase 5 : Controller Faible Phase 4
Phase 6 : Erreurs/Audit Faible Phase 4, 5
Phase 7 : Tests Moyenne Toutes phases

14. Checklist pre-implementation

  • PD-54 specification disponible et stable
  • Schema vault_merkle approuve
  • Triggers append-only valides en environnement de test
  • Format exact inclusion_proof confirme (array de strings hex)
  • hash_algorithm_id = "SHA-256" valide comme seul algo initial
  • Strategie audit ERR-MK-07 alignee avec AuditService existant

References

  • Specification : PD-237-specification.md
  • Tests : PD-237-tests.md
  • Epic : PD-186 BACKEND CORE
  • Dependance normative : PD-54
  • Pattern de reference : PD-236