@anonympins/fingerprint 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,498 +1,532 @@
1
- /**
2
- * @file @/pow.solver.js
3
- * @description Contient les fonctions côté client pour résoudre les différents types de challenges Proof-of-Work.
4
- * IMPORTANT: Pour les tâches d'optimisation, ce fichier a besoin d'accéder aux algorithmes de `library.js`.
5
- * Dans un vrai projet, il faudrait bundler une version client de `library.js` et l'importer ici.
6
- * Pour cet exemple, nous allons copier/coller les fonctions nécessaires.
7
- * Fichier compatible à la fois avec l'import de modules ES6 et l'injection directe dans un script HTML.
8
- */
9
-
10
- 'use strict';
11
-
12
- /**
13
- * Résout un challenge CPU basé sur une cible en utilisant un bloc de base binaire.
14
- * @param {Uint8Array} baseBlock - Le bloc de données initial (nonce, secret, fp) fourni par le serveur.
15
- * @param {bigint} target - La cible à atteindre.
16
- * @param {Function} progressCallback - Callback pour les mises à jour de progression.
17
- * @returns {Promise<number>} La solution (un nombre entier).
18
- */
19
- export async function solveCpuTargetInline(baseBlock, target, progressCallback) {
20
- // --- FIX: Add validation for the target to prevent BigInt conversion errors ---
21
- if (typeof target !== 'bigint' && (typeof target !== 'string' || !/^[0-9a-fA-F]+$/.test(target))) {
22
- throw new TypeError(`Invalid target type: expected a BigInt or a hex string, but got ${typeof target} with value ${target}`);
23
- }
24
-
25
- const cpuTarget = typeof target === 'bigint' ? target : BigInt('0x' + target);
26
- // --- END FIX ---
27
- const encoder = new TextEncoder();
28
-
29
- const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
30
- if (wasmModule && typeof wasmModule._solve_cpu_target === 'function') {
31
- const len = baseBlock.length;
32
- const ptr = wasmModule._malloc(len);
33
- wasmModule.HEAPU8.set(baseBlock, ptr);
34
- const targetStr = typeof target === 'string' ? target : target.toString(16);
35
- const targetPtr = wasmModule._malloc(targetStr.length + 1);
36
- for (let i = 0; i < targetStr.length; i++) {
37
- wasmModule.HEAP8[targetPtr + i] = targetStr.charCodeAt(i);
38
- }
39
- wasmModule.HEAP8[targetPtr + targetStr.length] = 0;
40
- const solution = wasmModule._solve_cpu_target(ptr, len, targetPtr);
41
- wasmModule._free(ptr);
42
- wasmModule._free(targetPtr);
43
- return solution;
44
- }
45
-
46
- let cpuSolution = 0;
47
-
48
- while (true) {
49
- const solutionBytes = encoder.encode(String(cpuSolution));
50
-
51
- // Concaténation binaire directe : c'est plus rapide et plus sûr.
52
- const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
53
- finalBlock.set(baseBlock);
54
- finalBlock.set(solutionBytes, baseBlock.length);
55
-
56
- if (cpuSolution === 0) {
57
- const reconstructedMsg = new TextDecoder().decode(finalBlock);
58
- console.log('[FP Solve Debug] Client will hash message:', reconstructedMsg);
59
- }
60
- const buf = await crypto.subtle.digest("SHA-256", finalBlock);
61
- const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
62
- // --- AJOUT DE LOGS POUR LE DÉBOGAGE CÔTÉ CLIENT ---
63
- if (cpuSolution === 0) { // Log only the first attempt
64
- console.log(`[FP Client Solve] Attempt 0 hash: "0x${hashHex}"`);
65
- }
66
- // --- FIN DES LOGS ---
67
- if (BigInt('0x' + hashHex) < cpuTarget) break;
68
- cpuSolution++;
69
- if (cpuSolution % 100000 === 0) {
70
- await new Promise(r => setTimeout(r, 0));
71
- if (progressCallback) progressCallback(cpuSolution);
72
- }
73
- }
74
- return cpuSolution;
75
- }
76
-
77
- /**
78
- * Résout un challenge CPU basé sur une cible (version Web Worker).
79
- * @param {string} message - Le message à hasher (ex: `ip:nonce:solution:secret`).
80
- * @param {bigint} target - La cible à atteindre.
81
- * @returns {Promise<number>} La solution (un nombre entier).
82
- */
83
- export async function solveCpuTarget(message, target) {
84
- // Vérifie si les Web Workers sont supportés par le navigateur.
85
- if (typeof(Worker) === "undefined") {
86
- console.warn("Web Workers not supported. Falling back to main thread calculation (UI may freeze).");
87
- // Ici, on pourrait remettre l'ancienne implémentation comme solution de secours.
88
- // Pour la clarté, nous supposons que les workers sont disponibles.
89
- throw new Error("Web Worker support is required for CPU challenges.");
90
- }
91
-
92
- return new Promise((resolve, reject) => {
93
- // Crée un worker à partir du script dédié. Le chemin doit être accessible publiquement.
94
- // Assurez-vous que `pow.worker.js` est servi par votre serveur statique.
95
- const worker = new Worker('./pow.worker.js');
96
-
97
- worker.onmessage = (event) => {
98
- resolve(event.data.solution);
99
- worker.terminate(); // Nettoie le worker une fois le travail terminé.
100
- };
101
-
102
- worker.onerror = (error) => {
103
- reject(error);
104
- worker.terminate();
105
- };
106
-
107
- // Envoie les données du challenge au worker pour qu'il commence le calcul.
108
- worker.postMessage({ message, target });
109
- });
110
- }
111
-
112
- /**
113
- * Résout un challenge basé sur la mémoire.
114
- * @param {string} seed - La graine pour l'initialisation de la mémoire.
115
- * @param {number} difficulty - La difficulté (en Mo).
116
- * @returns {Promise<number>} La solution (nombre entier).
117
- */
118
- export async function solveMemory(seed, difficulty) {
119
- // On définit un seuil pour savoir quand faire une pause, afin de ne pas bloquer le thread UI.
120
- const YIELD_THRESHOLD = 100000;
121
- const size = difficulty * 1024 * 1024;
122
- const buffer = new Uint32Array(size / 4);
123
-
124
- const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
125
- if (wasmModule && typeof wasmModule._solve_memory_challenge === 'function') {
126
- const seedPtr = wasmModule._malloc(seed.length + 1);
127
- for (let i = 0; i < seed.length; i++) {
128
- wasmModule.HEAP8[seedPtr + i] = seed.charCodeAt(i);
129
- }
130
- wasmModule.HEAP8[seedPtr + seed.length] = 0;
131
- const solution = wasmModule._solve_memory_challenge(seedPtr, difficulty);
132
- wasmModule._free(seedPtr);
133
- return solution;
134
- }
135
-
136
- let h = new TextEncoder().encode(seed).reduce((acc, v) => acc + v, 0);
137
-
138
- for (let i = 0; i < buffer.length; i++) {
139
- buffer[i] = (h = Math.imul(h ^ i, 1597334677));
140
- if (i % YIELD_THRESHOLD === 0) {
141
- await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
142
- }
143
- }
144
-
145
- let solution = 0;
146
- const iterations = size / 16;
147
- let addr = buffer.length > 0 ? buffer[0] % buffer.length : 0;
148
- for (let i = 0; i < iterations; i++) {
149
- addr = buffer[addr] % buffer.length;
150
- solution ^= addr;
151
- if (i % YIELD_THRESHOLD === 0) {
152
- await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
153
- }
154
- }
155
- return solution;
156
- }
157
-
158
- /**
159
- * Résout un challenge de type "Problème du Voyageur de Commerce" (TSP).
160
- * NOTE: Ceci est une implémentation simple (heuristique du plus proche voisin) et n'est pas garantie
161
- * de trouver la solution optimale, mais elle est suffisante pour un challenge.
162
- * @param {Array<{x: number, y: number}>} cities - Les coordonnées des villes.
163
- * @param {number} targetMaxDistance - La distance maximale acceptable.
164
- * @returns {Promise<{path: number[], distance: number}>} Le chemin et la distance.
165
- */
166
- export async function solveTsp(cities, targetMaxDistance) {
167
- // Utility function to calculate the distance between two cities
168
- function distance(city1, city2) {
169
- return Math.sqrt(Math.pow(city1.x - city2.x, 2) + Math.pow(city1.y - city2.y, 2));
170
- }
171
-
172
- // Utility function to evaluate the total distance of a path
173
- function evaluatePathDistance(cities, path) {
174
- let totalDistance = 0;
175
- for (let i = 0; i < path.length - 1; i++) {
176
- totalDistance += distance(cities[path[i]], cities[path[i + 1]]);
177
- }
178
- totalDistance += distance(cities[path[path.length - 1]], cities[path[0]]); // Return to start
179
- return totalDistance;
180
- }
181
-
182
- // Solveur simple du TSP (heuristique du plus proche voisin)
183
- function solveTspNearestNeighbor(cities) {
184
- const numCities = cities.length;
185
- if (numCities === 0) return [];
186
-
187
- let currentPath = [];
188
- let visited = new Array(numCities).fill(false);
189
-
190
- let currentCityIndex = 0; // Always start with the first city for reproducibility
191
- currentPath.push(currentCityIndex);
192
- visited[currentCityIndex] = true;
193
-
194
- for (let i = 1; i < numCities; i++) {
195
- let nearestCityIndex = -1;
196
- let minDistance = Infinity;
197
-
198
- for (let j = 0; j < numCities; j++) {
199
- if (!visited[j]) {
200
- const dist = distance(cities[currentCityIndex], cities[j]);
201
- if (dist < minDistance) {
202
- minDistance = dist;
203
- nearestCityIndex = j;
204
- }
205
- }
206
- }
207
- currentCityIndex = nearestCityIndex;
208
- currentPath.push(currentCityIndex);
209
- visited[currentCityIndex] = true;
210
- }
211
- return currentPath;
212
- }
213
-
214
- // To avoid freezing the browser, yield the thread from time to time
215
- await new Promise(resolve => setTimeout(resolve, 10));
216
- const solutionPath = solveTspNearestNeighbor(cities);
217
- const solutionDistance = evaluatePathDistance(cities, solutionPath);
218
-
219
- return { path: solutionPath, distance: solutionDistance };
220
- }
221
-
222
- // --- Fonctions d'optimisation copiées/adaptées de library.js pour le client ---
223
-
224
- const ClientOptimizers = {
225
- simulatedAnnealing(initialSolution, evaluator, neighbor, iterations, temp, cooling) {
226
- let currentSolution = initialSolution;
227
- let currentEnergy = evaluator(currentSolution);
228
- let temperature = temp;
229
-
230
- for (let i = 0; i < iterations; i++) {
231
- const newSolution = neighbor(currentSolution);
232
- const newEnergy = evaluator(newSolution);
233
- if (newEnergy < currentEnergy || Math.random() < Math.exp((currentEnergy - newEnergy) / temperature)) {
234
- currentSolution = newSolution;
235
- currentEnergy = newEnergy;
236
- }
237
- temperature *= cooling;
238
- }
239
- return { solution: currentSolution, energy: currentEnergy };
240
- },
241
-
242
- geneticAlgorithm(createIndividual, fitness, crossover, mutate, generations, popSize) {
243
- let population = Array.from({ length: popSize }, () => {
244
- const chromosome = createIndividual();
245
- return { chromosome, fitness: fitness(chromosome) };
246
- });
247
-
248
- for (let gen = 0; gen < generations; gen++) {
249
- population.sort((a, b) => a.fitness - b.fitness);
250
- const newPopulation = [population[0]]; // Elitism
251
- while (newPopulation.length < popSize) {
252
- const p1 = population[Math.floor(Math.random() * (popSize / 2))];
253
- const p2 = population[Math.floor(Math.random() * (popSize / 2))];
254
- let offspring = crossover(p1.chromosome, p2.chromosome);
255
- if (Math.random() < 0.1) offspring = mutate(offspring);
256
- newPopulation.push({ chromosome: offspring, fitness: fitness(offspring) });
257
- }
258
- population = newPopulation;
259
- }
260
- return population;
261
- }
262
- };
263
-
264
- /**
265
- * Résout une unité de travail utile (Useful Work Unit).
266
- * @param {object} task - La tâche envoyée par le serveur.
267
- * @returns {Promise<object>} Le résultat du calcul.
268
- */
269
- async function solveUsefulWorkTask(task) {
270
- await new Promise(r => setTimeout(r, 10)); // Yield thread
271
-
272
- switch (task.type) {
273
- case 'simulated_annealing_iterations': {
274
- const { cities } = task.payload;
275
- const distance = (c1, c2) => Math.sqrt(Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2));
276
- const evaluator = (path) => {
277
- let total = 0;
278
- for (let i = 0; i < path.length - 1; i++) total += distance(cities[path[i]], cities[path[i + 1]]);
279
- total += distance(cities[path[path.length - 1]], cities[path[0]]);
280
- return total;
281
- };
282
- const neighbor = (path) => {
283
- const newPath = [...path];
284
- const [i, j] = [Math.floor(Math.random() * path.length), Math.floor(Math.random() * path.length)];
285
- [newPath[i], newPath[j]] = [newPath[j], newPath[i]];
286
- return newPath;
287
- };
288
- const initialSolution = task.initialSolution || Array.from({ length: cities.length }, (_, i) => i).sort(() => 0.5 - Math.random());
289
-
290
- return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
291
- }
292
-
293
- case 'genetic_algorithm_generations': {
294
- const { assets, maxVolatility } = task.payload;
295
- const fitness = (weights) => {
296
- const total = weights.reduce((s, w) => s + w, 0);
297
- if (total === 0) return Infinity;
298
- const normW = weights.map(w => w / total);
299
- const ret = normW.reduce((s, w, i) => s + w * assets[i].expectedReturn, 0);
300
- const vol = normW.reduce((s, w, i) => s + w * assets[i].volatility, 0);
301
- if (vol > maxVolatility) return 1000 + (vol - maxVolatility);
302
- return -ret;
303
- };
304
- const createIndividual = () => Array.from({ length: assets.length }, Math.random);
305
- const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
306
- const mutate = p => { const n = [...p], i = Math.floor(Math.random() * n.length); n[i] += (Math.random() - 0.5) * 0.2; return n.map(v => Math.max(0, v)); };
307
-
308
- // Le client doit recréer la population si elle n'est pas fournie
309
- const initialPopulation = task.initialPopulation || Array.from({ length: task.payload.options.populationSize }, () => ({ chromosome: createIndividual(), fitness: 0 }));
310
- initialPopulation.forEach(p => p.fitness = fitness(p.chromosome));
311
-
312
- const finalPopulation = ClientOptimizers.geneticAlgorithm(createIndividual, fitness, crossover, mutate, task.generations, task.payload.options.populationSize);
313
- return { population: finalPopulation };
314
- }
315
-
316
- case 'run_multiple_parallel':
317
- // Côté client, on ne peut pas utiliser de vrais workers pour `runMultipleParallel`.
318
- // On exécute donc une version simplifiée : un seul cycle du solveur demandé.
319
- // Cela reste un travail coûteux et valide le principe du "Useful Work".
320
- const { solverName, baseSolverArgs } = task;
321
- const clientSolver = ClientOptimizers[solverName];
322
- if (!clientSolver) throw new Error(`Solver ${solverName} not found on client.`);
323
-
324
- // On simule l'appel avec les arguments de base.
325
- // Note: `baseSolverArgs` peut contenir des options.
326
- return clientSolver(...baseSolverArgs);
327
-
328
- case 'multi_objective_genetic_algorithm': {
329
- // C'est ici que la connexion se fait !
330
- // On cherche le solveur demandé (ex: 'cpc.solve') dans notre registre client.
331
- const solverFunction = ClientOptimizers[task.solverName];
332
- if (!solverFunction) {
333
- throw new Error(`Solver '${task.solverName}' not found on client.`);
334
- }
335
- // On appelle le solveur en lui passant le payload et les options.
336
- // La fonction `solveOptimalCPC` attend le payload comme premier argument.
337
- return solverFunction(task.payload, { generations: task.generations, initialFront: task.initialFront });
338
- }
339
-
340
- default:
341
- throw new Error(`Unknown useful work type: ${task.type}`);
342
- }
343
- }
344
-
345
- /**
346
- * Résout une tâche d'optimisation basée sur un algorithme génétique.
347
- * Reçoit une population et la fait évoluer pendant un certain nombre de générations.
348
- * NOTE: Cette fonction est une version simplifiée de l'AG de `library.js` adaptée au client.
349
- * @param {Array<object>} initialPopulation - La population de départ.
350
- * @param {number} generations - Le nombre de générations à exécuter.
351
- * @returns {Promise<Array<object>>} La population finale après évolution.
352
- */
353
- export async function solveOptimizationTask(initialPopulation, generations) {
354
- // Fonctions AG simplifiées (croisement, mutation)
355
- const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
356
- const mutate = (p) => {
357
- const newP = [...p];
358
- const i = Math.floor(Math.random() * newP.length);
359
- newP[i] += (Math.random() - 0.5) * 0.2;
360
- return newP;
361
- };
362
-
363
- let population = initialPopulation;
364
-
365
- for (let gen = 0; gen < generations; gen++) {
366
- // Sélection simple : on garde les 50% meilleurs
367
- const parents = population.sort((a, b) => a.fitness - b.fitness).slice(0, Math.ceil(population.length / 2));
368
- const newPopulation = [...parents]; // Élitisme
369
-
370
- while (newPopulation.length < population.length) {
371
- const parent1 = parents[Math.floor(Math.random() * parents.length)];
372
- const parent2 = parents[Math.floor(Math.random() * parents.length)];
373
- let offspring = crossover(parent1.chromosome, parent2.chromosome);
374
- if (Math.random() < 0.1) offspring = mutate(offspring);
375
- // La fitness sera recalculée côté serveur pour la vérification.
376
- newPopulation.push({ chromosome: offspring, fitness: -1 });
377
- }
378
- population = newPopulation;
379
- // Pause pour ne pas geler l'UI sur les longues tâches
380
- if (gen % 10 === 0) await new Promise(r => setTimeout(r, 0));
381
- }
382
- return population;
383
- }
384
-
385
- /**
386
- * @class ChallengeSolution
387
- * @description Encapsule une solution de challenge et fournit des méthodes pour la manipuler.
388
- * @private
389
- */
390
- class ChallengeSolution {
391
- constructor(type, nonce, rawSolution) {
392
- this.type = type;
393
- this.nonce = nonce;
394
- this.rawSolution = rawSolution;
395
- }
396
-
397
- /**
398
- * Applique les paramètres de la solution à un objet URL.
399
- * @param {URL} url - L'objet URL à modifier.
400
- */
401
- applyToUrl(url) {
402
- url.searchParams.set('pow_type', this.type);
403
- url.searchParams.set('pow_nonce', this.nonce);
404
-
405
- // Logique de formatage spécifique à chaque type de challenge
406
- if (this.type === 'cpu_mem' || this.type === 'cpu_mem_inline' || this.type === 'cpu_target') {
407
- Object.entries(this.rawSolution).forEach(([key, value]) => {
408
- url.searchParams.set(`pow_solution_${key}`, String(value));
409
- });
410
- } else if (this.type === 'useful_work_task') {
411
- url.searchParams.set('pow_solution_work_result', JSON.stringify(this.rawSolution.work_result));
412
- url.searchParams.set('pow_problem_id', this.rawSolution.problem_id);
413
- } else {
414
- // Pour les cas simples comme 'tsp' la solution est une seule valeur
415
- url.searchParams.set('pow_solution', JSON.stringify(this.rawSolution));
416
- }
417
- }
418
- }
419
-
420
- /**
421
- * Fonction principale qui reçoit un objet challenge et le résout.
422
- * @param {object} challenge - L'objet challenge reçu du serveur.
423
- * @param {string} [fingerprint=''] - L'empreinte de l'appareil qui résout le challenge.
424
- * @returns {Promise<ChallengeSolution>} Un objet `ChallengeSolution` encapsulant le résultat.
425
- */
426
- export async function solveChallenge(challenge, fingerprint = '') { // The fingerprint is now passed from the client library
427
- const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance, optimizationTask, usefulWorkTask } = challenge;
428
- let rawSolution = {};
429
-
430
- switch (type) {
431
- case 'cpu_target':
432
- // Note: This case is not fully exercised by tests as it relies on Web Workers.
433
- if (!cpuTarget) {
434
- throw new Error("Challenge data is missing 'cpuTarget' property.");
435
- }
436
- const target = cpuTarget; // Keep variable name for consistency below
437
- // Pour ce challenge simple, le baseBlock est juste le nonce.
438
- const baseBlockBytes = new TextEncoder().encode(nonce + ":");
439
- rawSolution.cpu = await solveCpuTargetInline(baseBlockBytes, target, null);
440
- break;
441
- case 'cpu_mem':
442
- // Pour les appels API, le client IP n'est pas connu, on ne le met pas dans le message
443
- const baseMessageCombined = `:${nonce}:${clientSecret}`;
444
- const memSeed = `:${nonce}:${clientSecret}`;
445
- const [cpuSol, memSol] = await Promise.all([
446
- (async () => {
447
- if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property."); // Pass fingerprint to solver
448
- const baseBlock = new Uint8Array(challenge.baseBlock);
449
- return solveCpuTargetInline(baseBlock, cpuTarget, null);
450
- })(),
451
- solveMemory(memSeed, memDifficulty)
452
- ]);
453
- rawSolution.cpu = cpuSol;
454
- rawSolution.mem = memSol;
455
- break;
456
- case 'cpu_mem_inline':
457
- // Version inline pour compatibilité HTML avec IP incluse
458
- const memSeedInline = `:${nonce}:${clientSecret}`;
459
- const [cpuSolInline, memSolInline] = await Promise.all([
460
- (async () => {
461
- if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property.");
462
- const baseBlock = new Uint8Array(challenge.baseBlock);
463
- return solveCpuTargetInline(baseBlock, cpuTarget, null);
464
- })(),
465
- solveMemory(memSeedInline, memDifficulty)
466
- ]);
467
- rawSolution.cpu = cpuSolInline;
468
- rawSolution.mem = memSolInline;
469
- break;
470
- case 'tsp':
471
- const tspResult = await solveTsp(cities, targetMaxDistance);
472
- // Pour ce challenge, la solution est juste le chemin.
473
- rawSolution = tspResult.path;
474
- break;
475
- case 'optimization_task':
476
- const finalPopulation = await solveOptimizationTask(optimizationTask.population, optimizationTask.generations);
477
- rawSolution = finalPopulation.map(p => p.chromosome); // On ne renvoie que les chromosomes
478
- break;
479
- case 'useful_work_task':
480
- const workResult = await solveUsefulWorkTask(usefulWorkTask.task);
481
- rawSolution.work_result = workResult;
482
- rawSolution.problem_id = usefulWorkTask.problemId;
483
- break;
484
- default:
485
- throw new Error(`Unknown challenge type: ${type}`);
486
- }
487
-
488
- return new ChallengeSolution(type, nonce, rawSolution);
489
- }
490
-
491
- // --- Compatibilité pour l'injection directe dans le HTML ---
492
- // Si le script est chargé dans un navigateur (window existe), on attache les fonctions nécessaires à window.
493
- if (typeof window !== 'undefined') {
494
- window.solveCpuChallengeInline = solveCpuTargetInline;
495
- window.solveMemoryChallenge = solveMemory;
496
- window.solveTspChallenge = solveTsp;
497
- window.solveChallenge = solveChallenge;
1
+ /**
2
+ * @file @/pow.solver.js
3
+ * @description Contient les fonctions côté client pour résoudre les différents types de challenges Proof-of-Work.
4
+ * IMPORTANT: Pour les tâches d'optimisation, ce fichier a besoin d'accéder aux algorithmes de `library.js`.
5
+ * Dans un vrai projet, il faudrait bundler une version client de `library.js` et l'importer ici.
6
+ * Pour cet exemple, nous allons copier/coller les fonctions nécessaires.
7
+ * Fichier compatible à la fois avec l'import de modules ES6 et l'injection directe dans un script HTML.
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ /**
13
+ * Résout un challenge CPU basé sur une cible en utilisant un bloc de base binaire.
14
+ * @param {Uint8Array} baseBlock - Le bloc de données initial (nonce, secret, fp) fourni par le serveur.
15
+ * @param {bigint} target - La cible à atteindre.
16
+ * @param {Function} progressCallback - Callback pour les mises à jour de progression.
17
+ * @returns {Promise<number>} La solution (un nombre entier).
18
+ */
19
+ export async function solveCpuTargetInline(baseBlock, target, progressCallback) {
20
+ // --- FIX: Add validation for the target to prevent BigInt conversion errors ---
21
+ if (typeof target !== 'bigint' && (typeof target !== 'string' || !/^[0-9a-fA-F]+$/.test(target))) {
22
+ throw new TypeError(`Invalid target type: expected a BigInt or a hex string, but got ${typeof target} with value ${target}`);
23
+ }
24
+
25
+ const cpuTarget = typeof target === 'bigint' ? target : BigInt('0x' + target);
26
+ // --- END FIX ---
27
+ const encoder = new TextEncoder();
28
+
29
+ const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
30
+ if (wasmModule && typeof wasmModule._solve_cpu_target === 'function') {
31
+ const len = baseBlock.length;
32
+ const ptr = wasmModule._malloc(len);
33
+ wasmModule.HEAPU8.set(baseBlock, ptr);
34
+ const targetStr = typeof target === 'string' ? target : target.toString(16);
35
+ const targetPtr = wasmModule._malloc(targetStr.length + 1);
36
+ for (let i = 0; i < targetStr.length; i++) {
37
+ wasmModule.HEAP8[targetPtr + i] = targetStr.charCodeAt(i);
38
+ }
39
+ wasmModule.HEAP8[targetPtr + targetStr.length] = 0;
40
+ const solution = wasmModule._solve_cpu_target(ptr, len, targetPtr);
41
+ wasmModule._free(ptr);
42
+ wasmModule._free(targetPtr);
43
+ return solution;
44
+ }
45
+
46
+ let cpuSolution = 0;
47
+
48
+ while (true) {
49
+ const solutionBytes = encoder.encode(String(cpuSolution));
50
+
51
+ // Concaténation binaire directe : c'est plus rapide et plus sûr.
52
+ const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
53
+ finalBlock.set(baseBlock);
54
+ finalBlock.set(solutionBytes, baseBlock.length);
55
+
56
+ if (cpuSolution === 0) {
57
+ const reconstructedMsg = new TextDecoder().decode(finalBlock);
58
+ console.log('[FP Solve Debug] Client will hash message:', reconstructedMsg);
59
+ }
60
+ const buf = await crypto.subtle.digest("SHA-256", finalBlock);
61
+ const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
62
+ // --- AJOUT DE LOGS POUR LE DÉBOGAGE CÔTÉ CLIENT ---
63
+ if (cpuSolution === 0) { // Log only the first attempt
64
+ console.log(`[FP Client Solve] Attempt 0 hash: "0x${hashHex}"`);
65
+ }
66
+ // --- FIN DES LOGS ---
67
+ if (BigInt('0x' + hashHex) < cpuTarget) break;
68
+ cpuSolution++;
69
+ if (cpuSolution % 100000 === 0) {
70
+ await new Promise(r => setTimeout(r, 0));
71
+ if (progressCallback) progressCallback(cpuSolution);
72
+ }
73
+ }
74
+ return cpuSolution;
75
+ }
76
+
77
+ /**
78
+ * Résout un challenge CPU basé sur une cible (version Web Worker).
79
+ * @param {string} message - Le message à hasher (ex: `ip:nonce:solution:secret`).
80
+ * @param {bigint} target - La cible à atteindre.
81
+ * @returns {Promise<number>} La solution (un nombre entier).
82
+ */
83
+ export async function solveCpuTarget(message, target) {
84
+ // Vérifie si les Web Workers sont supportés par le navigateur.
85
+ if (typeof(Worker) === "undefined") {
86
+ console.warn("Web Workers not supported. Falling back to main thread calculation (UI may freeze).");
87
+ // Ici, on pourrait remettre l'ancienne implémentation comme solution de secours.
88
+ // Pour la clarté, nous supposons que les workers sont disponibles.
89
+ throw new Error("Web Worker support is required for CPU challenges.");
90
+ }
91
+
92
+ return new Promise((resolve, reject) => {
93
+ // Crée un worker à partir du script dédié. Le chemin doit être accessible publiquement.
94
+ // Assurez-vous que `pow.worker.js` est servi par votre serveur statique.
95
+ const worker = new Worker('./pow.worker.js');
96
+
97
+ worker.onmessage = (event) => {
98
+ resolve(event.data.solution);
99
+ worker.terminate(); // Nettoie le worker une fois le travail terminé.
100
+ };
101
+
102
+ worker.onerror = (error) => {
103
+ reject(error);
104
+ worker.terminate();
105
+ };
106
+
107
+ // Envoie les données du challenge au worker pour qu'il commence le calcul.
108
+ worker.postMessage({ message, target });
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Résout un challenge basé sur la mémoire.
114
+ * @param {string} seed - La graine pour l'initialisation de la mémoire.
115
+ * @param {number} difficulty - La difficulté (en Mo).
116
+ * @returns {Promise<number>} La solution (nombre entier).
117
+ */
118
+ export async function solveMemory(seed, difficulty) {
119
+ // On définit un seuil pour savoir quand faire une pause, afin de ne pas bloquer le thread UI.
120
+ const YIELD_THRESHOLD = 100000;
121
+ const size = difficulty * 1024 * 1024;
122
+ const buffer = new Uint32Array(size / 4);
123
+
124
+ const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
125
+ if (wasmModule && typeof wasmModule._solve_memory_challenge === 'function') {
126
+ const seedPtr = wasmModule._malloc(seed.length + 1);
127
+ for (let i = 0; i < seed.length; i++) {
128
+ wasmModule.HEAP8[seedPtr + i] = seed.charCodeAt(i);
129
+ }
130
+ wasmModule.HEAP8[seedPtr + seed.length] = 0;
131
+ const solution = wasmModule._solve_memory_challenge(seedPtr, difficulty);
132
+ wasmModule._free(seedPtr);
133
+ return solution;
134
+ }
135
+
136
+ let h = new TextEncoder().encode(seed).reduce((acc, v) => acc + v, 0);
137
+
138
+ for (let i = 0; i < buffer.length; i++) {
139
+ buffer[i] = (h = Math.imul(h ^ i, 1597334677));
140
+ if (i % YIELD_THRESHOLD === 0) {
141
+ await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
142
+ }
143
+ }
144
+
145
+ let solution = 0;
146
+ const iterations = size / 16;
147
+ let addr = buffer.length > 0 ? buffer[0] % buffer.length : 0;
148
+ for (let i = 0; i < iterations; i++) {
149
+ addr = buffer[addr] % buffer.length;
150
+ solution ^= addr;
151
+ if (i % YIELD_THRESHOLD === 0) {
152
+ await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
153
+ }
154
+ }
155
+ return solution;
156
+ }
157
+
158
+ /**
159
+ * Résout un challenge de type "Problème du Voyageur de Commerce" (TSP).
160
+ * NOTE: Ceci est une implémentation simple (heuristique du plus proche voisin) et n'est pas garantie
161
+ * de trouver la solution optimale, mais elle est suffisante pour un challenge.
162
+ * @param {Array<{x: number, y: number}>} cities - Les coordonnées des villes.
163
+ * @param {number} targetMaxDistance - La distance maximale acceptable.
164
+ * @returns {Promise<{path: number[], distance: number}>} Le chemin et la distance.
165
+ */
166
+ export async function solveTsp(cities, targetMaxDistance) {
167
+ // Utility function to calculate the distance between two cities
168
+ function distance(city1, city2) {
169
+ return Math.sqrt(Math.pow(city1.x - city2.x, 2) + Math.pow(city1.y - city2.y, 2));
170
+ }
171
+
172
+ // Utility function to evaluate the total distance of a path
173
+ function evaluatePathDistance(cities, path) {
174
+ let totalDistance = 0;
175
+ for (let i = 0; i < path.length - 1; i++) {
176
+ totalDistance += distance(cities[path[i]], cities[path[i + 1]]);
177
+ }
178
+ totalDistance += distance(cities[path[path.length - 1]], cities[path[0]]); // Return to start
179
+ return totalDistance;
180
+ }
181
+
182
+ // Solveur simple du TSP (heuristique du plus proche voisin)
183
+ function solveTspNearestNeighbor(cities) {
184
+ const numCities = cities.length;
185
+ if (numCities === 0) return [];
186
+
187
+ let currentPath = [];
188
+ let visited = new Array(numCities).fill(false);
189
+
190
+ let currentCityIndex = 0; // Always start with the first city for reproducibility
191
+ currentPath.push(currentCityIndex);
192
+ visited[currentCityIndex] = true;
193
+
194
+ for (let i = 1; i < numCities; i++) {
195
+ let nearestCityIndex = -1;
196
+ let minDistance = Infinity;
197
+
198
+ for (let j = 0; j < numCities; j++) {
199
+ if (!visited[j]) {
200
+ const dist = distance(cities[currentCityIndex], cities[j]);
201
+ if (dist < minDistance) {
202
+ minDistance = dist;
203
+ nearestCityIndex = j;
204
+ }
205
+ }
206
+ }
207
+ currentCityIndex = nearestCityIndex;
208
+ currentPath.push(currentCityIndex);
209
+ visited[currentCityIndex] = true;
210
+ }
211
+ return currentPath;
212
+ }
213
+
214
+ // To avoid freezing the browser, yield the thread from time to time
215
+ await new Promise(resolve => setTimeout(resolve, 10));
216
+ const solutionPath = solveTspNearestNeighbor(cities);
217
+ const solutionDistance = evaluatePathDistance(cities, solutionPath);
218
+
219
+ return { path: solutionPath, distance: solutionDistance };
220
+ }
221
+
222
+ // --- Fonctions d'optimisation copiées/adaptées de library.js pour le client ---
223
+
224
+ const ClientOptimizers = {
225
+ simulatedAnnealing(initialSolution, evaluator, neighbor, iterations, temp, cooling) {
226
+ let currentSolution = initialSolution;
227
+ let currentEnergy = evaluator(currentSolution);
228
+ let temperature = temp;
229
+
230
+ for (let i = 0; i < iterations; i++) {
231
+ const newSolution = neighbor(currentSolution);
232
+ const newEnergy = evaluator(newSolution);
233
+ if (newEnergy < currentEnergy || Math.random() < Math.exp((currentEnergy - newEnergy) / temperature)) {
234
+ currentSolution = newSolution;
235
+ currentEnergy = newEnergy;
236
+ }
237
+ temperature *= cooling;
238
+ }
239
+ return { solution: currentSolution, energy: currentEnergy };
240
+ },
241
+
242
+ geneticAlgorithm(createIndividual, fitness, crossover, mutate, generations, popSize) {
243
+ let population = Array.from({ length: popSize }, () => {
244
+ const chromosome = createIndividual();
245
+ return { chromosome, fitness: fitness(chromosome) };
246
+ });
247
+
248
+ for (let gen = 0; gen < generations; gen++) {
249
+ population.sort((a, b) => a.fitness - b.fitness);
250
+ const newPopulation = [population[0]]; // Elitism
251
+ while (newPopulation.length < popSize) {
252
+ const p1 = population[Math.floor(Math.random() * (popSize / 2))];
253
+ const p2 = population[Math.floor(Math.random() * (popSize / 2))];
254
+ let offspring = crossover(p1.chromosome, p2.chromosome);
255
+ if (Math.random() < 0.1) offspring = mutate(offspring);
256
+ newPopulation.push({ chromosome: offspring, fitness: fitness(offspring) });
257
+ }
258
+ population = newPopulation;
259
+ }
260
+ return population;
261
+ }
262
+ };
263
+
264
+ /**
265
+ * Résout une unité de travail utile (Useful Work Unit).
266
+ * @param {object} task - La tâche envoyée par le serveur.
267
+ * @returns {Promise<object>} Le résultat du calcul.
268
+ */
269
+ async function solveUsefulWorkTask(task) {
270
+ await new Promise(r => setTimeout(r, 10)); // Yield thread
271
+
272
+ switch (task.type) {
273
+ case 'simulated_annealing_iterations': {
274
+ if (task.payload && task.payload.customers) {
275
+ const { customers, numFacilities, bounds } = task.payload;
276
+ const distanceSq = (p1, p2) => Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2);
277
+ const evaluator = (facilities) => {
278
+ let totalConnectionCost = 0;
279
+ for (const customer of customers) {
280
+ let minDistanceToCustomer = Infinity;
281
+ for (const facility of facilities) {
282
+ const d = distanceSq(customer, facility);
283
+ if (d < minDistanceToCustomer) {
284
+ minDistanceToCustomer = d;
285
+ }
286
+ }
287
+ totalConnectionCost += Math.sqrt(minDistanceToCustomer);
288
+ }
289
+ const fixedCostPerFacility = task.payload.options?.fixedCostPerFacility || 0;
290
+ return totalConnectionCost + facilities.length * fixedCostPerFacility;
291
+ };
292
+ const neighbor = (facilities) => {
293
+ const newFacilities = facilities.map((f) => ({ ...f }));
294
+ const i = Math.floor(Math.random() * numFacilities);
295
+ const moveX = (Math.random() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
296
+ const moveY = (Math.random() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
297
+ newFacilities[i].x = Math.max(bounds.minX, Math.min(bounds.maxX, newFacilities[i].x + moveX));
298
+ newFacilities[i].y = Math.max(bounds.minY, Math.min(bounds.maxY, newFacilities[i].y + moveY));
299
+ return newFacilities;
300
+ };
301
+ const initialSolution = task.initialSolution || Array.from({ length: numFacilities }, () => ({
302
+ x: bounds.minX + Math.random() * (bounds.maxX - bounds.minX),
303
+ y: bounds.minY + Math.random() * (bounds.maxY - bounds.minY),
304
+ }));
305
+ return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
306
+ }
307
+
308
+ const { cities } = task.payload;
309
+ const distance = (c1, c2) => Math.sqrt(Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2));
310
+ const evaluator = (path) => {
311
+ let total = 0;
312
+ for (let i = 0; i < path.length - 1; i++) total += distance(cities[path[i]], cities[path[i + 1]]);
313
+ total += distance(cities[path[path.length - 1]], cities[path[0]]);
314
+ return total;
315
+ };
316
+ const neighbor = (path) => {
317
+ const newPath = [...path];
318
+ const [i, j] = [Math.floor(Math.random() * path.length), Math.floor(Math.random() * path.length)];
319
+ [newPath[i], newPath[j]] = [newPath[j], newPath[i]];
320
+ return newPath;
321
+ };
322
+ const initialSolution = task.initialSolution || Array.from({ length: cities.length }, (_, i) => i).sort(() => 0.5 - Math.random());
323
+
324
+ return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
325
+ }
326
+
327
+ case 'genetic_algorithm_generations': {
328
+ const { assets, maxVolatility } = task.payload;
329
+ const fitness = (weights) => {
330
+ const total = weights.reduce((s, w) => s + w, 0);
331
+ if (total === 0) return Infinity;
332
+ const normW = weights.map(w => w / total);
333
+ const ret = normW.reduce((s, w, i) => s + w * assets[i].expectedReturn, 0);
334
+ const vol = normW.reduce((s, w, i) => s + w * assets[i].volatility, 0);
335
+ if (vol > maxVolatility) return 1000 + (vol - maxVolatility);
336
+ return -ret;
337
+ };
338
+ const createIndividual = () => Array.from({ length: assets.length }, Math.random);
339
+ const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
340
+ const mutate = p => { const n = [...p], i = Math.floor(Math.random() * n.length); n[i] += (Math.random() - 0.5) * 0.2; return n.map(v => Math.max(0, v)); };
341
+
342
+ // Le client doit recréer la population si elle n'est pas fournie
343
+ const initialPopulation = task.initialPopulation || Array.from({ length: task.payload.options.populationSize }, () => ({ chromosome: createIndividual(), fitness: 0 }));
344
+ initialPopulation.forEach(p => p.fitness = fitness(p.chromosome));
345
+
346
+ const finalPopulation = ClientOptimizers.geneticAlgorithm(createIndividual, fitness, crossover, mutate, task.generations, task.payload.options.populationSize);
347
+ return { population: finalPopulation };
348
+ }
349
+
350
+ case 'run_multiple_parallel':
351
+ // Côté client, on ne peut pas utiliser de vrais workers pour `runMultipleParallel`.
352
+ // On exécute donc une version simplifiée : un seul cycle du solveur demandé.
353
+ // Cela reste un travail coûteux et valide le principe du "Useful Work".
354
+ const { solverName, baseSolverArgs } = task;
355
+ const clientSolver = ClientOptimizers[solverName];
356
+ if (!clientSolver) throw new Error(`Solver ${solverName} not found on client.`);
357
+
358
+ // On simule l'appel avec les arguments de base.
359
+ // Note: `baseSolverArgs` peut contenir des options.
360
+ return clientSolver(...baseSolverArgs);
361
+
362
+ case 'multi_objective_genetic_algorithm': {
363
+ // C'est ici que la connexion se fait !
364
+ // On cherche le solveur demandé (ex: 'cpc.solve') dans notre registre client.
365
+ const solverFunction = ClientOptimizers[task.solverName];
366
+ if (!solverFunction) {
367
+ throw new Error(`Solver '${task.solverName}' not found on client.`);
368
+ }
369
+ // On appelle le solveur en lui passant le payload et les options.
370
+ // La fonction `solveOptimalCPC` attend le payload comme premier argument.
371
+ return solverFunction(task.payload, { generations: task.generations, initialFront: task.initialFront });
372
+ }
373
+
374
+ default:
375
+ throw new Error(`Unknown useful work type: ${task.type}`);
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Résout une tâche d'optimisation basée sur un algorithme génétique.
381
+ * Reçoit une population et la fait évoluer pendant un certain nombre de générations.
382
+ * NOTE: Cette fonction est une version simplifiée de l'AG de `library.js` adaptée au client.
383
+ * @param {Array<object>} initialPopulation - La population de départ.
384
+ * @param {number} generations - Le nombre de générations à exécuter.
385
+ * @returns {Promise<Array<object>>} La population finale après évolution.
386
+ */
387
+ export async function solveOptimizationTask(initialPopulation, generations) {
388
+ // Fonctions AG simplifiées (croisement, mutation)
389
+ const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
390
+ const mutate = (p) => {
391
+ const newP = [...p];
392
+ const i = Math.floor(Math.random() * newP.length);
393
+ newP[i] += (Math.random() - 0.5) * 0.2;
394
+ return newP;
395
+ };
396
+
397
+ let population = initialPopulation;
398
+
399
+ for (let gen = 0; gen < generations; gen++) {
400
+ // Sélection simple : on garde les 50% meilleurs
401
+ const parents = population.sort((a, b) => a.fitness - b.fitness).slice(0, Math.ceil(population.length / 2));
402
+ const newPopulation = [...parents]; // Élitisme
403
+
404
+ while (newPopulation.length < population.length) {
405
+ const parent1 = parents[Math.floor(Math.random() * parents.length)];
406
+ const parent2 = parents[Math.floor(Math.random() * parents.length)];
407
+ let offspring = crossover(parent1.chromosome, parent2.chromosome);
408
+ if (Math.random() < 0.1) offspring = mutate(offspring);
409
+ // La fitness sera recalculée côté serveur pour la vérification.
410
+ newPopulation.push({ chromosome: offspring, fitness: -1 });
411
+ }
412
+ population = newPopulation;
413
+ // Pause pour ne pas geler l'UI sur les longues tâches
414
+ if (gen % 10 === 0) await new Promise(r => setTimeout(r, 0));
415
+ }
416
+ return population;
417
+ }
418
+
419
+ /**
420
+ * @class ChallengeSolution
421
+ * @description Encapsule une solution de challenge et fournit des méthodes pour la manipuler.
422
+ * @private
423
+ */
424
+ class ChallengeSolution {
425
+ constructor(type, nonce, rawSolution) {
426
+ this.type = type;
427
+ this.nonce = nonce;
428
+ this.rawSolution = rawSolution;
429
+ }
430
+
431
+ /**
432
+ * Applique les paramètres de la solution à un objet URL.
433
+ * @param {URL} url - L'objet URL à modifier.
434
+ */
435
+ applyToUrl(url) {
436
+ url.searchParams.set('pow_type', this.type);
437
+ url.searchParams.set('pow_nonce', this.nonce);
438
+
439
+ // Logique de formatage spécifique à chaque type de challenge
440
+ if (this.type === 'cpu_mem' || this.type === 'cpu_mem_inline' || this.type === 'cpu_target') {
441
+ Object.entries(this.rawSolution).forEach(([key, value]) => {
442
+ url.searchParams.set(`pow_solution_${key}`, String(value));
443
+ });
444
+ } else if (this.type === 'useful_work_task') {
445
+ url.searchParams.set('pow_solution_work_result', JSON.stringify(this.rawSolution.work_result));
446
+ url.searchParams.set('pow_problem_id', this.rawSolution.problem_id);
447
+ } else {
448
+ // Pour les cas simples comme 'tsp' où la solution est une seule valeur
449
+ url.searchParams.set('pow_solution', JSON.stringify(this.rawSolution));
450
+ }
451
+ }
452
+ }
453
+
454
+ /**
455
+ * Fonction principale qui reçoit un objet challenge et le résout.
456
+ * @param {object} challenge - L'objet challenge reçu du serveur.
457
+ * @param {string} [fingerprint=''] - L'empreinte de l'appareil qui résout le challenge.
458
+ * @returns {Promise<ChallengeSolution>} Un objet `ChallengeSolution` encapsulant le résultat.
459
+ */
460
+ export async function solveChallenge(challenge, fingerprint = '') { // The fingerprint is now passed from the client library
461
+ const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance, optimizationTask, usefulWorkTask } = challenge;
462
+ let rawSolution = {};
463
+
464
+ switch (type) {
465
+ case 'cpu_target':
466
+ // Note: This case is not fully exercised by tests as it relies on Web Workers.
467
+ if (!cpuTarget) {
468
+ throw new Error("Challenge data is missing 'cpuTarget' property.");
469
+ }
470
+ const target = cpuTarget; // Keep variable name for consistency below
471
+ // Pour ce challenge simple, le baseBlock est juste le nonce.
472
+ const baseBlockBytes = new TextEncoder().encode(nonce + ":");
473
+ rawSolution.cpu = await solveCpuTargetInline(baseBlockBytes, target, null);
474
+ break;
475
+ case 'cpu_mem':
476
+ // Pour les appels API, le client IP n'est pas connu, on ne le met pas dans le message
477
+ const baseMessageCombined = `:${nonce}:${clientSecret}`;
478
+ const memSeed = `:${nonce}:${clientSecret}`;
479
+ const [cpuSol, memSol] = await Promise.all([
480
+ (async () => {
481
+ if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property."); // Pass fingerprint to solver
482
+ const baseBlock = new Uint8Array(challenge.baseBlock);
483
+ return solveCpuTargetInline(baseBlock, cpuTarget, null);
484
+ })(),
485
+ solveMemory(memSeed, memDifficulty)
486
+ ]);
487
+ rawSolution.cpu = cpuSol;
488
+ rawSolution.mem = memSol;
489
+ break;
490
+ case 'cpu_mem_inline':
491
+ // Version inline pour compatibilité HTML avec IP incluse
492
+ const memSeedInline = `:${nonce}:${clientSecret}`;
493
+ const [cpuSolInline, memSolInline] = await Promise.all([
494
+ (async () => {
495
+ if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property.");
496
+ const baseBlock = new Uint8Array(challenge.baseBlock);
497
+ return solveCpuTargetInline(baseBlock, cpuTarget, null);
498
+ })(),
499
+ solveMemory(memSeedInline, memDifficulty)
500
+ ]);
501
+ rawSolution.cpu = cpuSolInline;
502
+ rawSolution.mem = memSolInline;
503
+ break;
504
+ case 'tsp':
505
+ const tspResult = await solveTsp(cities, targetMaxDistance);
506
+ // Pour ce challenge, la solution est juste le chemin.
507
+ rawSolution = tspResult.path;
508
+ break;
509
+ case 'optimization_task':
510
+ const finalPopulation = await solveOptimizationTask(optimizationTask.population, optimizationTask.generations);
511
+ rawSolution = finalPopulation.map(p => p.chromosome); // On ne renvoie que les chromosomes
512
+ break;
513
+ case 'useful_work_task':
514
+ const workResult = await solveUsefulWorkTask(usefulWorkTask.task);
515
+ rawSolution.work_result = workResult;
516
+ rawSolution.problem_id = usefulWorkTask.problemId;
517
+ break;
518
+ default:
519
+ throw new Error(`Unknown challenge type: ${type}`);
520
+ }
521
+
522
+ return new ChallengeSolution(type, nonce, rawSolution);
523
+ }
524
+
525
+ // --- Compatibilité pour l'injection directe dans le HTML ---
526
+ // Si le script est chargé dans un navigateur (window existe), on attache les fonctions nécessaires à window.
527
+ if (typeof window !== 'undefined') {
528
+ window.solveCpuChallengeInline = solveCpuTargetInline;
529
+ window.solveMemoryChallenge = solveMemory;
530
+ window.solveTspChallenge = solveTsp;
531
+ window.solveChallenge = solveChallenge;
498
532
  }