@anonympins/fingerprint 0.5.2 → 0.5.5

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 (41) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/package.json +1 -1
  3. package/src/js/build-client.js +67 -67
  4. package/src/js/fingerprint.client.js +280 -38
  5. package/src/js/fingerprint.client.obfuscated.js +1 -1
  6. package/src/js/fingerprint.js +474 -163
  7. package/src/js/fingerprint.utils.js +213 -213
  8. package/src/js/library.js +24 -8
  9. package/src/js/mongodb-store.js +165 -79
  10. package/src/js/obfuscation.worker.js +23 -0
  11. package/src/js/pow.solver.inline.js +509 -14
  12. package/src/js/pow.solver.js +350 -32
  13. package/src/js/problem-manager.js +1 -1
  14. package/src/js/redis-store.js +114 -42
  15. package/src/js/tests/cross_parity.test.js +175 -0
  16. package/src/js/tests/dns-circuit-breaker.test.js +55 -0
  17. package/src/js/tests/fingerprint.client.init.test.js +143 -143
  18. package/src/js/tests/fingerprint.client.test.js +128 -128
  19. package/src/js/tests/fingerprint.test.js +57 -2
  20. package/src/js/tests/ip-reputation.test.js +1 -1
  21. package/src/js/tests/obfuscation.test.js +48 -0
  22. package/src/js/tests/pow.solver.test.js +5 -2
  23. package/src/js/tests/usefulWorkSolver.test.js +162 -0
  24. package/src/php/AutoTuner.php +42 -0
  25. package/src/php/Challenge/ChallengeUtils.php +21 -8
  26. package/src/php/FingerprintClient.php +22 -5
  27. package/src/php/FingerprintEngine.php +233 -57
  28. package/src/php/Optimization/Optimization.php +23 -16
  29. package/src/php/Optimization/OptimizationOperators.php +27 -0
  30. package/src/php/ProblemManager.php +1 -1
  31. package/src/php/Store/MongoDbStore.php +184 -104
  32. package/src/php/Store/RedisStore.php +91 -11
  33. package/src/php/Tests/ChallengeUtilsTest.php +285 -285
  34. package/src/php/Tests/DnsCircuitBreakerTest.php +106 -0
  35. package/src/php/Tests/IpReputationTest.php +1 -1
  36. package/src/php/Tests/PowTest.php +1 -1
  37. package/src/php/Tests/RequestUtilsTest.php +29 -0
  38. package/src/php/Tests/TLSClientHelloParserTest.php +177 -117
  39. package/src/php/Utils/BigInt.php +202 -202
  40. package/src/php/Utils/RequestUtils.php +74 -21
  41. package/src/php/Utils/TLSClientHelloParser.php +212 -116
@@ -6,6 +6,15 @@
6
6
 
7
7
  'use strict';
8
8
 
9
+ function secureRandom() {
10
+ if (typeof window !== 'undefined' && (window.crypto || window.msCrypto)) {
11
+ const array = new Uint32Array(1);
12
+ (window.crypto || window.msCrypto).getRandomValues(array);
13
+ return array[0] / 0x100000000;
14
+ }
15
+ return Math.random();
16
+ }
17
+
9
18
  function cyrb53(str, seed = 0) {
10
19
  const safeStr = (typeof str === 'string' ? str : String(str || '')).slice(0, 10000);
11
20
  let h1 = 0xdeadbeef ^ seed,
@@ -107,6 +116,480 @@ async function solveSpaceChallenge(seed, queries, nonce, clientSecret, peerBlock
107
116
  return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
108
117
  }
109
118
 
119
+ // --- Fonctions d'optimisation copiées/adaptées de library.js pour le client ---
120
+
121
+ function paretoDominates(objectivesA, objectivesB) {
122
+ let aIsBetterInOne = false;
123
+ for (let i = 0; i < objectivesA.length; i++) {
124
+ if (objectivesA[i] > objectivesB[i]) {
125
+ return false;
126
+ }
127
+ if (objectivesA[i] < objectivesB[i]) {
128
+ aIsBetterInOne = true;
129
+ }
130
+ }
131
+ return aIsBetterInOne;
132
+ }
133
+
134
+ function nonDominatedSort(populationWithObjectives) {
135
+ const fronts = [[]];
136
+ for (const p1 of populationWithObjectives) {
137
+ p1.dominationCount = 0;
138
+ p1.dominatedSolutions = [];
139
+ for (const p2 of populationWithObjectives) {
140
+ if (p1 === p2) continue;
141
+ if (paretoDominates(p1.objectives, p2.objectives)) {
142
+ p1.dominatedSolutions.push(p2);
143
+ } else if (paretoDominates(p2.objectives, p1.objectives)) {
144
+ p1.dominationCount++;
145
+ }
146
+ }
147
+ if (p1.dominationCount === 0) {
148
+ p1.rank = 0;
149
+ fronts[0].push(p1);
150
+ }
151
+ }
152
+
153
+ let i = 0;
154
+ while (fronts[i] && fronts[i].length > 0) {
155
+ const nextFront = [];
156
+ for (const p1 of fronts[i]) {
157
+ for (const p2 of p1.dominatedSolutions) {
158
+ p2.dominationCount--;
159
+ if (p2.dominationCount === 0) {
160
+ p2.rank = i + 1;
161
+ nextFront.push(p2);
162
+ }
163
+ }
164
+ }
165
+ i++;
166
+ if (nextFront.length > 0) {
167
+ fronts[i] = nextFront;
168
+ }
169
+ }
170
+ return fronts;
171
+ }
172
+
173
+ function calculateCrowdingDistance(front) {
174
+ if (front.length === 0) return;
175
+ front.forEach((p) => (p.crowdingDistance = 0));
176
+ const numObjectives = front[0].objectives.length;
177
+
178
+ for (let i = 0; i < numObjectives; i++) {
179
+ front.sort((a, b) => a.objectives[i] - b.objectives[i]);
180
+ const minObj = front[0].objectives[i];
181
+ const maxObj = front[front.length - 1].objectives[i];
182
+
183
+ front[0].crowdingDistance = Infinity;
184
+ front[front.length - 1].crowdingDistance = Infinity;
185
+
186
+ if (maxObj === minObj) continue;
187
+
188
+ for (let j = 1; j < front.length - 1; j++) {
189
+ front[j].crowdingDistance +=
190
+ (front[j + 1].objectives[i] - front[j - 1].objectives[i]) /
191
+ (maxObj - minObj);
192
+ }
193
+ }
194
+ }
195
+
196
+ const ClientOptimizers = {
197
+ simulatedAnnealing(initialSolution, evaluator, neighbor, iterations, temp, cooling) {
198
+ let currentSolution = initialSolution;
199
+ let currentEnergy = evaluator(currentSolution);
200
+ let temperature = temp;
201
+
202
+ for (let i = 0; i < iterations; i++) {
203
+ const newSolution = neighbor(currentSolution);
204
+ const newEnergy = evaluator(newSolution);
205
+ if (newEnergy < currentEnergy || secureRandom() < Math.exp((currentEnergy - newEnergy) / temperature)) {
206
+ currentSolution = newSolution;
207
+ currentEnergy = newEnergy;
208
+ }
209
+ temperature *= cooling;
210
+ }
211
+ return { solution: currentSolution, energy: currentEnergy };
212
+ },
213
+
214
+ geneticAlgorithm(createIndividual, fitness, crossover, mutate, generations, popSize) {
215
+ let population = Array.from({ length: popSize }, () => {
216
+ const chromosome = createIndividual();
217
+ return { chromosome, fitness: fitness(chromosome) };
218
+ });
219
+
220
+ for (let gen = 0; gen < generations; gen++) {
221
+ population.sort((a, b) => a.fitness - b.fitness);
222
+ const newPopulation = [population[0]]; // Elitism
223
+ while (newPopulation.length < popSize) {
224
+ const p1 = population[Math.floor(secureRandom() * (popSize / 2))];
225
+ const p2 = population[Math.floor(secureRandom() * (popSize / 2))];
226
+ let offspring = crossover(p1.chromosome, p2.chromosome);
227
+ if (secureRandom() < 0.1) offspring = mutate(offspring);
228
+ newPopulation.push({ chromosome: offspring, fitness: fitness(offspring) });
229
+ }
230
+ population = newPopulation;
231
+ }
232
+ return population;
233
+ },
234
+
235
+ geneticAlgorithmMultiObjective(createIndividual, fitnessFunction, crossover, mutate, options = {}) {
236
+ const generations = options.generations || 50;
237
+ const populationSize = options.populationSize || 40;
238
+ const mutationRate = options.mutationRate !== undefined ? options.mutationRate : 0.1;
239
+
240
+ let population = Array.from({ length: populationSize }, () => ({
241
+ individual: createIndividual(),
242
+ }));
243
+ population.forEach((p) => (p.objectives = fitnessFunction(p.individual)));
244
+
245
+ for (let gen = 0; gen < generations; gen++) {
246
+ const offspring = [];
247
+ for (let i = 0; i < populationSize; i++) {
248
+ const parent1 = population[Math.floor(secureRandom() * population.length)];
249
+ const parent2 = population[Math.floor(secureRandom() * population.length)];
250
+ let childIndividual = crossover(parent1.individual, parent2.individual);
251
+ if (secureRandom() < mutationRate) {
252
+ childIndividual = mutate(childIndividual);
253
+ }
254
+ const child = { individual: childIndividual };
255
+ child.objectives = fitnessFunction(child.individual);
256
+ offspring.push(child);
257
+ }
258
+
259
+ const combinedPopulation = [...population, ...offspring];
260
+ const fronts = nonDominatedSort(combinedPopulation);
261
+ const newPopulation = [];
262
+ for (const front of fronts) {
263
+ if (newPopulation.length + front.length <= populationSize) {
264
+ newPopulation.push(...front);
265
+ } else {
266
+ calculateCrowdingDistance(front);
267
+ front.sort((a, b) => b.crowdingDistance - a.crowdingDistance);
268
+ const remaining = populationSize - newPopulation.length;
269
+ newPopulation.push(...front.slice(0, remaining));
270
+ break;
271
+ }
272
+ }
273
+ population = newPopulation;
274
+ }
275
+
276
+ const finalFronts = nonDominatedSort(population);
277
+ const bestFront = finalFronts.length > 0 ? finalFronts[0] : [];
278
+ const uniqueSolutionsMap = new Map();
279
+ for (const p of bestFront) {
280
+ const key = JSON.stringify(p.objectives);
281
+ if (!uniqueSolutionsMap.has(key)) {
282
+ uniqueSolutionsMap.set(key, {
283
+ solution: p.individual,
284
+ objectives: p.objectives,
285
+ });
286
+ }
287
+ }
288
+ return Array.from(uniqueSolutionsMap.values());
289
+ },
290
+
291
+ 'cpc.solve'(context, options = {}) {
292
+ const optimalBaseCommission = context.platformParams ? context.platformParams.optimalBaseCommission : 0.3;
293
+ const optimalBonusFactor = context.platformParams ? context.platformParams.optimalBonusFactor : 0.1;
294
+ const websiteQualityScore = ((context.website && context.website.relevanceScore) || 50) / 100;
295
+ const effectiveCommissionRate = Math.max(0, optimalBaseCommission - websiteQualityScore * optimalBonusFactor);
296
+
297
+ const advertiserDemand = (cpc) => {
298
+ if (cpc <= 0) return Infinity;
299
+ return (context.advertiser.credits || 0) / cpc;
300
+ };
301
+
302
+ const supply = context.estimatedImpressions || 1;
303
+ const competingAdsCount = context.competingAds ? context.competingAds.length : 0;
304
+ const competitionFactor = Math.min(2.5, 1 + competingAdsCount * 0.1);
305
+
306
+ const fitnessFunction = (cpcMultiplier) => {
307
+ const adjustedCPC = 1.0 * cpcMultiplier * competitionFactor;
308
+ if (adjustedCPC < 0.1) return [Infinity, Infinity, Infinity];
309
+
310
+ const demand = advertiserDemand(adjustedCPC);
311
+ const estimatedClicks = Math.min(demand, supply);
312
+ const platformRevenue = estimatedClicks * adjustedCPC * effectiveCommissionRate;
313
+ const advertiserValue = estimatedClicks;
314
+ const marketImbalance = Math.abs(demand - supply);
315
+
316
+ return [-platformRevenue, -advertiserValue, marketImbalance];
317
+ };
318
+
319
+ const createIndividual = () => 0.5 + secureRandom() * 4.5;
320
+ const crossover = (cpc1, cpc2) => (cpc1 + cpc2) / 2;
321
+ const mutate = (cpc) => Math.max(0.1, cpc + (secureRandom() - 0.5) * 0.5);
322
+
323
+ const gaOptions = {
324
+ generations: 50,
325
+ populationSize: 40,
326
+ ...options,
327
+ };
328
+
329
+ const paretoFront = ClientOptimizers.geneticAlgorithmMultiObjective(
330
+ createIndividual,
331
+ fitnessFunction,
332
+ crossover,
333
+ mutate,
334
+ gaOptions
335
+ );
336
+
337
+ return {
338
+ paretoFront: paretoFront.map((result) => {
339
+ const cpcMultiplier = result.solution;
340
+ const finalCpc = Math.max(0.1, 1.0 * cpcMultiplier * competitionFactor);
341
+ return {
342
+ ...result,
343
+ solution: finalCpc,
344
+ };
345
+ })
346
+ };
347
+ },
348
+
349
+ 'fraud.solve'(context, options = {}) {
350
+ const legitimateClicks = context.legitimateClicks || [];
351
+ const fraudulentClicks = context.fraudulentClicks || [];
352
+
353
+ const fitnessFunction = (solution) => {
354
+ const [minTimeToClick, maxClickVariance, minMouseEntropy, minScrollEvents] = solution;
355
+ if (
356
+ minTimeToClick < 100 ||
357
+ minTimeToClick > 5000 ||
358
+ maxClickVariance < 1 ||
359
+ maxClickVariance > 10000 ||
360
+ minMouseEntropy < 0 ||
361
+ minMouseEntropy > 1 ||
362
+ minScrollEvents < 0
363
+ ) {
364
+ return [Infinity, Infinity];
365
+ }
366
+
367
+ const calculateClickVariance = (clicks) => {
368
+ if (!clicks || clicks.length < 2) return 0;
369
+ const meanX = clicks.reduce((sum, c) => sum + c.clickX, 0) / clicks.length;
370
+ const meanY = clicks.reduce((sum, c) => sum + c.clickY, 0) / clicks.length;
371
+ return clicks.reduce((sum, c) => sum + Math.pow(c.clickX - meanX, 2) + Math.pow(c.clickY - meanY, 2), 0) / clicks.length;
372
+ };
373
+
374
+ const getClicksByFingerprint = (clickData) => {
375
+ const grouped = {};
376
+ for (const click of clickData) {
377
+ if (!grouped[click.fingerprint]) grouped[click.fingerprint] = [];
378
+ grouped[click.fingerprint].push(click);
379
+ }
380
+ return grouped;
381
+ };
382
+
383
+ const legitimateGroups = getClicksByFingerprint(legitimateClicks);
384
+ const fraudulentGroups = getClicksByFingerprint(fraudulentClicks);
385
+
386
+ let truePositives = 0;
387
+ let falsePositives = 0;
388
+
389
+ for (const fingerprint in fraudulentGroups) {
390
+ const clicks = fraudulentGroups[fingerprint];
391
+ if (!clicks) continue;
392
+ const variance = calculateClickVariance(clicks);
393
+ const isTooFast = clicks.some((c) => c.timeToClick < minTimeToClick);
394
+ const isTooUniform = variance < maxClickVariance;
395
+ const hasLowEntropy = clicks.some((c) => c.mouseEntropy < minMouseEntropy);
396
+ const hasFewScrolls = clicks.some((c) => c.scrollEvents < minScrollEvents);
397
+
398
+ if (isTooFast || isTooUniform || hasLowEntropy || hasFewScrolls) {
399
+ truePositives++;
400
+ }
401
+ }
402
+
403
+ for (const fingerprint in legitimateGroups) {
404
+ const clicks = legitimateGroups[fingerprint];
405
+ if (!clicks) continue;
406
+ const variance = calculateClickVariance(clicks);
407
+ if (
408
+ clicks.some((c) => c.timeToClick < minTimeToClick || c.mouseEntropy < minMouseEntropy || c.scrollEvents < minScrollEvents) ||
409
+ variance < maxClickVariance
410
+ ) {
411
+ falsePositives++;
412
+ }
413
+ }
414
+
415
+ const totalFraudulent = Object.keys(fraudulentGroups).length || 1;
416
+ const totalLegitimate = Object.keys(legitimateGroups).length || 1;
417
+
418
+ const objective1 = 1 - truePositives / totalFraudulent;
419
+ const objective2 = falsePositives / totalLegitimate;
420
+
421
+ return [objective1, objective2];
422
+ };
423
+
424
+ const createIndividual = () => [
425
+ 100 + secureRandom() * 4900,
426
+ 1 + secureRandom() * 9999,
427
+ secureRandom() * 0.5,
428
+ Math.floor(secureRandom() * 10)
429
+ ];
430
+
431
+ const crossover = (s1, s2) => [
432
+ (s1[0] + s2[0]) / 2,
433
+ (s1[1] + s2[1]) / 2,
434
+ (s1[2] + s2[2]) / 2,
435
+ (s1[3] + s2[3]) / 2,
436
+ ];
437
+
438
+ const mutate = (solution) => {
439
+ const newSolution = [...solution];
440
+ const i = Math.floor(Math.random() * 4);
441
+ const mutationFactors = [500, 1000, 0.1, 2];
442
+ newSolution[i] += (secureRandom() - 0.5) * mutationFactors[i];
443
+ return newSolution;
444
+ };
445
+
446
+ const gaOptions = {
447
+ generations: 80,
448
+ populationSize: 60,
449
+ ...options,
450
+ };
451
+
452
+ const paretoFront = ClientOptimizers.geneticAlgorithmMultiObjective(
453
+ createIndividual,
454
+ fitnessFunction,
455
+ crossover,
456
+ mutate,
457
+ gaOptions
458
+ );
459
+
460
+ return {
461
+ paretoFront
462
+ };
463
+ }
464
+ };
465
+
466
+ async function solveUsefulWorkTask(task) {
467
+ await new Promise(r => setTimeout(r, 10)); // Yield thread
468
+
469
+ switch (task.type) {
470
+ case 'simulated_annealing_iterations': {
471
+ if (task.payload && task.payload.customers) {
472
+ const { customers, numFacilities, bounds } = task.payload;
473
+ const distanceSq = (p1, p2) => Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2);
474
+ const evaluator = (facilities) => {
475
+ let totalConnectionCost = 0;
476
+ for (const customer of customers) {
477
+ let minDistanceToCustomer = Infinity;
478
+ for (const facility of facilities) {
479
+ const d = distanceSq(customer, facility);
480
+ if (d < minDistanceToCustomer) {
481
+ minDistanceToCustomer = d;
482
+ }
483
+ }
484
+ totalConnectionCost += Math.sqrt(minDistanceToCustomer);
485
+ }
486
+ const fixedCostPerFacility = task.payload.options?.fixedCostPerFacility || 0;
487
+ return totalConnectionCost + facilities.length * fixedCostPerFacility;
488
+ };
489
+ const neighbor = (facilities) => {
490
+ const newFacilities = facilities.map((f) => ({ ...f }));
491
+ const i = Math.floor(secureRandom() * numFacilities);
492
+ const moveX = (secureRandom() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
493
+ const moveY = (secureRandom() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
494
+ newFacilities[i].x = Math.max(bounds.minX, min(bounds.maxX, newFacilities[i].x + moveX));
495
+ newFacilities[i].y = Math.max(bounds.minY, min(bounds.maxY, newFacilities[i].y + moveY));
496
+ return newFacilities;
497
+ };
498
+ const initialSolution = task.initialSolution || Array.from({ length: numFacilities }, () => ({
499
+ x: bounds.minX + secureRandom() * (bounds.maxX - bounds.minX),
500
+ y: bounds.minY + secureRandom() * (bounds.maxY - bounds.minY),
501
+ }));
502
+ return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
503
+ }
504
+
505
+ const { cities } = task.payload;
506
+ const distance = (c1, c2) => Math.sqrt(Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2));
507
+ const evaluator = (path) => {
508
+ let total = 0;
509
+ for (let i = 0; i < path.length - 1; i++) total += distance(cities[path[i]], cities[path[i + 1]]);
510
+ total += distance(cities[path[path.length - 1]], cities[path[0]]);
511
+ return total;
512
+ };
513
+ const neighbor = (path) => {
514
+ const newPath = [...path];
515
+ const [i, j] = [Math.floor(secureRandom() * path.length), Math.floor(secureRandom() * path.length)];
516
+ [newPath[i], newPath[j]] = [newPath[j], newPath[i]];
517
+ return newPath;
518
+ };
519
+ const initialSolution = task.initialSolution || Array.from({ length: cities.length }, (_, i) => i).sort(() => 0.5 - secureRandom());
520
+
521
+ return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
522
+ }
523
+
524
+ case 'genetic_algorithm_generations': {
525
+ const { assets, maxVolatility } = task.payload;
526
+ const fitness = (weights) => {
527
+ const total = weights.reduce((s, w) => s + w, 0);
528
+ if (total === 0) return Infinity;
529
+ const normW = weights.map(w => w / total);
530
+ const ret = normW.reduce((s, w, i) => s + w * assets[i].expectedReturn, 0);
531
+ const vol = normW.reduce((s, w, i) => s + w * assets[i].volatility, 0);
532
+ if (vol > maxVolatility) return 1000 + (vol - maxVolatility);
533
+ return -ret;
534
+ };
535
+ const createIndividual = () => Array.from({ length: assets.length }, secureRandom);
536
+ const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
537
+ const mutate = p => { const n = [...p], i = Math.floor(secureRandom() * n.length); n[i] += (secureRandom() - 0.5) * 0.2; return n.map(v => Math.max(0, v)); };
538
+
539
+ const initialPopulation = task.initialPopulation || Array.from({ length: task.payload.options.populationSize }, () => ({ chromosome: createIndividual(), fitness: 0 }));
540
+ initialPopulation.forEach(p => p.fitness = fitness(p.chromosome));
541
+
542
+ const finalPopulation = ClientOptimizers.geneticAlgorithm(createIndividual, fitness, crossover, mutate, task.generations, task.payload.options.populationSize);
543
+ return { population: finalPopulation };
544
+ }
545
+
546
+ case 'run_multiple_parallel':
547
+ const { solverName, baseSolverArgs } = task;
548
+ const clientSolver = ClientOptimizers[solverName];
549
+ if (!clientSolver) throw new Error(`Solver ${solverName} not found on client.`);
550
+ return clientSolver(...baseSolverArgs);
551
+
552
+ case 'multi_objective_genetic_algorithm': {
553
+ const solverFunction = ClientOptimizers[task.solverName];
554
+ if (!solverFunction) {
555
+ throw new Error(`Solver '${task.solverName}' not found on client.`);
556
+ }
557
+ return solverFunction(task.payload, { generations: task.generations, initialFront: task.initialFront });
558
+ }
559
+
560
+ default:
561
+ throw new Error(`Unknown useful work type: ${task.type}`);
562
+ }
563
+ }
564
+
565
+ async function solveOptimizationTask(initialPopulation, generations) {
566
+ const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
567
+ const mutate = (p) => {
568
+ const newP = [...p];
569
+ const i = Math.floor(secureRandom() * newP.length);
570
+ newP[i] += (secureRandom() - 0.5) * 0.2;
571
+ return newP;
572
+ };
573
+
574
+ let population = initialPopulation;
575
+
576
+ for (let gen = 0; gen < generations; gen++) {
577
+ const parents = population.sort((a, b) => a.fitness - b.fitness).slice(0, Math.ceil(population.length / 2));
578
+ const newPopulation = [...parents];
579
+
580
+ while (newPopulation.length < population.length) {
581
+ const parent1 = parents[Math.floor(secureRandom() * parents.length)];
582
+ const parent2 = parents[Math.floor(secureRandom() * parents.length)];
583
+ let offspring = crossover(parent1.chromosome, parent2.chromosome);
584
+ if (secureRandom() < 0.1) offspring = mutate(offspring);
585
+ newPopulation.push({ chromosome: offspring, fitness: -1 });
586
+ }
587
+ population = newPopulation;
588
+ if (gen % 10 === 0) await new Promise(r => setTimeout(r, 0));
589
+ }
590
+ return population;
591
+ }
592
+
110
593
  /**
111
594
  * Résout un challenge CPU basé sur une cible en utilisant un bloc de base binaire.
112
595
  * @param {Uint8Array} baseBlock - Le bloc de données initial (nonce, secret, fp) fourni par le serveur.
@@ -268,15 +751,17 @@ async function solveCpuTarget(message, target) {
268
751
  */
269
752
  async function solveMemory(seed, difficulty) {
270
753
  const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
754
+ let solution;
755
+ let useWasm = false;
271
756
  if (wasmModule && typeof wasmModule._solve_memory_challenge === 'function') {
272
757
  const encoder = new TextEncoder();
273
758
  const seedBytes = encoder.encode(seed);
274
759
  const ptr = wasmModule._malloc(seedBytes.length + 1);
275
760
  wasmModule.HEAPU8.set(seedBytes, ptr);
276
761
  wasmModule.HEAPU8[ptr + seedBytes.length] = 0; // Null-terminator
277
- const solution = wasmModule._solve_memory_challenge(ptr, difficulty);
762
+ solution = wasmModule._solve_memory_challenge(ptr, difficulty);
278
763
  wasmModule._free(ptr);
279
- return solution;
764
+ useWasm = true;
280
765
  }
281
766
 
282
767
  const size = difficulty * 1024 * 1024;
@@ -324,19 +809,21 @@ async function solveMemory(seed, difficulty) {
324
809
  }
325
810
  const merkleRoot = tree[tree.length - 1][0];
326
811
 
327
- function readBuffer(blocks, addr) {
328
- const blockIdx = Math.floor(addr / 1024);
329
- const elementIdx = addr % 1024;
330
- return blocks[blockIdx][elementIdx];
331
- }
812
+ if (!useWasm) {
813
+ function readBuffer(blocks, addr) {
814
+ const blockIdx = Math.floor(addr / 1024);
815
+ const elementIdx = addr % 1024;
816
+ return blocks[blockIdx][elementIdx];
817
+ }
332
818
 
333
- const totalElements = numBlocks * 1024;
334
- let addr = totalElements > 0 ? readBuffer(blocks, 0) % totalElements : 0;
335
- let solution = 0;
336
- const iterations = 1024;
337
- for (let i = 0; i < iterations; i++) {
338
- addr = readBuffer(blocks, addr) % totalElements;
339
- solution ^= addr;
819
+ const totalElements = numBlocks * 1024;
820
+ let addr = totalElements > 0 ? readBuffer(blocks, 0) % totalElements : 0;
821
+ solution = 0;
822
+ const iterations = 1024;
823
+ for (let i = 0; i < iterations; i++) {
824
+ addr = readBuffer(blocks, addr) % totalElements;
825
+ solution ^= addr;
826
+ }
340
827
  }
341
828
 
342
829
  const challengedIndices = [];
@@ -437,6 +924,14 @@ async function solveChallenge(challenge, fingerprint = '') { // fingerprint para
437
924
  const solutions = {};
438
925
 
439
926
  switch (type) {
927
+ case 'optimization_task':
928
+ const finalPopulation = await solveOptimizationTask(challenge.optimizationTask.population, challenge.optimizationTask.generations);
929
+ solutions.optimization = finalPopulation.map(p => p.chromosome);
930
+ break;
931
+ case 'useful_work_task':
932
+ solutions.work_result = await solveUsefulWorkTask(challenge.usefulWorkTask.task);
933
+ solutions.problem_id = challenge.usefulWorkTask.problemId;
934
+ break;
440
935
  case 'cpu_target':
441
936
  // Note: This case is not fully exercised by tests as it relies on Web Workers.
442
937
  if (!cpuTarget) {