@anonympins/fingerprint 0.1.4 → 0.2.1

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.
package/library.js CHANGED
@@ -289,20 +289,23 @@ const Optimization = {
289
289
  * @param {number} numCycles - Le nombre total de cycles à exécuter.
290
290
  * @param {boolean} [logProgress=false] - Si true, affiche la progression dans la console.
291
291
  * @param {object} [options={}] - Options pour la parallélisation.
292
- * @param {number} [options.concurrency] - Le nombre de workers à utiliser en parallèle. Par défaut, le nombre de cœurs CPU.
292
+ * @param {number} [options.concurrency] - Le nombre de workers à utiliser en parallèle. Par défaut, le nombre de cœurs CPU.
293
+ * @param {function(number): Array<any>} [options.workerDataGenerator] - Une fonction qui, pour chaque cycle (index), génère les arguments spécifiques à passer au solveur. Si non fournie, `baseSolverArgs` est utilisé tel quel.
293
294
  * @returns {Promise<{bestResult: object, stats: {scores: Array<number>, average: number, stdDev: number}}>} Le meilleur résultat et des statistiques.
294
295
  */
295
296
  async runMultipleParallel(
296
297
  solverName,
297
- baseSolverArgs,
298
+ baseSolverArgs = [], // Default to empty array if not provided
298
299
  numCycles,
299
300
  logProgress = false,
300
301
  options = {},
301
302
  ) {
302
303
  const concurrency = options.concurrency || os.cpus().length;
304
+ const workerDataGenerator = options.workerDataGenerator;
305
+
303
306
  if (logProgress) {
304
307
  console.log(
305
- ` (Utilisation d'un pool de ${concurrency} workers pour ${numCycles} cycles)`,
308
+ ` (Utilisation d'un pool de ${concurrency} workers pour ${numCycles} cycles avec le solveur ${solverName})`,
306
309
  );
307
310
  }
308
311
 
@@ -317,18 +320,15 @@ const Optimization = {
317
320
 
318
321
  const workerData = {
319
322
  solverName,
320
- solverArgs: [
321
- // Chaque worker génère son propre jeu de données aléatoires pour garantir l'indépendance des cycles.
322
- Array.from({ length: 50 }, () => ({
323
- x: Math.random() * 100,
324
- y: Math.random() * 100,
325
- })),
326
- ...baseSolverArgs,
327
- ],
323
+ solverArgs: workerDataGenerator
324
+ ? workerDataGenerator(taskIndex)
325
+ : baseSolverArgs,
328
326
  };
329
327
 
330
- const result = await new Promise((resolve, reject) => {
331
- const worker = new Worker("./worker.js", { workerData });
328
+ const result = await new Promise(async (resolve, reject) => {
329
+ // Le chemin du worker doit être absolu ou relatif au fichier appelant.
330
+ // On utilise import.meta.url pour résoudre le chemin de manière fiable.
331
+ const worker = new Worker(new URL('./optimization.worker.js', import.meta.url), { workerData });
332
332
  worker.on("message", resolve);
333
333
  worker.on("error", reject);
334
334
  worker.on("exit", (code) => {
@@ -1313,15 +1313,21 @@ Optimization.Operators.createOptimalTtlEvaluator = ({ suspicionScore }) => {
1313
1313
  * @param {string} numberString - Une chaîne de chiffres (ex: "123456789").
1314
1314
  * @returns {number} Un score de déviation (0 = parfait, > 0.15 = suspect).
1315
1315
  */
1316
- Optimization.Operators.benfordTest = (numberString) => {
1317
- const firstDigits = numberString.replace(/[^1-9]/g, ''); // Garde uniquement les chiffres de 1 à 9
1318
- if (firstDigits.length < 10) {
1316
+ Optimization.Operators.benfordTest = (numbers) => {
1317
+ if (!Array.isArray(numbers)) {
1318
+ // Si l'entrée n'est pas un tableau, on ne peut pas l'analyser.
1319
+ return 0;
1320
+ }
1321
+ const leadingDigits = numbers.map(n => String(n).trim().charAt(0))
1322
+ .filter(d => d >= '1' && d <= '9'); // On ne garde que les chiffres de 1 à 9.
1323
+
1324
+ if (leadingDigits.length < 10) {
1319
1325
  return 0; // Pas assez de données pour un test fiable
1320
1326
  }
1321
1327
 
1322
1328
  const counts = Array(10).fill(0);
1323
- for (let i = 0; i < firstDigits.length; i++) {
1324
- counts[parseInt(firstDigits[i], 10)]++;
1329
+ for (let i = 0; i < leadingDigits.length; i++) {
1330
+ counts[parseInt(leadingDigits[i], 10)]++;
1325
1331
  }
1326
1332
 
1327
1333
  // Distribution attendue selon la loi de Benford pour le premier chiffre
@@ -1332,7 +1338,7 @@ Optimization.Operators.benfordTest = (numberString) => {
1332
1338
 
1333
1339
  let totalDeviation = 0;
1334
1340
  for (let i = 1; i <= 9; i++) {
1335
- const observedFrequency = (counts[i] / firstDigits.length) * 100;
1341
+ const observedFrequency = (counts[i] / leadingDigits.length) * 100;
1336
1342
  const expectedFrequency = benfordDistribution[i];
1337
1343
  totalDeviation += Math.pow(observedFrequency - expectedFrequency, 2);
1338
1344
  }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @file @/optimization.worker.js
3
+ * @description Web Worker générique pour exécuter les algorithmes d'optimisation de la bibliothèque `Optimization`.
4
+ * Ce script s'exécute sur un thread séparé pour ne pas bloquer l'interface utilisateur.
5
+ */
6
+
7
+ import { parentPort, workerData } from 'worker_threads';
8
+ import { Optimization } from './library.js'; // Assurez-vous que le chemin est correct
9
+
10
+ if (parentPort) {
11
+ parentPort.on('message', async () => { // Le message est vide, on utilise workerData
12
+ const { solverName, solverArgs } = workerData;
13
+
14
+ // Gérer les solveurs imbriqués (ex: 'Operators.solvePortfolio')
15
+ const solverFunction = solverName.split('.').reduce((obj, prop) => obj && obj[prop], Optimization);
16
+
17
+ if (typeof solverFunction === 'function') {
18
+ try {
19
+ const result = await solverFunction(...solverArgs);
20
+ parentPort.postMessage(result);
21
+ } catch (error) {
22
+ parentPort.postMessage({ error: error.message, stack: error.stack });
23
+ }
24
+ } else {
25
+ parentPort.postMessage({ error: `Solver '${solverName}' not found or is not a function in Optimization library.` });
26
+ }
27
+ });
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anonympins/fingerprint",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "An HTTP(S) client mitigation and anti-bot protection library for Node.js/Express, based on digital fingerprinting and dynamic Proof-of-Work (PoW) challenges.",
5
5
  "main": "fingerprint.js",
6
6
  "type": "module",
@@ -15,6 +15,9 @@
15
15
  "fingerprint.client.js",
16
16
  "fingerprint.builder.js",
17
17
  "pow.solver.js",
18
+ "pow.worker.js",
19
+ "problem-manager.js",
20
+ "optimization.worker.js",
18
21
  "library.js",
19
22
  "redis-store.js",
20
23
  "mongodb-store.js",
package/pow.solver.js CHANGED
@@ -1,32 +1,51 @@
1
1
  /**
2
2
  * @file @/pow.solver.js
3
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.
4
7
  * Fichier compatible à la fois avec l'import de modules ES6 et l'injection directe dans un script HTML.
5
8
  */
6
9
 
7
10
  'use strict';
8
11
 
9
12
  /**
10
- * Résout un challenge CPU basé sur une cible (version inline pour compatibilité HTML).
11
- * @param {string} clientIp - L'adresse IP du client.
12
- * @param {string} nonce - Le nonce du challenge.
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.
13
15
  * @param {bigint} target - La cible à atteindre.
14
- * @param {string} clientSecret - Le secret client (optionnel).
15
16
  * @param {Function} progressCallback - Callback pour les mises à jour de progression.
16
17
  * @returns {Promise<number>} La solution (un nombre entier).
17
18
  */
18
- export async function solveCpuTargetInline(clientIp, nonce, target, clientSecret = null, progressCallback) {
19
- // Le 'target' est déjà un BigInt lorsqu'il est appelé depuis la page de challenge.
20
- // On s'assure juste qu'il est bien de ce type.
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
+
21
25
  const cpuTarget = typeof target === 'bigint' ? target : BigInt('0x' + target);
26
+ // --- END FIX ---
27
+ const encoder = new TextEncoder();
22
28
  let cpuSolution = 0;
23
- const ipPart = clientIp || ''; // Use empty string if IP is null/undefined
24
- while (true) { // When a clientSecret is used, the IP is omitted from the hash to make it independent of the network.
25
- const msg = clientSecret ?
26
- `${nonce}:${cpuSolution}:${clientSecret}` :
27
- `${ipPart}:${nonce}:${cpuSolution}`;
28
- const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(msg));
29
+
30
+ while (true) {
31
+ const solutionBytes = encoder.encode(String(cpuSolution));
32
+
33
+ // Concaténation binaire directe : c'est plus rapide et plus sûr.
34
+ const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
35
+ finalBlock.set(baseBlock);
36
+ finalBlock.set(solutionBytes, baseBlock.length);
37
+
38
+ if (cpuSolution === 0) {
39
+ const reconstructedMsg = new TextDecoder().decode(finalBlock);
40
+ console.log('[FP Solve Debug] Client will hash message:', reconstructedMsg);
41
+ }
42
+ const buf = await crypto.subtle.digest("SHA-256", finalBlock);
29
43
  const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
44
+ // --- AJOUT DE LOGS POUR LE DÉBOGAGE CÔTÉ CLIENT ---
45
+ if (cpuSolution === 0) { // Log only the first attempt
46
+ console.log(`[FP Client Solve] Attempt 0 hash: "0x${hashHex}"`);
47
+ }
48
+ // --- FIN DES LOGS ---
30
49
  if (BigInt('0x' + hashHex) < cpuTarget) break;
31
50
  cpuSolution++;
32
51
  if (cpuSolution % 100000 === 0) {
@@ -169,32 +188,186 @@ export async function solveTsp(cities, targetMaxDistance) {
169
188
  return { path: solutionPath, distance: solutionDistance };
170
189
  }
171
190
 
191
+ // --- Fonctions d'optimisation copiées/adaptées de library.js pour le client ---
192
+
193
+ const ClientOptimizers = {
194
+ simulatedAnnealing(initialSolution, evaluator, neighbor, iterations, temp, cooling) {
195
+ let currentSolution = initialSolution;
196
+ let currentEnergy = evaluator(currentSolution);
197
+ let temperature = temp;
198
+
199
+ for (let i = 0; i < iterations; i++) {
200
+ const newSolution = neighbor(currentSolution);
201
+ const newEnergy = evaluator(newSolution);
202
+ if (newEnergy < currentEnergy || Math.random() < Math.exp((currentEnergy - newEnergy) / temperature)) {
203
+ currentSolution = newSolution;
204
+ currentEnergy = newEnergy;
205
+ }
206
+ temperature *= cooling;
207
+ }
208
+ return { solution: currentSolution, energy: currentEnergy };
209
+ },
210
+
211
+ geneticAlgorithm(createIndividual, fitness, crossover, mutate, generations, popSize) {
212
+ let population = Array.from({ length: popSize }, () => {
213
+ const chromosome = createIndividual();
214
+ return { chromosome, fitness: fitness(chromosome) };
215
+ });
216
+
217
+ for (let gen = 0; gen < generations; gen++) {
218
+ population.sort((a, b) => a.fitness - b.fitness);
219
+ const newPopulation = [population[0]]; // Elitism
220
+ while (newPopulation.length < popSize) {
221
+ const p1 = population[Math.floor(Math.random() * (popSize / 2))];
222
+ const p2 = population[Math.floor(Math.random() * (popSize / 2))];
223
+ let offspring = crossover(p1.chromosome, p2.chromosome);
224
+ if (Math.random() < 0.1) offspring = mutate(offspring);
225
+ newPopulation.push({ chromosome: offspring, fitness: fitness(offspring) });
226
+ }
227
+ population = newPopulation;
228
+ }
229
+ return population;
230
+ }
231
+ };
232
+
233
+ /**
234
+ * Résout une unité de travail utile (Useful Work Unit).
235
+ * @param {object} task - La tâche envoyée par le serveur.
236
+ * @returns {Promise<object>} Le résultat du calcul.
237
+ */
238
+ async function solveUsefulWorkTask(task) {
239
+ await new Promise(r => setTimeout(r, 10)); // Yield thread
240
+
241
+ switch (task.type) {
242
+ case 'simulated_annealing_iterations': {
243
+ const { cities } = task.payload;
244
+ const distance = (c1, c2) => Math.sqrt(Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2));
245
+ const evaluator = (path) => {
246
+ let total = 0;
247
+ for (let i = 0; i < path.length - 1; i++) total += distance(cities[path[i]], cities[path[i + 1]]);
248
+ total += distance(cities[path[path.length - 1]], cities[path[0]]);
249
+ return total;
250
+ };
251
+ const neighbor = (path) => {
252
+ const newPath = [...path];
253
+ const [i, j] = [Math.floor(Math.random() * path.length), Math.floor(Math.random() * path.length)];
254
+ [newPath[i], newPath[j]] = [newPath[j], newPath[i]];
255
+ return newPath;
256
+ };
257
+ const initialSolution = task.initialSolution || Array.from({ length: cities.length }, (_, i) => i).sort(() => 0.5 - Math.random());
258
+
259
+ return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
260
+ }
261
+
262
+ case 'genetic_algorithm_generations': {
263
+ const { assets, maxVolatility } = task.payload;
264
+ const fitness = (weights) => {
265
+ const total = weights.reduce((s, w) => s + w, 0);
266
+ if (total === 0) return Infinity;
267
+ const normW = weights.map(w => w / total);
268
+ const ret = normW.reduce((s, w, i) => s + w * assets[i].expectedReturn, 0);
269
+ const vol = normW.reduce((s, w, i) => s + w * assets[i].volatility, 0);
270
+ if (vol > maxVolatility) return 1000 + (vol - maxVolatility);
271
+ return -ret;
272
+ };
273
+ const createIndividual = () => Array.from({ length: assets.length }, Math.random);
274
+ const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
275
+ 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)); };
276
+
277
+ // Le client doit recréer la population si elle n'est pas fournie
278
+ const initialPopulation = task.initialPopulation || Array.from({ length: task.payload.options.populationSize }, () => ({ chromosome: createIndividual(), fitness: 0 }));
279
+ initialPopulation.forEach(p => p.fitness = fitness(p.chromosome));
280
+
281
+ const finalPopulation = ClientOptimizers.geneticAlgorithm(createIndividual, fitness, crossover, mutate, task.generations, task.payload.options.populationSize);
282
+ return { population: finalPopulation };
283
+ }
284
+
285
+ case 'run_multiple_parallel':
286
+ // Côté client, on ne peut pas utiliser de vrais workers pour `runMultipleParallel`.
287
+ // On exécute donc une version simplifiée : un seul cycle du solveur demandé.
288
+ // Cela reste un travail coûteux et valide le principe du "Useful Work".
289
+ const { solverName, baseSolverArgs } = task;
290
+ const clientSolver = ClientOptimizers[solverName];
291
+ if (!clientSolver) throw new Error(`Solver ${solverName} not found on client.`);
292
+
293
+ // On simule l'appel avec les arguments de base.
294
+ // Note: `baseSolverArgs` peut contenir des options.
295
+ return clientSolver(...baseSolverArgs);
296
+
297
+ default:
298
+ throw new Error(`Unknown useful work type: ${task.type}`);
299
+ }
300
+ }
301
+
302
+ /**
303
+ * Résout une tâche d'optimisation basée sur un algorithme génétique.
304
+ * Reçoit une population et la fait évoluer pendant un certain nombre de générations.
305
+ * NOTE: Cette fonction est une version simplifiée de l'AG de `library.js` adaptée au client.
306
+ * @param {Array<object>} initialPopulation - La population de départ.
307
+ * @param {number} generations - Le nombre de générations à exécuter.
308
+ * @returns {Promise<Array<object>>} La population finale après évolution.
309
+ */
310
+ export async function solveOptimizationTask(initialPopulation, generations) {
311
+ // Fonctions AG simplifiées (croisement, mutation)
312
+ const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
313
+ const mutate = (p) => {
314
+ const newP = [...p];
315
+ const i = Math.floor(Math.random() * newP.length);
316
+ newP[i] += (Math.random() - 0.5) * 0.2;
317
+ return newP;
318
+ };
319
+
320
+ let population = initialPopulation;
321
+
322
+ for (let gen = 0; gen < generations; gen++) {
323
+ // Sélection simple : on garde les 50% meilleurs
324
+ const parents = population.sort((a, b) => a.fitness - b.fitness).slice(0, Math.ceil(population.length / 2));
325
+ const newPopulation = [...parents]; // Élitisme
326
+
327
+ while (newPopulation.length < population.length) {
328
+ const parent1 = parents[Math.floor(Math.random() * parents.length)];
329
+ const parent2 = parents[Math.floor(Math.random() * parents.length)];
330
+ let offspring = crossover(parent1.chromosome, parent2.chromosome);
331
+ if (Math.random() < 0.1) offspring = mutate(offspring);
332
+ // La fitness sera recalculée côté serveur pour la vérification.
333
+ newPopulation.push({ chromosome: offspring, fitness: -1 });
334
+ }
335
+ population = newPopulation;
336
+ // Pause pour ne pas geler l'UI sur les longues tâches
337
+ if (gen % 10 === 0) await new Promise(r => setTimeout(r, 0));
338
+ }
339
+ return population;
340
+ }
341
+
172
342
  /**
173
343
  * Fonction principale qui reçoit un objet challenge et le résout.
174
344
  * @param {object} challenge - L'objet challenge reçu du serveur.
175
345
  * @returns {Promise<object>} Un objet contenant la ou les solutions.
176
346
  */
177
- export async function solveChallenge(challenge) {
178
- const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance } = challenge;
347
+ export async function solveChallenge(challenge, fingerprint = '') { // The fingerprint is now passed from the client library
348
+ const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance, optimizationTask, usefulWorkTask } = challenge;
179
349
  const solutions = {};
180
350
 
181
351
  switch (type) {
182
352
  case 'cpu_target':
183
- const baseMessageCpu = `:${nonce}`; // L'IP est gérée côté serveur
353
+ // Note: This case is not fully exercised by tests as it relies on Web Workers.
184
354
  if (!cpuTarget) {
185
355
  throw new Error("Challenge data is missing 'cpuTarget' property.");
186
356
  }
187
357
  const target = cpuTarget; // Keep variable name for consistency below
188
- solutions.cpu = await solveCpuTarget(baseMessageCpu, BigInt('0x' + target));
358
+ // Pour ce challenge simple, le baseBlock est juste le nonce.
359
+ const baseBlockBytes = new TextEncoder().encode(nonce + ":");
360
+ solutions.cpu = await solveCpuTargetInline(baseBlockBytes, target, null);
189
361
  break;
190
362
  case 'cpu_mem':
191
363
  // Pour les appels API, le client IP n'est pas connu, on ne le met pas dans le message
192
- const baseMessageCombined = `${nonce}:${clientSecret}`;
364
+ const baseMessageCombined = `:${nonce}:${clientSecret}`;
193
365
  const memSeed = `:${nonce}:${clientSecret}`;
194
366
  const [cpuSol, memSol] = await Promise.all([
195
367
  (async () => {
196
- if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property.");
197
- return solveCpuTargetInline(null, nonce, cpuTarget, clientSecret);
368
+ if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property."); // Pass fingerprint to solver
369
+ const baseBlock = new Uint8Array(challenge.baseBlock);
370
+ return solveCpuTargetInline(baseBlock, cpuTarget, null);
198
371
  })(),
199
372
  solveMemory(memSeed, memDifficulty)
200
373
  ]);
@@ -203,11 +376,12 @@ export async function solveChallenge(challenge) {
203
376
  break;
204
377
  case 'cpu_mem_inline':
205
378
  // Version inline pour compatibilité HTML avec IP incluse
206
- const memSeedInline = `${nonce}:${clientSecret}`;
379
+ const memSeedInline = `:${nonce}:${clientSecret}`;
207
380
  const [cpuSolInline, memSolInline] = await Promise.all([
208
381
  (async () => {
209
382
  if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property.");
210
- return solveCpuTargetInline(clientIp, nonce, cpuTarget, clientSecret);
383
+ const baseBlock = new Uint8Array(challenge.baseBlock);
384
+ return solveCpuTargetInline(baseBlock, cpuTarget, null);
211
385
  })(),
212
386
  solveMemory(memSeedInline, memDifficulty)
213
387
  ]);
@@ -219,6 +393,15 @@ export async function solveChallenge(challenge) {
219
393
  solutions.tsp = tspResult.path;
220
394
  solutions.distance = tspResult.distance;
221
395
  break;
396
+ case 'optimization_task':
397
+ const finalPopulation = await solveOptimizationTask(optimizationTask.population, optimizationTask.generations);
398
+ solutions.population = finalPopulation.map(p => p.chromosome); // On ne renvoie que les chromosomes
399
+ break;
400
+ case 'useful_work_task':
401
+ const workResult = await solveUsefulWorkTask(usefulWorkTask.task);
402
+ solutions.work_result = workResult;
403
+ solutions.problem_id = usefulWorkTask.problemId;
404
+ break;
222
405
  default:
223
406
  throw new Error(`Unknown challenge type: ${type}`);
224
407
  }
package/pow.worker.js ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @file @/pow.worker.js
3
+ * @description Web Worker dédié à la résolution du challenge CPU.
4
+ * Ce script s'exécute sur un thread séparé pour ne pas bloquer l'interface utilisateur.
5
+ */
6
+
7
+ self.onmessage = async (event) => {
8
+ const { message, target } = event.data;
9
+ let solution = 0;
10
+ const encoder = new TextEncoder();
11
+
12
+ // La boucle de calcul intensive est isolée dans ce worker.
13
+ while (true) {
14
+ const currentMessage = `${message}:${solution}`;
15
+ const data = encoder.encode(currentMessage);
16
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
17
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
18
+ const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
19
+
20
+ if (BigInt('0x' + hashHex) < target) {
21
+ // Une fois la solution trouvée, on la renvoie au thread principal.
22
+ self.postMessage({ solution });
23
+ return;
24
+ }
25
+ solution++;
26
+ }
27
+ };
@@ -0,0 +1,175 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { Optimization } from './library.js';
3
+
4
+ /**
5
+ * @namespace ProblemInitializers
6
+ * @description Fonctions pour générer dynamiquement les données d'un problème.
7
+ */
8
+ const ProblemInitializers = {
9
+ /**
10
+ * Génère un ensemble de points aléatoires pour un problème de TSP.
11
+ * @param {object} params - Les paramètres de génération.
12
+ * @param {number} params.count - Le nombre de points à générer.
13
+ * @param {{x: number, y: number}} [params.bounds={x: 1000, y: 1000}] - Les limites spatiales.
14
+ * @returns {Array<{x: number, y: number}>}
15
+ */
16
+ 'generate:randomPoints': (params) => {
17
+ const { count, bounds = { x: 1000, y: 1000 } } = params;
18
+ if (isNaN(count)) return [];
19
+ return Array.from({ length: count }, () => ({ x: Math.random() * bounds.x, y: Math.random() * bounds.y }));
20
+ },
21
+
22
+ /**
23
+ * Génère un ensemble d'actifs financiers aléatoires pour un problème de portefeuille.
24
+ * @param {object} params - Les paramètres de génération.
25
+ * @param {number} params.count - Le nombre d'actifs à générer.
26
+ * @returns {Array<{expectedReturn: number, volatility: number}>}
27
+ */
28
+ 'generate:randomAssets': (params) => {
29
+ const { count } = params;
30
+ if (isNaN(count)) return [];
31
+ return Array.from({ length: count }, () => ({
32
+ expectedReturn: Math.random() * 0.2,
33
+ volatility: 0.1 + Math.random() * 0.3
34
+ }));
35
+ },
36
+
37
+ /**
38
+ * Crée une fonction qui génère des arguments pour chaque worker de `runMultipleParallel`.
39
+ * Permet de faire varier les paramètres (ex: solution initiale) pour chaque cycle.
40
+ * @param {object} params - Les paramètres de configuration.
41
+ * @param {Array<any>} params.baseArgs - Les arguments de base, communs à tous les workers.
42
+ * @param {object} params.variations - Décrit comment faire varier un argument.
43
+ * @returns {function(number): Array<any>} La fonction `workerDataGenerator`.
44
+ */
45
+ 'generate:parallelArgs': (params) => {
46
+ const { baseArgs, variations } = params;
47
+ return (cycleIndex) => {
48
+ const cycleArgs = [...baseArgs];
49
+ // Pour l'instant, on gère la variation de la solution initiale pour le TSP
50
+ if (variations?.initialSolution === 'random') {
51
+ cycleArgs[0] = cycleArgs[0].sort(() => Math.random() - 0.5);
52
+ }
53
+ return cycleArgs;
54
+ };
55
+ }
56
+ };
57
+
58
+ class ProblemManager {
59
+ constructor(configPath) {
60
+ this.configPath = configPath;
61
+ this.problems = this.loadProblems();
62
+ this.currentProblemIndex = 0;
63
+ }
64
+
65
+ loadProblems() {
66
+ try {
67
+ const data = readFileSync(this.configPath, 'utf-8');
68
+ const problems = JSON.parse(data);
69
+ // Initialisation dynamique des problèmes
70
+ for (const problem of problems) { // eslint-disable-line no-unused-vars
71
+ for (const key in problem.payload) {
72
+ const value = problem.payload[key];
73
+ // On cherche une instruction d'initialisation (ex: { "$init": "generate:randomPoints", ... })
74
+ if (typeof value === 'object' && value !== null && value.$init) {
75
+ const initializer = ProblemInitializers[value.$init];
76
+ if (initializer) {
77
+ // On remplace l'objet d'instruction par les données générées.
78
+ problem.payload[key] = initializer(value.params || {});
79
+ }
80
+ }
81
+ }
82
+ }
83
+ return problems;
84
+ } catch (error) {
85
+ console.error(`[ProblemManager] Erreur lors du chargement du fichier de problèmes: ${error.message}`);
86
+ return []; // Retourne un tableau vide en cas d'erreur pour éviter un crash
87
+ }
88
+ }
89
+
90
+ saveProblems() {
91
+ // Note: Dans un vrai scénario, utilisez une base de données pour éviter les race conditions.
92
+ try {
93
+ writeFileSync(this.configPath, JSON.stringify(this.problems, null, 2));
94
+ } catch (error) {
95
+ console.error(`[ProblemManager] Erreur lors de la sauvegarde du fichier de problèmes: ${error.message}`);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Sélectionne un problème et génère une unité de travail.
101
+ * @param {number} suspicionFactor - Le facteur de suspicion pour ajuster la difficulté.
102
+ * @returns {{problemId: string, task: object}|null}
103
+ */
104
+ dispatchWork(suspicionFactor) {
105
+ if (this.problems.length === 0) return null;
106
+
107
+ const problem = this.problems[this.currentProblemIndex];
108
+ this.currentProblemIndex = (this.currentProblemIndex + 1) % this.problems.length;
109
+
110
+ const task = { type: problem.workUnit.type };
111
+ const { scalingFactor } = problem.workUnit;
112
+
113
+ switch (problem.workUnit.type) {
114
+ case 'simulated_annealing_iterations':
115
+ // Assurer une difficulté minimale pour que le challenge soit significatif
116
+ const baseIterations = Math.max(15000, problem.workUnit.baseIterations || 0);
117
+ task.iterations = scalingFactor
118
+ ? Math.floor(baseIterations * Math.pow(scalingFactor, suspicionFactor))
119
+ : Math.floor(baseIterations * (0.5 + suspicionFactor));
120
+ task.payload = problem.payload;
121
+ task.initialSolution = problem.state.bestSolution;
122
+ break;
123
+
124
+ case 'genetic_algorithm_generations':
125
+ // Assurer une difficulté minimale pour que le challenge soit significatif
126
+ const baseGenerations = Math.max(50, problem.workUnit.baseGenerations || 0);
127
+ task.generations = scalingFactor
128
+ ? Math.floor(baseGenerations * Math.pow(scalingFactor, suspicionFactor))
129
+ : Math.floor(baseGenerations * (0.5 + suspicionFactor));
130
+ task.payload = problem.payload;
131
+ task.initialPopulation = problem.state.population;
132
+ break;
133
+
134
+ case 'run_multiple_parallel':
135
+ task.solverName = problem.workUnit.solverName;
136
+ task.numCycles = problem.workUnit.numCycles;
137
+ // Les arguments et le générateur sont dans le payload pour plus de flexibilité
138
+ task.baseSolverArgs = problem.payload.baseSolverArgs;
139
+ task.workerDataGenerator = problem.payload.workerDataGenerator;
140
+ task.logProgress = problem.payload.logProgress || false;
141
+ task.concurrency = problem.payload.concurrency;
142
+ break;
143
+ }
144
+
145
+ return { problemId: problem.id, task };
146
+ }
147
+
148
+ /**
149
+ * Intègre la solution d'un client dans l'état du problème.
150
+ * @param {string} problemId - L'ID du problème.
151
+ * @param {object} solutionData - La solution renvoyée par le client.
152
+ */
153
+ integrateSolution(problemId, solutionData) {
154
+ const problem = this.problems.find(p => p.id === problemId);
155
+ if (!problem) return;
156
+
157
+ switch (problem.workUnit.type) {
158
+ case 'simulated_annealing_iterations':
159
+ if (solutionData.energy < (parseFloat(problem.state.bestEnergy) || Infinity)) {
160
+ problem.state.bestSolution = solutionData.solution;
161
+ problem.state.bestEnergy = solutionData.energy;
162
+ console.log(`[ProblemManager] Nouvelle meilleure solution pour ${problemId}: ${solutionData.energy.toFixed(2)}`);
163
+ }
164
+ break;
165
+ case 'genetic_algorithm_generations':
166
+ problem.state.population = solutionData.population;
167
+ console.log(`[ProblemManager] Population mise à jour pour ${problemId}.`);
168
+ break;
169
+ }
170
+ this.saveProblems();
171
+ }
172
+ }
173
+
174
+ export { ProblemManager }; // Export the class for testing
175
+ export const problemManager = new ProblemManager('./problems.config.json');