@anonympins/fingerprint 0.2.1 → 0.2.3
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 +719 -598
- package/fingerprint.client.js +477 -471
- package/fingerprint.js +2953 -2673
- package/library.js +83 -8
- package/mongodb-store.js +52 -52
- package/package.json +88 -79
- package/pow.solver.js +61 -13
- package/pow.worker.js +26 -26
- package/problem-manager.js +249 -1
- 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
|
}
|
package/package.json
CHANGED
|
@@ -1,79 +1,88 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@anonympins/fingerprint",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "fingerprint.js",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"engines": {
|
|
8
|
-
"node": ">=20.0.0"
|
|
9
|
-
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"test": "vitest run --reporter=verbose"
|
|
12
|
-
},
|
|
13
|
-
"files": [
|
|
14
|
-
"fingerprint.js",
|
|
15
|
-
"fingerprint.client.js",
|
|
16
|
-
"fingerprint.builder.js",
|
|
17
|
-
"pow.solver.js",
|
|
18
|
-
"pow.worker.js",
|
|
19
|
-
"problem-manager.js",
|
|
20
|
-
"optimization.worker.js",
|
|
21
|
-
"library.js",
|
|
22
|
-
"redis-store.js",
|
|
23
|
-
"mongodb-store.js",
|
|
24
|
-
"sql-store.js",
|
|
25
|
-
"README.md",
|
|
26
|
-
"LICENSE"
|
|
27
|
-
],
|
|
28
|
-
"repository": {
|
|
29
|
-
"type": "git",
|
|
30
|
-
"url": "git+https://github.com/anonympins/fingerprint.git"
|
|
31
|
-
},
|
|
32
|
-
"keywords": [
|
|
33
|
-
"
|
|
34
|
-
"bot",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"express",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"
|
|
76
|
-
"optional": true
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@anonympins/fingerprint",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "Advanced anti-bot library for Node.js using multi-layer fingerprinting (JA3, client-side, headers), behavioral analysis, and adaptive Proof-of-Work (PoW) challenges to mitigate scraping, scalping, and automated threats.",
|
|
5
|
+
"main": "fingerprint.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20.0.0"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "vitest run --reporter=verbose"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"fingerprint.js",
|
|
15
|
+
"fingerprint.client.js",
|
|
16
|
+
"fingerprint.builder.js",
|
|
17
|
+
"pow.solver.js",
|
|
18
|
+
"pow.worker.js",
|
|
19
|
+
"problem-manager.js",
|
|
20
|
+
"optimization.worker.js",
|
|
21
|
+
"library.js",
|
|
22
|
+
"redis-store.js",
|
|
23
|
+
"mongodb-store.js",
|
|
24
|
+
"sql-store.js",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/anonympins/fingerprint.git"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"anti-bot",
|
|
34
|
+
"bot-detection",
|
|
35
|
+
"security",
|
|
36
|
+
"middleware",
|
|
37
|
+
"express",
|
|
38
|
+
"nodejs",
|
|
39
|
+
"fingerprint",
|
|
40
|
+
"device-fingerprinting",
|
|
41
|
+
"ja3",
|
|
42
|
+
"tls-fingerprinting",
|
|
43
|
+
"proof-of-work",
|
|
44
|
+
"pow",
|
|
45
|
+
"useful-proof-of-work",
|
|
46
|
+
"upow",
|
|
47
|
+
"waf",
|
|
48
|
+
"scraping",
|
|
49
|
+
"scalping",
|
|
50
|
+
"mitigation",
|
|
51
|
+
"rate-limiting",
|
|
52
|
+
"honeypot",
|
|
53
|
+
"behavioral-analysis"
|
|
54
|
+
],
|
|
55
|
+
"author": "anonympins",
|
|
56
|
+
"license": "MIT",
|
|
57
|
+
"bugs": {
|
|
58
|
+
"url": "https://github.com/anonympins/fingerprint/issues"
|
|
59
|
+
},
|
|
60
|
+
"homepage": "https://github.com/anonympins/fingerprint#readme",
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"body-parser": "^1.20.2",
|
|
63
|
+
"cookie-parser": "^1.4.6",
|
|
64
|
+
"express": "^4.18.2",
|
|
65
|
+
"prom-client": "^15.1.2",
|
|
66
|
+
"vitest": "^4.1.11"
|
|
67
|
+
},
|
|
68
|
+
"peerDependencies": {
|
|
69
|
+
"ioredis": "^5.3.2",
|
|
70
|
+
"knex": ">=3.0.0",
|
|
71
|
+
"mongodb": "^6.3.0",
|
|
72
|
+
"sqlite3": "^5.1.7"
|
|
73
|
+
},
|
|
74
|
+
"peerDependenciesMeta": {
|
|
75
|
+
"ioredis": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"mongodb": {
|
|
79
|
+
"optional": true
|
|
80
|
+
},
|
|
81
|
+
"knex": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"sqlite3": {
|
|
85
|
+
"optional": true
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
package/pow.solver.js
CHANGED
|
@@ -293,6 +293,18 @@ async function solveUsefulWorkTask(task) {
|
|
|
293
293
|
// On simule l'appel avec les arguments de base.
|
|
294
294
|
// Note: `baseSolverArgs` peut contenir des options.
|
|
295
295
|
return clientSolver(...baseSolverArgs);
|
|
296
|
+
|
|
297
|
+
case 'multi_objective_genetic_algorithm': {
|
|
298
|
+
// C'est ici que la connexion se fait !
|
|
299
|
+
// On cherche le solveur demandé (ex: 'cpc.solve') dans notre registre client.
|
|
300
|
+
const solverFunction = ClientOptimizers[task.solverName];
|
|
301
|
+
if (!solverFunction) {
|
|
302
|
+
throw new Error(`Solver '${task.solverName}' not found on client.`);
|
|
303
|
+
}
|
|
304
|
+
// On appelle le solveur en lui passant le payload et les options.
|
|
305
|
+
// La fonction `solveOptimalCPC` attend le payload comme premier argument.
|
|
306
|
+
return solverFunction(task.payload, { generations: task.generations, initialFront: task.initialFront });
|
|
307
|
+
}
|
|
296
308
|
|
|
297
309
|
default:
|
|
298
310
|
throw new Error(`Unknown useful work type: ${task.type}`);
|
|
@@ -339,14 +351,50 @@ export async function solveOptimizationTask(initialPopulation, generations) {
|
|
|
339
351
|
return population;
|
|
340
352
|
}
|
|
341
353
|
|
|
354
|
+
/**
|
|
355
|
+
* @class ChallengeSolution
|
|
356
|
+
* @description Encapsule une solution de challenge et fournit des méthodes pour la manipuler.
|
|
357
|
+
* @private
|
|
358
|
+
*/
|
|
359
|
+
class ChallengeSolution {
|
|
360
|
+
constructor(type, nonce, rawSolution) {
|
|
361
|
+
this.type = type;
|
|
362
|
+
this.nonce = nonce;
|
|
363
|
+
this.rawSolution = rawSolution;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Applique les paramètres de la solution à un objet URL.
|
|
368
|
+
* @param {URL} url - L'objet URL à modifier.
|
|
369
|
+
*/
|
|
370
|
+
applyToUrl(url) {
|
|
371
|
+
url.searchParams.set('pow_type', this.type);
|
|
372
|
+
url.searchParams.set('pow_nonce', this.nonce);
|
|
373
|
+
|
|
374
|
+
// Logique de formatage spécifique à chaque type de challenge
|
|
375
|
+
if (this.type === 'cpu_mem' || this.type === 'cpu_mem_inline' || this.type === 'cpu_target') {
|
|
376
|
+
Object.entries(this.rawSolution).forEach(([key, value]) => {
|
|
377
|
+
url.searchParams.set(`pow_solution_${key}`, String(value));
|
|
378
|
+
});
|
|
379
|
+
} else if (this.type === 'useful_work_task') {
|
|
380
|
+
url.searchParams.set('pow_solution_work_result', JSON.stringify(this.rawSolution.work_result));
|
|
381
|
+
url.searchParams.set('pow_problem_id', this.rawSolution.problem_id);
|
|
382
|
+
} else {
|
|
383
|
+
// Pour les cas simples comme 'tsp' où la solution est une seule valeur
|
|
384
|
+
url.searchParams.set('pow_solution', JSON.stringify(this.rawSolution));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
342
389
|
/**
|
|
343
390
|
* Fonction principale qui reçoit un objet challenge et le résout.
|
|
344
391
|
* @param {object} challenge - L'objet challenge reçu du serveur.
|
|
345
|
-
* @
|
|
392
|
+
* @param {string} [fingerprint=''] - L'empreinte de l'appareil qui résout le challenge.
|
|
393
|
+
* @returns {Promise<ChallengeSolution>} Un objet `ChallengeSolution` encapsulant le résultat.
|
|
346
394
|
*/
|
|
347
395
|
export async function solveChallenge(challenge, fingerprint = '') { // The fingerprint is now passed from the client library
|
|
348
396
|
const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance, optimizationTask, usefulWorkTask } = challenge;
|
|
349
|
-
|
|
397
|
+
let rawSolution = {};
|
|
350
398
|
|
|
351
399
|
switch (type) {
|
|
352
400
|
case 'cpu_target':
|
|
@@ -357,7 +405,7 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
|
|
|
357
405
|
const target = cpuTarget; // Keep variable name for consistency below
|
|
358
406
|
// Pour ce challenge simple, le baseBlock est juste le nonce.
|
|
359
407
|
const baseBlockBytes = new TextEncoder().encode(nonce + ":");
|
|
360
|
-
|
|
408
|
+
rawSolution.cpu = await solveCpuTargetInline(baseBlockBytes, target, null);
|
|
361
409
|
break;
|
|
362
410
|
case 'cpu_mem':
|
|
363
411
|
// Pour les appels API, le client IP n'est pas connu, on ne le met pas dans le message
|
|
@@ -371,8 +419,8 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
|
|
|
371
419
|
})(),
|
|
372
420
|
solveMemory(memSeed, memDifficulty)
|
|
373
421
|
]);
|
|
374
|
-
|
|
375
|
-
|
|
422
|
+
rawSolution.cpu = cpuSol;
|
|
423
|
+
rawSolution.mem = memSol;
|
|
376
424
|
break;
|
|
377
425
|
case 'cpu_mem_inline':
|
|
378
426
|
// Version inline pour compatibilité HTML avec IP incluse
|
|
@@ -385,28 +433,28 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
|
|
|
385
433
|
})(),
|
|
386
434
|
solveMemory(memSeedInline, memDifficulty)
|
|
387
435
|
]);
|
|
388
|
-
|
|
389
|
-
|
|
436
|
+
rawSolution.cpu = cpuSolInline;
|
|
437
|
+
rawSolution.mem = memSolInline;
|
|
390
438
|
break;
|
|
391
439
|
case 'tsp':
|
|
392
440
|
const tspResult = await solveTsp(cities, targetMaxDistance);
|
|
393
|
-
|
|
394
|
-
|
|
441
|
+
// Pour ce challenge, la solution est juste le chemin.
|
|
442
|
+
rawSolution = tspResult.path;
|
|
395
443
|
break;
|
|
396
444
|
case 'optimization_task':
|
|
397
445
|
const finalPopulation = await solveOptimizationTask(optimizationTask.population, optimizationTask.generations);
|
|
398
|
-
|
|
446
|
+
rawSolution = finalPopulation.map(p => p.chromosome); // On ne renvoie que les chromosomes
|
|
399
447
|
break;
|
|
400
448
|
case 'useful_work_task':
|
|
401
449
|
const workResult = await solveUsefulWorkTask(usefulWorkTask.task);
|
|
402
|
-
|
|
403
|
-
|
|
450
|
+
rawSolution.work_result = workResult;
|
|
451
|
+
rawSolution.problem_id = usefulWorkTask.problemId;
|
|
404
452
|
break;
|
|
405
453
|
default:
|
|
406
454
|
throw new Error(`Unknown challenge type: ${type}`);
|
|
407
455
|
}
|
|
408
456
|
|
|
409
|
-
return
|
|
457
|
+
return new ChallengeSolution(type, nonce, rawSolution);
|
|
410
458
|
}
|
|
411
459
|
|
|
412
460
|
// --- Compatibilité pour l'injection directe dans le HTML ---
|
package/pow.worker.js
CHANGED
|
@@ -1,27 +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
|
-
}
|
|
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
27
|
};
|