@anonympins/fingerprint 0.5.1 → 0.5.4

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +118 -115
  3. package/package.json +1 -1
  4. package/src/js/dynamic-wasm.js +52 -13
  5. package/src/js/fingerprint.builder.js +169 -168
  6. package/src/js/fingerprint.client.js +1271 -1189
  7. package/src/js/fingerprint.client.obfuscated.js +1 -1
  8. package/src/js/fingerprint.js +436 -162
  9. package/src/js/fingerprint.utils.js +30 -0
  10. package/src/js/gpu_pow.solver.js +96 -1
  11. package/src/js/library.js +40 -21
  12. package/src/js/mongodb-store.js +165 -79
  13. package/src/js/pow.solver.inline.js +627 -30
  14. package/src/js/pow.solver.js +453 -31
  15. package/src/js/problem-manager.js +1 -1
  16. package/src/js/redis-store.js +114 -42
  17. package/src/js/tests/dns-circuit-breaker.test.js +55 -0
  18. package/src/js/tests/fingerprint.client.init.test.js +143 -143
  19. package/src/js/tests/fingerprint.client.test.js +128 -128
  20. package/src/js/tests/fingerprint.engine.test.js +370 -370
  21. package/src/js/tests/fingerprint.test.js +119 -5
  22. package/src/js/tests/gpu_pow.test.js +81 -0
  23. package/src/js/tests/ip-reputation.test.js +1 -1
  24. package/src/js/tests/pow.solver.test.js +9 -6
  25. package/src/js/tests/usefulWorkSolver.test.js +162 -0
  26. package/src/php/Challenge/ChallengeUtils.php +137 -35
  27. package/src/php/FingerprintClient.php +22 -5
  28. package/src/php/FingerprintEngine.php +335 -58
  29. package/src/php/Optimization/Optimization.php +23 -16
  30. package/src/php/Optimization/OptimizationOperators.php +27 -0
  31. package/src/php/ProblemManager.php +1 -1
  32. package/src/php/Store/MongoDbStore.php +184 -104
  33. package/src/php/Store/RedisStore.php +91 -11
  34. package/src/php/Tests/ChallengeUtilsTest.php +285 -285
  35. package/src/php/Tests/DnsCircuitBreakerTest.php +106 -0
  36. package/src/php/Tests/IpReputationTest.php +1 -1
  37. package/src/php/Tests/PowTest.php +1 -1
  38. package/src/php/Tests/RequestUtilsTest.php +29 -0
  39. package/src/php/Tests/TLSClientHelloParserTest.php +177 -117
  40. package/src/php/Utils/BigInt.php +202 -202
  41. package/src/php/Utils/RequestUtils.php +74 -21
  42. package/src/php/Utils/TLSClientHelloParser.php +212 -116
@@ -10,10 +10,11 @@
10
10
  'use strict';
11
11
 
12
12
  function cyrb53(str, seed = 0) {
13
+ const safeStr = (typeof str === 'string' ? str : String(str || '')).slice(0, 10000);
13
14
  let h1 = 0xdeadbeef ^ seed,
14
15
  h2 = 0x41c6ce57 ^ seed;
15
- for (let i = 0, ch; i < str.length; i++) {
16
- ch = str.charCodeAt(i);
16
+ for (let i = 0, ch; i < safeStr.length; i++) {
17
+ ch = safeStr.charCodeAt(i);
17
18
  h1 = Math.imul(h1 ^ ch, 2654435761);
18
19
  h2 = Math.imul(h2 ^ ch, 1597334677);
19
20
  }
@@ -143,6 +144,53 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
143
144
  return solution;
144
145
  }
145
146
 
147
+ // Try Web Worker execution first if supported and not blocked by CSP
148
+ if (typeof window !== 'undefined' && typeof Worker !== 'undefined') {
149
+ try {
150
+ return await new Promise((resolve, reject) => {
151
+ const workerCode = `
152
+ self.onmessage = async (e) => {
153
+ const { baseBlock, target } = e.data;
154
+ const cpuTarget = BigInt(target);
155
+ const encoder = new TextEncoder();
156
+ let cpuSolution = 0;
157
+ while (true) {
158
+ const solutionBytes = encoder.encode(String(cpuSolution));
159
+ const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
160
+ finalBlock.set(baseBlock);
161
+ finalBlock.set(solutionBytes, baseBlock.length);
162
+ const buf = await crypto.subtle.digest("SHA-256", finalBlock);
163
+ const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
164
+ if (BigInt('0x' + hashHex) < cpuTarget) break;
165
+ cpuSolution++;
166
+ if (cpuSolution % 25000 === 0) {
167
+ self.postMessage({ type: 'progress', solution: cpuSolution });
168
+ }
169
+ }
170
+ self.postMessage({ type: 'success', solution: cpuSolution });
171
+ };
172
+ `;
173
+ const blob = new Blob([workerCode], { type: 'application/javascript' });
174
+ const worker = new Worker(URL.createObjectURL(blob));
175
+ worker.onmessage = (event) => {
176
+ if (event.data.type === 'progress') {
177
+ if (progressCallback) progressCallback(event.data.solution);
178
+ } else if (event.data.type === 'success') {
179
+ resolve(event.data.solution);
180
+ worker.terminate();
181
+ }
182
+ };
183
+ worker.onerror = (err) => {
184
+ worker.terminate();
185
+ reject(err);
186
+ };
187
+ worker.postMessage({ baseBlock, target: cpuTarget.toString() });
188
+ });
189
+ } catch (workerError) {
190
+ console.warn("Web Worker creation failed (possibly due to CSP). Falling back to main thread with scheduler/setTimeout yielding.");
191
+ }
192
+ }
193
+
146
194
  let cpuSolution = 0;
147
195
 
148
196
  while (true) {
@@ -166,8 +214,12 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
166
214
  // --- FIN DES LOGS ---
167
215
  if (BigInt('0x' + hashHex) < cpuTarget) break;
168
216
  cpuSolution++;
169
- if (cpuSolution % 100000 === 0) {
170
- await new Promise(r => setTimeout(r, 0));
217
+ if (cpuSolution % 50000 === 0) {
218
+ if (typeof scheduler !== 'undefined' && typeof scheduler.yield === 'function') {
219
+ await scheduler.yield();
220
+ } else {
221
+ await new Promise(r => setTimeout(r, 0));
222
+ }
171
223
  if (progressCallback) progressCallback(cpuSolution);
172
224
  }
173
225
  }
@@ -216,43 +268,104 @@ export async function solveCpuTarget(message, target) {
216
268
  * @returns {Promise<number>} La solution (nombre entier).
217
269
  */
218
270
  export async function solveMemory(seed, difficulty) {
219
- // On définit un seuil pour savoir quand faire une pause, afin de ne pas bloquer le thread UI.
220
- const YIELD_THRESHOLD = 100000;
221
- const size = difficulty * 1024 * 1024;
222
- const buffer = new Uint32Array(size / 4);
223
-
224
271
  const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
272
+ let solution;
273
+ let useWasm = false;
225
274
  if (wasmModule && typeof wasmModule._solve_memory_challenge === 'function') {
226
- const seedPtr = wasmModule._malloc(seed.length + 1);
227
- for (let i = 0; i < seed.length; i++) {
228
- wasmModule.HEAP8[seedPtr + i] = seed.charCodeAt(i);
275
+ const encoder = new TextEncoder();
276
+ const seedBytes = encoder.encode(seed);
277
+ const ptr = wasmModule._malloc(seedBytes.length + 1);
278
+ wasmModule.HEAPU8.set(seedBytes, ptr);
279
+ wasmModule.HEAPU8[ptr + seedBytes.length] = 0; // Null-terminator
280
+ solution = wasmModule._solve_memory_challenge(ptr, difficulty);
281
+ wasmModule._free(ptr);
282
+ useWasm = true;
283
+ }
284
+
285
+ const size = difficulty * 1024 * 1024;
286
+ const numBlocks = difficulty * 256;
287
+ if (numBlocks === 0) return { solution: 0, merkleRoot: '', proofs: {} };
288
+
289
+ async function hashBlock(block) {
290
+ const buf = await crypto.subtle.digest("SHA-256", block.buffer);
291
+ return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
292
+ }
293
+
294
+ function hexToBytes(hex) {
295
+ const bytes = new Uint8Array(hex.length / 2);
296
+ for (let i = 0; i < hex.length; i += 2) {
297
+ bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
229
298
  }
230
- wasmModule.HEAP8[seedPtr + seed.length] = 0;
231
- const solution = wasmModule._solve_memory_challenge(seedPtr, difficulty);
232
- wasmModule._free(seedPtr);
233
- return solution;
299
+ return bytes;
300
+ }
301
+
302
+ const leaves = [];
303
+ const blocks = [];
304
+ for (let b = 0; b < numBlocks; b++) {
305
+ const block = new Uint32Array(1024);
306
+ let h = cyrb53(seed + ":" + b);
307
+ for (let i = 0; i < 1024; i++) {
308
+ block[i] = (h = Math.imul(h ^ i, 1597334677));
309
+ }
310
+ blocks.push(block);
311
+ leaves.push(await hashBlock(block));
312
+ }
313
+
314
+ const tree = [leaves];
315
+ while (tree[tree.length - 1].length > 1) {
316
+ const currentLayer = tree[tree.length - 1];
317
+ const nextLayer = [];
318
+ for (let i = 0; i < currentLayer.length; i += 2) {
319
+ const left = currentLayer[i];
320
+ const right = currentLayer[i + 1] || left;
321
+ const combined = hexToBytes(left + right);
322
+ const hashBuf = await crypto.subtle.digest("SHA-256", combined);
323
+ const hashHex = Array.from(new Uint8Array(hashBuf)).map(b => b.toString(16).padStart(2, '0')).join('');
324
+ nextLayer.push(hashHex);
325
+ }
326
+ tree.push(nextLayer);
234
327
  }
328
+ const merkleRoot = tree[tree.length - 1][0];
235
329
 
236
- let h = new TextEncoder().encode(seed).reduce((acc, v) => acc + v, 0);
330
+ if (!useWasm) {
331
+ function readBuffer(blocks, addr) {
332
+ const blockIdx = Math.floor(addr / 1024);
333
+ const elementIdx = addr % 1024;
334
+ return blocks[blockIdx][elementIdx];
335
+ }
237
336
 
238
- for (let i = 0; i < buffer.length; i++) {
239
- buffer[i] = (h = Math.imul(h ^ i, 1597334677));
240
- if (i % YIELD_THRESHOLD === 0) {
241
- await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
337
+ const totalElements = numBlocks * 1024;
338
+ let addr = totalElements > 0 ? readBuffer(blocks, 0) % totalElements : 0;
339
+ solution = 0;
340
+ const iterations = 1024;
341
+ for (let i = 0; i < iterations; i++) {
342
+ addr = readBuffer(blocks, addr) % totalElements;
343
+ solution ^= addr;
242
344
  }
243
345
  }
244
346
 
245
- let solution = 0;
246
- const iterations = size / 16;
247
- let addr = buffer.length > 0 ? buffer[0] % buffer.length : 0;
248
- for (let i = 0; i < iterations; i++) {
249
- addr = buffer[addr] % buffer.length;
250
- solution ^= addr;
251
- if (i % YIELD_THRESHOLD === 0) {
252
- await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
347
+ const challengedIndices = [];
348
+ let h_idx = cyrb53(seed + ":" + solution);
349
+ for (let i = 0; i < 4; i++) {
350
+ h_idx = Math.imul(h_idx ^ i, 1597334677);
351
+ challengedIndices.push(Math.abs(h_idx) % numBlocks);
352
+ }
353
+
354
+ const proofs = {};
355
+ for (const index of challengedIndices) {
356
+ const proof = [];
357
+ let idx = index;
358
+ for (let layer = 0; layer < tree.length - 1; layer++) {
359
+ const isRight = idx % 2 === 1;
360
+ const siblingIdx = isRight ? idx - 1 : idx + 1;
361
+ const sibling = tree[layer][siblingIdx] || tree[layer][idx];
362
+ proof.push(sibling);
363
+ idx = Math.floor(idx / 2);
253
364
  }
365
+ proofs[index] = proof;
254
366
  }
255
- return solution;
367
+
368
+ return { solution, merkleRoot, proofs };
256
369
  }
257
370
 
258
371
  /**
@@ -321,6 +434,81 @@ export async function solveTsp(cities, targetMaxDistance) {
321
434
 
322
435
  // --- Fonctions d'optimisation copiées/adaptées de library.js pour le client ---
323
436
 
437
+ function paretoDominates(objectivesA, objectivesB) {
438
+ let aIsBetterInOne = false;
439
+ for (let i = 0; i < objectivesA.length; i++) {
440
+ if (objectivesA[i] > objectivesB[i]) {
441
+ return false;
442
+ }
443
+ if (objectivesA[i] < objectivesB[i]) {
444
+ aIsBetterInOne = true;
445
+ }
446
+ }
447
+ return aIsBetterInOne;
448
+ }
449
+
450
+ function nonDominatedSort(populationWithObjectives) {
451
+ const fronts = [[]];
452
+ for (const p1 of populationWithObjectives) {
453
+ p1.dominationCount = 0;
454
+ p1.dominatedSolutions = [];
455
+ for (const p2 of populationWithObjectives) {
456
+ if (p1 === p2) continue;
457
+ if (paretoDominates(p1.objectives, p2.objectives)) {
458
+ p1.dominatedSolutions.push(p2);
459
+ } else if (paretoDominates(p2.objectives, p1.objectives)) {
460
+ p1.dominationCount++;
461
+ }
462
+ }
463
+ if (p1.dominationCount === 0) {
464
+ p1.rank = 0;
465
+ fronts[0].push(p1);
466
+ }
467
+ }
468
+
469
+ let i = 0;
470
+ while (fronts[i] && fronts[i].length > 0) {
471
+ const nextFront = [];
472
+ for (const p1 of fronts[i]) {
473
+ for (const p2 of p1.dominatedSolutions) {
474
+ p2.dominationCount--;
475
+ if (p2.dominationCount === 0) {
476
+ p2.rank = i + 1;
477
+ nextFront.push(p2);
478
+ }
479
+ }
480
+ }
481
+ i++;
482
+ if (nextFront.length > 0) {
483
+ fronts[i] = nextFront;
484
+ }
485
+ }
486
+ return fronts;
487
+ }
488
+
489
+ function calculateCrowdingDistance(front) {
490
+ if (front.length === 0) return;
491
+ front.forEach((p) => (p.crowdingDistance = 0));
492
+ const numObjectives = front[0].objectives.length;
493
+
494
+ for (let i = 0; i < numObjectives; i++) {
495
+ front.sort((a, b) => a.objectives[i] - b.objectives[i]);
496
+ const minObj = front[0].objectives[i];
497
+ const maxObj = front[front.length - 1].objectives[i];
498
+
499
+ front[0].crowdingDistance = Infinity;
500
+ front[front.length - 1].crowdingDistance = Infinity;
501
+
502
+ if (maxObj === minObj) continue;
503
+
504
+ for (let j = 1; j < front.length - 1; j++) {
505
+ front[j].crowdingDistance +=
506
+ (front[j + 1].objectives[i] - front[j - 1].objectives[i]) /
507
+ (maxObj - minObj);
508
+ }
509
+ }
510
+ }
511
+
324
512
  const ClientOptimizers = {
325
513
  simulatedAnnealing(initialSolution, evaluator, neighbor, iterations, temp, cooling) {
326
514
  let currentSolution = initialSolution;
@@ -358,6 +546,236 @@ const ClientOptimizers = {
358
546
  population = newPopulation;
359
547
  }
360
548
  return population;
549
+ },
550
+
551
+ geneticAlgorithmMultiObjective(createIndividual, fitnessFunction, crossover, mutate, options = {}) {
552
+ const generations = options.generations || 50;
553
+ const populationSize = options.populationSize || 40;
554
+ const mutationRate = options.mutationRate !== undefined ? options.mutationRate : 0.1;
555
+
556
+ let population = Array.from({ length: populationSize }, () => ({
557
+ individual: createIndividual(),
558
+ }));
559
+ population.forEach((p) => (p.objectives = fitnessFunction(p.individual)));
560
+
561
+ for (let gen = 0; gen < generations; gen++) {
562
+ const offspring = [];
563
+ for (let i = 0; i < populationSize; i++) {
564
+ const parent1 = population[Math.floor(Math.random() * population.length)];
565
+ const parent2 = population[Math.floor(Math.random() * population.length)];
566
+ let childIndividual = crossover(parent1.individual, parent2.individual);
567
+ if (Math.random() < mutationRate) {
568
+ childIndividual = mutate(childIndividual);
569
+ }
570
+ const child = { individual: childIndividual };
571
+ child.objectives = fitnessFunction(child.individual);
572
+ offspring.push(child);
573
+ }
574
+
575
+ const combinedPopulation = [...population, ...offspring];
576
+ const fronts = nonDominatedSort(combinedPopulation);
577
+ const newPopulation = [];
578
+ for (const front of fronts) {
579
+ if (newPopulation.length + front.length <= populationSize) {
580
+ newPopulation.push(...front);
581
+ } else {
582
+ calculateCrowdingDistance(front);
583
+ front.sort((a, b) => b.crowdingDistance - a.crowdingDistance);
584
+ const remaining = populationSize - newPopulation.length;
585
+ newPopulation.push(...front.slice(0, remaining));
586
+ break;
587
+ }
588
+ }
589
+ population = newPopulation;
590
+ }
591
+
592
+ const finalFronts = nonDominatedSort(population);
593
+ const bestFront = finalFronts.length > 0 ? finalFronts[0] : [];
594
+ const uniqueSolutionsMap = new Map();
595
+ for (const p of bestFront) {
596
+ const key = JSON.stringify(p.objectives);
597
+ if (!uniqueSolutionsMap.has(key)) {
598
+ uniqueSolutionsMap.set(key, {
599
+ solution: p.individual,
600
+ objectives: p.objectives,
601
+ });
602
+ }
603
+ }
604
+ return Array.from(uniqueSolutionsMap.values());
605
+ },
606
+
607
+ 'cpc.solve'(context, options = {}) {
608
+ const optimalBaseCommission = context.platformParams ? context.platformParams.optimalBaseCommission : 0.3;
609
+ const optimalBonusFactor = context.platformParams ? context.platformParams.optimalBonusFactor : 0.1;
610
+ const websiteQualityScore = ((context.website && context.website.relevanceScore) || 50) / 100;
611
+ const effectiveCommissionRate = Math.max(0, optimalBaseCommission - websiteQualityScore * optimalBonusFactor);
612
+
613
+ const advertiserDemand = (cpc) => {
614
+ if (cpc <= 0) return Infinity;
615
+ return (context.advertiser.credits || 0) / cpc;
616
+ };
617
+
618
+ const supply = context.estimatedImpressions || 1;
619
+ const competingAdsCount = context.competingAds ? context.competingAds.length : 0;
620
+ const competitionFactor = Math.min(2.5, 1 + competingAdsCount * 0.1);
621
+
622
+ const fitnessFunction = (cpcMultiplier) => {
623
+ const adjustedCPC = 1.0 * cpcMultiplier * competitionFactor;
624
+ if (adjustedCPC < 0.1) return [Infinity, Infinity, Infinity];
625
+
626
+ const demand = advertiserDemand(adjustedCPC);
627
+ const estimatedClicks = Math.min(demand, supply);
628
+ const platformRevenue = estimatedClicks * adjustedCPC * effectiveCommissionRate;
629
+ const advertiserValue = estimatedClicks;
630
+ const marketImbalance = Math.abs(demand - supply);
631
+
632
+ return [-platformRevenue, -advertiserValue, marketImbalance];
633
+ };
634
+
635
+ const createIndividual = () => 0.5 + Math.random() * 4.5;
636
+ const crossover = (cpc1, cpc2) => (cpc1 + cpc2) / 2;
637
+ const mutate = (cpc) => Math.max(0.1, cpc + (Math.random() - 0.5) * 0.5);
638
+
639
+ const gaOptions = {
640
+ generations: 50,
641
+ populationSize: 40,
642
+ ...options,
643
+ };
644
+
645
+ const paretoFront = ClientOptimizers.geneticAlgorithmMultiObjective(
646
+ createIndividual,
647
+ fitnessFunction,
648
+ crossover,
649
+ mutate,
650
+ gaOptions
651
+ );
652
+
653
+ return {
654
+ paretoFront: paretoFront.map((result) => {
655
+ const cpcMultiplier = result.solution;
656
+ const finalCpc = Math.max(0.1, 1.0 * cpcMultiplier * competitionFactor);
657
+ return {
658
+ ...result,
659
+ solution: finalCpc,
660
+ };
661
+ })
662
+ };
663
+ },
664
+
665
+ 'fraud.solve'(context, options = {}) {
666
+ const legitimateClicks = context.legitimateClicks || [];
667
+ const fraudulentClicks = context.fraudulentClicks || [];
668
+
669
+ const fitnessFunction = (solution) => {
670
+ const [minTimeToClick, maxClickVariance, minMouseEntropy, minScrollEvents] = solution;
671
+ if (
672
+ minTimeToClick < 100 ||
673
+ minTimeToClick > 5000 ||
674
+ maxClickVariance < 1 ||
675
+ maxClickVariance > 10000 ||
676
+ minMouseEntropy < 0 ||
677
+ minMouseEntropy > 1 ||
678
+ minScrollEvents < 0
679
+ ) {
680
+ return [Infinity, Infinity];
681
+ }
682
+
683
+ const calculateClickVariance = (clicks) => {
684
+ if (!clicks || clicks.length < 2) return 0;
685
+ const meanX = clicks.reduce((sum, c) => sum + c.clickX, 0) / clicks.length;
686
+ const meanY = clicks.reduce((sum, c) => sum + c.clickY, 0) / clicks.length;
687
+ return clicks.reduce((sum, c) => sum + Math.pow(c.clickX - meanX, 2) + Math.pow(c.clickY - meanY, 2), 0) / clicks.length;
688
+ };
689
+
690
+ const getClicksByFingerprint = (clickData) => {
691
+ const grouped = {};
692
+ for (const click of clickData) {
693
+ if (!grouped[click.fingerprint]) grouped[click.fingerprint] = [];
694
+ grouped[click.fingerprint].push(click);
695
+ }
696
+ return grouped;
697
+ };
698
+
699
+ const legitimateGroups = getClicksByFingerprint(legitimateClicks);
700
+ const fraudulentGroups = getClicksByFingerprint(fraudulentClicks);
701
+
702
+ let truePositives = 0;
703
+ let falsePositives = 0;
704
+
705
+ for (const fingerprint in fraudulentGroups) {
706
+ const clicks = fraudulentGroups[fingerprint];
707
+ if (!clicks) continue;
708
+ const variance = calculateClickVariance(clicks);
709
+ const isTooFast = clicks.some((c) => c.timeToClick < minTimeToClick);
710
+ const isTooUniform = variance < maxClickVariance;
711
+ const hasLowEntropy = clicks.some((c) => c.mouseEntropy < minMouseEntropy);
712
+ const hasFewScrolls = clicks.some((c) => c.scrollEvents < minScrollEvents);
713
+
714
+ if (isTooFast || isTooUniform || hasLowEntropy || hasFewScrolls) {
715
+ truePositives++;
716
+ }
717
+ }
718
+
719
+ for (const fingerprint in legitimateGroups) {
720
+ const clicks = legitimateGroups[fingerprint];
721
+ if (!clicks) continue;
722
+ const variance = calculateClickVariance(clicks);
723
+ if (
724
+ clicks.some((c) => c.timeToClick < minTimeToClick || c.mouseEntropy < minMouseEntropy || c.scrollEvents < minScrollEvents) ||
725
+ variance < maxClickVariance
726
+ ) {
727
+ falsePositives++;
728
+ }
729
+ }
730
+
731
+ const totalFraudulent = Object.keys(fraudulentGroups).length || 1;
732
+ const totalLegitimate = Object.keys(legitimateGroups).length || 1;
733
+
734
+ const objective1 = 1 - truePositives / totalFraudulent;
735
+ const objective2 = falsePositives / totalLegitimate;
736
+
737
+ return [objective1, objective2];
738
+ };
739
+
740
+ const createIndividual = () => [
741
+ 100 + Math.random() * 4900,
742
+ 1 + Math.random() * 9999,
743
+ Math.random() * 0.5,
744
+ Math.floor(Math.random() * 10)
745
+ ];
746
+
747
+ const crossover = (s1, s2) => [
748
+ (s1[0] + s2[0]) / 2,
749
+ (s1[1] + s2[1]) / 2,
750
+ (s1[2] + s2[2]) / 2,
751
+ (s1[3] + s2[3]) / 2,
752
+ ];
753
+
754
+ const mutate = (solution) => {
755
+ const newSolution = [...solution];
756
+ const i = Math.floor(Math.random() * 4);
757
+ const mutationFactors = [500, 1000, 0.1, 2];
758
+ newSolution[i] += (Math.random() - 0.5) * mutationFactors[i];
759
+ return newSolution;
760
+ };
761
+
762
+ const gaOptions = {
763
+ generations: 80,
764
+ populationSize: 60,
765
+ ...options,
766
+ };
767
+
768
+ const paretoFront = ClientOptimizers.geneticAlgorithmMultiObjective(
769
+ createIndividual,
770
+ fitnessFunction,
771
+ crossover,
772
+ mutate,
773
+ gaOptions
774
+ );
775
+
776
+ return {
777
+ paretoFront
778
+ };
361
779
  }
362
780
  };
363
781
 
@@ -539,7 +957,11 @@ class ChallengeSolution {
539
957
  // Logique de formatage spécifique à chaque type de challenge
540
958
  if (this.type === 'cpu_mem' || this.type === 'cpu_mem_inline' || this.type === 'cpu_target') {
541
959
  Object.entries(this.rawSolution).forEach(([key, value]) => {
542
- url.searchParams.set(`pow_solution_${key}`, String(value));
960
+ if (typeof value === 'object' && value !== null) {
961
+ url.searchParams.set(`pow_solution_${key}`, JSON.stringify(value));
962
+ } else {
963
+ url.searchParams.set(`pow_solution_${key}`, String(value));
964
+ }
543
965
  });
544
966
  } else if (this.type === 'useful_work_task') {
545
967
  url.searchParams.set('pow_solution_work_result', JSON.stringify(this.rawSolution.work_result));
@@ -586,7 +586,7 @@ let managerPromise = null;
586
586
  * @returns {Promise<ProblemManager>} The singleton instance.
587
587
  */
588
588
  export function getProblemManager(options = {}, store) {
589
- const defaultPath = join(__dirname, '..', '..', 'problems.config.json');
589
+ const defaultPath = join(__dirname, '..', '..', 'config', 'problems.config.json');
590
590
  const { configPath = defaultPath, config } = options;
591
591
 
592
592
  const hasConfigChanged = problemManagerInstance && (