import * as Comlink from 'comlink'; import { ApiReturn, Device, HandshakeMessage, Member, MerkleProofResult, OutPointProcessMap, Process, ProcessState, RoleDefinition, SecretsStore, UserDiff } from '../../pkg/sdk_client'; import Database from '../services/database.service'; import { storeData, retrieveData } from '../services/storage.service'; import { BackUp } from '../types/index'; import { APP_CONFIG } from '../config/constants'; import { splitPrivateData } from '../utils/service.utils'; // Services internes au worker import { SdkService } from '../services/core/sdk.service'; import { WalletService } from '../services/domain/wallet.service'; import { ProcessService } from '../services/domain/process.service'; import { CryptoService } from '../services/domain/crypto.service'; export class CoreBackend { // Services private sdkService: SdkService; private walletService!: WalletService; private processService!: ProcessService; private cryptoService: CryptoService; private db!: Database; // État (State) private processId: string | null = null; private stateId: string | null = null; private membersList: Record = {}; private notifications: any[] | null = null; private currentBlockHeight: number = -1; private pendingKeyRequests: Map void> = new Map(); // Flags publics (State) public device1: boolean = false; public device2Ready: boolean = false; private isInitialized = false; // Callbacks vers le Main Thread private notifier: ((event: string, data?: any) => void) | null = null; private networkSender: ((flag: string, content: string) => void) | null = null; private relayUpdater: ((url: string, sp: string) => void) | null = null; private relayGetter: (() => Promise) | null = null; constructor() { this.sdkService = new SdkService(); this.cryptoService = new CryptoService(this.sdkService); // Initialisation temporaire this.walletService = new WalletService(this.sdkService, null as any); this.processService = new ProcessService(this.sdkService, null as any); } public async init(): Promise { if (this.isInitialized) return; console.log('[CoreWorker] ⚙️ Initialisation du Backend...'); await this.sdkService.init(); // Charge le WASM this.db = await Database.getInstance(); // Lance le Database Worker this.walletService = new WalletService(this.sdkService, this.db); this.processService = new ProcessService(this.sdkService, this.db); this.notifications = this.getNotifications(); this.isInitialized = true; console.log('[CoreWorker] ✅ Backend prêt.'); } // --- CONFIGURATION DES CALLBACKS --- public setCallbacks( notifier: (event: string, data?: any) => void, networkSender: (flag: string, content: string) => void, relayUpdater: (url: string, sp: string) => void, relayGetter: () => Promise ) { this.notifier = notifier; this.networkSender = networkSender; this.relayUpdater = relayUpdater; this.relayGetter = relayGetter; } // ========================================== // GETTERS & SETTERS (STATE) // ========================================== public setProcessId(id: string | null) { this.processId = id; } public setStateId(id: string | null) { this.stateId = id; } public getProcessId() { return this.processId; } public getStateId() { return this.stateId; } public getDevice1() { return this.device1; } // Ajouté public getDevice2Ready() { return this.device2Ready; } // Ajouté public resetState() { this.device1 = false; this.device2Ready = false; } // ========================================== // WALLET PROXY // ========================================== public isPaired() { return this.walletService.isPaired(); } public getAmount() { return this.walletService.getAmount(); } public getDeviceAddress() { return this.walletService.getDeviceAddress(); } public dumpDeviceFromMemory() { return this.walletService.dumpDeviceFromMemory(); } public dumpNeuteredDevice() { return this.walletService.dumpNeuteredDevice(); } public getPairingProcessId() { return this.walletService.getPairingProcessId(); } public async getDeviceFromDatabase() { return this.walletService.getDeviceFromDatabase(); } public restoreDevice(d: Device) { this.walletService.restoreDevice(d); } public pairDevice(pid: string, list: string[]) { this.walletService.pairDevice(pid, list); } public async unpairDevice() { await this.walletService.unpairDevice(); } public async saveDeviceInDatabase(d: Device) { await this.walletService.saveDeviceInDatabase(d); } public async createNewDevice() { return this.walletService.createNewDevice(this.currentBlockHeight > 0 ? this.currentBlockHeight : 0); } public async dumpWallet() { return this.walletService.dumpWallet(); } public async getMemberFromDevice() { return this.walletService.getMemberFromDevice(); } // ========================================== // PROCESS PROXY // ========================================== public async getProcess(id: string) { return this.processService.getProcess(id); } public async getProcesses() { return this.processService.getProcesses(); } public async restoreProcessesFromDB() { await this.processService.getProcesses(); } public getLastCommitedState(p: Process) { return this.processService.getLastCommitedState(p); } public getUncommitedStates(p: Process) { return this.processService.getUncommitedStates(p); } public getStateFromId(p: Process, id: string) { return this.processService.getStateFromId(p, id); } public getRoles(p: Process) { return this.processService.getRoles(p); } public getLastCommitedStateIndex(p: Process) { return this.processService.getLastCommitedStateIndex(p); } public async batchSaveProcessesToDb(p: Record) { return this.processService.batchSaveProcesses(p); } // ========================================== // CRYPTO HELPERS // ========================================== public decodeValue(val: number[]) { return this.sdkService.decodeValue(val); } public hexToBlob(hex: string) { return this.cryptoService.hexToBlob(hex); } public hexToUInt8Array(hex: string) { return this.cryptoService.hexToUInt8Array(hex); } public async blobToHex(blob: Blob) { return this.cryptoService.blobToHex(blob); } public getHashForFile(c: string, l: string, f: any) { return this.cryptoService.getHashForFile(c, l, f); } public getMerkleProofForFile(s: ProcessState, a: string) { return this.cryptoService.getMerkleProofForFile(s, a); } public validateMerkleProof(p: MerkleProofResult, h: string) { return this.cryptoService.validateMerkleProof(p, h); } private splitData(obj: Record) { return this.cryptoService.splitData(obj); } // ========================================== // MEMBERS // ========================================== public getAllMembers() { return this.membersList; } public getAllMembersSorted() { return Object.fromEntries(Object.entries(this.membersList).sort(([keyA], [keyB]) => keyA.localeCompare(keyB))); } public async ensureMembersAvailable(): Promise { if (Object.keys(this.membersList).length > 0) return; console.warn('[CoreWorker] Tentative de récupération des membres...'); } public getAddressesForMemberId(memberId: string): string[] | null { if (!this.membersList[memberId]) return null; return this.membersList[memberId].sp_addresses; } public compareMembers(memberA: string[], memberB: string[]): boolean { if (!memberA || !memberB) return false; if (memberA.length !== memberB.length) return false; return memberA.every((item) => memberB.includes(item)) && memberB.every((item) => memberA.includes(item)); } // ========================================== // UTILITAIRES DIVERS // ========================================== public createFaucetMessage() { return this.sdkService.getClient().create_faucet_msg(); } public isChildRole(parent: any, child: any): boolean { try { this.sdkService.getClient().is_child_role(JSON.stringify(parent), JSON.stringify(child)); return true; } catch (e) { console.error(e); return false; } } // ========================================== // LOGIQUE HANDSHAKE // ========================================== public async handleHandshakeMsg(url: string, parsedMsg: any) { try { const handshakeMsg: HandshakeMessage = JSON.parse(parsedMsg); if (handshakeMsg.sp_address && this.relayUpdater) { await this.relayUpdater(url, handshakeMsg.sp_address); } this.currentBlockHeight = handshakeMsg.chain_tip; if (!this.isPaired()) { console.log(`[CoreWorker] ⏳ Non pairé. Attente appairage...`); } this.updateDeviceBlockHeight(); if (handshakeMsg.peers_list) { this.membersList = { ...this.membersList, ...handshakeMsg.peers_list as Record }; } if (handshakeMsg.processes_list) { await this.syncProcessesFromHandshake(handshakeMsg.processes_list); } } catch (e) { console.error("Handshake Error", e); } } public async updateDeviceBlockHeight() { if (this.currentBlockHeight <= 0) return; const device = await this.walletService.getDeviceFromDatabase(); if (!device) return; if (device.sp_wallet.birthday === 0) { device.sp_wallet.birthday = this.currentBlockHeight; device.sp_wallet.last_scan = this.currentBlockHeight; await this.walletService.saveDeviceInDatabase(device); this.walletService.restoreDevice(device); } else if (device.sp_wallet.last_scan < this.currentBlockHeight) { console.log(`[CoreWorker] Scan requis de ${device.sp_wallet.last_scan} à ${this.currentBlockHeight}`); try { await this.sdkService.getClient().scan_blocks(this.currentBlockHeight, APP_CONFIG.URLS.BLINDBIT); const updatedDevice = this.walletService.dumpDeviceFromMemory(); await this.walletService.saveDeviceInDatabase(updatedDevice); } catch (e) { console.error('Scan error', e); } } } private async syncProcessesFromHandshake(newProcesses: OutPointProcessMap) { if (!newProcesses || Object.keys(newProcesses).length === 0) return; console.log(`[CoreWorker] Synchro ${Object.keys(newProcesses).length} processus...`); const toSave: Record = {}; const currentProcesses = await this.getProcesses(); if (Object.keys(currentProcesses).length === 0) { await this.processService.batchSaveProcesses(newProcesses); } else { for (const [processId, process] of Object.entries(newProcesses)) { const existing = currentProcesses[processId]; if (existing) { let newStates: string[] = []; let newRoles: Record[] = []; for (const state of process.states) { if (!state || !state.state_id) continue; if (state.state_id === APP_CONFIG.EMPTY_32_BYTES) { const existingTip = existing.states[existing.states.length - 1].commited_in; if (existingTip !== state.commited_in) { existing.states.pop(); existing.states.push(state); toSave[processId] = existing; } } else if (!this.processService.getStateFromId(existing, state.state_id)) { const existingLast = existing.states.pop(); if (existingLast) { existing.states.push(state); existing.states.push(existingLast); toSave[processId] = existing; if (this.rolesContainsUs(state.roles)) { newStates.push(state.state_id); newRoles.push(state.roles); } } } else { const existingState = this.processService.getStateFromId(existing, state.state_id); if (existingState && (!existingState.keys || Object.keys(existingState.keys).length === 0)) { if (this.rolesContainsUs(state.roles)) { newStates.push(state.state_id); newRoles.push(state.roles); } } } } if (newStates.length > 0) { await this.ensureConnections(existing); await this.requestDataFromPeers(processId, newStates, newRoles); } } else { toSave[processId] = process; } } if (Object.keys(toSave).length > 0) { await this.processService.batchSaveProcesses(toSave); } } if (this.notifier) this.notifier('processes-updated'); } // ========================================== // LOGIQUE MÉTIER // ========================================== public async getMyProcesses(): Promise { try { const pid = this.getPairingProcessId(); return await this.processService.getMyProcesses(pid); } catch (e) { return null; } } public async ensureConnections(process: Process, stateId: string | null = null): Promise { console.info(`[CoreWorker] 🔄 Check connexions (StateID: ${stateId || 'default'})`); if (!process) return; let state: ProcessState | null = null; if (stateId) state = this.processService.getStateFromId(process, stateId); if (!state && process.states.length >= 2) state = process.states[process.states.length - 2]; if (!state) return; await this.ensureMembersAvailable(); const members = new Set(); if (state.roles) { for (const role of Object.values(state.roles)) { for (const memberId of role.members) { const addrs = this.getAddressesForMemberId(memberId); if (addrs) members.add({ sp_addresses: addrs }); } } } if (members.size === 0) { let publicData: Record | null = null; for (let i = process.states.length - 1; i >= 0; i--) { const s = process.states[i]; if (s.public_data && s.public_data['pairedAddresses']) { publicData = s.public_data; break; } } if (publicData && publicData['pairedAddresses']) { const decoded = this.decodeValue(publicData['pairedAddresses']); if (decoded) members.add({ sp_addresses: decoded }); } } if (members.size === 0) return; const unconnected = new Set(); const myAddress = this.getDeviceAddress(); for (const member of Array.from(members)) { if (!member.sp_addresses) continue; for (const address of member.sp_addresses) { if (address === myAddress) continue; if ((await this.getSecretForAddress(address)) === null) unconnected.add(address); } } if (unconnected.size > 0) { console.log(`[CoreWorker] 📡 ${unconnected.size} non connectés. Connexion...`); await this.connectAddresses(Array.from(unconnected)); } } public async connectAddresses(addresses: string[]): Promise { if (addresses.length === 0) return null; const feeRate = APP_CONFIG.FEE_RATE; try { return this.sdkService.getClient().create_transaction(addresses, feeRate); } catch (error: any) { if (String(error).includes('Insufficient funds')) { await this.getTokensFromFaucet(); return this.sdkService.getClient().create_transaction(addresses, feeRate); } else { throw error; } } } private async getTokensFromFaucet(): Promise { console.log('[CoreWorker] 🚰 Demande Faucet...'); const availableAmt = this.getAmount(); const target: BigInt = APP_CONFIG.DEFAULT_AMOUNT * BigInt(10); if (availableAmt < target) { const msg = this.sdkService.getClient().create_faucet_msg(); if (this.networkSender) this.networkSender('Faucet', msg); let attempts = 3; while (attempts > 0) { if (this.getAmount() >= target) return; attempts--; await new Promise((r) => setTimeout(r, APP_CONFIG.TIMEOUTS.RETRY_DELAY)); } throw new Error('Montant insuffisant après faucet'); } } public async createPairingProcess(userName: string, pairWith: string[]): Promise { if (this.isPaired()) throw new Error('Déjà appairé'); const myAddress = this.getDeviceAddress(); pairWith.push(myAddress); const privateData = { description: 'pairing', counter: 0 }; const publicData = { memberPublicName: userName, pairedAddresses: pairWith }; const validation_fields = [...Object.keys(privateData), ...Object.keys(publicData), 'roles']; const roles = { pairing: { members: [], validation_rules: [{ quorum: 1.0, fields: validation_fields, min_sig_member: 1.0 }], storages: [APP_CONFIG.URLS.STORAGE], }, }; return this.createProcess(privateData, publicData, roles); } public async createProcess(privateData: any, publicData: any, roles: any, feeRate = APP_CONFIG.FEE_RATE): Promise { // Appel au main thread pour avoir l'adresse du relais const relay = this.relayGetter ? await this.relayGetter() : ''; if (!relay) throw new Error("Aucun relais disponible"); const { encodedPrivateData, encodedPublicData } = await this.prepareProcessData(privateData, publicData); const members = this.membersList; try { return await this.attemptCreateProcess(encodedPrivateData, roles, encodedPublicData, relay, feeRate, members); } catch (e: any) { if (String(e).includes('Insufficient funds')) { await this.getTokensFromFaucet(); return await this.attemptCreateProcess(encodedPrivateData, roles, encodedPublicData, relay, feeRate, members); } throw e; } } private async attemptCreateProcess(priv: any, roles: any, pub: any, relay: string, fee: number, members: any): Promise { const res = this.sdkService.getClient().create_new_process(priv, roles, pub, relay, fee, members); if (res.updated_process) { await this.ensureConnections(res.updated_process.current_process); } return res; } public async updateProcess(processId: string, newData: any, privateFields: string[], roles: any): Promise { const process = await this.processService.getProcess(processId); if (!process) throw new Error('Process not found'); let lastState = this.processService.getLastCommitedState(process); let currentProcess = process; if (!lastState) { const first = process.states[0]; if (this.rolesContainsUs(first.roles)) { const appRes = await this.approveChange(processId, first.state_id); await this.handleApiReturn(appRes); const prdRes = await this.createPrdUpdate(processId, first.state_id); await this.handleApiReturn(prdRes); } else if (first.validation_tokens.length > 0) { const res = await this.createPrdUpdate(processId, first.state_id); await this.handleApiReturn(res); } const updated = await this.processService.getProcess(processId); if (updated) currentProcess = updated; lastState = this.processService.getLastCommitedState(currentProcess); if (!lastState) throw new Error('Still no commited state'); } const lastStateIndex = this.getLastCommitedStateIndex(currentProcess); if (lastStateIndex === null) throw new Error('Index commited introuvable'); const privateData: any = {}; const publicData: any = {}; for (const field of Object.keys(newData)) { if (lastState.public_data[field]) { publicData[field] = newData[field]; continue; } if (privateFields.includes(field)) { privateData[field] = newData[field]; continue; } let isPrivate = false; for (let i = lastStateIndex; i >= 0; i--) { if (currentProcess.states[i].pcd_commitment[field]) { privateData[field] = newData[field]; isPrivate = true; break; } } if (!isPrivate) publicData[field] = newData[field]; } const finalRoles = roles || this.processService.getRoles(currentProcess); const { encodedPrivateData, encodedPublicData } = await this.prepareProcessData(privateData, publicData); const res = this.sdkService.getClient().update_process(currentProcess, encodedPrivateData, finalRoles, encodedPublicData, this.membersList); if (res.updated_process) await this.ensureConnections(res.updated_process.current_process); return res; } private async prepareProcessData(priv: any, pub: any) { const p1 = this.splitData(priv); const p2 = this.splitData(pub); return { encodedPrivateData: { ...this.sdkService.getClient().encode_json(p1.jsonCompatibleData), ...this.sdkService.getClient().encode_binary(p1.binaryData) }, encodedPublicData: { ...this.sdkService.getClient().encode_json(p2.jsonCompatibleData), ...this.sdkService.getClient().encode_binary(p2.binaryData) }, }; } // ========================================== // API METHODS (Actions) // ========================================== public async createPrdUpdate(pid: string, sid: string) { const p = await this.getProcess(pid); await this.ensureConnections(p!); return this.sdkService.getClient().create_update_message(p, sid, this.membersList); } public async createPrdResponse(pid: string, sid: string) { const p = await this.getProcess(pid); return this.sdkService.getClient().create_response_prd(p, sid, this.membersList); } public async approveChange(pid: string, sid: string) { const p = await this.getProcess(pid); const res = this.sdkService.getClient().validate_state(p, sid, this.membersList); if (res.updated_process) await this.ensureConnections(res.updated_process.current_process); return res; } public async rejectChange(pid: string, sid: string) { const p = await this.getProcess(pid); return this.sdkService.getClient().refuse_state(p, sid); } public async requestDataFromPeers(pid: string, sids: string[], roles: any) { const res = this.sdkService.getClient().request_data(pid, sids, roles, this.membersList); await this.handleApiReturn(res); } public async resetDevice() { this.sdkService.getClient().reset_device(); await this.db.clearMultipleStores(['wallet', 'shared_secrets', 'unconfirmed_secrets', 'processes', 'diffs']); } public async handleApiReturn(res: ApiReturn) { if (!res || Object.keys(res).length === 0) return; try { const txData = (res.partial_tx ? await this.handlePartialTx(res.partial_tx) : null) || res.new_tx_to_send; if (txData && txData.transaction.length != 0) { if (this.networkSender) this.networkSender('NewTx', JSON.stringify(txData)); await new Promise((r) => setTimeout(r, APP_CONFIG.TIMEOUTS.API_DELAY)); } if (res.secrets) await this.handleSecrets(res.secrets); if (res.updated_process) await this.handleUpdatedProcess(res.updated_process); if (res.push_to_storage) await this.handlePushToStorage(res.push_to_storage); if (res.commit_to_send && this.networkSender) this.networkSender('Commit', JSON.stringify(res.commit_to_send)); if (res.ciphers_to_send && this.networkSender) for (const c of res.ciphers_to_send) this.networkSender('Cipher', c); } catch (e) { console.error('ApiReturn Error:', e); } } private async handlePartialTx(partialTx: any): Promise { try { return this.sdkService.getClient().sign_transaction(partialTx).new_tx_to_send; } catch (e) { return null; } } private async handleSecrets(secrets: any) { const { unconfirmed_secrets, shared_secrets } = secrets; const unconfirmedList = unconfirmed_secrets && unconfirmed_secrets.length > 0 ? unconfirmed_secrets : []; const sharedList = shared_secrets && Object.keys(shared_secrets).length > 0 ? Object.entries(shared_secrets).map(([key, value]) => ({ key, value })) : []; if (unconfirmedList.length > 0 || sharedList.length > 0) { await this.db.saveSecretsBatch(unconfirmedList, sharedList); } } private async handleUpdatedProcess(updated: any) { const pid = updated.process_id; if (updated.encrypted_data) { for (const [h, c] of Object.entries(updated.encrypted_data as Record)) await this.saveBlobToDb(h, this.hexToBlob(c)); } await this.processService.saveProcessToDb(pid, updated.current_process); if (updated.diffs) await this.saveDiffsToDb(updated.diffs); this._resolvePendingKeyRequests(pid, updated.current_process); // Notification UI if (this.notifier) this.notifier('processes-updated'); } public async saveDiffsToDb(diffs: UserDiff[]) { await this.db.saveDiffs(diffs); } private _resolvePendingKeyRequests(processId: string, process: Process) { if (this.pendingKeyRequests.size === 0) return; for (const state of process.states) { if (!state.keys) continue; for (const [attr, key] of Object.entries(state.keys)) { const rid = `${processId}_${state.state_id}_${attr}`; if (this.pendingKeyRequests.has(rid)) { this.pendingKeyRequests.get(rid)?.(key as string); this.pendingKeyRequests.delete(rid); } } } } private async handlePushToStorage(hashes: string[]) { for (const hash of hashes) { try { const blob = await this.getBlobFromDb(hash); const diff = await this.getDiffByValue(hash); if (blob && diff) await this.saveDataToStorage(diff.storages, hash, blob, null); } catch (e) { console.error('Push error', e); } } } public async handleCommitError(response: string) { const content = JSON.parse(response); const errorMsg = content.error['GenericError']; if (!['State is identical to the previous state', 'Not enough valid proofs'].includes(errorMsg)) { // Retry via network callback if (this.networkSender) { setTimeout(() => this.networkSender!('Commit', JSON.stringify(content)), APP_CONFIG.TIMEOUTS.RETRY_DELAY); } } } public rolesContainsUs(roles: any) { return this.processService.rolesContainsMember(roles, this.getPairingProcessId()); } public async getSecretForAddress(address: string): Promise { return await this.db.getSharedSecret(address); } public async getAllDiffs(): Promise> { return await this.db.getAllDiffs(); } public async getDiffByValue(value: string): Promise { return await this.db.getDiff(value); } public async getAllSecrets(): Promise { return await this.db.getAllSecrets(); } // ========================================== // STORAGE & DB // ========================================== public async saveBlobToDb(h: string, d: Blob) { await this.db.saveBlob(h, d); } public async getBlobFromDb(h: string) { return await this.db.getBlob(h); } public async fetchValueFromStorage(h: string) { return retrieveData([APP_CONFIG.URLS.STORAGE], h); } public async saveDataToStorage(s: string[], h: string, d: Blob, ttl: number | null) { return storeData(s, h, d, ttl); } // ========================================== // HELPERS // ========================================== public getProcessName(p: Process) { const pub = this.getPublicData(p); if (pub && pub['processName']) return this.decodeValue(pub['processName']); return null; } public getPublicData(p: Process) { const last = this.getLastCommitedState(p); return last ? last.public_data : p.states[0]?.public_data || null; } public updateMemberPublicName(pid: string, name: string) { return this.updateProcess(pid, { memberPublicName: name }, [], null); } // ========================================== // UI HELPERS // ========================================== public getNotifications() { return this.notifications; } public setNotifications(n: any[]) { this.notifications = n; } // ========================================== // PARSING & RESEAU ENTRANT // ========================================== async parseCipher(msg: string) { try { const res = this.sdkService.getClient().parse_cipher(msg, this.membersList, await this.getProcesses()); await this.handleApiReturn(res); } catch (e) { console.error('Cipher Error', e); } } async parseNewTx(msg: string) { const parsed = JSON.parse(msg); if (parsed.error) return; const prevouts = this.sdkService.getClient().get_prevouts(parsed.transaction); for (const p of Object.values(await this.getProcesses())) { const tip = p.states[p.states.length - 1].commited_in; if (prevouts.includes(tip)) { const newTip = this.sdkService.getClient().get_txid(parsed.transaction); const newStateId = this.sdkService.getClient().get_opreturn(parsed.transaction); this.sdkService.getClient().process_commit_new_state(p, newStateId, newTip); break; } } try { const res = this.sdkService.getClient().parse_new_tx(msg, 0, this.membersList); if (res && (res.partial_tx || res.new_tx_to_send || res.secrets || res.updated_process)) { await this.handleApiReturn(res); const d = this.dumpDeviceFromMemory(); const old = await this.getDeviceFromDatabase(); if (old && old.pairing_process_commitment) d.pairing_process_commitment = old.pairing_process_commitment; await this.saveDeviceInDatabase(d); } } catch (e) { } } // ========================================== // BACKUP & RESTORE // ========================================== public async importJSON(backup: BackUp) { await this.resetDevice(); await this.walletService.saveDeviceInDatabase(backup.device); this.walletService.restoreDevice(backup.device); await this.processService.batchSaveProcesses(backup.processes); await this.restoreSecretsFromBackUp(backup.secrets); } public async restoreSecretsFromBackUp(secretsStore: SecretsStore) { const sharedList = Object.entries(secretsStore.shared_secrets).map(([key, value]) => ({ key, value })); await this.db.saveSecretsBatch(secretsStore.unconfirmed_secrets, sharedList); await this.restoreSecretsFromDB(); } public async restoreSecretsFromDB() { const secretsStore = await this.db.getAllSecrets(); this.sdkService.getClient().set_shared_secrets(JSON.stringify(secretsStore)); console.log("[CoreWorker] 🔐 Secrets restaurés depuis la DB"); } public async createBackUp() { const device = await this.walletService.getDeviceFromDatabase(); if (!device) return null; return { device, processes: await this.processService.getProcesses(), secrets: await this.getAllSecrets() }; } // ========================================== // DECRYPT ATTRIBUTE // ========================================== public async decryptAttribute(processId: string, state: ProcessState, attribute: string): Promise { console.groupCollapsed(`[CoreWorker] 🔑 Déchiffrement de '${attribute}' (Process: ${processId})`); try { let hash: string | null | undefined = state.pcd_commitment[attribute]; let key: string | null | undefined = state.keys[attribute]; const pairingProcessId = this.getPairingProcessId(); if (!hash) { console.warn(`⚠️ L'attribut n'existe pas (pas de hash).`); return null; } if (!key) { if (!this._checkAccess(state, attribute, pairingProcessId)) { console.log(`⛔ Accès non autorisé. Abandon.`); return null; } const result = await this._fetchMissingKey(processId, state, attribute); hash = result.hash; key = result.key; } if (hash && key) { const blob = await this.getBlobFromDb(hash); if (!blob) { console.error(`💥 Échec: Blob non trouvé en BDD pour le hash ${hash}`); return null; } try { const buf = await blob.arrayBuffer(); const cipher = new Uint8Array(buf); const keyUIntArray = this.hexToUInt8Array(key); const clear = this.sdkService.getClient().decrypt_data(keyUIntArray, cipher); if (!clear) throw new Error('decrypt_data returned null'); const decoded = this.sdkService.getClient().decode_value(clear); console.log(`✅ Attribut '${attribute}' déchiffré avec succès.`); return decoded; } catch (e) { console.error(`💥 Échec du déchiffrement: ${e}`); return null; } } return null; } catch (error) { console.error(`💥 Erreur:`, error); return null; } finally { console.groupEnd(); } } private _checkAccess(state: ProcessState, attribute: string, pairingProcessId: string): boolean { const roles = state.roles; return Object.values(roles).some((role) => { const isMember = role.members.includes(pairingProcessId); if (!isMember) return false; return Object.values(role.validation_rules).some((rule) => rule.fields.includes(attribute)); }); } private async _fetchMissingKey(processId: string, state: ProcessState, attribute: string): Promise<{ hash: string | null; key: string | null }> { try { const process = await this.getProcess(processId); if (!process) return { hash: null, key: null }; await this.ensureConnections(process); await this.requestDataFromPeers(processId, [state.state_id], [state.roles]); const requestId = `${processId}_${state.state_id}_${attribute}`; const keyRequestPromise = new Promise((resolve, reject) => { const timeout = setTimeout(() => { this.pendingKeyRequests.delete(requestId); reject(new Error(`Timeout waiting for key: ${attribute}`)); }, APP_CONFIG.TIMEOUTS.KEY_REQUEST); this.pendingKeyRequests.set(requestId, (key: string) => { clearTimeout(timeout); resolve(key); }); }); const receivedKey = await keyRequestPromise; const updatedProcess = await this.getProcess(processId); if (!updatedProcess) return { hash: null, key: null }; const updatedState = this.getStateFromId(updatedProcess, state.state_id); const updatedHash = updatedState ? updatedState.pcd_commitment[attribute] : state.pcd_commitment[attribute]; return { hash: updatedHash, key: receivedKey }; } catch (e) { return { hash: null, key: null }; } } } Comlink.expose(new CoreBackend());