@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.
- package/CHANGELOG.md +29 -0
- package/README.md +1 -1
- package/composer.json +8 -1
- package/package.json +1 -1
- package/public/fingerprint-wordpress.zip +0 -0
- package/src/js/build-client.js +8 -10
- package/src/js/fingerprint.builder.js +37 -38
- package/src/js/fingerprint.client.js +1531 -1463
- package/src/js/fingerprint.client.obfuscated.js +1 -0
- package/src/js/fingerprint.js +190 -16
- package/src/js/fingerprint.utils.js +213 -213
- package/src/js/library.js +340 -376
- package/src/js/mongodb-store.js +1 -1
- package/src/js/optimization.worker.js +27 -27
- package/src/js/pow.solver.inline.js +154 -56
- package/src/js/pow.solver.js +180 -92
- package/src/js/pow.worker.js +48 -48
- package/src/js/redis-store.js +1 -1
- package/src/js/tests/fingerprint.client.init.test.js +146 -143
- package/src/js/tests/fingerprint.test.js +124 -10
- package/src/js/tests/ip-reputation.test.js +1 -0
- package/src/js/upow-model-task.js +14 -15
- package/src/php/AutoTuner.php +34 -34
- package/src/php/Challenge/ChallengeUtils.php +57 -11
- package/src/php/Config/SecurityProfiles.php +20 -0
- package/src/php/DirectFingerprint.php +190 -145
- package/src/php/FingerprintBuilder.php +29 -30
- package/src/php/FingerprintClient.php +147 -148
- package/src/php/FingerprintEngine.php +44 -17
- package/src/php/Ja3AnomalyDetector.php +33 -32
- package/src/php/Optimization/FunctionRegistry.php +7 -7
- package/src/php/Optimization/Optimization.php +24 -24
- package/src/php/Optimization/OptimizationOperators.php +26 -25
- package/src/php/Optimization/ProblemInitializers.php +52 -52
- package/src/php/ProblemManager.php +31 -34
- package/src/php/RequestContext.php +8 -1
- package/src/php/Store/IStore.php +7 -8
- package/src/php/Store/InMemoryStore.php +66 -66
- package/src/php/Store/MongoDbStore.php +5 -5
- package/src/php/Store/RedisStore.php +5 -5
- package/src/php/Store/StoreManager.php +35 -35
- package/src/php/Tests/ChallengeUtilsTest.php +453 -453
- package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
- package/src/php/Tests/FingerprintEngineTest.php +38 -0
- package/src/php/Tests/IpReputationTest.php +10 -0
- package/src/php/Tests/MaliciousPatternsTest.php +103 -103
- package/src/php/Tests/MetricsTest.php +97 -97
- package/src/php/Tests/ProblemManagerTest.php +376 -376
- package/src/php/Tests/QuicFingerprintTest.php +53 -53
- package/src/php/Tests/RequestUtilsTest.php +471 -471
- package/src/php/Tests/TLSClientHelloParserTest.php +177 -177
- package/src/php/Tests/config/ed25519_key.json +3 -3
- package/src/php/Utils/BlockList.php +99 -99
- package/src/php/Utils/Env.php +49 -49
- package/src/php/Utils/MaliciousPatterns.php +74 -74
- package/src/php/Utils/MetricsManager.php +209 -209
- package/src/php/Utils/RequestUtils.php +129 -7
- package/src/php/Utils/TLSClientHelloParser.php +369 -369
- package/src/php/WordPress/WpDbStore.php +155 -0
- package/src/php/WordPress/fingerprint-wordpress.php +554 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +262 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +268 -0
- package/src/php/WordPress/package.php +218 -0
- package/src/php/bin/auto-tune.php +118 -118
package/src/js/pow.solver.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file @/pow.solver.js
|
|
3
|
-
* @description
|
|
4
|
-
* IMPORTANT:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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';
|
|
@@ -17,6 +17,88 @@ function secureRandom() {
|
|
|
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);
|
|
@@ -158,11 +240,11 @@ export async function solveSpaceChallenge(seed, queries, nonce, clientSecret, pe
|
|
|
158
240
|
}
|
|
159
241
|
|
|
160
242
|
/**
|
|
161
|
-
*
|
|
162
|
-
* @param {Uint8Array} baseBlock -
|
|
163
|
-
* @param {bigint} target -
|
|
164
|
-
* @param {Function} progressCallback -
|
|
165
|
-
* @returns {Promise<number>}
|
|
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.
|
|
166
248
|
*/
|
|
167
249
|
export async function solveCpuTargetInline(baseBlock, target, progressCallback) {
|
|
168
250
|
// --- FIX: Add validation for the target to prevent BigInt conversion errors ---
|
|
@@ -198,6 +280,7 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
|
|
|
198
280
|
if (typeof window !== 'undefined' && typeof Worker !== 'undefined') {
|
|
199
281
|
try {
|
|
200
282
|
const staticWorkerPath = window.ClientLibrary?.workerPath || (window.ClientConfig && window.ClientConfig.workerPath);
|
|
283
|
+
const sha256FnStr = sha256Sync.toString();
|
|
201
284
|
if (staticWorkerPath) {
|
|
202
285
|
return await new Promise((resolve, reject) => {
|
|
203
286
|
const worker = new Worker(staticWorkerPath);
|
|
@@ -208,7 +291,7 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
|
|
|
208
291
|
resolve(event.data.solution);
|
|
209
292
|
worker.terminate();
|
|
210
293
|
} else if (event.data.solution !== undefined) {
|
|
211
|
-
// Fallback
|
|
294
|
+
// Fallback for backward compatibility with previous signature
|
|
212
295
|
resolve(event.data.solution);
|
|
213
296
|
worker.terminate();
|
|
214
297
|
}
|
|
@@ -226,15 +309,29 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
|
|
|
226
309
|
const { baseBlock, target } = e.data;
|
|
227
310
|
const cpuTarget = BigInt(target);
|
|
228
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};
|
|
229
314
|
let cpuSolution = 0;
|
|
230
315
|
while (true) {
|
|
231
316
|
const solutionBytes = encoder.encode(String(cpuSolution));
|
|
232
317
|
const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
|
|
233
318
|
finalBlock.set(baseBlock);
|
|
234
319
|
finalBlock.set(solutionBytes, baseBlock.length);
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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;
|
|
238
335
|
cpuSolution++;
|
|
239
336
|
if (cpuSolution % 25000 === 0) {
|
|
240
337
|
self.postMessage({ type: 'progress', solution: cpuSolution });
|
|
@@ -269,22 +366,17 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
|
|
|
269
366
|
while (true) {
|
|
270
367
|
const solutionBytes = encoder.encode(String(cpuSolution));
|
|
271
368
|
|
|
272
|
-
//
|
|
369
|
+
// Direct binary concatenation: faster and safer.
|
|
273
370
|
const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
|
|
274
371
|
finalBlock.set(baseBlock);
|
|
275
372
|
finalBlock.set(solutionBytes, baseBlock.length);
|
|
276
373
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
console.log('[FP Solve Debug] Client will hash message:', reconstructedMsg);
|
|
280
|
-
}
|
|
281
|
-
const buf = await crypto.subtle.digest("SHA-256", finalBlock);
|
|
282
|
-
const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
283
|
-
// --- AJOUT DE LOGS POUR LE DÉBOGAGE CÔTÉ CLIENT ---
|
|
374
|
+
const hashHex = await safeDigestSha256Hex(finalBlock);
|
|
375
|
+
// --- CLIENT-SIDE DEBUG LOGS ---
|
|
284
376
|
if (cpuSolution === 0) { // Log only the first attempt
|
|
285
377
|
console.log(`[FP Client Solve] Attempt 0 hash: "0x${hashHex}"`);
|
|
286
378
|
}
|
|
287
|
-
// ---
|
|
379
|
+
// --- END DEBUG LOGS ---
|
|
288
380
|
if (BigInt('0x' + hashHex) < cpuTarget) break;
|
|
289
381
|
cpuSolution++;
|
|
290
382
|
if (cpuSolution % 50000 === 0) {
|
|
@@ -300,28 +392,28 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
|
|
|
300
392
|
}
|
|
301
393
|
|
|
302
394
|
/**
|
|
303
|
-
*
|
|
304
|
-
* @param {string} message -
|
|
305
|
-
* @param {bigint} target -
|
|
306
|
-
* @returns {Promise<number>}
|
|
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.
|
|
307
399
|
*/
|
|
308
400
|
export async function solveCpuTarget(message, target) {
|
|
309
401
|
// Vérifie si les Web Workers sont supportés par le navigateur.
|
|
310
402
|
if (typeof(Worker) === "undefined") {
|
|
311
403
|
console.warn("Web Workers not supported. Falling back to main thread calculation (UI may freeze).");
|
|
312
|
-
//
|
|
313
|
-
//
|
|
404
|
+
// An inline fallback implementation could be placed here if needed.
|
|
405
|
+
// For clarity, we assume worker availability.
|
|
314
406
|
throw new Error("Web Worker support is required for CPU challenges.");
|
|
315
407
|
}
|
|
316
408
|
|
|
317
409
|
return new Promise((resolve, reject) => {
|
|
318
|
-
//
|
|
319
|
-
//
|
|
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.
|
|
320
412
|
const worker = new Worker('./pow.worker.js');
|
|
321
413
|
|
|
322
414
|
worker.onmessage = (event) => {
|
|
323
415
|
resolve(event.data.solution);
|
|
324
|
-
worker.terminate(); //
|
|
416
|
+
worker.terminate(); // Clean up worker once finished.
|
|
325
417
|
};
|
|
326
418
|
|
|
327
419
|
worker.onerror = (error) => {
|
|
@@ -329,16 +421,16 @@ export async function solveCpuTarget(message, target) {
|
|
|
329
421
|
worker.terminate();
|
|
330
422
|
};
|
|
331
423
|
|
|
332
|
-
//
|
|
424
|
+
// Send challenge data to the worker to start calculation.
|
|
333
425
|
worker.postMessage({ message, target });
|
|
334
426
|
});
|
|
335
427
|
}
|
|
336
428
|
|
|
337
429
|
/**
|
|
338
|
-
*
|
|
339
|
-
* @param {string} seed -
|
|
340
|
-
* @param {number} difficulty -
|
|
341
|
-
* @returns {Promise<number>}
|
|
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.
|
|
342
434
|
*/
|
|
343
435
|
export async function solveMemory(seed, difficulty) {
|
|
344
436
|
const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
|
|
@@ -360,8 +452,7 @@ export async function solveMemory(seed, difficulty) {
|
|
|
360
452
|
if (numBlocks === 0) return { solution: 0, merkleRoot: '', proofs: {} };
|
|
361
453
|
|
|
362
454
|
async function hashBlock(block) {
|
|
363
|
-
|
|
364
|
-
return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
455
|
+
return await safeDigestSha256Hex(block.buffer);
|
|
365
456
|
}
|
|
366
457
|
|
|
367
458
|
function hexToBytes(hex) {
|
|
@@ -392,8 +483,7 @@ export async function solveMemory(seed, difficulty) {
|
|
|
392
483
|
const left = currentLayer[i];
|
|
393
484
|
const right = currentLayer[i + 1] || left;
|
|
394
485
|
const combined = hexToBytes(left + right);
|
|
395
|
-
const
|
|
396
|
-
const hashHex = Array.from(new Uint8Array(hashBuf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
486
|
+
const hashHex = await safeDigestSha256Hex(combined);
|
|
397
487
|
nextLayer.push(hashHex);
|
|
398
488
|
}
|
|
399
489
|
tree.push(nextLayer);
|
|
@@ -442,12 +532,12 @@ export async function solveMemory(seed, difficulty) {
|
|
|
442
532
|
}
|
|
443
533
|
|
|
444
534
|
/**
|
|
445
|
-
*
|
|
446
|
-
* NOTE:
|
|
447
|
-
*
|
|
448
|
-
* @param {Array<{x: number, y: number}>} cities -
|
|
449
|
-
* @param {number} targetMaxDistance -
|
|
450
|
-
* @returns {Promise<{path: number[], distance: number}>}
|
|
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.
|
|
451
541
|
*/
|
|
452
542
|
export async function solveTsp(cities, targetMaxDistance) {
|
|
453
543
|
// Utility function to calculate the distance between two cities
|
|
@@ -505,7 +595,7 @@ export async function solveTsp(cities, targetMaxDistance) {
|
|
|
505
595
|
return { path: solutionPath, distance: solutionDistance };
|
|
506
596
|
}
|
|
507
597
|
|
|
508
|
-
// ---
|
|
598
|
+
// --- Optimization functions adapted from library.js for client usage ---
|
|
509
599
|
|
|
510
600
|
function paretoDominates(objectivesA, objectivesB) {
|
|
511
601
|
let aIsBetterInOne = false;
|
|
@@ -853,9 +943,9 @@ const ClientOptimizers = {
|
|
|
853
943
|
};
|
|
854
944
|
|
|
855
945
|
/**
|
|
856
|
-
*
|
|
857
|
-
* @param {object} task -
|
|
858
|
-
* @returns {Promise<object>}
|
|
946
|
+
* Solves a Useful Work Unit (uPoW).
|
|
947
|
+
* @param {object} task - Task payload sent by the server.
|
|
948
|
+
* @returns {Promise<object>} Computation result.
|
|
859
949
|
*/
|
|
860
950
|
async function solveUsefulWorkTask(task) {
|
|
861
951
|
await new Promise(r => setTimeout(r, 10)); // Yield thread
|
|
@@ -930,7 +1020,7 @@ async function solveUsefulWorkTask(task) {
|
|
|
930
1020
|
const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
|
|
931
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)); };
|
|
932
1022
|
|
|
933
|
-
//
|
|
1023
|
+
// The client must recreate the population if not provided
|
|
934
1024
|
const initialPopulation = task.initialPopulation || Array.from({ length: task.payload.options.populationSize }, () => ({ chromosome: createIndividual(), fitness: 0 }));
|
|
935
1025
|
initialPopulation.forEach(p => p.fitness = fitness(p.chromosome));
|
|
936
1026
|
|
|
@@ -939,33 +1029,31 @@ async function solveUsefulWorkTask(task) {
|
|
|
939
1029
|
}
|
|
940
1030
|
|
|
941
1031
|
case 'run_multiple_parallel':
|
|
942
|
-
//
|
|
943
|
-
//
|
|
944
|
-
//
|
|
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.
|
|
945
1035
|
const { solverName, baseSolverArgs } = task;
|
|
946
1036
|
const clientSolver = ClientOptimizers[solverName];
|
|
947
1037
|
if (!clientSolver) throw new Error(`Solver ${solverName} not found on client.`);
|
|
948
1038
|
|
|
949
|
-
//
|
|
950
|
-
// Note: `baseSolverArgs`
|
|
1039
|
+
// Execute solver with base arguments.
|
|
1040
|
+
// Note: `baseSolverArgs` may contain solver options.
|
|
951
1041
|
return clientSolver(...baseSolverArgs);
|
|
952
1042
|
|
|
953
1043
|
case 'multi_objective_genetic_algorithm': {
|
|
954
|
-
//
|
|
955
|
-
// 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.
|
|
956
1045
|
const solverFunction = ClientOptimizers[task.solverName];
|
|
957
1046
|
if (!solverFunction) {
|
|
958
1047
|
throw new Error(`Solver '${task.solverName}' not found on client.`);
|
|
959
1048
|
}
|
|
960
1049
|
// On appelle le solveur en lui passant le payload et les options.
|
|
961
|
-
//
|
|
1050
|
+
// Invoke solver with payload and options.
|
|
962
1051
|
return solverFunction(task.payload, { generations: task.generations, initialFront: task.initialFront });
|
|
963
1052
|
}
|
|
964
1053
|
|
|
965
1054
|
case 'pytorch_onnx_learning': {
|
|
966
|
-
//
|
|
967
|
-
//
|
|
968
|
-
// 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.
|
|
969
1057
|
const { weights, payload } = task;
|
|
970
1058
|
const inputs = payload.inputs || [];
|
|
971
1059
|
const labels = payload.labels || [];
|
|
@@ -991,7 +1079,7 @@ async function solveUsefulWorkTask(task) {
|
|
|
991
1079
|
const labels = payload.labels || [];
|
|
992
1080
|
try {
|
|
993
1081
|
const tf = await loadTfjs();
|
|
994
|
-
//
|
|
1082
|
+
// Non-blocking model loading
|
|
995
1083
|
const model = await tf.loadLayersModel(modelPath);
|
|
996
1084
|
|
|
997
1085
|
const xs = tf.tensor2d(inputs);
|
|
@@ -1003,7 +1091,7 @@ async function solveUsefulWorkTask(task) {
|
|
|
1003
1091
|
return tf.losses.meanSquaredError(ys, preds);
|
|
1004
1092
|
};
|
|
1005
1093
|
|
|
1006
|
-
//
|
|
1094
|
+
// Automatic differentiation for exact gradient computation
|
|
1007
1095
|
const gFn = tf.grad(lossFn);
|
|
1008
1096
|
const grads = gFn(trainableVars);
|
|
1009
1097
|
|
|
@@ -1034,15 +1122,15 @@ async function solveUsefulWorkTask(task) {
|
|
|
1034
1122
|
}
|
|
1035
1123
|
|
|
1036
1124
|
/**
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
1039
|
-
* NOTE:
|
|
1040
|
-
* @param {Array<object>} initialPopulation -
|
|
1041
|
-
* @param {number} generations -
|
|
1042
|
-
* @returns {Promise<Array<object>>}
|
|
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.
|
|
1043
1131
|
*/
|
|
1044
1132
|
export async function solveOptimizationTask(initialPopulation, generations) {
|
|
1045
|
-
//
|
|
1133
|
+
// Simplified GA operators (crossover, mutation)
|
|
1046
1134
|
const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
|
|
1047
1135
|
const mutate = (p) => {
|
|
1048
1136
|
const newP = [...p];
|
|
@@ -1054,20 +1142,20 @@ export async function solveOptimizationTask(initialPopulation, generations) {
|
|
|
1054
1142
|
let population = initialPopulation;
|
|
1055
1143
|
|
|
1056
1144
|
for (let gen = 0; gen < generations; gen++) {
|
|
1057
|
-
//
|
|
1145
|
+
// Simple selection: keep top 50%
|
|
1058
1146
|
const parents = population.sort((a, b) => a.fitness - b.fitness).slice(0, Math.ceil(population.length / 2));
|
|
1059
|
-
const newPopulation = [...parents]; //
|
|
1147
|
+
const newPopulation = [...parents]; // Elitism
|
|
1060
1148
|
|
|
1061
1149
|
while (newPopulation.length < population.length) {
|
|
1062
1150
|
const parent1 = parents[Math.floor(secureRandom() * parents.length)];
|
|
1063
1151
|
const parent2 = parents[Math.floor(secureRandom() * parents.length)];
|
|
1064
1152
|
let offspring = crossover(parent1.chromosome, parent2.chromosome);
|
|
1065
1153
|
if (secureRandom() < 0.1) offspring = mutate(offspring);
|
|
1066
|
-
//
|
|
1154
|
+
// Fitness is recalculated server-side for verification.
|
|
1067
1155
|
newPopulation.push({ chromosome: offspring, fitness: -1 });
|
|
1068
1156
|
}
|
|
1069
1157
|
population = newPopulation;
|
|
1070
|
-
//
|
|
1158
|
+
// Yield execution to prevent freezing the UI on long tasks
|
|
1071
1159
|
if (gen % 10 === 0) await new Promise(r => setTimeout(r, 0));
|
|
1072
1160
|
}
|
|
1073
1161
|
return population;
|
|
@@ -1075,7 +1163,7 @@ export async function solveOptimizationTask(initialPopulation, generations) {
|
|
|
1075
1163
|
|
|
1076
1164
|
/**
|
|
1077
1165
|
* @class ChallengeSolution
|
|
1078
|
-
* @description
|
|
1166
|
+
* @description Encapsulates a challenge solution and formatting helpers.
|
|
1079
1167
|
* @private
|
|
1080
1168
|
*/
|
|
1081
1169
|
class ChallengeSolution {
|
|
@@ -1086,14 +1174,14 @@ class ChallengeSolution {
|
|
|
1086
1174
|
}
|
|
1087
1175
|
|
|
1088
1176
|
/**
|
|
1089
|
-
*
|
|
1090
|
-
* @param {URL} url -
|
|
1177
|
+
* Applies solution parameters to a URL instance.
|
|
1178
|
+
* @param {URL} url - Target URL object to modify.
|
|
1091
1179
|
*/
|
|
1092
1180
|
applyToUrl(url) {
|
|
1093
1181
|
url.searchParams.set('pow_type', this.type);
|
|
1094
1182
|
url.searchParams.set('pow_nonce', this.nonce);
|
|
1095
1183
|
|
|
1096
|
-
//
|
|
1184
|
+
// Challenge-specific parameter formatting
|
|
1097
1185
|
if (this.type === 'cpu_mem' || this.type === 'cpu_mem_inline' || this.type === 'cpu_target') {
|
|
1098
1186
|
Object.entries(this.rawSolution).forEach(([key, value]) => {
|
|
1099
1187
|
if (typeof value === 'object' && value !== null) {
|
|
@@ -1106,17 +1194,17 @@ class ChallengeSolution {
|
|
|
1106
1194
|
url.searchParams.set('pow_solution_work_result', JSON.stringify(this.rawSolution.work_result));
|
|
1107
1195
|
url.searchParams.set('pow_problem_id', this.rawSolution.problem_id);
|
|
1108
1196
|
} else {
|
|
1109
|
-
//
|
|
1197
|
+
// Single-value solutions like TSP
|
|
1110
1198
|
url.searchParams.set('pow_solution', JSON.stringify(this.rawSolution));
|
|
1111
1199
|
}
|
|
1112
1200
|
}
|
|
1113
1201
|
}
|
|
1114
1202
|
|
|
1115
1203
|
/**
|
|
1116
|
-
*
|
|
1117
|
-
* @param {object} challenge -
|
|
1118
|
-
* @param {string} [fingerprint=''] -
|
|
1119
|
-
* @returns {Promise<ChallengeSolution>}
|
|
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.
|
|
1120
1208
|
*/
|
|
1121
1209
|
export async function solveChallenge(challenge, fingerprint = '') { // The fingerprint is now passed from the client library
|
|
1122
1210
|
const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance, optimizationTask, usefulWorkTask, queries, sizeMb } = challenge;
|
|
@@ -1129,12 +1217,12 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
|
|
|
1129
1217
|
throw new Error("Challenge data is missing 'cpuTarget' property.");
|
|
1130
1218
|
}
|
|
1131
1219
|
const target = cpuTarget; // Keep variable name for consistency below
|
|
1132
|
-
//
|
|
1220
|
+
// For this simple challenge, baseBlock is the nonce.
|
|
1133
1221
|
const baseBlockBytes = new TextEncoder().encode(nonce + ":");
|
|
1134
1222
|
rawSolution.cpu = await solveCpuTargetInline(baseBlockBytes, target, null);
|
|
1135
1223
|
break;
|
|
1136
1224
|
case 'cpu_mem':
|
|
1137
|
-
//
|
|
1225
|
+
// For API calls, client IP is omitted from client-side message reconstruction
|
|
1138
1226
|
const baseMessageCombined = `:${nonce}:${clientSecret}`;
|
|
1139
1227
|
const memSeed = `:${nonce}:${clientSecret}`;
|
|
1140
1228
|
const [cpuSol, memSol] = await Promise.all([
|
|
@@ -1149,7 +1237,7 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
|
|
|
1149
1237
|
rawSolution.mem = memSol;
|
|
1150
1238
|
break;
|
|
1151
1239
|
case 'cpu_mem_inline':
|
|
1152
|
-
//
|
|
1240
|
+
// Inline version for HTML compatibility with IP
|
|
1153
1241
|
const memSeedInline = `:${nonce}:${clientSecret}`;
|
|
1154
1242
|
const [cpuSolInline, memSolInline] = await Promise.all([
|
|
1155
1243
|
(async () => {
|
|
@@ -1164,12 +1252,12 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
|
|
|
1164
1252
|
break;
|
|
1165
1253
|
case 'tsp':
|
|
1166
1254
|
const tspResult = await solveTsp(cities, targetMaxDistance);
|
|
1167
|
-
//
|
|
1255
|
+
// For TSP, solution is the tour path array
|
|
1168
1256
|
rawSolution = tspResult.path;
|
|
1169
1257
|
break;
|
|
1170
1258
|
case 'optimization_task':
|
|
1171
1259
|
const finalPopulation = await solveOptimizationTask(optimizationTask.population, optimizationTask.generations);
|
|
1172
|
-
rawSolution = finalPopulation.map(p => p.chromosome); //
|
|
1260
|
+
rawSolution = finalPopulation.map(p => p.chromosome); // Return chromosomes only
|
|
1173
1261
|
break;
|
|
1174
1262
|
case 'useful_work_task':
|
|
1175
1263
|
const workResult = await solveUsefulWorkTask(usefulWorkTask.task);
|
|
@@ -1187,8 +1275,8 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
|
|
|
1187
1275
|
return new ChallengeSolution(type, nonce, rawSolution);
|
|
1188
1276
|
}
|
|
1189
1277
|
|
|
1190
|
-
// ---
|
|
1191
|
-
//
|
|
1278
|
+
// --- Direct HTML script injection compatibility ---
|
|
1279
|
+
// If loaded in browser context (window exists), expose methods on window.
|
|
1192
1280
|
if (typeof window !== 'undefined') {
|
|
1193
1281
|
window.solveCpuChallengeInline = solveCpuTargetInline;
|
|
1194
1282
|
window.solveMemoryChallenge = solveMemory;
|
package/src/js/pow.worker.js
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file @/pow.worker.js
|
|
3
|
-
* @description Web Worker
|
|
4
|
-
*
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
self.onmessage = async (event) => {
|
|
8
|
-
const { message, baseBlock, target } = event.data;
|
|
9
|
-
const cpuTarget = BigInt(target);
|
|
10
|
-
let solution = 0;
|
|
11
|
-
const encoder = new TextEncoder();
|
|
12
|
-
if (baseBlock) {
|
|
13
|
-
// Mode solveCpuTargetInline (Uint8Array base block)
|
|
14
|
-
const blockArray = new Uint8Array(baseBlock);
|
|
15
|
-
while (true) {
|
|
16
|
-
const solutionBytes = encoder.encode(String(solution));
|
|
17
|
-
const finalBlock = new Uint8Array(blockArray.length + solutionBytes.length);
|
|
18
|
-
finalBlock.set(blockArray);
|
|
19
|
-
finalBlock.set(solutionBytes, blockArray.length);
|
|
20
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', finalBlock);
|
|
21
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
22
|
-
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
23
|
-
|
|
24
|
-
if (BigInt('0x' + hashHex) < cpuTarget) {
|
|
25
|
-
self.postMessage({ type: 'success', solution });
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
solution++;
|
|
29
|
-
if (solution % 25000 === 0) {
|
|
30
|
-
self.postMessage({ type: 'progress', solution });
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
} else {
|
|
34
|
-
// Mode solveCpuTarget standard/legacy (Message string)
|
|
35
|
-
while (true) {
|
|
36
|
-
const currentMessage = `${message}:${solution}`;
|
|
37
|
-
const data = encoder.encode(currentMessage);
|
|
38
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
39
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
40
|
-
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
41
|
-
|
|
42
|
-
if (BigInt('0x' + hashHex) < cpuTarget) {
|
|
43
|
-
self.postMessage({ solution });
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
solution++;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @file @/pow.worker.js
|
|
3
|
+
* @description Dedicated Web Worker for CPU challenge solving.
|
|
4
|
+
* Executes on a separate thread to avoid blocking the main UI thread.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
self.onmessage = async (event) => {
|
|
8
|
+
const { message, baseBlock, target } = event.data;
|
|
9
|
+
const cpuTarget = BigInt(target);
|
|
10
|
+
let solution = 0;
|
|
11
|
+
const encoder = new TextEncoder();
|
|
12
|
+
if (baseBlock) {
|
|
13
|
+
// Mode solveCpuTargetInline (Uint8Array base block)
|
|
14
|
+
const blockArray = new Uint8Array(baseBlock);
|
|
15
|
+
while (true) {
|
|
16
|
+
const solutionBytes = encoder.encode(String(solution));
|
|
17
|
+
const finalBlock = new Uint8Array(blockArray.length + solutionBytes.length);
|
|
18
|
+
finalBlock.set(blockArray);
|
|
19
|
+
finalBlock.set(solutionBytes, blockArray.length);
|
|
20
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', finalBlock);
|
|
21
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
22
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
23
|
+
|
|
24
|
+
if (BigInt('0x' + hashHex) < cpuTarget) {
|
|
25
|
+
self.postMessage({ type: 'success', solution });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
solution++;
|
|
29
|
+
if (solution % 25000 === 0) {
|
|
30
|
+
self.postMessage({ type: 'progress', solution });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
// Mode solveCpuTarget standard/legacy (Message string)
|
|
35
|
+
while (true) {
|
|
36
|
+
const currentMessage = `${message}:${solution}`;
|
|
37
|
+
const data = encoder.encode(currentMessage);
|
|
38
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
39
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
40
|
+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
41
|
+
|
|
42
|
+
if (BigInt('0x' + hashHex) < cpuTarget) {
|
|
43
|
+
self.postMessage({ solution });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
solution++;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
49
|
};
|