@anonympins/fingerprint 0.1.3 → 0.2.0
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/README.md +97 -41
- package/fingerprint.client.js +470 -458
- package/fingerprint.js +608 -267
- package/library.js +1619 -1577
- package/package.json +1 -1
- package/pow.solver.js +177 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anonympins/fingerprint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "An HTTP(S) client mitigation and anti-bot protection library for Node.js/Express, based on digital fingerprinting and dynamic Proof-of-Work (PoW) challenges.",
|
|
5
5
|
"main": "fingerprint.js",
|
|
6
6
|
"type": "module",
|
package/pow.solver.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file @/pow.solver.js
|
|
3
3
|
* @description Contient les fonctions côté client pour résoudre les différents types de challenges Proof-of-Work.
|
|
4
|
+
* IMPORTANT: Pour les tâches d'optimisation, ce fichier a besoin d'accéder aux algorithmes de `library.js`.
|
|
5
|
+
* Dans un vrai projet, il faudrait bundler une version client de `library.js` et l'importer ici.
|
|
6
|
+
* Pour cet exemple, nous allons copier/coller les fonctions nécessaires.
|
|
4
7
|
* Fichier compatible à la fois avec l'import de modules ES6 et l'injection directe dans un script HTML.
|
|
5
8
|
*/
|
|
6
9
|
|
|
@@ -16,7 +19,9 @@
|
|
|
16
19
|
* @returns {Promise<number>} La solution (un nombre entier).
|
|
17
20
|
*/
|
|
18
21
|
export async function solveCpuTargetInline(clientIp, nonce, target, clientSecret = null, progressCallback) {
|
|
19
|
-
|
|
22
|
+
// Le 'target' est déjà un BigInt lorsqu'il est appelé depuis la page de challenge.
|
|
23
|
+
// On s'assure juste qu'il est bien de ce type.
|
|
24
|
+
const cpuTarget = typeof target === 'bigint' ? target : BigInt('0x' + target);
|
|
20
25
|
let cpuSolution = 0;
|
|
21
26
|
const ipPart = clientIp || ''; // Use empty string if IP is null/undefined
|
|
22
27
|
while (true) { // When a clientSecret is used, the IP is omitted from the hash to make it independent of the network.
|
|
@@ -77,18 +82,28 @@ export async function solveCpuTarget(message, target) {
|
|
|
77
82
|
* @returns {Promise<number>} La solution (nombre entier).
|
|
78
83
|
*/
|
|
79
84
|
export async function solveMemory(seed, difficulty) {
|
|
85
|
+
// On définit un seuil pour savoir quand faire une pause, afin de ne pas bloquer le thread UI.
|
|
86
|
+
const YIELD_THRESHOLD = 100000;
|
|
80
87
|
const size = difficulty * 1024 * 1024;
|
|
81
88
|
const buffer = new Uint32Array(size / 4);
|
|
82
89
|
let h = new TextEncoder().encode(seed).reduce((acc, v) => acc + v, 0);
|
|
90
|
+
|
|
83
91
|
for (let i = 0; i < buffer.length; i++) {
|
|
84
92
|
buffer[i] = (h = Math.imul(h ^ i, 1597334677));
|
|
93
|
+
if (i % YIELD_THRESHOLD === 0) {
|
|
94
|
+
await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
|
|
95
|
+
}
|
|
85
96
|
}
|
|
97
|
+
|
|
86
98
|
let solution = 0;
|
|
87
99
|
const iterations = size / 16;
|
|
88
100
|
let addr = buffer.length > 0 ? buffer[0] % buffer.length : 0;
|
|
89
101
|
for (let i = 0; i < iterations; i++) {
|
|
90
102
|
addr = buffer[addr] % buffer.length;
|
|
91
103
|
solution ^= addr;
|
|
104
|
+
if (i % YIELD_THRESHOLD === 0) {
|
|
105
|
+
await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
|
|
106
|
+
}
|
|
92
107
|
}
|
|
93
108
|
return solution;
|
|
94
109
|
}
|
|
@@ -157,13 +172,164 @@ export async function solveTsp(cities, targetMaxDistance) {
|
|
|
157
172
|
return { path: solutionPath, distance: solutionDistance };
|
|
158
173
|
}
|
|
159
174
|
|
|
175
|
+
// --- Fonctions d'optimisation copiées/adaptées de library.js pour le client ---
|
|
176
|
+
|
|
177
|
+
const ClientOptimizers = {
|
|
178
|
+
simulatedAnnealing(initialSolution, evaluator, neighbor, iterations, temp, cooling) {
|
|
179
|
+
let currentSolution = initialSolution;
|
|
180
|
+
let currentEnergy = evaluator(currentSolution);
|
|
181
|
+
let temperature = temp;
|
|
182
|
+
|
|
183
|
+
for (let i = 0; i < iterations; i++) {
|
|
184
|
+
const newSolution = neighbor(currentSolution);
|
|
185
|
+
const newEnergy = evaluator(newSolution);
|
|
186
|
+
if (newEnergy < currentEnergy || Math.random() < Math.exp((currentEnergy - newEnergy) / temperature)) {
|
|
187
|
+
currentSolution = newSolution;
|
|
188
|
+
currentEnergy = newEnergy;
|
|
189
|
+
}
|
|
190
|
+
temperature *= cooling;
|
|
191
|
+
}
|
|
192
|
+
return { solution: currentSolution, energy: currentEnergy };
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
geneticAlgorithm(createIndividual, fitness, crossover, mutate, generations, popSize) {
|
|
196
|
+
let population = Array.from({ length: popSize }, () => {
|
|
197
|
+
const chromosome = createIndividual();
|
|
198
|
+
return { chromosome, fitness: fitness(chromosome) };
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
for (let gen = 0; gen < generations; gen++) {
|
|
202
|
+
population.sort((a, b) => a.fitness - b.fitness);
|
|
203
|
+
const newPopulation = [population[0]]; // Elitism
|
|
204
|
+
while (newPopulation.length < popSize) {
|
|
205
|
+
const p1 = population[Math.floor(Math.random() * (popSize / 2))];
|
|
206
|
+
const p2 = population[Math.floor(Math.random() * (popSize / 2))];
|
|
207
|
+
let offspring = crossover(p1.chromosome, p2.chromosome);
|
|
208
|
+
if (Math.random() < 0.1) offspring = mutate(offspring);
|
|
209
|
+
newPopulation.push({ chromosome: offspring, fitness: fitness(offspring) });
|
|
210
|
+
}
|
|
211
|
+
population = newPopulation;
|
|
212
|
+
}
|
|
213
|
+
return population;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Résout une unité de travail utile (Useful Work Unit).
|
|
219
|
+
* @param {object} task - La tâche envoyée par le serveur.
|
|
220
|
+
* @returns {Promise<object>} Le résultat du calcul.
|
|
221
|
+
*/
|
|
222
|
+
async function solveUsefulWorkTask(task) {
|
|
223
|
+
await new Promise(r => setTimeout(r, 10)); // Yield thread
|
|
224
|
+
|
|
225
|
+
switch (task.type) {
|
|
226
|
+
case 'simulated_annealing_iterations': {
|
|
227
|
+
const { cities } = task.payload;
|
|
228
|
+
const distance = (c1, c2) => Math.sqrt(Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2));
|
|
229
|
+
const evaluator = (path) => {
|
|
230
|
+
let total = 0;
|
|
231
|
+
for (let i = 0; i < path.length - 1; i++) total += distance(cities[path[i]], cities[path[i + 1]]);
|
|
232
|
+
total += distance(cities[path[path.length - 1]], cities[path[0]]);
|
|
233
|
+
return total;
|
|
234
|
+
};
|
|
235
|
+
const neighbor = (path) => {
|
|
236
|
+
const newPath = [...path];
|
|
237
|
+
const [i, j] = [Math.floor(Math.random() * path.length), Math.floor(Math.random() * path.length)];
|
|
238
|
+
[newPath[i], newPath[j]] = [newPath[j], newPath[i]];
|
|
239
|
+
return newPath;
|
|
240
|
+
};
|
|
241
|
+
const initialSolution = task.initialSolution || Array.from({ length: cities.length }, (_, i) => i).sort(() => 0.5 - Math.random());
|
|
242
|
+
|
|
243
|
+
return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
case 'genetic_algorithm_generations': {
|
|
247
|
+
const { assets, maxVolatility } = task.payload;
|
|
248
|
+
const fitness = (weights) => {
|
|
249
|
+
const total = weights.reduce((s, w) => s + w, 0);
|
|
250
|
+
if (total === 0) return Infinity;
|
|
251
|
+
const normW = weights.map(w => w / total);
|
|
252
|
+
const ret = normW.reduce((s, w, i) => s + w * assets[i].expectedReturn, 0);
|
|
253
|
+
const vol = normW.reduce((s, w, i) => s + w * assets[i].volatility, 0);
|
|
254
|
+
if (vol > maxVolatility) return 1000 + (vol - maxVolatility);
|
|
255
|
+
return -ret;
|
|
256
|
+
};
|
|
257
|
+
const createIndividual = () => Array.from({ length: assets.length }, Math.random);
|
|
258
|
+
const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
|
|
259
|
+
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)); };
|
|
260
|
+
|
|
261
|
+
// Le client doit recréer la population si elle n'est pas fournie
|
|
262
|
+
const initialPopulation = task.initialPopulation || Array.from({ length: task.payload.options.populationSize }, () => ({ chromosome: createIndividual(), fitness: 0 }));
|
|
263
|
+
initialPopulation.forEach(p => p.fitness = fitness(p.chromosome));
|
|
264
|
+
|
|
265
|
+
const finalPopulation = ClientOptimizers.geneticAlgorithm(createIndividual, fitness, crossover, mutate, task.generations, task.payload.options.populationSize);
|
|
266
|
+
return { population: finalPopulation };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
case 'run_multiple_parallel':
|
|
270
|
+
// Côté client, on ne peut pas utiliser de vrais workers pour `runMultipleParallel`.
|
|
271
|
+
// On exécute donc une version simplifiée : un seul cycle du solveur demandé.
|
|
272
|
+
// Cela reste un travail coûteux et valide le principe du "Useful Work".
|
|
273
|
+
const { solverName, baseSolverArgs } = task;
|
|
274
|
+
const clientSolver = ClientOptimizers[solverName];
|
|
275
|
+
if (!clientSolver) throw new Error(`Solver ${solverName} not found on client.`);
|
|
276
|
+
|
|
277
|
+
// On simule l'appel avec les arguments de base.
|
|
278
|
+
// Note: `baseSolverArgs` peut contenir des options.
|
|
279
|
+
return clientSolver(...baseSolverArgs);
|
|
280
|
+
|
|
281
|
+
default:
|
|
282
|
+
throw new Error(`Unknown useful work type: ${task.type}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Résout une tâche d'optimisation basée sur un algorithme génétique.
|
|
288
|
+
* Reçoit une population et la fait évoluer pendant un certain nombre de générations.
|
|
289
|
+
* NOTE: Cette fonction est une version simplifiée de l'AG de `library.js` adaptée au client.
|
|
290
|
+
* @param {Array<object>} initialPopulation - La population de départ.
|
|
291
|
+
* @param {number} generations - Le nombre de générations à exécuter.
|
|
292
|
+
* @returns {Promise<Array<object>>} La population finale après évolution.
|
|
293
|
+
*/
|
|
294
|
+
export async function solveOptimizationTask(initialPopulation, generations) {
|
|
295
|
+
// Fonctions AG simplifiées (croisement, mutation)
|
|
296
|
+
const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
|
|
297
|
+
const mutate = (p) => {
|
|
298
|
+
const newP = [...p];
|
|
299
|
+
const i = Math.floor(Math.random() * newP.length);
|
|
300
|
+
newP[i] += (Math.random() - 0.5) * 0.2;
|
|
301
|
+
return newP;
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
let population = initialPopulation;
|
|
305
|
+
|
|
306
|
+
for (let gen = 0; gen < generations; gen++) {
|
|
307
|
+
// Sélection simple : on garde les 50% meilleurs
|
|
308
|
+
const parents = population.sort((a, b) => a.fitness - b.fitness).slice(0, Math.ceil(population.length / 2));
|
|
309
|
+
const newPopulation = [...parents]; // Élitisme
|
|
310
|
+
|
|
311
|
+
while (newPopulation.length < population.length) {
|
|
312
|
+
const parent1 = parents[Math.floor(Math.random() * parents.length)];
|
|
313
|
+
const parent2 = parents[Math.floor(Math.random() * parents.length)];
|
|
314
|
+
let offspring = crossover(parent1.chromosome, parent2.chromosome);
|
|
315
|
+
if (Math.random() < 0.1) offspring = mutate(offspring);
|
|
316
|
+
// La fitness sera recalculée côté serveur pour la vérification.
|
|
317
|
+
newPopulation.push({ chromosome: offspring, fitness: -1 });
|
|
318
|
+
}
|
|
319
|
+
population = newPopulation;
|
|
320
|
+
// Pause pour ne pas geler l'UI sur les longues tâches
|
|
321
|
+
if (gen % 10 === 0) await new Promise(r => setTimeout(r, 0));
|
|
322
|
+
}
|
|
323
|
+
return population;
|
|
324
|
+
}
|
|
325
|
+
|
|
160
326
|
/**
|
|
161
327
|
* Fonction principale qui reçoit un objet challenge et le résout.
|
|
162
328
|
* @param {object} challenge - L'objet challenge reçu du serveur.
|
|
163
329
|
* @returns {Promise<object>} Un objet contenant la ou les solutions.
|
|
164
330
|
*/
|
|
165
331
|
export async function solveChallenge(challenge) {
|
|
166
|
-
const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance } = challenge;
|
|
332
|
+
const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance, optimizationTask, usefulWorkTask } = challenge;
|
|
167
333
|
const solutions = {};
|
|
168
334
|
|
|
169
335
|
switch (type) {
|
|
@@ -207,6 +373,15 @@ export async function solveChallenge(challenge) {
|
|
|
207
373
|
solutions.tsp = tspResult.path;
|
|
208
374
|
solutions.distance = tspResult.distance;
|
|
209
375
|
break;
|
|
376
|
+
case 'optimization_task':
|
|
377
|
+
const finalPopulation = await solveOptimizationTask(optimizationTask.population, optimizationTask.generations);
|
|
378
|
+
solutions.population = finalPopulation.map(p => p.chromosome); // On ne renvoie que les chromosomes
|
|
379
|
+
break;
|
|
380
|
+
case 'useful_work_task':
|
|
381
|
+
const workResult = await solveUsefulWorkTask(usefulWorkTask.task);
|
|
382
|
+
solutions.work_result = workResult;
|
|
383
|
+
solutions.problem_id = usefulWorkTask.problemId;
|
|
384
|
+
break;
|
|
210
385
|
default:
|
|
211
386
|
throw new Error(`Unknown challenge type: ${type}`);
|
|
212
387
|
}
|