@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.
- package/CHANGELOG.md +27 -0
- package/README.md +118 -115
- package/package.json +1 -1
- package/src/js/dynamic-wasm.js +52 -13
- package/src/js/fingerprint.builder.js +169 -168
- package/src/js/fingerprint.client.js +1271 -1189
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +436 -162
- package/src/js/fingerprint.utils.js +30 -0
- package/src/js/gpu_pow.solver.js +96 -1
- package/src/js/library.js +40 -21
- package/src/js/mongodb-store.js +165 -79
- package/src/js/pow.solver.inline.js +627 -30
- package/src/js/pow.solver.js +453 -31
- package/src/js/problem-manager.js +1 -1
- package/src/js/redis-store.js +114 -42
- package/src/js/tests/dns-circuit-breaker.test.js +55 -0
- package/src/js/tests/fingerprint.client.init.test.js +143 -143
- package/src/js/tests/fingerprint.client.test.js +128 -128
- package/src/js/tests/fingerprint.engine.test.js +370 -370
- package/src/js/tests/fingerprint.test.js +119 -5
- package/src/js/tests/gpu_pow.test.js +81 -0
- package/src/js/tests/ip-reputation.test.js +1 -1
- package/src/js/tests/pow.solver.test.js +9 -6
- package/src/js/tests/usefulWorkSolver.test.js +162 -0
- package/src/php/Challenge/ChallengeUtils.php +137 -35
- package/src/php/FingerprintClient.php +22 -5
- package/src/php/FingerprintEngine.php +335 -58
- package/src/php/Optimization/Optimization.php +23 -16
- package/src/php/Optimization/OptimizationOperators.php +27 -0
- package/src/php/ProblemManager.php +1 -1
- package/src/php/Store/MongoDbStore.php +184 -104
- package/src/php/Store/RedisStore.php +91 -11
- package/src/php/Tests/ChallengeUtilsTest.php +285 -285
- package/src/php/Tests/DnsCircuitBreakerTest.php +106 -0
- package/src/php/Tests/IpReputationTest.php +1 -1
- package/src/php/Tests/PowTest.php +1 -1
- package/src/php/Tests/RequestUtilsTest.php +29 -0
- package/src/php/Tests/TLSClientHelloParserTest.php +177 -117
- package/src/php/Utils/BigInt.php +202 -202
- package/src/php/Utils/RequestUtils.php +74 -21
- package/src/php/Utils/TLSClientHelloParser.php +212 -116
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
9
|
function cyrb53(str, seed = 0) {
|
|
10
|
+
const safeStr = (typeof str === 'string' ? str : String(str || '')).slice(0, 10000);
|
|
10
11
|
let h1 = 0xdeadbeef ^ seed,
|
|
11
12
|
h2 = 0x41c6ce57 ^ seed;
|
|
12
|
-
for (let i = 0, ch; i <
|
|
13
|
-
ch =
|
|
13
|
+
for (let i = 0, ch; i < safeStr.length; i++) {
|
|
14
|
+
ch = safeStr.charCodeAt(i);
|
|
14
15
|
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
15
16
|
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
16
17
|
}
|
|
@@ -106,6 +107,480 @@ async function solveSpaceChallenge(seed, queries, nonce, clientSecret, peerBlock
|
|
|
106
107
|
return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
107
108
|
}
|
|
108
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
|
+
|
|
109
584
|
/**
|
|
110
585
|
* Résout un challenge CPU basé sur une cible en utilisant un bloc de base binaire.
|
|
111
586
|
* @param {Uint8Array} baseBlock - Le bloc de données initial (nonce, secret, fp) fourni par le serveur.
|
|
@@ -140,6 +615,53 @@ async function solveCpuTargetInline(baseBlock, target, progressCallback) {
|
|
|
140
615
|
return solution;
|
|
141
616
|
}
|
|
142
617
|
|
|
618
|
+
// Try Web Worker execution first if supported and not blocked by CSP
|
|
619
|
+
if (typeof window !== 'undefined' && typeof Worker !== 'undefined') {
|
|
620
|
+
try {
|
|
621
|
+
return await new Promise((resolve, reject) => {
|
|
622
|
+
const workerCode = `
|
|
623
|
+
self.onmessage = async (e) => {
|
|
624
|
+
const { baseBlock, target } = e.data;
|
|
625
|
+
const cpuTarget = BigInt(target);
|
|
626
|
+
const encoder = new TextEncoder();
|
|
627
|
+
let cpuSolution = 0;
|
|
628
|
+
while (true) {
|
|
629
|
+
const solutionBytes = encoder.encode(String(cpuSolution));
|
|
630
|
+
const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
|
|
631
|
+
finalBlock.set(baseBlock);
|
|
632
|
+
finalBlock.set(solutionBytes, baseBlock.length);
|
|
633
|
+
const buf = await crypto.subtle.digest("SHA-256", finalBlock);
|
|
634
|
+
const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
635
|
+
if (BigInt('0x' + hashHex) < cpuTarget) break;
|
|
636
|
+
cpuSolution++;
|
|
637
|
+
if (cpuSolution % 25000 === 0) {
|
|
638
|
+
self.postMessage({ type: 'progress', solution: cpuSolution });
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
self.postMessage({ type: 'success', solution: cpuSolution });
|
|
642
|
+
};
|
|
643
|
+
`;
|
|
644
|
+
const blob = new Blob([workerCode], { type: 'application/javascript' });
|
|
645
|
+
const worker = new Worker(URL.createObjectURL(blob));
|
|
646
|
+
worker.onmessage = (event) => {
|
|
647
|
+
if (event.data.type === 'progress') {
|
|
648
|
+
if (progressCallback) progressCallback(event.data.solution);
|
|
649
|
+
} else if (event.data.type === 'success') {
|
|
650
|
+
resolve(event.data.solution);
|
|
651
|
+
worker.terminate();
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
worker.onerror = (err) => {
|
|
655
|
+
worker.terminate();
|
|
656
|
+
reject(err);
|
|
657
|
+
};
|
|
658
|
+
worker.postMessage({ baseBlock, target: cpuTarget.toString() });
|
|
659
|
+
});
|
|
660
|
+
} catch (workerError) {
|
|
661
|
+
console.warn("Web Worker creation failed (possibly due to CSP). Falling back to main thread with scheduler/setTimeout yielding.");
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
143
665
|
let cpuSolution = 0;
|
|
144
666
|
|
|
145
667
|
while (true) {
|
|
@@ -165,8 +687,12 @@ async function solveCpuTargetInline(baseBlock, target, progressCallback) {
|
|
|
165
687
|
// --- FIN DES LOGS ---
|
|
166
688
|
if (BigInt('0x' + hashHex) < cpuTarget) break;
|
|
167
689
|
cpuSolution++;
|
|
168
|
-
|
|
169
|
-
|
|
690
|
+
if (cpuSolution % 50000 === 0) {
|
|
691
|
+
if (typeof scheduler !== 'undefined' && typeof scheduler.yield === 'function') {
|
|
692
|
+
await scheduler.yield();
|
|
693
|
+
} else {
|
|
694
|
+
await new Promise(r => setTimeout(r, 0));
|
|
695
|
+
}
|
|
170
696
|
if (progressCallback) progressCallback(cpuSolution);
|
|
171
697
|
}
|
|
172
698
|
}
|
|
@@ -215,41 +741,104 @@ async function solveCpuTarget(message, target) {
|
|
|
215
741
|
* @returns {Promise<number>} La solution (nombre entier).
|
|
216
742
|
*/
|
|
217
743
|
async function solveMemory(seed, difficulty) {
|
|
218
|
-
// On définit un seuil pour savoir quand faire une pause, afin de ne pas bloquer le thread UI.
|
|
219
|
-
const YIELD_THRESHOLD = 100000;
|
|
220
|
-
const size = difficulty * 1024 * 1024;
|
|
221
|
-
const buffer = new Uint32Array(size / 4);
|
|
222
|
-
|
|
223
744
|
const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
|
|
745
|
+
let solution;
|
|
746
|
+
let useWasm = false;
|
|
224
747
|
if (wasmModule && typeof wasmModule._solve_memory_challenge === 'function') {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
748
|
+
const encoder = new TextEncoder();
|
|
749
|
+
const seedBytes = encoder.encode(seed);
|
|
750
|
+
const ptr = wasmModule._malloc(seedBytes.length + 1);
|
|
751
|
+
wasmModule.HEAPU8.set(seedBytes, ptr);
|
|
752
|
+
wasmModule.HEAPU8[ptr + seedBytes.length] = 0; // Null-terminator
|
|
753
|
+
solution = wasmModule._solve_memory_challenge(ptr, difficulty);
|
|
754
|
+
wasmModule._free(ptr);
|
|
755
|
+
useWasm = true;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const size = difficulty * 1024 * 1024;
|
|
759
|
+
const numBlocks = difficulty * 256;
|
|
760
|
+
if (numBlocks === 0) return { solution: 0, merkleRoot: '', proofs: {} };
|
|
761
|
+
|
|
762
|
+
async function hashBlock(block) {
|
|
763
|
+
const buf = await crypto.subtle.digest("SHA-256", block.buffer);
|
|
764
|
+
return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function hexToBytes(hex) {
|
|
768
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
769
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
770
|
+
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
228
771
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
772
|
+
return bytes;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const leaves = [];
|
|
776
|
+
const blocks = [];
|
|
777
|
+
for (let b = 0; b < numBlocks; b++) {
|
|
778
|
+
const block = new Uint32Array(1024);
|
|
779
|
+
let h = cyrb53(seed + ":" + b);
|
|
780
|
+
for (let i = 0; i < 1024; i++) {
|
|
781
|
+
block[i] = (h = Math.imul(h ^ i, 1597334677));
|
|
782
|
+
}
|
|
783
|
+
blocks.push(block);
|
|
784
|
+
leaves.push(await hashBlock(block));
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const tree = [leaves];
|
|
788
|
+
while (tree[tree.length - 1].length > 1) {
|
|
789
|
+
const currentLayer = tree[tree.length - 1];
|
|
790
|
+
const nextLayer = [];
|
|
791
|
+
for (let i = 0; i < currentLayer.length; i += 2) {
|
|
792
|
+
const left = currentLayer[i];
|
|
793
|
+
const right = currentLayer[i + 1] || left;
|
|
794
|
+
const combined = hexToBytes(left + right);
|
|
795
|
+
const hashBuf = await crypto.subtle.digest("SHA-256", combined);
|
|
796
|
+
const hashHex = Array.from(new Uint8Array(hashBuf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
797
|
+
nextLayer.push(hashHex);
|
|
798
|
+
}
|
|
799
|
+
tree.push(nextLayer);
|
|
233
800
|
}
|
|
801
|
+
const merkleRoot = tree[tree.length - 1][0];
|
|
802
|
+
|
|
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
|
+
}
|
|
234
809
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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;
|
|
240
817
|
}
|
|
241
818
|
}
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
let
|
|
245
|
-
for (let i = 0; i <
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
819
|
+
|
|
820
|
+
const challengedIndices = [];
|
|
821
|
+
let h_idx = cyrb53(seed + ":" + solution);
|
|
822
|
+
for (let i = 0; i < 4; i++) {
|
|
823
|
+
h_idx = Math.imul(h_idx ^ i, 1597334677);
|
|
824
|
+
challengedIndices.push(Math.abs(h_idx) % numBlocks);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const proofs = {};
|
|
828
|
+
for (const index of challengedIndices) {
|
|
829
|
+
const proof = [];
|
|
830
|
+
let idx = index;
|
|
831
|
+
for (let layer = 0; layer < tree.length - 1; layer++) {
|
|
832
|
+
const isRight = idx % 2 === 1;
|
|
833
|
+
const siblingIdx = isRight ? idx - 1 : idx + 1;
|
|
834
|
+
const sibling = tree[layer][siblingIdx] || tree[layer][idx];
|
|
835
|
+
proof.push(sibling);
|
|
836
|
+
idx = Math.floor(idx / 2);
|
|
250
837
|
}
|
|
838
|
+
proofs[index] = proof;
|
|
251
839
|
}
|
|
252
|
-
|
|
840
|
+
|
|
841
|
+
return { solution, merkleRoot, proofs };
|
|
253
842
|
}
|
|
254
843
|
|
|
255
844
|
/**
|
|
@@ -326,6 +915,14 @@ async function solveChallenge(challenge, fingerprint = '') { // fingerprint para
|
|
|
326
915
|
const solutions = {};
|
|
327
916
|
|
|
328
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;
|
|
329
926
|
case 'cpu_target':
|
|
330
927
|
// Note: This case is not fully exercised by tests as it relies on Web Workers.
|
|
331
928
|
if (!cpuTarget) {
|