**Motivations:** - Implement BIP39 mnemonic import for identity creation - Add password-based key protection for enhanced security - Improve pairing workflow with QR code and URL display - Migrate hash cache from LocalStorage to IndexedDB for better scalability - Update signet-dashboard and mempool components **Root causes:** - N/A (feature implementations) **Correctifs:** - N/A (no bug fixes in this commit) **Evolutions:** - BIP39 mnemonic import: Support for 12/24 word English mnemonics with BIP32 derivation path m/44'/0'/0'/0/0 - Key protection: Password-based encryption of private keys at rest with unlock/lock functionality - Pairing workflow: QR code and URL display for device pairing, form-based word exchange between devices - IndexedDB migration: Hash cache moved from LocalStorage to IndexedDB to avoid size limitations - Global action bar: URL parameter support for navigation - Pairing connection: Enhanced pairing status management **Pages affectées:** - userwallet/src/utils/identity.ts - userwallet/src/utils/keyProtection.ts - userwallet/src/utils/sessionUnlockedKey.ts - userwallet/src/utils/indexedDbStorage.ts - userwallet/src/utils/cache.ts - userwallet/src/utils/pairing.ts - userwallet/src/components/UnlockScreen.tsx - userwallet/src/components/PairingDisplayScreen.tsx - userwallet/src/components/PairingSetupBlock.tsx - userwallet/src/components/GlobalActionBar.tsx - userwallet/src/components/HomeScreen.tsx - userwallet/src/components/ImportIdentityScreen.tsx - userwallet/src/components/DataExportImportScreen.tsx - userwallet/src/hooks/useIdentity.ts - userwallet/src/hooks/usePairingConnected.ts - userwallet/src/services/syncService.ts - userwallet/src/services/pairingConfirm.ts - userwallet/src/App.tsx - userwallet/package.json - userwallet/docs/specs.md - userwallet/docs/storage.md - userwallet/docs/synthese.md - signet-dashboard/public/*.html - signet-dashboard/public/app.js - signet-dashboard/public/styles.css - mempool (submodule updates) - hash_list.txt, hash_list_cache.txt, utxo_list.txt, utxo_list_cache.txt, fees_list.txt - features/*.md (documentation files)
6.1 KiB
6.1 KiB
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 viaSAVE_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<string, StoredMessage>- Messages chiffrés indexés par hashsignatures: Map<string, StoredSignature[]>- Signatures indexées par hash de messagekeys: Map<string, StoredKey[]>- Clés de déchiffrement indexées par hash de messageseenHashes: Set<string>- Hash vus pour déduplication
Sur disque :
- Fichier
{STORAGE_PATH}/messages.jsoncontenant :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.jsonsi 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 :
{
msg: {
hash: string,
message_chiffre: string,
datajson_public: {
services_uuid: string[],
types_uuid: string[],
timestamp?: number,
...
}
},
received_at: number,
relayed: boolean
}
StoredSignature :
{
msg: {
signature: {
hash: string,
cle_publique: string,
signature: string,
nonce: string,
materiel?: object
},
hash_cible?: string
},
received_at: number,
relayed: boolean
}
StoredKey :
{
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 :
-
userwallet_identity: Identité locale{ uuid: string, privateKey: string, publicKey: string, name?: string, t0_anniversaire: number, version: string } -
userwallet_relays: Configuration des relaisArray<{ endpoint: string, priority: number, enabled: boolean, last_sync?: number }> -
userwallet_pairs: Configuration des pairsArray<{ uuid: string, membres_parents_uuid: string[], is_local: boolean, can_sign: boolean, publicKey?: string // clé publique identité de l'autre device (ECDH pairing) }> -
userwallet_hash_cache: Cache des hash vus (IndexedDB, storekv)string[] // Array de hash -
userwallet_pairing_confirm: Confirmations de pairing (IndexedDB, storekv)Array<{ pairLocal: string, pairRemote: string, hash: string, version: number }> -
userwallet_keypair: (Legacy) Paire de clés -
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_cacheetpairing_confirmlus 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, storekv(key/value). - Utilisation :
hash_cache,userwallet_pairing_confirm(confirmations de pairing).utils/indexedDbStorageexposeidbGet,idbSet,idbRemove. - Migration : Au premier
HashCache.init(), si IndexedDB vide etlocalStoragecontientuserwallet_hash_cache, copie vers IndexedDB puis suppression du localStorage.
Pas de synchronisation cloud (hors scope, jamais).