@anonympins/fingerprint 0.4.4 → 0.4.6

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.
@@ -1,286 +1,382 @@
1
- /**
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`).
5
- */
6
-
7
- 'use strict';
8
-
9
- /**
10
- * Résout un challenge CPU basé sur une cible en utilisant un bloc de base binaire.
11
- * @param {Uint8Array} baseBlock - Le bloc de données initial (nonce, secret, fp) fourni par le serveur.
12
- * @param {bigint} target - La cible à atteindre.
13
- * @param {Function} progressCallback - Callback pour les mises à jour de progression.
14
- * @returns {Promise<number>} La solution (un nombre entier).
15
- */
16
- async function solveCpuTargetInline(baseBlock, target, progressCallback) {
17
- // --- FIX: Add validation for the target to prevent BigInt conversion errors ---
18
- if (typeof target !== 'bigint' && (typeof target !== 'string' || !/^[0-9a-fA-F]+$/.test(target))) {
19
- throw new TypeError(`Invalid target type: expected a BigInt or a hex string, but got ${typeof target} with value ${target}`);
20
- }
21
-
22
- const cpuTarget = typeof target === 'bigint' ? target : BigInt('0x' + target);
23
- // --- END FIX ---
24
- const encoder = new TextEncoder();
25
-
26
- const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
27
- if (wasmModule && typeof wasmModule._solve_cpu_target === 'function') {
28
- const len = baseBlock.length;
29
- const ptr = wasmModule._malloc(len);
30
- wasmModule.HEAPU8.set(baseBlock, ptr);
31
- const targetStr = typeof target === 'string' ? target : target.toString(16);
32
- const targetPtr = wasmModule._malloc(targetStr.length + 1);
33
- for (let i = 0; i < targetStr.length; i++) {
34
- wasmModule.HEAP8[targetPtr + i] = targetStr.charCodeAt(i);
35
- }
36
- wasmModule.HEAP8[targetPtr + targetStr.length] = 0;
37
- const solution = wasmModule._solve_cpu_target(ptr, len, targetPtr);
38
- wasmModule._free(ptr);
39
- wasmModule._free(targetPtr);
40
- return solution;
41
- }
42
-
43
- let cpuSolution = 0;
44
-
45
- while (true) {
46
- const solutionBytes = encoder.encode(String(cpuSolution));
47
-
48
- // Concaténation binaire directe : c'est plus rapide et plus sûr.
49
- const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
50
- finalBlock.set(baseBlock);
51
- finalBlock.set(solutionBytes, baseBlock.length);
52
-
53
- if (cpuSolution === 0) {
54
- // Pour le débogage, on peut afficher le message reconstruit.
55
- const reconstructedMsg = new TextDecoder().decode(finalBlock);
56
- console.log(`[FP Client Solve] Hashing message: "${reconstructedMsg}"`);
57
- }
58
-
59
- const buf = await crypto.subtle.digest("SHA-256", finalBlock);
60
- const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
61
- // --- AJOUT DE LOGS POUR LE DÉBOGAGE CÔTÉ CLIENT ---
62
- if (cpuSolution === 0) { // Log only the first attempt
63
- console.log(`[FP Client Solve] Attempt 0 hash: "0x${hashHex}"`);
64
- }
65
- // --- FIN DES LOGS ---
66
- if (BigInt('0x' + hashHex) < cpuTarget) break;
67
- cpuSolution++;
68
- if (cpuSolution % 100000 === 0) {
69
- await new Promise(r => setTimeout(r, 0));
70
- if (progressCallback) progressCallback(cpuSolution);
71
- }
72
- }
73
- return cpuSolution;
74
- }
75
-
76
- /**
77
- * Résout un challenge CPU basé sur une cible (version Web Worker).
78
- * @param {string} message - Le message à hasher (ex: `ip:nonce:solution:secret`).
79
- * @param {bigint} target - La cible à atteindre.
80
- * @returns {Promise<number>} La solution (un nombre entier).
81
- */
82
- async function solveCpuTarget(message, target) {
83
- // Vérifie si les Web Workers sont supportés par le navigateur.
84
- if (typeof(Worker) === "undefined") {
85
- console.warn("Web Workers not supported. Falling back to main thread calculation (UI may freeze).");
86
- // Ici, on pourrait remettre l'ancienne implémentation comme solution de secours.
87
- // Pour la clarté, nous supposons que les workers sont disponibles.
88
- throw new Error("Web Worker support is required for CPU challenges.");
89
- }
90
-
91
- return new Promise((resolve, reject) => {
92
- // Crée un worker à partir du script dédié. Le chemin doit être accessible publiquement.
93
- // Assurez-vous que `pow.worker.js` est servi par votre serveur statique.
94
- const worker = new Worker('./pow.worker.js');
95
-
96
- worker.onmessage = (event) => {
97
- resolve(event.data.solution);
98
- worker.terminate(); // Nettoie le worker une fois le travail terminé.
99
- };
100
-
101
- worker.onerror = (error) => {
102
- reject(error);
103
- worker.terminate();
104
- };
105
-
106
- // Envoie les données du challenge au worker pour qu'il commence le calcul.
107
- worker.postMessage({ message, target });
108
- });
109
- }
110
-
111
- /**
112
- * Résout un challenge basé sur la mémoire.
113
- * @param {string} seed - La graine pour l'initialisation de la mémoire.
114
- * @param {number} difficulty - La difficulté (en Mo).
115
- * @returns {Promise<number>} La solution (nombre entier).
116
- */
117
- async function solveMemory(seed, difficulty) {
118
- // On définit un seuil pour savoir quand faire une pause, afin de ne pas bloquer le thread UI.
119
- const YIELD_THRESHOLD = 100000;
120
- const size = difficulty * 1024 * 1024;
121
- const buffer = new Uint32Array(size / 4);
122
-
123
- const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
124
- if (wasmModule && typeof wasmModule._solve_memory_challenge === 'function') {
125
- const seedPtr = wasmModule._malloc(seed.length + 1);
126
- for (let i = 0; i < seed.length; i++) {
127
- wasmModule.HEAP8[seedPtr + i] = seed.charCodeAt(i);
128
- }
129
- wasmModule.HEAP8[seedPtr + seed.length] = 0;
130
- const solution = wasmModule._solve_memory_challenge(seedPtr, difficulty);
131
- wasmModule._free(seedPtr);
132
- return solution;
133
- }
134
-
135
- let h = new TextEncoder().encode(seed).reduce((acc, v) => acc + v, 0);
136
- for (let i = 0; i < buffer.length; i++) {
137
- buffer[i] = (h = Math.imul(h ^ i, 1597334677));
138
- if (i % YIELD_THRESHOLD === 0) {
139
- await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
140
- }
141
- }
142
- let solution = 0;
143
- const iterations = size / 16;
144
- let addr = buffer.length > 0 ? buffer[0] % buffer.length : 0;
145
- for (let i = 0; i < iterations; i++) {
146
- addr = buffer[addr] % buffer.length;
147
- solution ^= addr;
148
- if (i % YIELD_THRESHOLD === 0) {
149
- await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
150
- }
151
- }
152
- return solution;
153
- }
154
-
155
- /**
156
- * Résout un challenge de type "Problème du Voyageur de Commerce" (TSP).
157
- * NOTE: Ceci est une implémentation simple (heuristique du plus proche voisin) et n'est pas garantie
158
- * de trouver la solution optimale, mais elle est suffisante pour un challenge.
159
- * @param {Array<{x: number, y: number}>} cities - Les coordonnées des villes.
160
- * @param {number} targetMaxDistance - La distance maximale acceptable.
161
- * @returns {Promise<{path: number[], distance: number}>} Le chemin et la distance.
162
- */
163
- async function solveTsp(cities, targetMaxDistance) {
164
- // Utility function to calculate the distance between two cities
165
- function distance(city1, city2) {
166
- return Math.sqrt(Math.pow(city1.x - city2.x, 2) + Math.pow(city1.y - city2.y, 2));
167
- }
168
-
169
- // Utility function to evaluate the total distance of a path
170
- function evaluatePathDistance(cities, path) {
171
- let totalDistance = 0;
172
- for (let i = 0; i < path.length - 1; i++) {
173
- totalDistance += distance(cities[path[i]], cities[path[i + 1]]);
174
- }
175
- totalDistance += distance(cities[path[path.length - 1]], cities[path[0]]); // Return to start
176
- return totalDistance;
177
- }
178
-
179
- // Solveur simple du TSP (heuristique du plus proche voisin)
180
- function solveTspNearestNeighbor(cities) {
181
- const numCities = cities.length;
182
- if (numCities === 0) return [];
183
-
184
- let currentPath = [];
185
- let visited = new Array(numCities).fill(false);
186
-
187
- let currentCityIndex = 0; // Always start with the first city for reproducibility
188
- currentPath.push(currentCityIndex);
189
- visited[currentCityIndex] = true;
190
-
191
- for (let i = 1; i < numCities; i++) {
192
- let nearestCityIndex = -1;
193
- let minDistance = Infinity;
194
-
195
- for (let j = 0; j < numCities; j++) {
196
- if (!visited[j]) {
197
- const dist = distance(cities[currentCityIndex], cities[j]);
198
- if (dist < minDistance) {
199
- minDistance = dist;
200
- nearestCityIndex = j;
201
- }
202
- }
203
- }
204
- currentCityIndex = nearestCityIndex;
205
- currentPath.push(currentCityIndex);
206
- visited[currentCityIndex] = true;
207
- }
208
- return currentPath;
209
- }
210
-
211
- // To avoid freezing the browser, yield the thread from time to time
212
- await new Promise(resolve => setTimeout(resolve, 10));
213
- const solutionPath = solveTspNearestNeighbor(cities);
214
- const solutionDistance = evaluatePathDistance(cities, solutionPath);
215
-
216
- return { path: solutionPath, distance: solutionDistance };
217
- }
218
-
219
- /**
220
- * Fonction principale qui reçoit un objet challenge et le résout.
221
- * @param {object} challenge - L'objet challenge reçu du serveur.
222
- * @returns {Promise<object>} Un objet contenant la ou les solutions.
223
- */
224
- async function solveChallenge(challenge, fingerprint = '') { // fingerprint parameter was already here, but unused in some calls
225
- const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance } = challenge;
226
- const solutions = {};
227
-
228
- switch (type) {
229
- case 'cpu_target':
230
- // Note: This case is not fully exercised by tests as it relies on Web Workers.
231
- if (!cpuTarget) {
232
- throw new Error("Challenge data is missing 'cpuTarget' property.");
233
- }
234
- const target = cpuTarget; // Keep variable name for consistency below
235
- // Pour ce challenge simple, le baseBlock est juste le nonce.
236
- const baseBlockBytes = new TextEncoder().encode(nonce + ":");
237
- solutions.cpu = await solveCpuTargetInline(baseBlockBytes, target, null);
238
- break;
239
- case 'cpu_mem':
240
- // Pour les appels API, le client IP n'est pas connu, on ne le met pas dans le message
241
- const memSeed = `:${nonce}:${clientSecret}`;
242
- const [cpuSol, memSol] = await Promise.all([
243
- (async () => {
244
- if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property."); // Pass fingerprint to solver
245
- const baseBlock = new Uint8Array(challenge.baseBlock);
246
- return solveCpuTargetInline(baseBlock, cpuTarget, null);
247
- })(),
248
- solveMemory(memSeed, memDifficulty)
249
- ]);
250
- solutions.cpu = cpuSol;
251
- solutions.mem = memSol;
252
- break;
253
- case 'cpu_mem_inline':
254
- // Version inline pour compatibilité HTML avec IP incluse
255
- const memSeedInline = `:${nonce}:${clientSecret}`;
256
- const [cpuSolInline, memSolInline] = await Promise.all([
257
- (async () => {
258
- if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property.");
259
- const baseBlock = new Uint8Array(challenge.baseBlock);
260
- return solveCpuTargetInline(baseBlock, cpuTarget, null);
261
- })(),
262
- solveMemory(memSeedInline, memDifficulty)
263
- ]);
264
- solutions.cpu = cpuSolInline;
265
- solutions.mem = memSolInline;
266
- break;
267
- case 'tsp':
268
- const tspResult = await solveTsp(cities, targetMaxDistance);
269
- solutions.tsp = tspResult.path;
270
- solutions.distance = tspResult.distance;
271
- break;
272
- default:
273
- throw new Error(`Unknown challenge type: ${type}`);
274
- }
275
-
276
- return solutions;
277
- }
278
-
279
- // --- Compatibilité pour l'injection directe dans le HTML ---
280
- // Si le script est chargé dans un navigateur (window existe), on attache les fonctions nécessaires à window.
281
- if (typeof window !== 'undefined') {
282
- window.solveCpuChallengeInline = solveCpuTargetInline;
283
- window.solveMemoryChallenge = solveMemory;
284
- window.solveTspChallenge = solveTsp;
285
- window.solveChallenge = solveChallenge;
1
+ /**
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`).
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ function cyrb53(str, seed = 0) {
10
+ let h1 = 0xdeadbeef ^ seed,
11
+ h2 = 0x41c6ce57 ^ seed;
12
+ for (let i = 0, ch; i < str.length; i++) {
13
+ ch = str.charCodeAt(i);
14
+ h1 = Math.imul(h1 ^ ch, 2654435761);
15
+ h2 = Math.imul(h2 ^ ch, 1597334677);
16
+ }
17
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
18
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
19
+ return 4294967296 * (2097151 & h2) + (h1 >>> 0);
20
+ }
21
+
22
+ function openDb() {
23
+ return new Promise((resolve, reject) => {
24
+ const request = indexedDB.open("pospace-db", 1);
25
+ request.onupgradeneeded = (e) => {
26
+ const db = e.target.result;
27
+ if (!db.objectStoreNames.contains("blocks")) {
28
+ db.createObjectStore("blocks");
29
+ }
30
+ };
31
+ request.onsuccess = (e) => resolve(e.target.result);
32
+ request.onerror = (e) => reject(e.target.error);
33
+ });
34
+ }
35
+
36
+ function generateBlock(seed, blockIndex, blockSize = 1024) {
37
+ const block = new Uint8Array(blockSize);
38
+ let h = cyrb53(seed + ":" + blockIndex);
39
+ for (let i = 0; i < blockSize; i++) {
40
+ h = Math.imul(h ^ i, 1597334677);
41
+ block[i] = h & 0xff;
42
+ }
43
+ return block;
44
+ }
45
+
46
+ async function initializeSpace(seed, sizeMb) {
47
+ const db = await openDb();
48
+ const transaction = db.transaction("blocks", "readwrite");
49
+ const store = transaction.objectStore("blocks");
50
+ const numBlocks = sizeMb * 1024;
51
+ const metadataKey = "pospace-metadata";
52
+
53
+ const metaReq = store.get(metadataKey);
54
+ const meta = await new Promise((resolve) => {
55
+ metaReq.onsuccess = () => resolve(metaReq.result);
56
+ });
57
+ if (meta && meta.sizeMb === sizeMb && meta.seed === seed) {
58
+ return;
59
+ }
60
+
61
+ const CHUNK_SIZE = 1000;
62
+ for (let i = 0; i < numBlocks; i += CHUNK_SIZE) {
63
+ const end = Math.min(numBlocks, i + CHUNK_SIZE);
64
+ for (let j = i; j < end; j++) {
65
+ const block = generateBlock(seed, j);
66
+ store.put(block, j);
67
+ }
68
+ await new Promise(r => setTimeout(r, 0));
69
+ }
70
+ store.put({ sizeMb, seed }, metadataKey);
71
+ }
72
+
73
+ async function solveSpaceChallenge(seed, queries, nonce, clientSecret) {
74
+ const db = await openDb();
75
+ const transaction = db.transaction("blocks", "readonly");
76
+ const store = transaction.objectStore("blocks");
77
+
78
+ let combined = new Uint8Array(queries.length * 1024);
79
+ for (let i = 0; i < queries.length; i++) {
80
+ const idx = queries[i];
81
+ const getReq = store.get(idx);
82
+ let block = await new Promise((resolve) => {
83
+ getReq.onsuccess = () => resolve(getReq.result);
84
+ });
85
+ if (!block) {
86
+ block = generateBlock(seed, idx);
87
+ }
88
+ combined.set(block, i * 1024);
89
+ }
90
+
91
+ const encoder = new TextEncoder();
92
+ const nonceBytes = encoder.encode(nonce + ":" + clientSecret);
93
+ const finalBlock = new Uint8Array(combined.length + nonceBytes.length);
94
+ finalBlock.set(combined);
95
+ finalBlock.set(nonceBytes, combined.length);
96
+
97
+ const buf = await crypto.subtle.digest("SHA-256", finalBlock);
98
+ return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
99
+ }
100
+
101
+ /**
102
+ * Résout un challenge CPU basé sur une cible en utilisant un bloc de base binaire.
103
+ * @param {Uint8Array} baseBlock - Le bloc de données initial (nonce, secret, fp) fourni par le serveur.
104
+ * @param {bigint} target - La cible à atteindre.
105
+ * @param {Function} progressCallback - Callback pour les mises à jour de progression.
106
+ * @returns {Promise<number>} La solution (un nombre entier).
107
+ */
108
+ async function solveCpuTargetInline(baseBlock, target, progressCallback) {
109
+ // --- FIX: Add validation for the target to prevent BigInt conversion errors ---
110
+ if (typeof target !== 'bigint' && (typeof target !== 'string' || !/^[0-9a-fA-F]+$/.test(target))) {
111
+ throw new TypeError(`Invalid target type: expected a BigInt or a hex string, but got ${typeof target} with value ${target}`);
112
+ }
113
+
114
+ const cpuTarget = typeof target === 'bigint' ? target : BigInt('0x' + target);
115
+ // --- END FIX ---
116
+ const encoder = new TextEncoder();
117
+
118
+ const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
119
+ if (wasmModule && typeof wasmModule._solve_cpu_target === 'function') {
120
+ const len = baseBlock.length;
121
+ const ptr = wasmModule._malloc(len);
122
+ wasmModule.HEAPU8.set(baseBlock, ptr);
123
+ const targetStr = typeof target === 'string' ? target : target.toString(16);
124
+ const targetPtr = wasmModule._malloc(targetStr.length + 1);
125
+ for (let i = 0; i < targetStr.length; i++) {
126
+ wasmModule.HEAP8[targetPtr + i] = targetStr.charCodeAt(i);
127
+ }
128
+ wasmModule.HEAP8[targetPtr + targetStr.length] = 0;
129
+ const solution = wasmModule._solve_cpu_target(ptr, len, targetPtr);
130
+ wasmModule._free(ptr);
131
+ wasmModule._free(targetPtr);
132
+ return solution;
133
+ }
134
+
135
+ let cpuSolution = 0;
136
+
137
+ while (true) {
138
+ const solutionBytes = encoder.encode(String(cpuSolution));
139
+
140
+ // Concaténation binaire directe : c'est plus rapide et plus sûr.
141
+ const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
142
+ finalBlock.set(baseBlock);
143
+ finalBlock.set(solutionBytes, baseBlock.length);
144
+
145
+ if (cpuSolution === 0) {
146
+ // Pour le débogage, on peut afficher le message reconstruit.
147
+ const reconstructedMsg = new TextDecoder().decode(finalBlock);
148
+ console.log(`[FP Client Solve] Hashing message: "${reconstructedMsg}"`);
149
+ }
150
+
151
+ const buf = await crypto.subtle.digest("SHA-256", finalBlock);
152
+ const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
153
+ // --- AJOUT DE LOGS POUR LE DÉBOGAGE CÔTÉ CLIENT ---
154
+ if (cpuSolution === 0) { // Log only the first attempt
155
+ console.log(`[FP Client Solve] Attempt 0 hash: "0x${hashHex}"`);
156
+ }
157
+ // --- FIN DES LOGS ---
158
+ if (BigInt('0x' + hashHex) < cpuTarget) break;
159
+ cpuSolution++;
160
+ if (cpuSolution % 100000 === 0) {
161
+ await new Promise(r => setTimeout(r, 0));
162
+ if (progressCallback) progressCallback(cpuSolution);
163
+ }
164
+ }
165
+ return cpuSolution;
166
+ }
167
+
168
+ /**
169
+ * Résout un challenge CPU basé sur une cible (version Web Worker).
170
+ * @param {string} message - Le message à hasher (ex: `ip:nonce:solution:secret`).
171
+ * @param {bigint} target - La cible à atteindre.
172
+ * @returns {Promise<number>} La solution (un nombre entier).
173
+ */
174
+ async function solveCpuTarget(message, target) {
175
+ // Vérifie si les Web Workers sont supportés par le navigateur.
176
+ if (typeof(Worker) === "undefined") {
177
+ console.warn("Web Workers not supported. Falling back to main thread calculation (UI may freeze).");
178
+ // Ici, on pourrait remettre l'ancienne implémentation comme solution de secours.
179
+ // Pour la clarté, nous supposons que les workers sont disponibles.
180
+ throw new Error("Web Worker support is required for CPU challenges.");
181
+ }
182
+
183
+ return new Promise((resolve, reject) => {
184
+ // Crée un worker à partir du script dédié. Le chemin doit être accessible publiquement.
185
+ // Assurez-vous que `pow.worker.js` est servi par votre serveur statique.
186
+ const worker = new Worker('./pow.worker.js');
187
+
188
+ worker.onmessage = (event) => {
189
+ resolve(event.data.solution);
190
+ worker.terminate(); // Nettoie le worker une fois le travail terminé.
191
+ };
192
+
193
+ worker.onerror = (error) => {
194
+ reject(error);
195
+ worker.terminate();
196
+ };
197
+
198
+ // Envoie les données du challenge au worker pour qu'il commence le calcul.
199
+ worker.postMessage({ message, target });
200
+ });
201
+ }
202
+
203
+ /**
204
+ * Résout un challenge basé sur la mémoire.
205
+ * @param {string} seed - La graine pour l'initialisation de la mémoire.
206
+ * @param {number} difficulty - La difficulté (en Mo).
207
+ * @returns {Promise<number>} La solution (nombre entier).
208
+ */
209
+ async function solveMemory(seed, difficulty) {
210
+ // On définit un seuil pour savoir quand faire une pause, afin de ne pas bloquer le thread UI.
211
+ const YIELD_THRESHOLD = 100000;
212
+ const size = difficulty * 1024 * 1024;
213
+ const buffer = new Uint32Array(size / 4);
214
+
215
+ const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
216
+ if (wasmModule && typeof wasmModule._solve_memory_challenge === 'function') {
217
+ const seedPtr = wasmModule._malloc(seed.length + 1);
218
+ for (let i = 0; i < seed.length; i++) {
219
+ wasmModule.HEAP8[seedPtr + i] = seed.charCodeAt(i);
220
+ }
221
+ wasmModule.HEAP8[seedPtr + seed.length] = 0;
222
+ const solution = wasmModule._solve_memory_challenge(seedPtr, difficulty);
223
+ wasmModule._free(seedPtr);
224
+ return solution;
225
+ }
226
+
227
+ let h = new TextEncoder().encode(seed).reduce((acc, v) => acc + v, 0);
228
+ for (let i = 0; i < buffer.length; i++) {
229
+ buffer[i] = (h = Math.imul(h ^ i, 1597334677));
230
+ if (i % YIELD_THRESHOLD === 0) {
231
+ await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
232
+ }
233
+ }
234
+ let solution = 0;
235
+ const iterations = size / 16;
236
+ let addr = buffer.length > 0 ? buffer[0] % buffer.length : 0;
237
+ for (let i = 0; i < iterations; i++) {
238
+ addr = buffer[addr] % buffer.length;
239
+ solution ^= addr;
240
+ if (i % YIELD_THRESHOLD === 0) {
241
+ await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
242
+ }
243
+ }
244
+ return solution;
245
+ }
246
+
247
+ /**
248
+ * Résout un challenge de type "Problème du Voyageur de Commerce" (TSP).
249
+ * NOTE: Ceci est une implémentation simple (heuristique du plus proche voisin) et n'est pas garantie
250
+ * de trouver la solution optimale, mais elle est suffisante pour un challenge.
251
+ * @param {Array<{x: number, y: number}>} cities - Les coordonnées des villes.
252
+ * @param {number} targetMaxDistance - La distance maximale acceptable.
253
+ * @returns {Promise<{path: number[], distance: number}>} Le chemin et la distance.
254
+ */
255
+ async function solveTsp(cities, targetMaxDistance) {
256
+ // Utility function to calculate the distance between two cities
257
+ function distance(city1, city2) {
258
+ return Math.sqrt(Math.pow(city1.x - city2.x, 2) + Math.pow(city1.y - city2.y, 2));
259
+ }
260
+
261
+ // Utility function to evaluate the total distance of a path
262
+ function evaluatePathDistance(cities, path) {
263
+ let totalDistance = 0;
264
+ for (let i = 0; i < path.length - 1; i++) {
265
+ totalDistance += distance(cities[path[i]], cities[path[i + 1]]);
266
+ }
267
+ totalDistance += distance(cities[path[path.length - 1]], cities[path[0]]); // Return to start
268
+ return totalDistance;
269
+ }
270
+
271
+ // Solveur simple du TSP (heuristique du plus proche voisin)
272
+ function solveTspNearestNeighbor(cities) {
273
+ const numCities = cities.length;
274
+ if (numCities === 0) return [];
275
+
276
+ let currentPath = [];
277
+ let visited = new Array(numCities).fill(false);
278
+
279
+ let currentCityIndex = 0; // Always start with the first city for reproducibility
280
+ currentPath.push(currentCityIndex);
281
+ visited[currentCityIndex] = true;
282
+
283
+ for (let i = 1; i < numCities; i++) {
284
+ let nearestCityIndex = -1;
285
+ let minDistance = Infinity;
286
+
287
+ for (let j = 0; j < numCities; j++) {
288
+ if (!visited[j]) {
289
+ const dist = distance(cities[currentCityIndex], cities[j]);
290
+ if (dist < minDistance) {
291
+ minDistance = dist;
292
+ nearestCityIndex = j;
293
+ }
294
+ }
295
+ }
296
+ currentCityIndex = nearestCityIndex;
297
+ currentPath.push(currentCityIndex);
298
+ visited[currentCityIndex] = true;
299
+ }
300
+ return currentPath;
301
+ }
302
+
303
+ // To avoid freezing the browser, yield the thread from time to time
304
+ await new Promise(resolve => setTimeout(resolve, 10));
305
+ const solutionPath = solveTspNearestNeighbor(cities);
306
+ const solutionDistance = evaluatePathDistance(cities, solutionPath);
307
+
308
+ return { path: solutionPath, distance: solutionDistance };
309
+ }
310
+
311
+ /**
312
+ * Fonction principale qui reçoit un objet challenge et le résout.
313
+ * @param {object} challenge - L'objet challenge reçu du serveur.
314
+ * @returns {Promise<object>} Un objet contenant la ou les solutions.
315
+ */
316
+ async function solveChallenge(challenge, fingerprint = '') { // fingerprint parameter was already here, but unused in some calls
317
+ const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance, queries, sizeMb } = challenge;
318
+ const solutions = {};
319
+
320
+ switch (type) {
321
+ case 'cpu_target':
322
+ // Note: This case is not fully exercised by tests as it relies on Web Workers.
323
+ if (!cpuTarget) {
324
+ throw new Error("Challenge data is missing 'cpuTarget' property.");
325
+ }
326
+ const target = cpuTarget; // Keep variable name for consistency below
327
+ // Pour ce challenge simple, le baseBlock est juste le nonce.
328
+ const baseBlockBytes = new TextEncoder().encode(nonce + ":");
329
+ solutions.cpu = await solveCpuTargetInline(baseBlockBytes, target, null);
330
+ break;
331
+ case 'cpu_mem':
332
+ // Pour les appels API, le client IP n'est pas connu, on ne le met pas dans le message
333
+ const memSeed = `:${nonce}:${clientSecret}`;
334
+ const [cpuSol, memSol] = await Promise.all([
335
+ (async () => {
336
+ if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property."); // Pass fingerprint to solver
337
+ const baseBlock = new Uint8Array(challenge.baseBlock);
338
+ return solveCpuTargetInline(baseBlock, cpuTarget, null);
339
+ })(),
340
+ solveMemory(memSeed, memDifficulty)
341
+ ]);
342
+ solutions.cpu = cpuSol;
343
+ solutions.mem = memSol;
344
+ break;
345
+ case 'cpu_mem_inline':
346
+ // Version inline pour compatibilité HTML avec IP incluse
347
+ const memSeedInline = `:${nonce}:${clientSecret}`;
348
+ const [cpuSolInline, memSolInline] = await Promise.all([
349
+ (async () => {
350
+ if (!cpuTarget) throw new Error("Challenge data is missing 'cpuTarget' property.");
351
+ const baseBlock = new Uint8Array(challenge.baseBlock);
352
+ return solveCpuTargetInline(baseBlock, cpuTarget, null);
353
+ })(),
354
+ solveMemory(memSeedInline, memDifficulty)
355
+ ]);
356
+ solutions.cpu = cpuSolInline;
357
+ solutions.mem = memSolInline;
358
+ break;
359
+ case 'tsp':
360
+ const tspResult = await solveTsp(cities, targetMaxDistance);
361
+ solutions.tsp = tspResult.path;
362
+ solutions.distance = tspResult.distance;
363
+ break;
364
+ case 'pospace':
365
+ await initializeSpace(nonce + ":" + clientSecret, sizeMb || 100);
366
+ solutions.hash = await solveSpaceChallenge(nonce + ":" + clientSecret, queries, nonce, clientSecret);
367
+ break;
368
+ default:
369
+ throw new Error(`Unknown challenge type: ${type}`);
370
+ }
371
+
372
+ return solutions;
373
+ }
374
+
375
+ // --- Compatibilité pour l'injection directe dans le HTML ---
376
+ // Si le script est chargé dans un navigateur (window existe), on attache les fonctions nécessaires à window.
377
+ if (typeof window !== 'undefined') {
378
+ window.solveCpuChallengeInline = solveCpuTargetInline;
379
+ window.solveMemoryChallenge = solveMemory;
380
+ window.solveTspChallenge = solveTsp;
381
+ window.solveChallenge = solveChallenge;
286
382
  }