@anonympins/fingerprint 0.7.0 → 0.7.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +1 -1
  3. package/composer.json +8 -1
  4. package/package.json +1 -1
  5. package/public/fingerprint-wordpress.zip +0 -0
  6. package/src/js/build-client.js +8 -10
  7. package/src/js/fingerprint.builder.js +37 -38
  8. package/src/js/fingerprint.client.js +1531 -1463
  9. package/src/js/fingerprint.client.obfuscated.js +1 -0
  10. package/src/js/fingerprint.js +190 -16
  11. package/src/js/fingerprint.utils.js +213 -213
  12. package/src/js/library.js +340 -376
  13. package/src/js/mongodb-store.js +1 -1
  14. package/src/js/optimization.worker.js +27 -27
  15. package/src/js/pow.solver.inline.js +154 -56
  16. package/src/js/pow.solver.js +180 -92
  17. package/src/js/pow.worker.js +48 -48
  18. package/src/js/redis-store.js +1 -1
  19. package/src/js/tests/fingerprint.client.init.test.js +146 -143
  20. package/src/js/tests/fingerprint.test.js +124 -10
  21. package/src/js/tests/ip-reputation.test.js +1 -0
  22. package/src/js/upow-model-task.js +14 -15
  23. package/src/php/AutoTuner.php +34 -34
  24. package/src/php/Challenge/ChallengeUtils.php +57 -11
  25. package/src/php/Config/SecurityProfiles.php +20 -0
  26. package/src/php/DirectFingerprint.php +190 -145
  27. package/src/php/FingerprintBuilder.php +29 -30
  28. package/src/php/FingerprintClient.php +147 -148
  29. package/src/php/FingerprintEngine.php +44 -17
  30. package/src/php/Ja3AnomalyDetector.php +33 -32
  31. package/src/php/Optimization/FunctionRegistry.php +7 -7
  32. package/src/php/Optimization/Optimization.php +24 -24
  33. package/src/php/Optimization/OptimizationOperators.php +26 -25
  34. package/src/php/Optimization/ProblemInitializers.php +52 -52
  35. package/src/php/ProblemManager.php +31 -34
  36. package/src/php/RequestContext.php +8 -1
  37. package/src/php/Store/IStore.php +7 -8
  38. package/src/php/Store/InMemoryStore.php +66 -66
  39. package/src/php/Store/MongoDbStore.php +5 -5
  40. package/src/php/Store/RedisStore.php +5 -5
  41. package/src/php/Store/StoreManager.php +35 -35
  42. package/src/php/Tests/ChallengeUtilsTest.php +453 -453
  43. package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
  44. package/src/php/Tests/FingerprintEngineTest.php +38 -0
  45. package/src/php/Tests/IpReputationTest.php +10 -0
  46. package/src/php/Tests/MaliciousPatternsTest.php +103 -103
  47. package/src/php/Tests/MetricsTest.php +97 -97
  48. package/src/php/Tests/ProblemManagerTest.php +376 -376
  49. package/src/php/Tests/QuicFingerprintTest.php +53 -53
  50. package/src/php/Tests/RequestUtilsTest.php +471 -471
  51. package/src/php/Tests/TLSClientHelloParserTest.php +177 -177
  52. package/src/php/Tests/config/ed25519_key.json +3 -3
  53. package/src/php/Utils/BlockList.php +99 -99
  54. package/src/php/Utils/Env.php +49 -49
  55. package/src/php/Utils/MaliciousPatterns.php +74 -74
  56. package/src/php/Utils/MetricsManager.php +209 -209
  57. package/src/php/Utils/RequestUtils.php +129 -7
  58. package/src/php/Utils/TLSClientHelloParser.php +369 -369
  59. package/src/php/WordPress/WpDbStore.php +155 -0
  60. package/src/php/WordPress/fingerprint-wordpress.php +554 -0
  61. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
  62. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +262 -0
  63. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
  64. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +268 -0
  65. package/src/php/WordPress/package.php +218 -0
  66. package/src/php/bin/auto-tune.php +118 -118
@@ -49,7 +49,7 @@ export function createMongoDbStore(db, collectionName = 'fingerprint_store') {
49
49
  reconnecting = false;
50
50
  clearInterval(interval);
51
51
  } catch (err) {
52
- // Toujours déconnecté
52
+ // Connection still down, continue retry loop
53
53
  }
54
54
  }, 5000);
55
55
  if (interval.unref) interval.unref();
@@ -1,28 +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
- });
1
+ /**
2
+ * @file @/optimization.worker.js
3
+ * @description Generic Web Worker running optimization routines from `Optimization`.
4
+ * Runs on a dedicated worker thread to maintain main thread responsiveness.
5
+ */
6
+
7
+ import {parentPort, workerData} from 'worker_threads';
8
+ import {Optimization} from './library.js';
9
+
10
+ if (parentPort) {
11
+ parentPort.on('message', async () => { // Empty message payload; parameters loaded via workerData
12
+ const { solverName, solverArgs } = workerData;
13
+
14
+ // Resolve nested solver paths (e.g. '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
28
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @file @/pow.solver.inline.js
3
- * @description Contient les fonctions côté client pour résoudre les différents types de challenges Proof-of-Work.
4
- * Fichier compatible avec l'injection directe dans un script HTML (sans `export`).
3
+ * @description Client-side functions to solve various Proof-of-Work challenges.
4
+ * Compatible with direct HTML script injection (no `export`).
5
5
  */
6
6
 
7
7
  'use strict';
@@ -14,7 +14,94 @@ function secureRandom() {
14
14
  }
15
15
  return Math.random();
16
16
  }
17
+ function sha256Sync(bytes) {
18
+ if (bytes instanceof ArrayBuffer) {
19
+ bytes = new Uint8Array(bytes);
20
+ } else if (typeof bytes === 'string') {
21
+ bytes = new TextEncoder().encode(bytes);
22
+ } else if (ArrayBuffer.isView(bytes) && !(bytes instanceof Uint8Array)) {
23
+ bytes = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
24
+ }
25
+
26
+ var l = bytes.length;
27
+ var bitLen = l * 8;
28
+ var K = new Uint32Array([
29
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
30
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
31
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76ca1422,
32
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
33
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
34
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
35
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
36
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
37
+ ]);
38
+ var H = new Uint32Array([
39
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
40
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
41
+ ]);
42
+
43
+ // Standard SHA-256 padding
44
+ var padLen = (((l + 8) >> 6) + 1) << 6;
45
+ var buf = new Uint8Array(padLen);
46
+ buf.set(bytes);
47
+ buf[l] = 0x80;
48
+
49
+ var view = new DataView(buf.buffer);
50
+ // 64-bit big-endian length in bits (padLen - 8)
51
+ var highBitLen = Math.floor(bitLen / 0x100000000);
52
+ view.setUint32(padLen - 8, highBitLen >>> 0);
53
+ view.setUint32(padLen - 4, bitLen >>> 0);
54
+
55
+ var W = new Int32Array(64);
56
+ for (var i = 0; i < padLen; i += 64) {
57
+ for (var t = 0; t < 16; t++) {
58
+ // Explicit big-endian read (false)
59
+ W[t] = view.getInt32(i + t * 4, false);
60
+ }
61
+ for (var t = 16; t < 64; t++) {
62
+ var s0 = ((W[t - 15] >>> 7) | (W[t - 15] << 25)) ^ ((W[t - 15] >>> 18) | (W[t - 15] << 14)) ^ (W[t - 15] >>> 3);
63
+ var s1 = ((W[t - 2] >>> 17) | (W[t - 2] << 15)) ^ ((W[t - 2] >>> 19) | (W[t - 2] << 13)) ^ (W[t - 2] >>> 10);
64
+ W[t] = (W[t - 16] + s0 + W[t - 7] + s1) | 0;
65
+ }
66
+ 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];
67
+ for (var t = 0; t < 64; t++) {
68
+ var S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
69
+ var ch = (e & f) ^ ((~e) & g);
70
+ var temp1 = (h + S1 + ch + K[t] + W[t]) | 0;
71
+ var S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
72
+ var maj = (a & b) ^ (a & c) ^ (b & c);
73
+ var temp2 = (S0 + maj) | 0;
74
+ h = g; g = f; f = e; e = (d + temp1) | 0;
75
+ d = c; c = b; b = a; a = (temp1 + temp2) | 0;
76
+ }
77
+ H[0] = (H[0] + a) | 0;
78
+ H[1] = (H[1] + b) | 0;
79
+ H[2] = (H[2] + c) | 0;
80
+ H[3] = (H[3] + d) | 0;
81
+ H[4] = (H[4] + e) | 0;
82
+ H[5] = (H[5] + f) | 0;
83
+ H[6] = (H[6] + g) | 0;
84
+ H[7] = (H[7] + h) | 0;
85
+ }
86
+ var hex = '';
87
+ for (var i = 0; i < 8; i++) {
88
+ hex += (H[i] >>> 0).toString(16).padStart(8, '0');
89
+ }
90
+ return hex;
91
+ }
17
92
 
93
+ async function safeDigestSha256Hex(bytes) {
94
+ const cryptoObj = (typeof globalThis !== 'undefined' && globalThis.crypto) ? globalThis.crypto : (typeof window !== 'undefined' ? window.crypto : null);
95
+ if (cryptoObj && cryptoObj.subtle && typeof cryptoObj.subtle.digest === 'function') {
96
+ try {
97
+ const buf = await cryptoObj.subtle.digest("SHA-256", bytes);
98
+ return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
99
+ } catch (e) {
100
+ // Fallback on failure
101
+ }
102
+ }
103
+ return sha256Sync(bytes);
104
+ }
18
105
  function cyrb53(str, seed = 0) {
19
106
  const safeStr = (typeof str === 'string' ? str : String(str || '')).slice(0, 10000);
20
107
  let h1 = 0xdeadbeef ^ seed,
@@ -168,12 +255,11 @@ async function solveSpaceChallenge(seed, queries, nonce, clientSecret, peerBlock
168
255
  const finalBlock = new Uint8Array(finalCombined.length + nonceBytes.length);
169
256
  finalBlock.set(finalCombined);
170
257
  finalBlock.set(nonceBytes, finalCombined.length);
171
-
172
- const buf = await crypto.subtle.digest("SHA-256", finalBlock);
173
- return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
258
+
259
+ return await safeDigestSha256Hex(finalBlock);
174
260
  }
175
261
 
176
- // --- Fonctions d'optimisation copiées/adaptées de library.js pour le client ---
262
+ // --- Optimization functions adapted from library.js for client usage ---
177
263
 
178
264
  function paretoDominates(objectivesA, objectivesB) {
179
265
  let aIsBetterInOne = false;
@@ -736,11 +822,11 @@ async function solveOptimizationTask(initialPopulation, generations) {
736
822
  }
737
823
 
738
824
  /**
739
- * Résout un challenge CPU basé sur une cible en utilisant un bloc de base binaire.
740
- * @param {Uint8Array} baseBlock - Le bloc de données initial (nonce, secret, fp) fourni par le serveur.
741
- * @param {bigint} target - La cible à atteindre.
742
- * @param {Function} progressCallback - Callback pour les mises à jour de progression.
743
- * @returns {Promise<number>} La solution (un nombre entier).
825
+ * Solves a target-based CPU challenge using a binary base block.
826
+ * @param {Uint8Array} baseBlock - Initial data block (nonce, secret, fp) provided by the server.
827
+ * @param {bigint} target - Target difficulty to reach.
828
+ * @param {Function} progressCallback - Progress update callback.
829
+ * @returns {Promise<number>} Solution integer.
744
830
  */
745
831
  async function solveCpuTargetInline(baseBlock, target, progressCallback) {
746
832
  // --- FIX: Add validation for the target to prevent BigInt conversion errors ---
@@ -776,17 +862,18 @@ async function solveCpuTargetInline(baseBlock, target, progressCallback) {
776
862
  if (typeof window !== 'undefined' && typeof Worker !== 'undefined') {
777
863
  try {
778
864
  const staticWorkerPath = window.ClientLibrary?.workerPath || (window.ClientConfig && window.ClientConfig.workerPath);
865
+ const sha256FnStr = sha256Sync.toString();
779
866
  if (staticWorkerPath) {
780
867
  return await new Promise((resolve, reject) => {
781
868
  const worker = new Worker(staticWorkerPath);
782
869
  worker.onmessage = (event) => {
783
- if (event.data.type === 'progress') {
870
+ if (event.data && event.data.type === 'progress') {
784
871
  if (progressCallback) progressCallback(event.data.solution);
785
- } else if (event.data.type === 'success') {
872
+ } else if (event.data && event.data.type === 'success') {
786
873
  resolve(event.data.solution);
787
874
  worker.terminate();
788
- } else if (event.data.solution !== undefined) {
789
- // Fallback pour compatibilité avec l'ancienne signature
875
+ } else if (event.data && event.data.solution !== undefined) {
876
+ // Fallback for backward compatibility with previous signature
790
877
  resolve(event.data.solution);
791
878
  worker.terminate();
792
879
  }
@@ -804,15 +891,29 @@ async function solveCpuTargetInline(baseBlock, target, progressCallback) {
804
891
  const { baseBlock, target } = e.data;
805
892
  const cpuTarget = BigInt(target);
806
893
  const encoder = new TextEncoder();
807
- let cpuSolution = 0;
894
+ const hasSubtle = typeof self.crypto !== 'undefined' && self.crypto && self.crypto.subtle && typeof self.crypto.subtle.digest === 'function';
895
+ const sha256Fallback = ${sha256FnStr};
896
+ let cpuSolution = 0;
808
897
  while (true) {
809
898
  const solutionBytes = encoder.encode(String(cpuSolution));
810
899
  const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
811
900
  finalBlock.set(baseBlock);
812
901
  finalBlock.set(solutionBytes, baseBlock.length);
813
- const buf = await crypto.subtle.digest("SHA-256", finalBlock);
814
- const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
815
- if (BigInt('0x' + hashHex) < cpuTarget) break;
902
+ let hashHex = '';
903
+ if (hasSubtle) {
904
+ try {
905
+ const buf = await self.crypto.subtle.digest("SHA-256", finalBlock);
906
+ const arr = new Uint8Array(buf);
907
+ for (let j = 0; j < arr.length; j++) {
908
+ hashHex += arr[j].toString(16).padStart(2, '0');
909
+ }
910
+ } catch (subtleErr) {
911
+ hashHex = sha256Fallback(finalBlock);
912
+ }
913
+ } else {
914
+ hashHex = sha256Fallback(finalBlock);
915
+ }
916
+ if (BigInt('0x' + hashHex) < cpuTarget) break;
816
917
  cpuSolution++;
817
918
  if (cpuSolution % 25000 === 0) {
818
919
  self.postMessage({ type: 'progress', solution: cpuSolution });
@@ -847,24 +948,23 @@ async function solveCpuTargetInline(baseBlock, target, progressCallback) {
847
948
  while (true) {
848
949
  const solutionBytes = encoder.encode(String(cpuSolution));
849
950
 
850
- // Concaténation binaire directe : c'est plus rapide et plus sûr.
951
+ // Direct binary concatenation: faster and safer.
851
952
  const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
852
953
  finalBlock.set(baseBlock);
853
954
  finalBlock.set(solutionBytes, baseBlock.length);
854
955
 
855
956
  if (cpuSolution === 0) {
856
- // Pour le débogage, on peut afficher le message reconstruit.
957
+ // For debugging, print reconstructed message.
857
958
  const reconstructedMsg = new TextDecoder().decode(finalBlock);
858
959
  console.log(`[FP Client Solve] Hashing message: "${reconstructedMsg}"`);
859
960
  }
860
961
 
861
- const buf = await crypto.subtle.digest("SHA-256", finalBlock);
862
- const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
863
- // --- AJOUT DE LOGS POUR LE DÉBOGAGE CÔTÉ CLIENT ---
962
+ const hashHex = await safeDigestSha256Hex(finalBlock);
963
+ // --- CLIENT-SIDE DEBUG LOGS ---
864
964
  if (cpuSolution === 0) { // Log only the first attempt
865
965
  console.log(`[FP Client Solve] Attempt 0 hash: "0x${hashHex}"`);
866
966
  }
867
- // --- FIN DES LOGS ---
967
+ // --- END DEBUG LOGS ---
868
968
  if (BigInt('0x' + hashHex) < cpuTarget) break;
869
969
  cpuSolution++;
870
970
  if (cpuSolution % 50000 === 0) {
@@ -880,28 +980,28 @@ async function solveCpuTargetInline(baseBlock, target, progressCallback) {
880
980
  }
881
981
 
882
982
  /**
883
- * Résout un challenge CPU basé sur une cible (version Web Worker).
884
- * @param {string} message - Le message à hasher (ex: `ip:nonce:solution:secret`).
885
- * @param {bigint} target - La cible à atteindre.
886
- * @returns {Promise<number>} La solution (un nombre entier).
983
+ * Solves a target-based CPU challenge (Web Worker version).
984
+ * @param {string} message - Message to hash (e.g. `ip:nonce:solution:secret`).
985
+ * @param {bigint} target - Target difficulty to reach.
986
+ * @returns {Promise<number>} Solution integer.
887
987
  */
888
988
  async function solveCpuTarget(message, target) {
889
989
  // Vérifie si les Web Workers sont supportés par le navigateur.
890
990
  if (typeof(Worker) === "undefined") {
891
991
  console.warn("Web Workers not supported. Falling back to main thread calculation (UI may freeze).");
892
- // Ici, on pourrait remettre l'ancienne implémentation comme solution de secours.
893
- // Pour la clarté, nous supposons que les workers sont disponibles.
992
+ // An inline fallback implementation could be placed here if needed.
993
+ // For clarity, we assume workers are available.
894
994
  throw new Error("Web Worker support is required for CPU challenges.");
895
995
  }
896
996
 
897
997
  return new Promise((resolve, reject) => {
898
- // Crée un worker à partir du script dédié. Le chemin doit être accessible publiquement.
899
- // Assurez-vous que `pow.worker.js` est servi par votre serveur statique.
998
+ // Create worker from dedicated script. The path must be publicly accessible.
999
+ // Ensure `pow.worker.js` is served by your static file server.
900
1000
  const worker = new Worker('./pow.worker.js');
901
1001
 
902
1002
  worker.onmessage = (event) => {
903
1003
  resolve(event.data.solution);
904
- worker.terminate(); // Nettoie le worker une fois le travail terminé.
1004
+ worker.terminate(); // Clean up worker once finished.
905
1005
  };
906
1006
 
907
1007
  worker.onerror = (error) => {
@@ -909,16 +1009,16 @@ async function solveCpuTarget(message, target) {
909
1009
  worker.terminate();
910
1010
  };
911
1011
 
912
- // Envoie les données du challenge au worker pour qu'il commence le calcul.
1012
+ // Send challenge data to worker to begin computation.
913
1013
  worker.postMessage({ message, target });
914
1014
  });
915
1015
  }
916
1016
 
917
1017
  /**
918
- * Résout un challenge basé sur la mémoire.
919
- * @param {string} seed - La graine pour l'initialisation de la mémoire.
920
- * @param {number} difficulty - La difficulté (en Mo).
921
- * @returns {Promise<number>} La solution (nombre entier).
1018
+ * Solves a memory-hard challenge.
1019
+ * @param {string} seed - Seed for memory initialization.
1020
+ * @param {number} difficulty - Difficulty in MB.
1021
+ * @returns {Promise<number>} Solution integer.
922
1022
  */
923
1023
  async function solveMemory(seed, difficulty) {
924
1024
  const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
@@ -940,8 +1040,7 @@ async function solveMemory(seed, difficulty) {
940
1040
  if (numBlocks === 0) return { solution: 0, merkleRoot: '', proofs: {} };
941
1041
 
942
1042
  async function hashBlock(block) {
943
- const buf = await crypto.subtle.digest("SHA-256", block.buffer);
944
- return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
1043
+ return await safeDigestSha256Hex(block.buffer);
945
1044
  }
946
1045
 
947
1046
  function hexToBytes(hex) {
@@ -972,8 +1071,7 @@ async function solveMemory(seed, difficulty) {
972
1071
  const left = currentLayer[i];
973
1072
  const right = currentLayer[i + 1] || left;
974
1073
  const combined = hexToBytes(left + right);
975
- const hashBuf = await crypto.subtle.digest("SHA-256", combined);
976
- const hashHex = Array.from(new Uint8Array(hashBuf)).map(b => b.toString(16).padStart(2, '0')).join('');
1074
+ const hashHex = await safeDigestSha256Hex(combined);
977
1075
  nextLayer.push(hashHex);
978
1076
  }
979
1077
  tree.push(nextLayer);
@@ -1022,12 +1120,12 @@ async function solveMemory(seed, difficulty) {
1022
1120
  }
1023
1121
 
1024
1122
  /**
1025
- * Résout un challenge de type "Problème du Voyageur de Commerce" (TSP).
1026
- * NOTE: Ceci est une implémentation simple (heuristique du plus proche voisin) et n'est pas garantie
1027
- * de trouver la solution optimale, mais elle est suffisante pour un challenge.
1028
- * @param {Array<{x: number, y: number}>} cities - Les coordonnées des villes.
1029
- * @param {number} targetMaxDistance - La distance maximale acceptable.
1030
- * @returns {Promise<{path: number[], distance: number}>} Le chemin et la distance.
1123
+ * Solves a Traveling Salesperson Problem (TSP) challenge.
1124
+ * NOTE: Uses a nearest-neighbor heuristic which may not find the global optimum,
1125
+ * but is sufficient for verification challenges.
1126
+ * @param {Array<{x: number, y: number}>} cities - City coordinates.
1127
+ * @param {number} targetMaxDistance - Maximum acceptable distance.
1128
+ * @returns {Promise<{path: number[], distance: number}>} Tour path and distance.
1031
1129
  */
1032
1130
  async function solveTsp(cities, targetMaxDistance) {
1033
1131
  // Utility function to calculate the distance between two cities
@@ -1086,9 +1184,9 @@ async function solveTsp(cities, targetMaxDistance) {
1086
1184
  }
1087
1185
 
1088
1186
  /**
1089
- * Fonction principale qui reçoit un objet challenge et le résout.
1090
- * @param {object} challenge - L'objet challenge reçu du serveur.
1091
- * @returns {Promise<object>} Un objet contenant la ou les solutions.
1187
+ * Main entry point to resolve a received challenge payload.
1188
+ * @param {object} challenge - Challenge payload received from the server.
1189
+ * @returns {Promise<object>} Object containing solution(s).
1092
1190
  */
1093
1191
  async function solveChallenge(challenge, fingerprint = '') { // fingerprint parameter was already here, but unused in some calls
1094
1192
  const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance, queries, sizeMb } = challenge;
@@ -1114,7 +1212,7 @@ async function solveChallenge(challenge, fingerprint = '') { // fingerprint para
1114
1212
  solutions.cpu = await solveCpuTargetInline(baseBlockBytes, target, null);
1115
1213
  break;
1116
1214
  case 'cpu_mem':
1117
- // Pour les appels API, le client IP n'est pas connu, on ne le met pas dans le message
1215
+ // For API calls, client IP is omitted from client-side message reconstruction
1118
1216
  const memSeed = `:${nonce}:${clientSecret}`;
1119
1217
  const [cpuSol, memSol] = await Promise.all([
1120
1218
  (async () => {
@@ -1128,7 +1226,7 @@ async function solveChallenge(challenge, fingerprint = '') { // fingerprint para
1128
1226
  solutions.mem = memSol;
1129
1227
  break;
1130
1228
  case 'cpu_mem_inline':
1131
- // Version inline pour compatibilité HTML avec IP incluse
1229
+ // Inline version for HTML compatibility with IP
1132
1230
  const memSeedInline = `:${nonce}:${clientSecret}`;
1133
1231
  const [cpuSolInline, memSolInline] = await Promise.all([
1134
1232
  (async () => {
@@ -1157,8 +1255,8 @@ async function solveChallenge(challenge, fingerprint = '') { // fingerprint para
1157
1255
  return solutions;
1158
1256
  }
1159
1257
 
1160
- // --- Compatibilité pour l'injection directe dans le HTML ---
1161
- // Si le script est chargé dans un navigateur (window existe), on attache les fonctions nécessaires à window.
1258
+ // --- Direct HTML script injection compatibility ---
1259
+ // If loaded in browser context (window exists), expose methods on window.
1162
1260
  if (typeof window !== 'undefined') {
1163
1261
  window.solveCpuChallengeInline = solveCpuTargetInline;
1164
1262
  window.solveMemoryChallenge = solveMemory;