docs(process): spec chiffrement/déchiffrement + séquences et pseudo-code
This commit is contained in:
parent
8ea50fed9a
commit
f63bf086cf
109
docs/PROCESS_ENCRYPTION_AND_DECRYPTION.md
Normal file
109
docs/PROCESS_ENCRYPTION_AND_DECRYPTION.md
Normal file
@ -0,0 +1,109 @@
|
||||
### Chiffrement des données de processus et déchiffrement (4NK)
|
||||
|
||||
Ce document décrit comment les données privées d’un processus sont chiffrées, stockées et déchiffrées côté client dans l’écosystème 4NK, en s’alignant sur les composants existants (`ihm_client`, service worker/IndexedDB, relays, wasm `sdk_client`).
|
||||
|
||||
### Principes
|
||||
- **Séparation public/privé**: lors de la création/mise à jour d’un process, le client sépare explicitement les champs privés des champs publics.
|
||||
- **Encodage**: les objets JSON simples et binaires sont encodés via le wasm (`encode_json`, `encode_binary`).
|
||||
- **Chiffrement & Clés**: le wasm chiffre les champs privés et associe des clés par attribut dans `state.keys[attribute]` pour les membres autorisés (déterminés par `roles` + règles de validation).
|
||||
- **Stockage**:
|
||||
- Les données chiffrées sont référencées par un hash (`pcd_commitment[attribute]`) et stockées localement en IndexedDB (store `data`) via `handleApiReturn`.
|
||||
- Les clés partagées (confirmées ou en attente) sont stockées dans `shared_secrets` / `unconfirmed_secrets` et injectées dans le wasm via `set_shared_secrets`.
|
||||
- **Rôle du relay**: les pairs publient/échangent ciphers, diffs, clés via les connections relay. Au moins un « handshake » relay doit avoir eu lieu pour récupérer/échanger les clés.
|
||||
|
||||
### Séquence — Création d’un processus avec données privées
|
||||
1) L’app sépare `privateData` et `publicData`.
|
||||
2) Encodage des deux jeux de données via wasm.
|
||||
3) `create_new_process` (wasm) produit `updated_process` avec:
|
||||
- `current_process.states[*].pcd_commitment[attribute]` (hash → blob chiffré)
|
||||
- `current_process.states[*].keys[attribute]` pour les membres autorisés
|
||||
4) `handleApiReturn`:
|
||||
- Sauvegarde des blobs dans IndexedDB (`data`), des diffs, et du process dans `processes`.
|
||||
- Alimente `shared_secrets`/`unconfirmed_secrets` et `set_shared_secrets` → wasm mémorise les secrets.
|
||||
5) `checkConnections` s’assure que les pairs nécessaires sont connectés (fonds « faucet » si besoin), puis `request_data` peut être utilisé pour obtenir des clés/manquants.
|
||||
|
||||
Pseudo-code (TypeScript, côté `ihm_client`)
|
||||
```ts
|
||||
const { jsonCompatibleData, binaryData } = splitData(privateData);
|
||||
const encodedPrivate = {
|
||||
...sdk.encode_json(jsonCompatibleData),
|
||||
...sdk.encode_binary(binaryData)
|
||||
};
|
||||
const encodedPublic = encode(publicData);
|
||||
|
||||
const res = sdk.create_new_process(encodedPrivate, roles, encodedPublic, relayAddress, feeRate, services.getAllMembers());
|
||||
await services.handleApiReturn(res); // stocke blobs + diffs + process + secrets
|
||||
await services.checkConnections(res.updated_process.current_process);
|
||||
```
|
||||
|
||||
### Séquence — Récupération et déchiffrement des données privées
|
||||
Objectif: obtenir les champs déchiffrés d’un état particulier.
|
||||
|
||||
1) Préconditions:
|
||||
- Appareil pairé (`is_paired()` et `services.isPaired()` → true)
|
||||
- Au moins un handshake relay reçu (liste des membres non vide)
|
||||
- `restoreSecretsFromDB()` exécuté (injecte les secrets en wasm)
|
||||
2) Côté hôte, appeler l’iframe via `RETRIEVE_DATA { processId, stateId, accessToken }`.
|
||||
3) Routeur `ihm_client` → `handleDecryptState`:
|
||||
- Vérifie `validateToken(accessToken, origin)`
|
||||
- Charge le `process` → `services.getProcess(processId)`
|
||||
- Vérifie/établit les connexions (`checkConnections(process, stateId)`) pour récupérer clés manquantes
|
||||
- Pour chaque attribut privé: `decryptAttribute(processId, state, attribute)`
|
||||
4) `decryptAttribute`:
|
||||
- Si clé absente mais rôle autorisé → `requestDataFromPeers(processId, [stateId], [roles])` puis retries
|
||||
- Lit le blob chiffré en IndexedDB via `pcd_commitment[attribute]`
|
||||
- Déchiffre avec `sdk.decrypt_data(key, cipher)` puis `sdk.decode_value(clear)`
|
||||
5) Réponse `DATA_RETRIEVED` avec les champs déchiffrés.
|
||||
|
||||
Pseudo-code déchiffrement (extrait)
|
||||
```ts
|
||||
async function decryptAttribute(processId: string, state: ProcessState, attribute: string) {
|
||||
const hash = state.pcd_commitment[attribute];
|
||||
let key = state.keys[attribute];
|
||||
if (!key && rolesContainUs(state.roles, getPairingProcessId())) {
|
||||
await services.checkConnections(await services.getProcess(processId), state.state_id);
|
||||
await services.requestDataFromPeers(processId, [state.state_id], [state.roles]);
|
||||
await retryDelay();
|
||||
key = state.keys[attribute];
|
||||
}
|
||||
if (!hash || !key) return null;
|
||||
const blob = await services.getBlobFromDb(hash);
|
||||
if (!blob) return null;
|
||||
const cipher = new Uint8Array(await blob.arrayBuffer());
|
||||
const clear = sdk.decrypt_data(hexToU8(key), cipher);
|
||||
return sdk.decode_value(clear);
|
||||
}
|
||||
```
|
||||
|
||||
### Séquence — Mise à jour avec nouveaux champs privés
|
||||
1) L’app calcule `privateData`/`publicData` du delta.
|
||||
2) `update_process` (wasm) produit `updated_process` et, si nécessaire, de nouvelles clés par attribut.
|
||||
3) `handleApiReturn` met à jour blobs/diffs/process/secrets.
|
||||
4) Les pairs reçoivent ciphers/keys via relay; `request_data` sert à combler les manquants.
|
||||
|
||||
### Bonnes pratiques et diagnostics
|
||||
- Toujours valider le token (`VALIDATE_TOKEN`) avant des opérations sensibles.
|
||||
- Attendre un **handshake relay** au démarrage avant de tenter d’obtenir des clés.
|
||||
- Vérifier que `rolesContainsUs(roles)` est vrai pour les attributs visés — sinon pas d’accès aux clés.
|
||||
- Confirmer qu’IndexedDB contient:
|
||||
- `data[hash]` (blob chiffré)
|
||||
- `shared_secrets[...]` utiles → `restoreSecretsFromDB()` doit tourner à l’init
|
||||
- En cas d’échec de déchiffrement:
|
||||
- Absence de blob → `handleApiReturn` n’a pas été invoqué sur un `updated_process` contenant `encrypted_data`
|
||||
- Absence de clé → relancer `checkConnections` + `requestDataFromPeers`, vérifier la connectivité relay/fonds
|
||||
|
||||
### Exemple de flux hôte ↔ iframe (postMessage)
|
||||
```ts
|
||||
// 1) L’hôte liste les processus (publique) puis demande les privés d’un état
|
||||
postMessage({ type: 'GET_PROCESSES', messageId, accessToken });
|
||||
// ... reçoit PROCESSES_RETRIEVED
|
||||
postMessage({ type: 'RETRIEVE_DATA', messageId, processId, stateId, accessToken });
|
||||
// ... reçoit DATA_RETRIEVED { data: { monChampPrive: '...' } }
|
||||
```
|
||||
|
||||
### Résumé
|
||||
- Les données privées ne sont jamais renvoyées en clair par `GET_PROCESSES`.
|
||||
- Le déchiffrement s’effectue côté client via `RETRIEVE_DATA` et `decryptAttribute`, avec clés/ blobs provenant du relay + IndexedDB.
|
||||
- La disponibilité des clés dépend des rôles, du pairing, de la connectivité et des secrets chargés.
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user