@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
@@ -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,20 +1,107 @@
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';
8
8
 
9
9
  function secureRandom() {
10
- if (typeof window !== 'undefined' && (window.crypto || window.msCrypto)) {
10
+ if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.getRandomValues) {
11
11
  const array = new Uint32Array(1);
12
- (window.crypto || window.msCrypto).getRandomValues(array);
13
- return array[0] / 0x100000000;
12
+ globalThis.crypto.getRandomValues(array);
13
+ return array[0] / 0xffffffff;
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,
@@ -53,6 +140,25 @@ function generateBlock(seed, blockIndex, blockSize = 1024) {
53
140
  return block;
54
141
  }
55
142
 
143
+ async function readSpaceBlock(blockIdx) {
144
+ const db = await openDb();
145
+ const transaction = db.transaction("blocks", "readonly");
146
+ const store = transaction.objectStore("blocks");
147
+ const getReq = store.get(blockIdx);
148
+ return new Promise((resolve, reject) => {
149
+ getReq.onsuccess = () => {
150
+ const block = getReq.result;
151
+ if (block) {
152
+ const hex = Array.from(block).map(b => b.toString(16).padStart(2, '0')).join('');
153
+ resolve(hex);
154
+ } else {
155
+ reject(new Error("Block not found"));
156
+ }
157
+ };
158
+ getReq.onerror = () => reject(getReq.error);
159
+ });
160
+ }
161
+
56
162
  async function loadTfjs() {
57
163
  if (typeof tf !== 'undefined') return tf;
58
164
  if (typeof window !== 'undefined') {
@@ -149,12 +255,11 @@ async function solveSpaceChallenge(seed, queries, nonce, clientSecret, peerBlock
149
255
  const finalBlock = new Uint8Array(finalCombined.length + nonceBytes.length);
150
256
  finalBlock.set(finalCombined);
151
257
  finalBlock.set(nonceBytes, finalCombined.length);
152
-
153
- const buf = await crypto.subtle.digest("SHA-256", finalBlock);
154
- return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
258
+
259
+ return await safeDigestSha256Hex(finalBlock);
155
260
  }
156
261
 
157
- // --- Fonctions d'optimisation copiées/adaptées de library.js pour le client ---
262
+ // --- Optimization functions adapted from library.js for client usage ---
158
263
 
159
264
  function paretoDominates(objectivesA, objectivesB) {
160
265
  let aIsBetterInOne = false;
@@ -475,7 +580,7 @@ const ClientOptimizers = {
475
580
 
476
581
  const mutate = (solution) => {
477
582
  const newSolution = [...solution];
478
- const i = Math.floor(Math.random() * 4);
583
+ const i = Math.floor(secureRandom() * 4);
479
584
  const mutationFactors = [500, 1000, 0.1, 2];
480
585
  newSolution[i] += (secureRandom() - 0.5) * mutationFactors[i];
481
586
  return newSolution;
@@ -717,11 +822,11 @@ async function solveOptimizationTask(initialPopulation, generations) {
717
822
  }
718
823
 
719
824
  /**
720
- * Résout un challenge CPU basé sur une cible en utilisant un bloc de base binaire.
721
- * @param {Uint8Array} baseBlock - Le bloc de données initial (nonce, secret, fp) fourni par le serveur.
722
- * @param {bigint} target - La cible à atteindre.
723
- * @param {Function} progressCallback - Callback pour les mises à jour de progression.
724
- * @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.
725
830
  */
726
831
  async function solveCpuTargetInline(baseBlock, target, progressCallback) {
727
832
  // --- FIX: Add validation for the target to prevent BigInt conversion errors ---
@@ -741,33 +846,74 @@ async function solveCpuTargetInline(baseBlock, target, progressCallback) {
741
846
  const targetStr = typeof target === 'string' ? target : target.toString(16);
742
847
  const targetPtr = wasmModule._malloc(targetStr.length + 1);
743
848
  for (let i = 0; i < targetStr.length; i++) {
744
- wasmModule.HEAP8[targetPtr + i] = targetStr.charCodeAt(i);
849
+ wasmModule.HEAPU8[targetPtr + i] = targetStr.charCodeAt(i);
850
+ }
851
+ wasmModule.HEAPU8[targetPtr + targetStr.length] = 0; // Null-terminator
852
+ try {
853
+ const solution = wasmModule._solve_cpu_target(ptr, len, targetPtr);
854
+ return solution;
855
+ } finally {
856
+ wasmModule._free(ptr);
857
+ wasmModule._free(targetPtr);
745
858
  }
746
- wasmModule.HEAP8[targetPtr + targetStr.length] = 0;
747
- const solution = wasmModule._solve_cpu_target(ptr, len, targetPtr);
748
- wasmModule._free(ptr);
749
- wasmModule._free(targetPtr);
750
- return solution;
751
859
  }
752
860
 
753
861
  // Try Web Worker execution first if supported and not blocked by CSP
754
862
  if (typeof window !== 'undefined' && typeof Worker !== 'undefined') {
755
863
  try {
864
+ const staticWorkerPath = window.ClientLibrary?.workerPath || (window.ClientConfig && window.ClientConfig.workerPath);
865
+ const sha256FnStr = sha256Sync.toString();
866
+ if (staticWorkerPath) {
867
+ return await new Promise((resolve, reject) => {
868
+ const worker = new Worker(staticWorkerPath);
869
+ worker.onmessage = (event) => {
870
+ if (event.data && event.data.type === 'progress') {
871
+ if (progressCallback) progressCallback(event.data.solution);
872
+ } else if (event.data && event.data.type === 'success') {
873
+ resolve(event.data.solution);
874
+ worker.terminate();
875
+ } else if (event.data && event.data.solution !== undefined) {
876
+ // Fallback for backward compatibility with previous signature
877
+ resolve(event.data.solution);
878
+ worker.terminate();
879
+ }
880
+ };
881
+ worker.onerror = (err) => {
882
+ worker.terminate();
883
+ reject(err);
884
+ };
885
+ worker.postMessage({ baseBlock, target: cpuTarget.toString() });
886
+ });
887
+ }
756
888
  return await new Promise((resolve, reject) => {
757
889
  const workerCode = `
758
890
  self.onmessage = async (e) => {
759
891
  const { baseBlock, target } = e.data;
760
892
  const cpuTarget = BigInt(target);
761
893
  const encoder = new TextEncoder();
762
- 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;
763
897
  while (true) {
764
898
  const solutionBytes = encoder.encode(String(cpuSolution));
765
899
  const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
766
900
  finalBlock.set(baseBlock);
767
901
  finalBlock.set(solutionBytes, baseBlock.length);
768
- const buf = await crypto.subtle.digest("SHA-256", finalBlock);
769
- const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
770
- 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;
771
917
  cpuSolution++;
772
918
  if (cpuSolution % 25000 === 0) {
773
919
  self.postMessage({ type: 'progress', solution: cpuSolution });
@@ -802,24 +948,23 @@ async function solveCpuTargetInline(baseBlock, target, progressCallback) {
802
948
  while (true) {
803
949
  const solutionBytes = encoder.encode(String(cpuSolution));
804
950
 
805
- // Concaténation binaire directe : c'est plus rapide et plus sûr.
951
+ // Direct binary concatenation: faster and safer.
806
952
  const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
807
953
  finalBlock.set(baseBlock);
808
954
  finalBlock.set(solutionBytes, baseBlock.length);
809
955
 
810
956
  if (cpuSolution === 0) {
811
- // Pour le débogage, on peut afficher le message reconstruit.
957
+ // For debugging, print reconstructed message.
812
958
  const reconstructedMsg = new TextDecoder().decode(finalBlock);
813
959
  console.log(`[FP Client Solve] Hashing message: "${reconstructedMsg}"`);
814
960
  }
815
961
 
816
- const buf = await crypto.subtle.digest("SHA-256", finalBlock);
817
- const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
818
- // --- AJOUT DE LOGS POUR LE DÉBOGAGE CÔTÉ CLIENT ---
962
+ const hashHex = await safeDigestSha256Hex(finalBlock);
963
+ // --- CLIENT-SIDE DEBUG LOGS ---
819
964
  if (cpuSolution === 0) { // Log only the first attempt
820
965
  console.log(`[FP Client Solve] Attempt 0 hash: "0x${hashHex}"`);
821
966
  }
822
- // --- FIN DES LOGS ---
967
+ // --- END DEBUG LOGS ---
823
968
  if (BigInt('0x' + hashHex) < cpuTarget) break;
824
969
  cpuSolution++;
825
970
  if (cpuSolution % 50000 === 0) {
@@ -835,28 +980,28 @@ async function solveCpuTargetInline(baseBlock, target, progressCallback) {
835
980
  }
836
981
 
837
982
  /**
838
- * Résout un challenge CPU basé sur une cible (version Web Worker).
839
- * @param {string} message - Le message à hasher (ex: `ip:nonce:solution:secret`).
840
- * @param {bigint} target - La cible à atteindre.
841
- * @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.
842
987
  */
843
988
  async function solveCpuTarget(message, target) {
844
989
  // Vérifie si les Web Workers sont supportés par le navigateur.
845
990
  if (typeof(Worker) === "undefined") {
846
991
  console.warn("Web Workers not supported. Falling back to main thread calculation (UI may freeze).");
847
- // Ici, on pourrait remettre l'ancienne implémentation comme solution de secours.
848
- // 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.
849
994
  throw new Error("Web Worker support is required for CPU challenges.");
850
995
  }
851
996
 
852
997
  return new Promise((resolve, reject) => {
853
- // Crée un worker à partir du script dédié. Le chemin doit être accessible publiquement.
854
- // 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.
855
1000
  const worker = new Worker('./pow.worker.js');
856
1001
 
857
1002
  worker.onmessage = (event) => {
858
1003
  resolve(event.data.solution);
859
- worker.terminate(); // Nettoie le worker une fois le travail terminé.
1004
+ worker.terminate(); // Clean up worker once finished.
860
1005
  };
861
1006
 
862
1007
  worker.onerror = (error) => {
@@ -864,16 +1009,16 @@ async function solveCpuTarget(message, target) {
864
1009
  worker.terminate();
865
1010
  };
866
1011
 
867
- // Envoie les données du challenge au worker pour qu'il commence le calcul.
1012
+ // Send challenge data to worker to begin computation.
868
1013
  worker.postMessage({ message, target });
869
1014
  });
870
1015
  }
871
1016
 
872
1017
  /**
873
- * Résout un challenge basé sur la mémoire.
874
- * @param {string} seed - La graine pour l'initialisation de la mémoire.
875
- * @param {number} difficulty - La difficulté (en Mo).
876
- * @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.
877
1022
  */
878
1023
  async function solveMemory(seed, difficulty) {
879
1024
  const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
@@ -895,8 +1040,7 @@ async function solveMemory(seed, difficulty) {
895
1040
  if (numBlocks === 0) return { solution: 0, merkleRoot: '', proofs: {} };
896
1041
 
897
1042
  async function hashBlock(block) {
898
- const buf = await crypto.subtle.digest("SHA-256", block.buffer);
899
- return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
1043
+ return await safeDigestSha256Hex(block.buffer);
900
1044
  }
901
1045
 
902
1046
  function hexToBytes(hex) {
@@ -927,8 +1071,7 @@ async function solveMemory(seed, difficulty) {
927
1071
  const left = currentLayer[i];
928
1072
  const right = currentLayer[i + 1] || left;
929
1073
  const combined = hexToBytes(left + right);
930
- const hashBuf = await crypto.subtle.digest("SHA-256", combined);
931
- const hashHex = Array.from(new Uint8Array(hashBuf)).map(b => b.toString(16).padStart(2, '0')).join('');
1074
+ const hashHex = await safeDigestSha256Hex(combined);
932
1075
  nextLayer.push(hashHex);
933
1076
  }
934
1077
  tree.push(nextLayer);
@@ -977,12 +1120,12 @@ async function solveMemory(seed, difficulty) {
977
1120
  }
978
1121
 
979
1122
  /**
980
- * Résout un challenge de type "Problème du Voyageur de Commerce" (TSP).
981
- * NOTE: Ceci est une implémentation simple (heuristique du plus proche voisin) et n'est pas garantie
982
- * de trouver la solution optimale, mais elle est suffisante pour un challenge.
983
- * @param {Array<{x: number, y: number}>} cities - Les coordonnées des villes.
984
- * @param {number} targetMaxDistance - La distance maximale acceptable.
985
- * @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.
986
1129
  */
987
1130
  async function solveTsp(cities, targetMaxDistance) {
988
1131
  // Utility function to calculate the distance between two cities
@@ -1041,9 +1184,9 @@ async function solveTsp(cities, targetMaxDistance) {
1041
1184
  }
1042
1185
 
1043
1186
  /**
1044
- * Fonction principale qui reçoit un objet challenge et le résout.
1045
- * @param {object} challenge - L'objet challenge reçu du serveur.
1046
- * @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).
1047
1190
  */
1048
1191
  async function solveChallenge(challenge, fingerprint = '') { // fingerprint parameter was already here, but unused in some calls
1049
1192
  const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance, queries, sizeMb } = challenge;
@@ -1069,7 +1212,7 @@ async function solveChallenge(challenge, fingerprint = '') { // fingerprint para
1069
1212
  solutions.cpu = await solveCpuTargetInline(baseBlockBytes, target, null);
1070
1213
  break;
1071
1214
  case 'cpu_mem':
1072
- // 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
1073
1216
  const memSeed = `:${nonce}:${clientSecret}`;
1074
1217
  const [cpuSol, memSol] = await Promise.all([
1075
1218
  (async () => {
@@ -1083,7 +1226,7 @@ async function solveChallenge(challenge, fingerprint = '') { // fingerprint para
1083
1226
  solutions.mem = memSol;
1084
1227
  break;
1085
1228
  case 'cpu_mem_inline':
1086
- // Version inline pour compatibilité HTML avec IP incluse
1229
+ // Inline version for HTML compatibility with IP
1087
1230
  const memSeedInline = `:${nonce}:${clientSecret}`;
1088
1231
  const [cpuSolInline, memSolInline] = await Promise.all([
1089
1232
  (async () => {
@@ -1112,11 +1255,12 @@ async function solveChallenge(challenge, fingerprint = '') { // fingerprint para
1112
1255
  return solutions;
1113
1256
  }
1114
1257
 
1115
- // --- Compatibilité pour l'injection directe dans le HTML ---
1116
- // 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.
1117
1260
  if (typeof window !== 'undefined') {
1118
1261
  window.solveCpuChallengeInline = solveCpuTargetInline;
1119
1262
  window.solveMemoryChallenge = solveMemory;
1263
+ window.readSpaceBlock = readSpaceBlock;
1120
1264
  window.solveTspChallenge = solveTsp;
1121
1265
  window.solveChallenge = solveChallenge;
1122
1266
  }