@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
package/src/js/pow.solver.js
CHANGED
|
@@ -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 <
|
|
16
|
-
ch =
|
|
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
|
-
|
|
170
|
-
|
|
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
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
let
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 && (
|