# Stockage des données - UserWallet **Author:** Équipe 4NK **Date:** 2026-01-26 ## Synthèse - **Relais (api-relay)** : Stockage hybride (mémoire + `./data/messages.json`). Messages, `seenHashes`, signatures et clés persistés. Sauvegarde à l’arrêt (SIGINT/SIGTERM) et périodique (configurable via `SAVE_INTERVAL_SECONDS`). - **Front (userwallet)** : LocalStorage (`userwallet_identity`, `userwallet_relays`, `userwallet_pairs`, `userwallet_hash_cache` ; legacy : `userwallet_keypair`, `userwallet_services`). IndexedDB : `hash_cache`, `userwallet_pairing_confirm`. Graphe contractuel en mémoire uniquement (`GraphResolver`). ## Stockage sur le relais (api-relay) ### Architecture Le relais utilise un stockage **hybride** : mémoire + persistance sur disque. ### Structure de stockage **En mémoire :** - `messages: Map` - Messages chiffrés indexés par hash - `signatures: Map` - Signatures indexées par hash de message - `keys: Map` - Clés de déchiffrement indexées par hash de message - `seenHashes: Set` - Hash vus pour déduplication **Sur disque :** - Fichier `{STORAGE_PATH}/messages.json` contenant : - `messages`: Array de `[hash, StoredMessage]` - `seenHashes`: Array de hash (string[]) - `signatures`: Array de `[hash, StoredSignature[]]` - `keys`: Array de `[hash, StoredKey[]]` ### Persistance - **Chargement** : Au démarrage, charge `messages.json` si présent (ENOENT = premier run, démarrage à vide). - **Sauvegarde** : À l’arrêt (SIGINT/SIGTERM) et périodiquement si `SAVE_INTERVAL_SECONDS` > 0 (défaut 300 s). ### Format de données **StoredMessage :** ```typescript { msg: { hash: string, message_chiffre: string, datajson_public: { services_uuid: string[], types_uuid: string[], timestamp?: number, ... } }, received_at: number, relayed: boolean } ``` **StoredSignature :** ```typescript { msg: { signature: { hash: string, cle_publique: string, signature: string, nonce: string, materiel?: object }, hash_cible?: string }, received_at: number, relayed: boolean } ``` **StoredKey :** ```typescript { msg: { hash_message: string, cle_de_chiffrement_message: { algo: string, params: object, cle_chiffree?: string }, df_ecdh_scannable: string }, received_at: number, relayed: boolean } ``` ### Configuration - `STORAGE_PATH` : Chemin du répertoire de stockage (défaut: `./data`) - `SAVE_INTERVAL_SECONDS` : Intervalle de sauvegarde périodique en secondes (défaut: 300). Mettre à 0 pour désactiver. ### Limitations actuelles - Pas de base de données (SQLite/PostgreSQL recommandé en production) - Pas de compression des données ## Stockage sur le front (userwallet) ### Architecture Le front utilise **LocalStorage** du navigateur pour toutes les données locales. ### Structure de stockage **Clés utilisées :** 1. **`userwallet_identity`** : Identité locale ```typescript { uuid: string, privateKey: string, publicKey: string, name?: string, t0_anniversaire: number, version: string } ``` 2. **`userwallet_relays`** : Configuration des relais ```typescript Array<{ endpoint: string, priority: number, enabled: boolean, last_sync?: number }> ``` 3. **`userwallet_pairs`** : Configuration des pairs ```typescript Array<{ uuid: string, membres_parents_uuid: string[], is_local: boolean, can_sign: boolean, publicKey?: string // clé publique identité de l'autre device (ECDH pairing) }> ``` 4. **`userwallet_hash_cache`** : Cache des hash vus (IndexedDB, store `kv`) ```typescript string[] // Array de hash ``` 5. **`userwallet_pairing_confirm`** : Confirmations de pairing (IndexedDB, store `kv`) ```typescript Array<{ pairLocal: string, pairRemote: string, hash: string, version: number }> ``` 6. **`userwallet_keypair`** : (Legacy) Paire de clés 7. **`userwallet_services`** : (Legacy) Services configurés ### Données en mémoire (non persistées) - **Graphe contractuel** : Résolu dynamiquement depuis les messages synchronisés - Services, Contrats, Champs, Actions, Membres, Pairs - Stocké dans `GraphResolver.cache` (Map) - Perdu au rechargement de page ### Limitations - **Taille limitée** : LocalStorage a une limite (~5-10MB selon navigateur) - **Pas de synchronisation** : Données locales uniquement - **Sécurité** : Clés privées stockées en clair (à chiffrer avec mot de passe) ### Export / Import - **Export** : Écran « Export / Import données » (`/data`). Télécharge un JSON (identité, relais, pairs, hash_cache, pairing_confirm, keypair, services). `hash_cache` et `pairing_confirm` lus depuis IndexedDB. - **Import** : Même écran, bouton « Choisir un fichier ». Remplace les données locales (`hash_cache`, `pairing_confirm` écrits en IndexedDB si présents dans l’export) puis recharge la page. ### Protection par mot de passe - **Activation** : Écran « Export / Import données » → section « Protection par mot de passe ». Mot de passe + confirmation (min. 8 caractères). Chiffrement AES-GCM (clé dérivée PBKDF2-HMAC-SHA256, 100k itérations). - **Déverrouillage** : Si protection activée, écran « Déverrouiller » affiché tant que la session n’est pas ouverte. Mot de passe → clé en session, puis accès aux écrans normaux. - **Verrouillage** : Bouton « Verrouiller » (accueil ou /data). Vide la session uniquement ; stockage chiffré inchangé. - **Désactivation** : /data → « Désactiver la protection » (mot de passe actuel). Déchiffrement et réécriture de l’identité avec clé en clair. ### IndexedDB - **Base** : `userwallet`, store `kv` (key/value). - **Utilisation** : `hash_cache`, `userwallet_pairing_confirm` (confirmations de pairing). `utils/indexedDbStorage` expose `idbGet`, `idbSet`, `idbRemove`. - **Migration** : Au premier `HashCache.init()`, si IndexedDB vide et `localStorage` contient `userwallet_hash_cache`, copie vers IndexedDB puis suppression du localStorage. Pas de synchronisation cloud (hors scope, jamais).