@anonympins/fingerprint 0.2.0 → 0.2.2
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 +60 -4
- package/fingerprint.builder.js +160 -103
- package/fingerprint.client.js +11 -22
- package/fingerprint.js +324 -355
- package/library.js +83 -8
- package/mongodb-store.js +52 -52
- package/optimization.worker.js +28 -0
- package/package.json +4 -1
- package/pow.solver.js +89 -33
- package/pow.worker.js +27 -0
- package/problem-manager.js +247 -0
- package/redis-store.js +42 -42
- package/sql-store.js +77 -77
package/library.js
CHANGED
|
@@ -1583,29 +1583,104 @@ Optimization.Operators.createFullSecurityConfigEvaluator = ({ trafficData }) =>
|
|
|
1583
1583
|
* @returns {Array<{solution: object, objectives: number[]}>} Le front de Pareto des configurations optimales.
|
|
1584
1584
|
*/
|
|
1585
1585
|
Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
|
|
1586
|
-
const fitnessFunction = Optimization.Operators.createFullSecurityConfigEvaluator(context);
|
|
1586
|
+
const fitnessFunction = Optimization.Operators.createFullSecurityConfigEvaluator(context);
|
|
1587
1587
|
|
|
1588
1588
|
// Un "individu" est un objet de configuration complet
|
|
1589
1589
|
const createIndividual = () => ({
|
|
1590
1590
|
thresholds: {
|
|
1591
|
-
low:
|
|
1592
|
-
medium: 40 + Math.random() *
|
|
1593
|
-
high: 70 + Math.random() *
|
|
1591
|
+
low: 15 + Math.random() * 20, // 15-35
|
|
1592
|
+
medium: 40 + Math.random() * 25, // 40-65
|
|
1593
|
+
high: 70 + Math.random() * 20, // 70-90
|
|
1594
1594
|
},
|
|
1595
1595
|
weights: {
|
|
1596
1596
|
historyScore: Math.random(),
|
|
1597
1597
|
rotationScore: Math.random(),
|
|
1598
1598
|
headerAnomalyScore: Math.random(),
|
|
1599
|
-
requestPatternScore: Math.random(),
|
|
1599
|
+
requestPatternScore: 0.5 + Math.random(), // Donner plus d'importance aux patterns
|
|
1600
1600
|
inconsistencyScore: Math.random(),
|
|
1601
1601
|
honeypotScore: 1.0, // Garder le honeypot à 1.0 est une bonne pratique
|
|
1602
|
+
behaviorScore: Math.random(),
|
|
1603
|
+
crossLayerInconsistencyScore: Math.random(),
|
|
1604
|
+
timeInconsistencyScore: Math.random(),
|
|
1602
1605
|
},
|
|
1603
|
-
|
|
1606
|
+
patterns: {
|
|
1607
|
+
velocityThreshold: 100 + Math.random() * 400, // 100-500ms
|
|
1608
|
+
velocityWeight: 10 + Math.random() * 40,
|
|
1609
|
+
burstThreshold: 300 + Math.random() * 700, // 300-1000ms
|
|
1610
|
+
burstWeight: 20 + Math.random() * 40,
|
|
1611
|
+
scrapeThreshold: 500 + Math.random() * 1000, // 500-1500ms
|
|
1612
|
+
scrapeWeight: 15 + Math.random() * 35,
|
|
1613
|
+
sequenceLength: 3 + Math.floor(Math.random() * 3), // 3-5
|
|
1614
|
+
sequenceWeight: 20 + Math.random() * 50,
|
|
1615
|
+
regularityThreshold: 50 + Math.random() * 200, // 50-250ms
|
|
1616
|
+
regularityWeight: 20 + Math.random() * 40,
|
|
1617
|
+
decayFactor: 0.85 + Math.random() * 0.14, // 0.85-0.99
|
|
1618
|
+
inactivityReset: 15000 + Math.random() * 45000, // 15s-60s
|
|
1619
|
+
}
|
|
1604
1620
|
});
|
|
1605
1621
|
|
|
1606
1622
|
// Le crossover et la mutation doivent maintenant opérer sur des objets complexes
|
|
1607
|
-
const crossover = (c1, c2) => {
|
|
1608
|
-
|
|
1623
|
+
const crossover = (c1, c2) => {
|
|
1624
|
+
const child = JSON.parse(JSON.stringify(c1)); // Deep copy
|
|
1625
|
+
// Croisement pour chaque groupe de paramètres
|
|
1626
|
+
for (const key in child.thresholds) {
|
|
1627
|
+
child.thresholds[key] = (c1.thresholds[key] + c2.thresholds[key]) / 2;
|
|
1628
|
+
}
|
|
1629
|
+
for (const key in child.weights) {
|
|
1630
|
+
if (key !== 'honeypotScore') { // Ne pas croiser le poids du honeypot
|
|
1631
|
+
child.weights[key] = (c1.weights[key] + c2.weights[key]) / 2;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
for (const key in child.patterns) {
|
|
1635
|
+
child.patterns[key] = (c1.patterns[key] + c2.patterns[key]) / 2;
|
|
1636
|
+
}
|
|
1637
|
+
return child;
|
|
1638
|
+
};
|
|
1639
|
+
|
|
1640
|
+
const mutate = (c) => {
|
|
1641
|
+
const newConfig = JSON.parse(JSON.stringify(c));
|
|
1642
|
+
|
|
1643
|
+
// --- NOUVELLE LOGIQUE : Sélection de section pondérée ---
|
|
1644
|
+
// On donne plus de poids à la mutation des 'patterns' et des 'weights',
|
|
1645
|
+
// car ils ont un impact plus direct sur la détection que les seuils.
|
|
1646
|
+
const sections = [
|
|
1647
|
+
{ name: 'patterns', weight: 0.5 }, // 50% de chance
|
|
1648
|
+
{ name: 'weights', weight: 0.35 }, // 35% de chance
|
|
1649
|
+
{ name: 'thresholds', weight: 0.15 } // 15% de chance
|
|
1650
|
+
];
|
|
1651
|
+
const rand = Math.random();
|
|
1652
|
+
let cumulativeWeight = 0;
|
|
1653
|
+
let sectionToMutate = 'patterns'; // Fallback
|
|
1654
|
+
for (const section of sections) {
|
|
1655
|
+
cumulativeWeight += section.weight;
|
|
1656
|
+
if (rand < cumulativeWeight) {
|
|
1657
|
+
sectionToMutate = section.name;
|
|
1658
|
+
break;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
const keys = Object.keys(newConfig[sectionToMutate]);
|
|
1663
|
+
const keyToMutate = keys[Math.floor(Math.random() * keys.length)];
|
|
1664
|
+
|
|
1665
|
+
if (keyToMutate === 'honeypotScore') return newConfig; // Ne pas muter le poids du honeypot
|
|
1666
|
+
|
|
1667
|
+
// Appliquer une mutation avec une amplitude variable
|
|
1668
|
+
const mutationAmount = (Math.random() - 0.5) * 0.4; // +/- 20%
|
|
1669
|
+
newConfig[sectionToMutate][keyToMutate] *= (1 + mutationAmount);
|
|
1670
|
+
|
|
1671
|
+
// S'assurer que les valeurs restent dans des limites raisonnables
|
|
1672
|
+
if (sectionToMutate === 'weights') {
|
|
1673
|
+
newConfig[sectionToMutate][keyToMutate] = Math.max(0, Math.min(1.5, newConfig[sectionToMutate][keyToMutate]));
|
|
1674
|
+
}
|
|
1675
|
+
if (keyToMutate === 'decayFactor') {
|
|
1676
|
+
newConfig.patterns.decayFactor = Math.max(0.8, Math.min(0.999, newConfig.patterns.decayFactor));
|
|
1677
|
+
}
|
|
1678
|
+
if (keyToMutate.includes('Threshold') || keyToMutate.includes('Reset')) {
|
|
1679
|
+
newConfig.patterns[keyToMutate] = Math.max(50, newConfig.patterns[keyToMutate]);
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
return newConfig;
|
|
1683
|
+
};
|
|
1609
1684
|
|
|
1610
1685
|
return Optimization.geneticAlgorithmMultiObjective(
|
|
1611
1686
|
createIndividual,
|
package/mongodb-store.js
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Creates a store adapter for MongoDB.
|
|
3
|
-
* This adapter uses a collection as a key-value store and leverages MongoDB's TTL indexes
|
|
4
|
-
* for automatic expiration of documents.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Creates a store adapter for a MongoDB collection.
|
|
9
|
-
* It's recommended to pass the `db` object and let the adapter handle the collection.
|
|
10
|
-
*
|
|
11
|
-
* **Note:** For TTL to work, you must create a TTL index on the `expiresAt` field in your collection.
|
|
12
|
-
* In the mongo shell, run:
|
|
13
|
-
* `db.yourCollectionName.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 })`
|
|
14
|
-
*
|
|
15
|
-
* @param {import('mongodb').Db} db - An instance of a MongoDB Db object.
|
|
16
|
-
* @param {string} [collectionName='fingerprint_store'] - The name of the collection to use.
|
|
17
|
-
* @returns {import('./fingerprint.js').IStore} An object that complies with the IStore interface.
|
|
18
|
-
*/
|
|
19
|
-
export function createMongoDbStore(db, collectionName = 'fingerprint_store') {
|
|
20
|
-
const collection = db.collection(collectionName);
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
async get(key) {
|
|
24
|
-
const doc = await collection.findOne({ _id: key });
|
|
25
|
-
// The TTL index automatically removes expired documents, so no need to check `expiresAt` here.
|
|
26
|
-
return doc ? doc.value : null;
|
|
27
|
-
},
|
|
28
|
-
async set(key, value, ttl) {
|
|
29
|
-
const doc = {
|
|
30
|
-
_id: key,
|
|
31
|
-
value: value,
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
if (ttl && ttl > 0) {
|
|
35
|
-
// Set the expiration date for the TTL index.
|
|
36
|
-
doc.expiresAt = new Date(Date.now() + ttl * 1000);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
await collection.updateOne(
|
|
40
|
-
{ _id: key },
|
|
41
|
-
{ $set: doc },
|
|
42
|
-
{ upsert: true }
|
|
43
|
-
);
|
|
44
|
-
},
|
|
45
|
-
async has(key) {
|
|
46
|
-
const count = await collection.countDocuments({ _id: key });
|
|
47
|
-
return count > 0;
|
|
48
|
-
},
|
|
49
|
-
async delete(key) {
|
|
50
|
-
await collection.deleteOne({ _id: key });
|
|
51
|
-
},
|
|
52
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* @file Creates a store adapter for MongoDB.
|
|
3
|
+
* This adapter uses a collection as a key-value store and leverages MongoDB's TTL indexes
|
|
4
|
+
* for automatic expiration of documents.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a store adapter for a MongoDB collection.
|
|
9
|
+
* It's recommended to pass the `db` object and let the adapter handle the collection.
|
|
10
|
+
*
|
|
11
|
+
* **Note:** For TTL to work, you must create a TTL index on the `expiresAt` field in your collection.
|
|
12
|
+
* In the mongo shell, run:
|
|
13
|
+
* `db.yourCollectionName.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 })`
|
|
14
|
+
*
|
|
15
|
+
* @param {import('mongodb').Db} db - An instance of a MongoDB Db object.
|
|
16
|
+
* @param {string} [collectionName='fingerprint_store'] - The name of the collection to use.
|
|
17
|
+
* @returns {import('./fingerprint.js').IStore} An object that complies with the IStore interface.
|
|
18
|
+
*/
|
|
19
|
+
export function createMongoDbStore(db, collectionName = 'fingerprint_store') {
|
|
20
|
+
const collection = db.collection(collectionName);
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
async get(key) {
|
|
24
|
+
const doc = await collection.findOne({ _id: key });
|
|
25
|
+
// The TTL index automatically removes expired documents, so no need to check `expiresAt` here.
|
|
26
|
+
return doc ? doc.value : null;
|
|
27
|
+
},
|
|
28
|
+
async set(key, value, ttl) {
|
|
29
|
+
const doc = {
|
|
30
|
+
_id: key,
|
|
31
|
+
value: value,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
if (ttl && ttl > 0) {
|
|
35
|
+
// Set the expiration date for the TTL index.
|
|
36
|
+
doc.expiresAt = new Date(Date.now() + ttl * 1000);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
await collection.updateOne(
|
|
40
|
+
{ _id: key },
|
|
41
|
+
{ $set: doc },
|
|
42
|
+
{ upsert: true }
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
async has(key) {
|
|
46
|
+
const count = await collection.countDocuments({ _id: key });
|
|
47
|
+
return count > 0;
|
|
48
|
+
},
|
|
49
|
+
async delete(key) {
|
|
50
|
+
await collection.deleteOne({ _id: key });
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
53
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file @/optimization.worker.js
|
|
3
|
+
* @description Web Worker générique pour exécuter les algorithmes d'optimisation de la bibliothèque `Optimization`.
|
|
4
|
+
* Ce script s'exécute sur un thread séparé pour ne pas bloquer l'interface utilisateur.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { parentPort, workerData } from 'worker_threads';
|
|
8
|
+
import { Optimization } from './library.js'; // Assurez-vous que le chemin est correct
|
|
9
|
+
|
|
10
|
+
if (parentPort) {
|
|
11
|
+
parentPort.on('message', async () => { // Le message est vide, on utilise workerData
|
|
12
|
+
const { solverName, solverArgs } = workerData;
|
|
13
|
+
|
|
14
|
+
// Gérer les solveurs imbriqués (ex: 'Operators.solvePortfolio')
|
|
15
|
+
const solverFunction = solverName.split('.').reduce((obj, prop) => obj && obj[prop], Optimization);
|
|
16
|
+
|
|
17
|
+
if (typeof solverFunction === 'function') {
|
|
18
|
+
try {
|
|
19
|
+
const result = await solverFunction(...solverArgs);
|
|
20
|
+
parentPort.postMessage(result);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
parentPort.postMessage({ error: error.message, stack: error.stack });
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
parentPort.postMessage({ error: `Solver '${solverName}' not found or is not a function in Optimization library.` });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anonympins/fingerprint",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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",
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
"fingerprint.client.js",
|
|
16
16
|
"fingerprint.builder.js",
|
|
17
17
|
"pow.solver.js",
|
|
18
|
+
"pow.worker.js",
|
|
19
|
+
"problem-manager.js",
|
|
20
|
+
"optimization.worker.js",
|
|
18
21
|
"library.js",
|
|
19
22
|
"redis-store.js",
|
|
20
23
|
"mongodb-store.js",
|
package/pow.solver.js
CHANGED
|
@@ -10,26 +10,42 @@
|
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Résout un challenge CPU basé sur une cible
|
|
14
|
-
* @param {
|
|
15
|
-
* @param {string} nonce - Le nonce du challenge.
|
|
13
|
+
* Résout un challenge CPU basé sur une cible en utilisant un bloc de base binaire.
|
|
14
|
+
* @param {Uint8Array} baseBlock - Le bloc de données initial (nonce, secret, fp) fourni par le serveur.
|
|
16
15
|
* @param {bigint} target - La cible à atteindre.
|
|
17
|
-
* @param {string} clientSecret - Le secret client (optionnel).
|
|
18
16
|
* @param {Function} progressCallback - Callback pour les mises à jour de progression.
|
|
19
17
|
* @returns {Promise<number>} La solution (un nombre entier).
|
|
20
18
|
*/
|
|
21
|
-
export async function solveCpuTargetInline(
|
|
22
|
-
//
|
|
23
|
-
|
|
19
|
+
export async function solveCpuTargetInline(baseBlock, target, progressCallback) {
|
|
20
|
+
// --- FIX: Add validation for the target to prevent BigInt conversion errors ---
|
|
21
|
+
if (typeof target !== 'bigint' && (typeof target !== 'string' || !/^[0-9a-fA-F]+$/.test(target))) {
|
|
22
|
+
throw new TypeError(`Invalid target type: expected a BigInt or a hex string, but got ${typeof target} with value ${target}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
24
25
|
const cpuTarget = typeof target === 'bigint' ? target : BigInt('0x' + target);
|
|
26
|
+
// --- END FIX ---
|
|
27
|
+
const encoder = new TextEncoder();
|
|
25
28
|
let cpuSolution = 0;
|
|
26
|
-
|
|
27
|
-
while (true) {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
29
|
+
|
|
30
|
+
while (true) {
|
|
31
|
+
const solutionBytes = encoder.encode(String(cpuSolution));
|
|
32
|
+
|
|
33
|
+
// Concaténation binaire directe : c'est plus rapide et plus sûr.
|
|
34
|
+
const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
|
|
35
|
+
finalBlock.set(baseBlock);
|
|
36
|
+
finalBlock.set(solutionBytes, baseBlock.length);
|
|
37
|
+
|
|
38
|
+
if (cpuSolution === 0) {
|
|
39
|
+
const reconstructedMsg = new TextDecoder().decode(finalBlock);
|
|
40
|
+
console.log('[FP Solve Debug] Client will hash message:', reconstructedMsg);
|
|
41
|
+
}
|
|
42
|
+
const buf = await crypto.subtle.digest("SHA-256", finalBlock);
|
|
32
43
|
const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
44
|
+
// --- AJOUT DE LOGS POUR LE DÉBOGAGE CÔTÉ CLIENT ---
|
|
45
|
+
if (cpuSolution === 0) { // Log only the first attempt
|
|
46
|
+
console.log(`[FP Client Solve] Attempt 0 hash: "0x${hashHex}"`);
|
|
47
|
+
}
|
|
48
|
+
// --- FIN DES LOGS ---
|
|
33
49
|
if (BigInt('0x' + hashHex) < cpuTarget) break;
|
|
34
50
|
cpuSolution++;
|
|
35
51
|
if (cpuSolution % 100000 === 0) {
|
|
@@ -323,70 +339,110 @@ export async function solveOptimizationTask(initialPopulation, generations) {
|
|
|
323
339
|
return population;
|
|
324
340
|
}
|
|
325
341
|
|
|
342
|
+
/**
|
|
343
|
+
* @class ChallengeSolution
|
|
344
|
+
* @description Encapsule une solution de challenge et fournit des méthodes pour la manipuler.
|
|
345
|
+
* @private
|
|
346
|
+
*/
|
|
347
|
+
class ChallengeSolution {
|
|
348
|
+
constructor(type, nonce, rawSolution) {
|
|
349
|
+
this.type = type;
|
|
350
|
+
this.nonce = nonce;
|
|
351
|
+
this.rawSolution = rawSolution;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Applique les paramètres de la solution à un objet URL.
|
|
356
|
+
* @param {URL} url - L'objet URL à modifier.
|
|
357
|
+
*/
|
|
358
|
+
applyToUrl(url) {
|
|
359
|
+
url.searchParams.set('pow_type', this.type);
|
|
360
|
+
url.searchParams.set('pow_nonce', this.nonce);
|
|
361
|
+
|
|
362
|
+
// Logique de formatage spécifique à chaque type de challenge
|
|
363
|
+
if (this.type === 'cpu_mem' || this.type === 'cpu_mem_inline' || this.type === 'cpu_target') {
|
|
364
|
+
Object.entries(this.rawSolution).forEach(([key, value]) => {
|
|
365
|
+
url.searchParams.set(`pow_solution_${key}`, String(value));
|
|
366
|
+
});
|
|
367
|
+
} else if (this.type === 'useful_work_task') {
|
|
368
|
+
url.searchParams.set('pow_solution_work_result', JSON.stringify(this.rawSolution.work_result));
|
|
369
|
+
url.searchParams.set('pow_problem_id', this.rawSolution.problem_id);
|
|
370
|
+
} else {
|
|
371
|
+
// Pour les cas simples comme 'tsp' où la solution est une seule valeur
|
|
372
|
+
url.searchParams.set('pow_solution', JSON.stringify(this.rawSolution));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
326
377
|
/**
|
|
327
378
|
* Fonction principale qui reçoit un objet challenge et le résout.
|
|
328
379
|
* @param {object} challenge - L'objet challenge reçu du serveur.
|
|
329
|
-
* @
|
|
380
|
+
* @param {string} [fingerprint=''] - L'empreinte de l'appareil qui résout le challenge.
|
|
381
|
+
* @returns {Promise<ChallengeSolution>} Un objet `ChallengeSolution` encapsulant le résultat.
|
|
330
382
|
*/
|
|
331
|
-
export async function solveChallenge(challenge) {
|
|
383
|
+
export async function solveChallenge(challenge, fingerprint = '') { // The fingerprint is now passed from the client library
|
|
332
384
|
const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance, optimizationTask, usefulWorkTask } = challenge;
|
|
333
|
-
|
|
385
|
+
let rawSolution = {};
|
|
334
386
|
|
|
335
387
|
switch (type) {
|
|
336
388
|
case 'cpu_target':
|
|
337
|
-
|
|
389
|
+
// Note: This case is not fully exercised by tests as it relies on Web Workers.
|
|
338
390
|
if (!cpuTarget) {
|
|
339
391
|
throw new Error("Challenge data is missing 'cpuTarget' property.");
|
|
340
392
|
}
|
|
341
393
|
const target = cpuTarget; // Keep variable name for consistency below
|
|
342
|
-
|
|
394
|
+
// Pour ce challenge simple, le baseBlock est juste le nonce.
|
|
395
|
+
const baseBlockBytes = new TextEncoder().encode(nonce + ":");
|
|
396
|
+
rawSolution.cpu = await solveCpuTargetInline(baseBlockBytes, target, null);
|
|
343
397
|
break;
|
|
344
398
|
case 'cpu_mem':
|
|
345
399
|
// Pour les appels API, le client IP n'est pas connu, on ne le met pas dans le message
|
|
346
|
-
const baseMessageCombined =
|
|
400
|
+
const baseMessageCombined = `:${nonce}:${clientSecret}`;
|
|
347
401
|
const memSeed = `:${nonce}:${clientSecret}`;
|
|
348
402
|
const [cpuSol, memSol] = await Promise.all([
|
|
349
403
|
(async () => {
|
|
350
|
-
if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property.");
|
|
351
|
-
|
|
404
|
+
if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property."); // Pass fingerprint to solver
|
|
405
|
+
const baseBlock = new Uint8Array(challenge.baseBlock);
|
|
406
|
+
return solveCpuTargetInline(baseBlock, cpuTarget, null);
|
|
352
407
|
})(),
|
|
353
408
|
solveMemory(memSeed, memDifficulty)
|
|
354
409
|
]);
|
|
355
|
-
|
|
356
|
-
|
|
410
|
+
rawSolution.cpu = cpuSol;
|
|
411
|
+
rawSolution.mem = memSol;
|
|
357
412
|
break;
|
|
358
413
|
case 'cpu_mem_inline':
|
|
359
414
|
// Version inline pour compatibilité HTML avec IP incluse
|
|
360
|
-
const memSeedInline =
|
|
415
|
+
const memSeedInline = `:${nonce}:${clientSecret}`;
|
|
361
416
|
const [cpuSolInline, memSolInline] = await Promise.all([
|
|
362
417
|
(async () => {
|
|
363
418
|
if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property.");
|
|
364
|
-
|
|
419
|
+
const baseBlock = new Uint8Array(challenge.baseBlock);
|
|
420
|
+
return solveCpuTargetInline(baseBlock, cpuTarget, null);
|
|
365
421
|
})(),
|
|
366
422
|
solveMemory(memSeedInline, memDifficulty)
|
|
367
423
|
]);
|
|
368
|
-
|
|
369
|
-
|
|
424
|
+
rawSolution.cpu = cpuSolInline;
|
|
425
|
+
rawSolution.mem = memSolInline;
|
|
370
426
|
break;
|
|
371
427
|
case 'tsp':
|
|
372
428
|
const tspResult = await solveTsp(cities, targetMaxDistance);
|
|
373
|
-
|
|
374
|
-
|
|
429
|
+
// Pour ce challenge, la solution est juste le chemin.
|
|
430
|
+
rawSolution = tspResult.path;
|
|
375
431
|
break;
|
|
376
432
|
case 'optimization_task':
|
|
377
433
|
const finalPopulation = await solveOptimizationTask(optimizationTask.population, optimizationTask.generations);
|
|
378
|
-
|
|
434
|
+
rawSolution = finalPopulation.map(p => p.chromosome); // On ne renvoie que les chromosomes
|
|
379
435
|
break;
|
|
380
436
|
case 'useful_work_task':
|
|
381
437
|
const workResult = await solveUsefulWorkTask(usefulWorkTask.task);
|
|
382
|
-
|
|
383
|
-
|
|
438
|
+
rawSolution.work_result = workResult;
|
|
439
|
+
rawSolution.problem_id = usefulWorkTask.problemId;
|
|
384
440
|
break;
|
|
385
441
|
default:
|
|
386
442
|
throw new Error(`Unknown challenge type: ${type}`);
|
|
387
443
|
}
|
|
388
444
|
|
|
389
|
-
return
|
|
445
|
+
return new ChallengeSolution(type, nonce, rawSolution);
|
|
390
446
|
}
|
|
391
447
|
|
|
392
448
|
// --- Compatibilité pour l'injection directe dans le HTML ---
|
package/pow.worker.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file @/pow.worker.js
|
|
3
|
+
* @description Web Worker dédié à la résolution du challenge CPU.
|
|
4
|
+
* Ce script s'exécute sur un thread séparé pour ne pas bloquer l'interface utilisateur.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
self.onmessage = async (event) => {
|
|
8
|
+
const { message, target } = event.data;
|
|
9
|
+
let solution = 0;
|
|
10
|
+
const encoder = new TextEncoder();
|
|
11
|
+
|
|
12
|
+
// La boucle de calcul intensive est isolée dans ce worker.
|
|
13
|
+
while (true) {
|
|
14
|
+
const currentMessage = `${message}:${solution}`;
|
|
15
|
+
const data = encoder.encode(currentMessage);
|
|
16
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
17
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
18
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
19
|
+
|
|
20
|
+
if (BigInt('0x' + hashHex) < target) {
|
|
21
|
+
// Une fois la solution trouvée, on la renvoie au thread principal.
|
|
22
|
+
self.postMessage({ solution });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
solution++;
|
|
26
|
+
}
|
|
27
|
+
};
|