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