@anonympins/fingerprint 0.6.7 → 0.7.1

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +3 -7
  3. package/composer.json +8 -1
  4. package/package.json +1 -1
  5. package/public/fingerprint-wordpress.zip +0 -0
  6. package/public/fp.wasm +0 -0
  7. package/src/js/build-client.js +9 -11
  8. package/src/js/dynamic-wasm.js +7 -7
  9. package/src/js/fingerprint.builder.js +37 -38
  10. package/src/js/fingerprint.client.js +236 -152
  11. package/src/js/fingerprint.client.obfuscated.js +1 -1
  12. package/src/js/fingerprint.js +517 -101
  13. package/src/js/fingerprint.utils.js +213 -213
  14. package/src/js/library.js +355 -384
  15. package/src/js/mongodb-store.js +1 -1
  16. package/src/js/optimization.worker.js +27 -27
  17. package/src/js/pow.solver.inline.js +206 -62
  18. package/src/js/pow.solver.js +235 -101
  19. package/src/js/pow.worker.js +48 -26
  20. package/src/js/redis-store.js +1 -1
  21. package/src/js/tests/fingerprint.client.init.test.js +146 -143
  22. package/src/js/tests/fingerprint.test.js +101 -40
  23. package/src/js/tests/ip-reputation.test.js +1 -0
  24. package/src/js/upow-model-task.js +14 -15
  25. package/src/php/AutoTuner.php +34 -34
  26. package/src/php/Challenge/ChallengeUtils.php +204 -32
  27. package/src/php/Config/SecurityProfiles.php +21 -0
  28. package/src/php/DirectFingerprint.php +190 -145
  29. package/src/php/FingerprintBuilder.php +29 -30
  30. package/src/php/FingerprintClient.php +24 -25
  31. package/src/php/FingerprintEngine.php +1836 -1880
  32. package/src/php/Ja3AnomalyDetector.php +258 -227
  33. package/src/php/Optimization/FunctionRegistry.php +7 -7
  34. package/src/php/Optimization/Optimization.php +24 -24
  35. package/src/php/Optimization/OptimizationOperators.php +28 -27
  36. package/src/php/Optimization/ProblemInitializers.php +52 -52
  37. package/src/php/ProblemManager.php +31 -34
  38. package/src/php/RequestContext.php +8 -1
  39. package/src/php/Store/IStore.php +7 -8
  40. package/src/php/Store/InMemoryStore.php +66 -66
  41. package/src/php/Store/MongoDbStore.php +5 -5
  42. package/src/php/Store/RedisStore.php +5 -5
  43. package/src/php/Store/StoreManager.php +35 -35
  44. package/src/php/Tests/ChallengeUtilsTest.php +453 -453
  45. package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
  46. package/src/php/Tests/FingerprintEngineTest.php +662 -693
  47. package/src/php/Tests/IpReputationTest.php +10 -0
  48. package/src/php/Tests/MaliciousPatternsTest.php +103 -103
  49. package/src/php/Tests/MetricsTest.php +97 -97
  50. package/src/php/Tests/ProblemManagerTest.php +376 -376
  51. package/src/php/Tests/QuicFingerprintTest.php +53 -53
  52. package/src/php/Tests/RequestUtilsTest.php +471 -471
  53. package/src/php/Tests/TLSClientHelloParserTest.php +177 -177
  54. package/src/php/Tests/config/ed25519_key.json +3 -3
  55. package/src/php/Utils/Env.php +49 -49
  56. package/src/php/Utils/MaliciousPatterns.php +74 -74
  57. package/src/php/Utils/MetricsManager.php +209 -209
  58. package/src/php/Utils/RequestUtils.php +236 -19
  59. package/src/php/Utils/TLSClientHelloParser.php +369 -212
  60. package/src/php/WordPress/WpDbStore.php +155 -0
  61. package/src/php/WordPress/fingerprint-wordpress.php +217 -0
  62. package/src/php/WordPress/package.php +123 -0
  63. package/src/php/bin/auto-tune.php +118 -118
@@ -1,22 +1,104 @@
1
1
  /**
2
2
  * @file @/pow.solver.js
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.
7
- * Fichier compatible à la fois avec l'import de modules ES6 et l'injection directe dans un script HTML.
3
+ * @description Client-side functions to solve various Proof-of-Work challenges.
4
+ * IMPORTANT: For optimization tasks, this file requires access to algorithms in `library.js`.
5
+ * In production, a client bundle of `library.js` should be bundled and imported here.
6
+ * For this implementation, the necessary functions are embedded.
7
+ * Compatible with both ES6 module imports and direct HTML script injection.
8
8
  */
9
9
 
10
10
  'use strict';
11
11
 
12
12
  function secureRandom() {
13
- if (typeof window !== 'undefined' && (window.crypto || window.msCrypto)) {
13
+ if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.getRandomValues) {
14
14
  const array = new Uint32Array(1);
15
- (window.crypto || window.msCrypto).getRandomValues(array);
16
- return array[0] / 0x100000000;
15
+ globalThis.crypto.getRandomValues(array);
16
+ return array[0] / 0xffffffff;
17
17
  }
18
18
  return Math.random();
19
19
  }
20
+ /**
21
+ * Standalone synchronous SHA-256 implementation usable anywhere (HTTP, insecure contexts, Workers).
22
+ */
23
+ function sha256Sync(bytes) {
24
+ if (bytes instanceof ArrayBuffer) {
25
+ bytes = new Uint8Array(bytes);
26
+ } else if (ArrayBuffer.isView(bytes) && !(bytes instanceof Uint8Array)) {
27
+ bytes = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
28
+ }
29
+ var K = [
30
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
31
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
32
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76ca1422,
33
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
34
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
35
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
36
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
37
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
38
+ ];
39
+ var H = [
40
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
41
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
42
+ ];
43
+ var l = bytes.length;
44
+ var bitLen = l * 8;
45
+ var padLen = ((l + 8) >> 6 << 6) + 64;
46
+ var buf = new Uint8Array(padLen);
47
+ buf.set(bytes);
48
+ buf[l] = 0x80;
49
+ var view = new DataView(buf.buffer);
50
+ view.setUint32(padLen - 4, bitLen >>> 0);
51
+ view.setUint32(padLen - 8, Math.floor(bitLen / 0x100000000));
52
+
53
+ var W = new Int32Array(64);
54
+ for (var i = 0; i < padLen; i += 64) {
55
+ for (var t = 0; t < 16; t++) {
56
+ W[t] = view.getInt32(i + t * 4);
57
+ }
58
+ for (var t = 16; t < 64; t++) {
59
+ var s0 = ((W[t - 15] >>> 7) | (W[t - 15] << 25)) ^ ((W[t - 15] >>> 18) | (W[t - 15] << 14)) ^ (W[t - 15] >>> 3);
60
+ var s1 = ((W[t - 2] >>> 17) | (W[t - 2] << 15)) ^ ((W[t - 2] >>> 19) | (W[t - 2] << 13)) ^ (W[t - 2] >>> 10);
61
+ W[t] = (W[t - 16] + s0 + W[t - 7] + s1) | 0;
62
+ }
63
+ var a = H[0], b = H[1], c = H[2], d = H[3], e = H[4], f = H[5], g = H[6], h = H[7];
64
+ for (var t = 0; t < 64; t++) {
65
+ var S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
66
+ var ch = (e & f) ^ ((~e) & g);
67
+ var temp1 = (h + S1 + ch + K[t] + W[t]) | 0;
68
+ var S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
69
+ var maj = (a & b) ^ (a & c) ^ (b & c);
70
+ var temp2 = (S0 + maj) | 0;
71
+ h = g; g = f; f = e; e = (d + temp1) | 0;
72
+ d = c; c = b; b = a; a = (temp1 + temp2) | 0;
73
+ }
74
+ H[0] = (H[0] + a) | 0;
75
+ H[1] = (H[1] + b) | 0;
76
+ H[2] = (H[2] + c) | 0;
77
+ H[3] = (H[3] + d) | 0;
78
+ H[4] = (H[4] + e) | 0;
79
+ H[5] = (H[5] + f) | 0;
80
+ H[6] = (H[6] + g) | 0;
81
+ H[7] = (H[7] + h) | 0;
82
+ }
83
+ var hex = '';
84
+ for (var i = 0; i < 8; i++) {
85
+ hex += (H[i] >>> 0).toString(16).padStart(8, '0');
86
+ }
87
+ return hex;
88
+ }
89
+
90
+ async function safeDigestSha256Hex(bytes) {
91
+ const cryptoObj = (typeof globalThis !== 'undefined' && globalThis.crypto) ? globalThis.crypto : (typeof window !== 'undefined' ? window.crypto : null);
92
+ if (cryptoObj && cryptoObj.subtle && typeof cryptoObj.subtle.digest === 'function') {
93
+ try {
94
+ const buf = await cryptoObj.subtle.digest("SHA-256", bytes);
95
+ return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
96
+ } catch (e) {
97
+ // Transparent fallback
98
+ }
99
+ }
100
+ return sha256Sync(bytes);
101
+ }
20
102
 
21
103
  function cyrb53(str, seed = 0) {
22
104
  const safeStr = (typeof str === 'string' ? str : String(str || '')).slice(0, 10000);
@@ -102,6 +184,25 @@ export async function initializeSpace(seed, sizeMb) {
102
184
  store.put({ sizeMb, seed }, metadataKey);
103
185
  }
104
186
 
187
+ export async function readSpaceBlock(blockIdx) {
188
+ const db = await openDb();
189
+ const transaction = db.transaction("blocks", "readonly");
190
+ const store = transaction.objectStore("blocks");
191
+ const getReq = store.get(blockIdx);
192
+ return new Promise((resolve, reject) => {
193
+ getReq.onsuccess = () => {
194
+ const block = getReq.result;
195
+ if (block) {
196
+ const hex = Array.from(block).map(b => b.toString(16).padStart(2, '0')).join('');
197
+ resolve(hex);
198
+ } else {
199
+ reject(new Error("Block not found"));
200
+ }
201
+ };
202
+ getReq.onerror = () => reject(getReq.error);
203
+ });
204
+ }
205
+
105
206
  export async function solveSpaceChallenge(seed, queries, nonce, clientSecret, peerBlock = '') {
106
207
  const db = await openDb();
107
208
  const transaction = db.transaction("blocks", "readonly");
@@ -139,11 +240,11 @@ export async function solveSpaceChallenge(seed, queries, nonce, clientSecret, pe
139
240
  }
140
241
 
141
242
  /**
142
- * Résout un challenge CPU basé sur une cible en utilisant un bloc de base binaire.
143
- * @param {Uint8Array} baseBlock - Le bloc de données initial (nonce, secret, fp) fourni par le serveur.
144
- * @param {bigint} target - La cible à atteindre.
145
- * @param {Function} progressCallback - Callback pour les mises à jour de progression.
146
- * @returns {Promise<number>} La solution (un nombre entier).
243
+ * Solves a target-based CPU challenge using a binary base block.
244
+ * @param {Uint8Array} baseBlock - Initial data block (nonce, secret, fp) provided by the server.
245
+ * @param {bigint} target - Target difficulty to reach.
246
+ * @param {Function} progressCallback - Progress update callback.
247
+ * @returns {Promise<number>} Solution integer.
147
248
  */
148
249
  export async function solveCpuTargetInline(baseBlock, target, progressCallback) {
149
250
  // --- FIX: Add validation for the target to prevent BigInt conversion errors ---
@@ -163,33 +264,74 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
163
264
  const targetStr = typeof target === 'string' ? target : target.toString(16);
164
265
  const targetPtr = wasmModule._malloc(targetStr.length + 1);
165
266
  for (let i = 0; i < targetStr.length; i++) {
166
- wasmModule.HEAP8[targetPtr + i] = targetStr.charCodeAt(i);
267
+ wasmModule.HEAPU8[targetPtr + i] = targetStr.charCodeAt(i);
268
+ }
269
+ wasmModule.HEAPU8[targetPtr + targetStr.length] = 0; // Null-terminator
270
+ try {
271
+ const solution = wasmModule._solve_cpu_target(ptr, len, targetPtr);
272
+ return solution;
273
+ } finally {
274
+ wasmModule._free(ptr);
275
+ wasmModule._free(targetPtr);
167
276
  }
168
- wasmModule.HEAP8[targetPtr + targetStr.length] = 0;
169
- const solution = wasmModule._solve_cpu_target(ptr, len, targetPtr);
170
- wasmModule._free(ptr);
171
- wasmModule._free(targetPtr);
172
- return solution;
173
277
  }
174
278
 
175
279
  // Try Web Worker execution first if supported and not blocked by CSP
176
280
  if (typeof window !== 'undefined' && typeof Worker !== 'undefined') {
177
281
  try {
282
+ const staticWorkerPath = window.ClientLibrary?.workerPath || (window.ClientConfig && window.ClientConfig.workerPath);
283
+ const sha256FnStr = sha256Sync.toString();
284
+ if (staticWorkerPath) {
285
+ return await new Promise((resolve, reject) => {
286
+ const worker = new Worker(staticWorkerPath);
287
+ worker.onmessage = (event) => {
288
+ if (event.data.type === 'progress') {
289
+ if (progressCallback) progressCallback(event.data.solution);
290
+ } else if (event.data.type === 'success') {
291
+ resolve(event.data.solution);
292
+ worker.terminate();
293
+ } else if (event.data.solution !== undefined) {
294
+ // Fallback for backward compatibility with previous signature
295
+ resolve(event.data.solution);
296
+ worker.terminate();
297
+ }
298
+ };
299
+ worker.onerror = (err) => {
300
+ worker.terminate();
301
+ reject(err);
302
+ };
303
+ worker.postMessage({ baseBlock, target: cpuTarget.toString() });
304
+ });
305
+ }
178
306
  return await new Promise((resolve, reject) => {
179
307
  const workerCode = `
180
308
  self.onmessage = async (e) => {
181
309
  const { baseBlock, target } = e.data;
182
310
  const cpuTarget = BigInt(target);
183
311
  const encoder = new TextEncoder();
312
+ const hasSubtle = typeof self.crypto !== 'undefined' && self.crypto && self.crypto.subtle && typeof self.crypto.subtle.digest === 'function';
313
+ const sha256Fallback = ${sha256FnStr};
184
314
  let cpuSolution = 0;
185
315
  while (true) {
186
316
  const solutionBytes = encoder.encode(String(cpuSolution));
187
317
  const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
188
318
  finalBlock.set(baseBlock);
189
319
  finalBlock.set(solutionBytes, baseBlock.length);
190
- const buf = await crypto.subtle.digest("SHA-256", finalBlock);
191
- const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
192
- if (BigInt('0x' + hashHex) < cpuTarget) break;
320
+ let hashHex = '';
321
+ if (hasSubtle) {
322
+ try {
323
+ const buf = await self.crypto.subtle.digest("SHA-256", finalBlock);
324
+ const arr = new Uint8Array(buf);
325
+ for (let j = 0; j < arr.length; j++) {
326
+ hashHex += arr[j].toString(16).padStart(2, '0');
327
+ }
328
+ } catch (subtleErr) {
329
+ hashHex = sha256Fallback(finalBlock);
330
+ }
331
+ } else {
332
+ hashHex = sha256Fallback(finalBlock);
333
+ }
334
+ if (BigInt('0x' + hashHex) < cpuTarget) break;
193
335
  cpuSolution++;
194
336
  if (cpuSolution % 25000 === 0) {
195
337
  self.postMessage({ type: 'progress', solution: cpuSolution });
@@ -224,22 +366,17 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
224
366
  while (true) {
225
367
  const solutionBytes = encoder.encode(String(cpuSolution));
226
368
 
227
- // Concaténation binaire directe : c'est plus rapide et plus sûr.
369
+ // Direct binary concatenation: faster and safer.
228
370
  const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
229
371
  finalBlock.set(baseBlock);
230
372
  finalBlock.set(solutionBytes, baseBlock.length);
231
373
 
232
- if (cpuSolution === 0) {
233
- const reconstructedMsg = new TextDecoder().decode(finalBlock);
234
- console.log('[FP Solve Debug] Client will hash message:', reconstructedMsg);
235
- }
236
- const buf = await crypto.subtle.digest("SHA-256", finalBlock);
237
- const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
238
- // --- AJOUT DE LOGS POUR LE DÉBOGAGE CÔTÉ CLIENT ---
374
+ const hashHex = await safeDigestSha256Hex(finalBlock);
375
+ // --- CLIENT-SIDE DEBUG LOGS ---
239
376
  if (cpuSolution === 0) { // Log only the first attempt
240
377
  console.log(`[FP Client Solve] Attempt 0 hash: "0x${hashHex}"`);
241
378
  }
242
- // --- FIN DES LOGS ---
379
+ // --- END DEBUG LOGS ---
243
380
  if (BigInt('0x' + hashHex) < cpuTarget) break;
244
381
  cpuSolution++;
245
382
  if (cpuSolution % 50000 === 0) {
@@ -255,28 +392,28 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
255
392
  }
256
393
 
257
394
  /**
258
- * Résout un challenge CPU basé sur une cible (version Web Worker).
259
- * @param {string} message - Le message à hasher (ex: `ip:nonce:solution:secret`).
260
- * @param {bigint} target - La cible à atteindre.
261
- * @returns {Promise<number>} La solution (un nombre entier).
395
+ * Solves a target-based CPU challenge (Web Worker version).
396
+ * @param {string} message - Message to hash (e.g. `ip:nonce:solution:secret`).
397
+ * @param {bigint} target - Target difficulty to reach.
398
+ * @returns {Promise<number>} Solution integer.
262
399
  */
263
400
  export async function solveCpuTarget(message, target) {
264
401
  // Vérifie si les Web Workers sont supportés par le navigateur.
265
402
  if (typeof(Worker) === "undefined") {
266
403
  console.warn("Web Workers not supported. Falling back to main thread calculation (UI may freeze).");
267
- // Ici, on pourrait remettre l'ancienne implémentation comme solution de secours.
268
- // Pour la clarté, nous supposons que les workers sont disponibles.
404
+ // An inline fallback implementation could be placed here if needed.
405
+ // For clarity, we assume worker availability.
269
406
  throw new Error("Web Worker support is required for CPU challenges.");
270
407
  }
271
408
 
272
409
  return new Promise((resolve, reject) => {
273
- // Crée un worker à partir du script dédié. Le chemin doit être accessible publiquement.
274
- // Assurez-vous que `pow.worker.js` est servi par votre serveur statique.
410
+ // Create a worker from the dedicated script. The path must be publicly accessible.
411
+ // Ensure `pow.worker.js` is served by your static assets server.
275
412
  const worker = new Worker('./pow.worker.js');
276
413
 
277
414
  worker.onmessage = (event) => {
278
415
  resolve(event.data.solution);
279
- worker.terminate(); // Nettoie le worker une fois le travail terminé.
416
+ worker.terminate(); // Clean up worker once finished.
280
417
  };
281
418
 
282
419
  worker.onerror = (error) => {
@@ -284,16 +421,16 @@ export async function solveCpuTarget(message, target) {
284
421
  worker.terminate();
285
422
  };
286
423
 
287
- // Envoie les données du challenge au worker pour qu'il commence le calcul.
424
+ // Send challenge data to the worker to start calculation.
288
425
  worker.postMessage({ message, target });
289
426
  });
290
427
  }
291
428
 
292
429
  /**
293
- * Résout un challenge basé sur la mémoire.
294
- * @param {string} seed - La graine pour l'initialisation de la mémoire.
295
- * @param {number} difficulty - La difficulté (en Mo).
296
- * @returns {Promise<number>} La solution (nombre entier).
430
+ * Solves a memory-hard challenge.
431
+ * @param {string} seed - Seed for memory initialization.
432
+ * @param {number} difficulty - Difficulty in MB.
433
+ * @returns {Promise<number>} Solution integer.
297
434
  */
298
435
  export async function solveMemory(seed, difficulty) {
299
436
  const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
@@ -315,8 +452,7 @@ export async function solveMemory(seed, difficulty) {
315
452
  if (numBlocks === 0) return { solution: 0, merkleRoot: '', proofs: {} };
316
453
 
317
454
  async function hashBlock(block) {
318
- const buf = await crypto.subtle.digest("SHA-256", block.buffer);
319
- return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
455
+ return await safeDigestSha256Hex(block.buffer);
320
456
  }
321
457
 
322
458
  function hexToBytes(hex) {
@@ -347,8 +483,7 @@ export async function solveMemory(seed, difficulty) {
347
483
  const left = currentLayer[i];
348
484
  const right = currentLayer[i + 1] || left;
349
485
  const combined = hexToBytes(left + right);
350
- const hashBuf = await crypto.subtle.digest("SHA-256", combined);
351
- const hashHex = Array.from(new Uint8Array(hashBuf)).map(b => b.toString(16).padStart(2, '0')).join('');
486
+ const hashHex = await safeDigestSha256Hex(combined);
352
487
  nextLayer.push(hashHex);
353
488
  }
354
489
  tree.push(nextLayer);
@@ -397,12 +532,12 @@ export async function solveMemory(seed, difficulty) {
397
532
  }
398
533
 
399
534
  /**
400
- * Résout un challenge de type "Problème du Voyageur de Commerce" (TSP).
401
- * NOTE: Ceci est une implémentation simple (heuristique du plus proche voisin) et n'est pas garantie
402
- * de trouver la solution optimale, mais elle est suffisante pour un challenge.
403
- * @param {Array<{x: number, y: number}>} cities - Les coordonnées des villes.
404
- * @param {number} targetMaxDistance - La distance maximale acceptable.
405
- * @returns {Promise<{path: number[], distance: number}>} Le chemin et la distance.
535
+ * Solves a Traveling Salesperson Problem (TSP) challenge.
536
+ * NOTE: Uses a nearest-neighbor heuristic which may not find the global optimum,
537
+ * but is sufficient for verification challenges.
538
+ * @param {Array<{x: number, y: number}>} cities - City coordinates.
539
+ * @param {number} targetMaxDistance - Maximum acceptable distance.
540
+ * @returns {Promise<{path: number[], distance: number}>} Tour path and distance.
406
541
  */
407
542
  export async function solveTsp(cities, targetMaxDistance) {
408
543
  // Utility function to calculate the distance between two cities
@@ -460,7 +595,7 @@ export async function solveTsp(cities, targetMaxDistance) {
460
595
  return { path: solutionPath, distance: solutionDistance };
461
596
  }
462
597
 
463
- // --- Fonctions d'optimisation copiées/adaptées de library.js pour le client ---
598
+ // --- Optimization functions adapted from library.js for client usage ---
464
599
 
465
600
  function paretoDominates(objectivesA, objectivesB) {
466
601
  let aIsBetterInOne = false;
@@ -781,7 +916,7 @@ const ClientOptimizers = {
781
916
 
782
917
  const mutate = (solution) => {
783
918
  const newSolution = [...solution];
784
- const i = Math.floor(Math.random() * 4);
919
+ const i = Math.floor(secureRandom() * 4);
785
920
  const mutationFactors = [500, 1000, 0.1, 2];
786
921
  newSolution[i] += (secureRandom() - 0.5) * mutationFactors[i];
787
922
  return newSolution;
@@ -808,9 +943,9 @@ const ClientOptimizers = {
808
943
  };
809
944
 
810
945
  /**
811
- * Résout une unité de travail utile (Useful Work Unit).
812
- * @param {object} task - La tâche envoyée par le serveur.
813
- * @returns {Promise<object>} Le résultat du calcul.
946
+ * Solves a Useful Work Unit (uPoW).
947
+ * @param {object} task - Task payload sent by the server.
948
+ * @returns {Promise<object>} Computation result.
814
949
  */
815
950
  async function solveUsefulWorkTask(task) {
816
951
  await new Promise(r => setTimeout(r, 10)); // Yield thread
@@ -885,7 +1020,7 @@ async function solveUsefulWorkTask(task) {
885
1020
  const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
886
1021
  const mutate = p => { const n = [...p], i = Math.floor(secureRandom() * n.length); n[i] += (secureRandom() - 0.5) * 0.2; return n.map(v => Math.max(0, v)); };
887
1022
 
888
- // Le client doit recréer la population si elle n'est pas fournie
1023
+ // The client must recreate the population if not provided
889
1024
  const initialPopulation = task.initialPopulation || Array.from({ length: task.payload.options.populationSize }, () => ({ chromosome: createIndividual(), fitness: 0 }));
890
1025
  initialPopulation.forEach(p => p.fitness = fitness(p.chromosome));
891
1026
 
@@ -894,33 +1029,31 @@ async function solveUsefulWorkTask(task) {
894
1029
  }
895
1030
 
896
1031
  case 'run_multiple_parallel':
897
- // Côté client, on ne peut pas utiliser de vrais workers pour `runMultipleParallel`.
898
- // On exécute donc une version simplifiée : un seul cycle du solveur demandé.
899
- // Cela reste un travail coûteux et valide le principe du "Useful Work".
1032
+ // On the client side, Web Workers are not nested for runMultipleParallel.
1033
+ // Run a single cycle of the requested solver.
1034
+ // This still represents non-trivial computational work for useful PoW.
900
1035
  const { solverName, baseSolverArgs } = task;
901
1036
  const clientSolver = ClientOptimizers[solverName];
902
1037
  if (!clientSolver) throw new Error(`Solver ${solverName} not found on client.`);
903
1038
 
904
- // On simule l'appel avec les arguments de base.
905
- // Note: `baseSolverArgs` peut contenir des options.
1039
+ // Execute solver with base arguments.
1040
+ // Note: `baseSolverArgs` may contain solver options.
906
1041
  return clientSolver(...baseSolverArgs);
907
1042
 
908
1043
  case 'multi_objective_genetic_algorithm': {
909
- // C'est ici que la connexion se fait !
910
- // On cherche le solveur demandé (ex: 'cpc.solve') dans notre registre client.
1044
+ // Look up requested solver (e.g. 'cpc.solve') in the client registry.
911
1045
  const solverFunction = ClientOptimizers[task.solverName];
912
1046
  if (!solverFunction) {
913
1047
  throw new Error(`Solver '${task.solverName}' not found on client.`);
914
1048
  }
915
1049
  // On appelle le solveur en lui passant le payload et les options.
916
- // La fonction `solveOptimalCPC` attend le payload comme premier argument.
1050
+ // Invoke solver with payload and options.
917
1051
  return solverFunction(task.payload, { generations: task.generations, initialFront: task.initialFront });
918
1052
  }
919
1053
 
920
1054
  case 'pytorch_onnx_learning': {
921
- // Dans un environnement de production réel avec ONNX Runtime Web, on chargerait le modèle .onnx
922
- // et on exécuterait un calcul de gradient (backward pass).
923
- // Ici, nous simulons le calcul de gradient mathématique équivalent pour la régression/classification.
1055
+ // In a production environment with ONNX Runtime Web, load .onnx and execute gradient backward pass.
1056
+ // Here, simulate the gradient backward pass for regression/classification.
924
1057
  const { weights, payload } = task;
925
1058
  const inputs = payload.inputs || [];
926
1059
  const labels = payload.labels || [];
@@ -946,7 +1079,7 @@ async function solveUsefulWorkTask(task) {
946
1079
  const labels = payload.labels || [];
947
1080
  try {
948
1081
  const tf = await loadTfjs();
949
- // Chargement non-bloquant du modèle au format JSON
1082
+ // Non-blocking model loading
950
1083
  const model = await tf.loadLayersModel(modelPath);
951
1084
 
952
1085
  const xs = tf.tensor2d(inputs);
@@ -958,7 +1091,7 @@ async function solveUsefulWorkTask(task) {
958
1091
  return tf.losses.meanSquaredError(ys, preds);
959
1092
  };
960
1093
 
961
- // Dérivation automatique pour le calcul exact des gradients
1094
+ // Automatic differentiation for exact gradient computation
962
1095
  const gFn = tf.grad(lossFn);
963
1096
  const grads = gFn(trainableVars);
964
1097
 
@@ -989,15 +1122,15 @@ async function solveUsefulWorkTask(task) {
989
1122
  }
990
1123
 
991
1124
  /**
992
- * Résout une tâche d'optimisation basée sur un algorithme génétique.
993
- * Reçoit une population et la fait évoluer pendant un certain nombre de générations.
994
- * NOTE: Cette fonction est une version simplifiée de l'AG de `library.js` adaptée au client.
995
- * @param {Array<object>} initialPopulation - La population de départ.
996
- * @param {number} generations - Le nombre de générations à exécuter.
997
- * @returns {Promise<Array<object>>} La population finale après évolution.
1125
+ * Solves an optimization task using a genetic algorithm.
1126
+ * Evolves a population for a specified number of generations.
1127
+ * NOTE: Simplified client adaptation of GA in `library.js`.
1128
+ * @param {Array<object>} initialPopulation - Starting population.
1129
+ * @param {number} generations - Number of generations to run.
1130
+ * @returns {Promise<Array<object>>} Final evolved population.
998
1131
  */
999
1132
  export async function solveOptimizationTask(initialPopulation, generations) {
1000
- // Fonctions AG simplifiées (croisement, mutation)
1133
+ // Simplified GA operators (crossover, mutation)
1001
1134
  const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
1002
1135
  const mutate = (p) => {
1003
1136
  const newP = [...p];
@@ -1009,20 +1142,20 @@ export async function solveOptimizationTask(initialPopulation, generations) {
1009
1142
  let population = initialPopulation;
1010
1143
 
1011
1144
  for (let gen = 0; gen < generations; gen++) {
1012
- // Sélection simple : on garde les 50% meilleurs
1145
+ // Simple selection: keep top 50%
1013
1146
  const parents = population.sort((a, b) => a.fitness - b.fitness).slice(0, Math.ceil(population.length / 2));
1014
- const newPopulation = [...parents]; // Élitisme
1147
+ const newPopulation = [...parents]; // Elitism
1015
1148
 
1016
1149
  while (newPopulation.length < population.length) {
1017
1150
  const parent1 = parents[Math.floor(secureRandom() * parents.length)];
1018
1151
  const parent2 = parents[Math.floor(secureRandom() * parents.length)];
1019
1152
  let offspring = crossover(parent1.chromosome, parent2.chromosome);
1020
1153
  if (secureRandom() < 0.1) offspring = mutate(offspring);
1021
- // La fitness sera recalculée côté serveur pour la vérification.
1154
+ // Fitness is recalculated server-side for verification.
1022
1155
  newPopulation.push({ chromosome: offspring, fitness: -1 });
1023
1156
  }
1024
1157
  population = newPopulation;
1025
- // Pause pour ne pas geler l'UI sur les longues tâches
1158
+ // Yield execution to prevent freezing the UI on long tasks
1026
1159
  if (gen % 10 === 0) await new Promise(r => setTimeout(r, 0));
1027
1160
  }
1028
1161
  return population;
@@ -1030,7 +1163,7 @@ export async function solveOptimizationTask(initialPopulation, generations) {
1030
1163
 
1031
1164
  /**
1032
1165
  * @class ChallengeSolution
1033
- * @description Encapsule une solution de challenge et fournit des méthodes pour la manipuler.
1166
+ * @description Encapsulates a challenge solution and formatting helpers.
1034
1167
  * @private
1035
1168
  */
1036
1169
  class ChallengeSolution {
@@ -1041,14 +1174,14 @@ class ChallengeSolution {
1041
1174
  }
1042
1175
 
1043
1176
  /**
1044
- * Applique les paramètres de la solution à un objet URL.
1045
- * @param {URL} url - L'objet URL à modifier.
1177
+ * Applies solution parameters to a URL instance.
1178
+ * @param {URL} url - Target URL object to modify.
1046
1179
  */
1047
1180
  applyToUrl(url) {
1048
1181
  url.searchParams.set('pow_type', this.type);
1049
1182
  url.searchParams.set('pow_nonce', this.nonce);
1050
1183
 
1051
- // Logique de formatage spécifique à chaque type de challenge
1184
+ // Challenge-specific parameter formatting
1052
1185
  if (this.type === 'cpu_mem' || this.type === 'cpu_mem_inline' || this.type === 'cpu_target') {
1053
1186
  Object.entries(this.rawSolution).forEach(([key, value]) => {
1054
1187
  if (typeof value === 'object' && value !== null) {
@@ -1061,17 +1194,17 @@ class ChallengeSolution {
1061
1194
  url.searchParams.set('pow_solution_work_result', JSON.stringify(this.rawSolution.work_result));
1062
1195
  url.searchParams.set('pow_problem_id', this.rawSolution.problem_id);
1063
1196
  } else {
1064
- // Pour les cas simples comme 'tsp' où la solution est une seule valeur
1197
+ // Single-value solutions like TSP
1065
1198
  url.searchParams.set('pow_solution', JSON.stringify(this.rawSolution));
1066
1199
  }
1067
1200
  }
1068
1201
  }
1069
1202
 
1070
1203
  /**
1071
- * Fonction principale qui reçoit un objet challenge et le résout.
1072
- * @param {object} challenge - L'objet challenge reçu du serveur.
1073
- * @param {string} [fingerprint=''] - L'empreinte de l'appareil qui résout le challenge.
1074
- * @returns {Promise<ChallengeSolution>} Un objet `ChallengeSolution` encapsulant le résultat.
1204
+ * Main entry point to resolve a received challenge payload.
1205
+ * @param {object} challenge - Challenge payload received from the server.
1206
+ * @param {string} [fingerprint=''] - Device fingerprint of the solver.
1207
+ * @returns {Promise<ChallengeSolution>} `ChallengeSolution` wrapping the result.
1075
1208
  */
1076
1209
  export async function solveChallenge(challenge, fingerprint = '') { // The fingerprint is now passed from the client library
1077
1210
  const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance, optimizationTask, usefulWorkTask, queries, sizeMb } = challenge;
@@ -1084,12 +1217,12 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
1084
1217
  throw new Error("Challenge data is missing 'cpuTarget' property.");
1085
1218
  }
1086
1219
  const target = cpuTarget; // Keep variable name for consistency below
1087
- // Pour ce challenge simple, le baseBlock est juste le nonce.
1220
+ // For this simple challenge, baseBlock is the nonce.
1088
1221
  const baseBlockBytes = new TextEncoder().encode(nonce + ":");
1089
1222
  rawSolution.cpu = await solveCpuTargetInline(baseBlockBytes, target, null);
1090
1223
  break;
1091
1224
  case 'cpu_mem':
1092
- // Pour les appels API, le client IP n'est pas connu, on ne le met pas dans le message
1225
+ // For API calls, client IP is omitted from client-side message reconstruction
1093
1226
  const baseMessageCombined = `:${nonce}:${clientSecret}`;
1094
1227
  const memSeed = `:${nonce}:${clientSecret}`;
1095
1228
  const [cpuSol, memSol] = await Promise.all([
@@ -1104,7 +1237,7 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
1104
1237
  rawSolution.mem = memSol;
1105
1238
  break;
1106
1239
  case 'cpu_mem_inline':
1107
- // Version inline pour compatibilité HTML avec IP incluse
1240
+ // Inline version for HTML compatibility with IP
1108
1241
  const memSeedInline = `:${nonce}:${clientSecret}`;
1109
1242
  const [cpuSolInline, memSolInline] = await Promise.all([
1110
1243
  (async () => {
@@ -1119,12 +1252,12 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
1119
1252
  break;
1120
1253
  case 'tsp':
1121
1254
  const tspResult = await solveTsp(cities, targetMaxDistance);
1122
- // Pour ce challenge, la solution est juste le chemin.
1255
+ // For TSP, solution is the tour path array
1123
1256
  rawSolution = tspResult.path;
1124
1257
  break;
1125
1258
  case 'optimization_task':
1126
1259
  const finalPopulation = await solveOptimizationTask(optimizationTask.population, optimizationTask.generations);
1127
- rawSolution = finalPopulation.map(p => p.chromosome); // On ne renvoie que les chromosomes
1260
+ rawSolution = finalPopulation.map(p => p.chromosome); // Return chromosomes only
1128
1261
  break;
1129
1262
  case 'useful_work_task':
1130
1263
  const workResult = await solveUsefulWorkTask(usefulWorkTask.task);
@@ -1142,11 +1275,12 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
1142
1275
  return new ChallengeSolution(type, nonce, rawSolution);
1143
1276
  }
1144
1277
 
1145
- // --- Compatibilité pour l'injection directe dans le HTML ---
1146
- // Si le script est chargé dans un navigateur (window existe), on attache les fonctions nécessaires à window.
1278
+ // --- Direct HTML script injection compatibility ---
1279
+ // If loaded in browser context (window exists), expose methods on window.
1147
1280
  if (typeof window !== 'undefined') {
1148
1281
  window.solveCpuChallengeInline = solveCpuTargetInline;
1149
1282
  window.solveMemoryChallenge = solveMemory;
1283
+ window.readSpaceBlock = readSpaceBlock;
1150
1284
  window.solveTspChallenge = solveTsp;
1151
1285
  window.solveChallenge = solveChallenge;
1152
1286
  }