@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.
- package/CHANGELOG.md +33 -0
- package/README.md +3 -7
- package/composer.json +8 -1
- package/package.json +1 -1
- package/public/fingerprint-wordpress.zip +0 -0
- package/public/fp.wasm +0 -0
- package/src/js/build-client.js +9 -11
- package/src/js/dynamic-wasm.js +7 -7
- package/src/js/fingerprint.builder.js +37 -38
- package/src/js/fingerprint.client.js +236 -152
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +517 -101
- package/src/js/fingerprint.utils.js +213 -213
- package/src/js/library.js +355 -384
- package/src/js/mongodb-store.js +1 -1
- package/src/js/optimization.worker.js +27 -27
- package/src/js/pow.solver.inline.js +206 -62
- package/src/js/pow.solver.js +235 -101
- package/src/js/pow.worker.js +48 -26
- 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 +101 -40
- 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 +204 -32
- package/src/php/Config/SecurityProfiles.php +21 -0
- package/src/php/DirectFingerprint.php +190 -145
- package/src/php/FingerprintBuilder.php +29 -30
- package/src/php/FingerprintClient.php +24 -25
- package/src/php/FingerprintEngine.php +1836 -1880
- package/src/php/Ja3AnomalyDetector.php +258 -227
- package/src/php/Optimization/FunctionRegistry.php +7 -7
- package/src/php/Optimization/Optimization.php +24 -24
- package/src/php/Optimization/OptimizationOperators.php +28 -27
- 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 +662 -693
- 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/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 +236 -19
- package/src/php/Utils/TLSClientHelloParser.php +369 -212
- package/src/php/WordPress/WpDbStore.php +155 -0
- package/src/php/WordPress/fingerprint-wordpress.php +217 -0
- package/src/php/WordPress/package.php +123 -0
- package/src/php/bin/auto-tune.php +118 -118
package/src/js/pow.solver.js
CHANGED
|
@@ -1,22 +1,104 @@
|
|
|
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';
|
|
11
11
|
|
|
12
12
|
function secureRandom() {
|
|
13
|
-
if (typeof
|
|
13
|
+
if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.getRandomValues) {
|
|
14
14
|
const array = new Uint32Array(1);
|
|
15
|
-
|
|
16
|
-
return array[0] /
|
|
15
|
+
globalThis.crypto.getRandomValues(array);
|
|
16
|
+
return array[0] / 0xffffffff;
|
|
17
17
|
}
|
|
18
18
|
return Math.random();
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Standalone synchronous SHA-256 implementation usable anywhere (HTTP, insecure contexts, Workers).
|
|
22
|
+
*/
|
|
23
|
+
function sha256Sync(bytes) {
|
|
24
|
+
if (bytes instanceof ArrayBuffer) {
|
|
25
|
+
bytes = new Uint8Array(bytes);
|
|
26
|
+
} else if (ArrayBuffer.isView(bytes) && !(bytes instanceof Uint8Array)) {
|
|
27
|
+
bytes = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
28
|
+
}
|
|
29
|
+
var K = [
|
|
30
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
31
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
32
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76ca1422,
|
|
33
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
34
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
35
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
36
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
37
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
|
38
|
+
];
|
|
39
|
+
var H = [
|
|
40
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
|
41
|
+
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
|
|
42
|
+
];
|
|
43
|
+
var l = bytes.length;
|
|
44
|
+
var bitLen = l * 8;
|
|
45
|
+
var padLen = ((l + 8) >> 6 << 6) + 64;
|
|
46
|
+
var buf = new Uint8Array(padLen);
|
|
47
|
+
buf.set(bytes);
|
|
48
|
+
buf[l] = 0x80;
|
|
49
|
+
var view = new DataView(buf.buffer);
|
|
50
|
+
view.setUint32(padLen - 4, bitLen >>> 0);
|
|
51
|
+
view.setUint32(padLen - 8, Math.floor(bitLen / 0x100000000));
|
|
52
|
+
|
|
53
|
+
var W = new Int32Array(64);
|
|
54
|
+
for (var i = 0; i < padLen; i += 64) {
|
|
55
|
+
for (var t = 0; t < 16; t++) {
|
|
56
|
+
W[t] = view.getInt32(i + t * 4);
|
|
57
|
+
}
|
|
58
|
+
for (var t = 16; t < 64; t++) {
|
|
59
|
+
var s0 = ((W[t - 15] >>> 7) | (W[t - 15] << 25)) ^ ((W[t - 15] >>> 18) | (W[t - 15] << 14)) ^ (W[t - 15] >>> 3);
|
|
60
|
+
var s1 = ((W[t - 2] >>> 17) | (W[t - 2] << 15)) ^ ((W[t - 2] >>> 19) | (W[t - 2] << 13)) ^ (W[t - 2] >>> 10);
|
|
61
|
+
W[t] = (W[t - 16] + s0 + W[t - 7] + s1) | 0;
|
|
62
|
+
}
|
|
63
|
+
var a = H[0], b = H[1], c = H[2], d = H[3], e = H[4], f = H[5], g = H[6], h = H[7];
|
|
64
|
+
for (var t = 0; t < 64; t++) {
|
|
65
|
+
var S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
|
|
66
|
+
var ch = (e & f) ^ ((~e) & g);
|
|
67
|
+
var temp1 = (h + S1 + ch + K[t] + W[t]) | 0;
|
|
68
|
+
var S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
|
|
69
|
+
var maj = (a & b) ^ (a & c) ^ (b & c);
|
|
70
|
+
var temp2 = (S0 + maj) | 0;
|
|
71
|
+
h = g; g = f; f = e; e = (d + temp1) | 0;
|
|
72
|
+
d = c; c = b; b = a; a = (temp1 + temp2) | 0;
|
|
73
|
+
}
|
|
74
|
+
H[0] = (H[0] + a) | 0;
|
|
75
|
+
H[1] = (H[1] + b) | 0;
|
|
76
|
+
H[2] = (H[2] + c) | 0;
|
|
77
|
+
H[3] = (H[3] + d) | 0;
|
|
78
|
+
H[4] = (H[4] + e) | 0;
|
|
79
|
+
H[5] = (H[5] + f) | 0;
|
|
80
|
+
H[6] = (H[6] + g) | 0;
|
|
81
|
+
H[7] = (H[7] + h) | 0;
|
|
82
|
+
}
|
|
83
|
+
var hex = '';
|
|
84
|
+
for (var i = 0; i < 8; i++) {
|
|
85
|
+
hex += (H[i] >>> 0).toString(16).padStart(8, '0');
|
|
86
|
+
}
|
|
87
|
+
return hex;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function safeDigestSha256Hex(bytes) {
|
|
91
|
+
const cryptoObj = (typeof globalThis !== 'undefined' && globalThis.crypto) ? globalThis.crypto : (typeof window !== 'undefined' ? window.crypto : null);
|
|
92
|
+
if (cryptoObj && cryptoObj.subtle && typeof cryptoObj.subtle.digest === 'function') {
|
|
93
|
+
try {
|
|
94
|
+
const buf = await cryptoObj.subtle.digest("SHA-256", bytes);
|
|
95
|
+
return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
96
|
+
} catch (e) {
|
|
97
|
+
// Transparent fallback
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return sha256Sync(bytes);
|
|
101
|
+
}
|
|
20
102
|
|
|
21
103
|
function cyrb53(str, seed = 0) {
|
|
22
104
|
const safeStr = (typeof str === 'string' ? str : String(str || '')).slice(0, 10000);
|
|
@@ -102,6 +184,25 @@ export async function initializeSpace(seed, sizeMb) {
|
|
|
102
184
|
store.put({ sizeMb, seed }, metadataKey);
|
|
103
185
|
}
|
|
104
186
|
|
|
187
|
+
export async function readSpaceBlock(blockIdx) {
|
|
188
|
+
const db = await openDb();
|
|
189
|
+
const transaction = db.transaction("blocks", "readonly");
|
|
190
|
+
const store = transaction.objectStore("blocks");
|
|
191
|
+
const getReq = store.get(blockIdx);
|
|
192
|
+
return new Promise((resolve, reject) => {
|
|
193
|
+
getReq.onsuccess = () => {
|
|
194
|
+
const block = getReq.result;
|
|
195
|
+
if (block) {
|
|
196
|
+
const hex = Array.from(block).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
197
|
+
resolve(hex);
|
|
198
|
+
} else {
|
|
199
|
+
reject(new Error("Block not found"));
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
getReq.onerror = () => reject(getReq.error);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
105
206
|
export async function solveSpaceChallenge(seed, queries, nonce, clientSecret, peerBlock = '') {
|
|
106
207
|
const db = await openDb();
|
|
107
208
|
const transaction = db.transaction("blocks", "readonly");
|
|
@@ -139,11 +240,11 @@ export async function solveSpaceChallenge(seed, queries, nonce, clientSecret, pe
|
|
|
139
240
|
}
|
|
140
241
|
|
|
141
242
|
/**
|
|
142
|
-
*
|
|
143
|
-
* @param {Uint8Array} baseBlock -
|
|
144
|
-
* @param {bigint} target -
|
|
145
|
-
* @param {Function} progressCallback -
|
|
146
|
-
* @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.
|
|
147
248
|
*/
|
|
148
249
|
export async function solveCpuTargetInline(baseBlock, target, progressCallback) {
|
|
149
250
|
// --- FIX: Add validation for the target to prevent BigInt conversion errors ---
|
|
@@ -163,33 +264,74 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
|
|
|
163
264
|
const targetStr = typeof target === 'string' ? target : target.toString(16);
|
|
164
265
|
const targetPtr = wasmModule._malloc(targetStr.length + 1);
|
|
165
266
|
for (let i = 0; i < targetStr.length; i++) {
|
|
166
|
-
wasmModule.
|
|
267
|
+
wasmModule.HEAPU8[targetPtr + i] = targetStr.charCodeAt(i);
|
|
268
|
+
}
|
|
269
|
+
wasmModule.HEAPU8[targetPtr + targetStr.length] = 0; // Null-terminator
|
|
270
|
+
try {
|
|
271
|
+
const solution = wasmModule._solve_cpu_target(ptr, len, targetPtr);
|
|
272
|
+
return solution;
|
|
273
|
+
} finally {
|
|
274
|
+
wasmModule._free(ptr);
|
|
275
|
+
wasmModule._free(targetPtr);
|
|
167
276
|
}
|
|
168
|
-
wasmModule.HEAP8[targetPtr + targetStr.length] = 0;
|
|
169
|
-
const solution = wasmModule._solve_cpu_target(ptr, len, targetPtr);
|
|
170
|
-
wasmModule._free(ptr);
|
|
171
|
-
wasmModule._free(targetPtr);
|
|
172
|
-
return solution;
|
|
173
277
|
}
|
|
174
278
|
|
|
175
279
|
// Try Web Worker execution first if supported and not blocked by CSP
|
|
176
280
|
if (typeof window !== 'undefined' && typeof Worker !== 'undefined') {
|
|
177
281
|
try {
|
|
282
|
+
const staticWorkerPath = window.ClientLibrary?.workerPath || (window.ClientConfig && window.ClientConfig.workerPath);
|
|
283
|
+
const sha256FnStr = sha256Sync.toString();
|
|
284
|
+
if (staticWorkerPath) {
|
|
285
|
+
return await new Promise((resolve, reject) => {
|
|
286
|
+
const worker = new Worker(staticWorkerPath);
|
|
287
|
+
worker.onmessage = (event) => {
|
|
288
|
+
if (event.data.type === 'progress') {
|
|
289
|
+
if (progressCallback) progressCallback(event.data.solution);
|
|
290
|
+
} else if (event.data.type === 'success') {
|
|
291
|
+
resolve(event.data.solution);
|
|
292
|
+
worker.terminate();
|
|
293
|
+
} else if (event.data.solution !== undefined) {
|
|
294
|
+
// Fallback for backward compatibility with previous signature
|
|
295
|
+
resolve(event.data.solution);
|
|
296
|
+
worker.terminate();
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
worker.onerror = (err) => {
|
|
300
|
+
worker.terminate();
|
|
301
|
+
reject(err);
|
|
302
|
+
};
|
|
303
|
+
worker.postMessage({ baseBlock, target: cpuTarget.toString() });
|
|
304
|
+
});
|
|
305
|
+
}
|
|
178
306
|
return await new Promise((resolve, reject) => {
|
|
179
307
|
const workerCode = `
|
|
180
308
|
self.onmessage = async (e) => {
|
|
181
309
|
const { baseBlock, target } = e.data;
|
|
182
310
|
const cpuTarget = BigInt(target);
|
|
183
311
|
const encoder = new TextEncoder();
|
|
312
|
+
const hasSubtle = typeof self.crypto !== 'undefined' && self.crypto && self.crypto.subtle && typeof self.crypto.subtle.digest === 'function';
|
|
313
|
+
const sha256Fallback = ${sha256FnStr};
|
|
184
314
|
let cpuSolution = 0;
|
|
185
315
|
while (true) {
|
|
186
316
|
const solutionBytes = encoder.encode(String(cpuSolution));
|
|
187
317
|
const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
|
|
188
318
|
finalBlock.set(baseBlock);
|
|
189
319
|
finalBlock.set(solutionBytes, baseBlock.length);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
320
|
+
let hashHex = '';
|
|
321
|
+
if (hasSubtle) {
|
|
322
|
+
try {
|
|
323
|
+
const buf = await self.crypto.subtle.digest("SHA-256", finalBlock);
|
|
324
|
+
const arr = new Uint8Array(buf);
|
|
325
|
+
for (let j = 0; j < arr.length; j++) {
|
|
326
|
+
hashHex += arr[j].toString(16).padStart(2, '0');
|
|
327
|
+
}
|
|
328
|
+
} catch (subtleErr) {
|
|
329
|
+
hashHex = sha256Fallback(finalBlock);
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
hashHex = sha256Fallback(finalBlock);
|
|
333
|
+
}
|
|
334
|
+
if (BigInt('0x' + hashHex) < cpuTarget) break;
|
|
193
335
|
cpuSolution++;
|
|
194
336
|
if (cpuSolution % 25000 === 0) {
|
|
195
337
|
self.postMessage({ type: 'progress', solution: cpuSolution });
|
|
@@ -224,22 +366,17 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
|
|
|
224
366
|
while (true) {
|
|
225
367
|
const solutionBytes = encoder.encode(String(cpuSolution));
|
|
226
368
|
|
|
227
|
-
//
|
|
369
|
+
// Direct binary concatenation: faster and safer.
|
|
228
370
|
const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
|
|
229
371
|
finalBlock.set(baseBlock);
|
|
230
372
|
finalBlock.set(solutionBytes, baseBlock.length);
|
|
231
373
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
console.log('[FP Solve Debug] Client will hash message:', reconstructedMsg);
|
|
235
|
-
}
|
|
236
|
-
const buf = await crypto.subtle.digest("SHA-256", finalBlock);
|
|
237
|
-
const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
238
|
-
// --- AJOUT DE LOGS POUR LE DÉBOGAGE CÔTÉ CLIENT ---
|
|
374
|
+
const hashHex = await safeDigestSha256Hex(finalBlock);
|
|
375
|
+
// --- CLIENT-SIDE DEBUG LOGS ---
|
|
239
376
|
if (cpuSolution === 0) { // Log only the first attempt
|
|
240
377
|
console.log(`[FP Client Solve] Attempt 0 hash: "0x${hashHex}"`);
|
|
241
378
|
}
|
|
242
|
-
// ---
|
|
379
|
+
// --- END DEBUG LOGS ---
|
|
243
380
|
if (BigInt('0x' + hashHex) < cpuTarget) break;
|
|
244
381
|
cpuSolution++;
|
|
245
382
|
if (cpuSolution % 50000 === 0) {
|
|
@@ -255,28 +392,28 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
|
|
|
255
392
|
}
|
|
256
393
|
|
|
257
394
|
/**
|
|
258
|
-
*
|
|
259
|
-
* @param {string} message -
|
|
260
|
-
* @param {bigint} target -
|
|
261
|
-
* @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.
|
|
262
399
|
*/
|
|
263
400
|
export async function solveCpuTarget(message, target) {
|
|
264
401
|
// Vérifie si les Web Workers sont supportés par le navigateur.
|
|
265
402
|
if (typeof(Worker) === "undefined") {
|
|
266
403
|
console.warn("Web Workers not supported. Falling back to main thread calculation (UI may freeze).");
|
|
267
|
-
//
|
|
268
|
-
//
|
|
404
|
+
// An inline fallback implementation could be placed here if needed.
|
|
405
|
+
// For clarity, we assume worker availability.
|
|
269
406
|
throw new Error("Web Worker support is required for CPU challenges.");
|
|
270
407
|
}
|
|
271
408
|
|
|
272
409
|
return new Promise((resolve, reject) => {
|
|
273
|
-
//
|
|
274
|
-
//
|
|
410
|
+
// Create a worker from the dedicated script. The path must be publicly accessible.
|
|
411
|
+
// Ensure `pow.worker.js` is served by your static assets server.
|
|
275
412
|
const worker = new Worker('./pow.worker.js');
|
|
276
413
|
|
|
277
414
|
worker.onmessage = (event) => {
|
|
278
415
|
resolve(event.data.solution);
|
|
279
|
-
worker.terminate(); //
|
|
416
|
+
worker.terminate(); // Clean up worker once finished.
|
|
280
417
|
};
|
|
281
418
|
|
|
282
419
|
worker.onerror = (error) => {
|
|
@@ -284,16 +421,16 @@ export async function solveCpuTarget(message, target) {
|
|
|
284
421
|
worker.terminate();
|
|
285
422
|
};
|
|
286
423
|
|
|
287
|
-
//
|
|
424
|
+
// Send challenge data to the worker to start calculation.
|
|
288
425
|
worker.postMessage({ message, target });
|
|
289
426
|
});
|
|
290
427
|
}
|
|
291
428
|
|
|
292
429
|
/**
|
|
293
|
-
*
|
|
294
|
-
* @param {string} seed -
|
|
295
|
-
* @param {number} difficulty -
|
|
296
|
-
* @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.
|
|
297
434
|
*/
|
|
298
435
|
export async function solveMemory(seed, difficulty) {
|
|
299
436
|
const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
|
|
@@ -315,8 +452,7 @@ export async function solveMemory(seed, difficulty) {
|
|
|
315
452
|
if (numBlocks === 0) return { solution: 0, merkleRoot: '', proofs: {} };
|
|
316
453
|
|
|
317
454
|
async function hashBlock(block) {
|
|
318
|
-
|
|
319
|
-
return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
455
|
+
return await safeDigestSha256Hex(block.buffer);
|
|
320
456
|
}
|
|
321
457
|
|
|
322
458
|
function hexToBytes(hex) {
|
|
@@ -347,8 +483,7 @@ export async function solveMemory(seed, difficulty) {
|
|
|
347
483
|
const left = currentLayer[i];
|
|
348
484
|
const right = currentLayer[i + 1] || left;
|
|
349
485
|
const combined = hexToBytes(left + right);
|
|
350
|
-
const
|
|
351
|
-
const hashHex = Array.from(new Uint8Array(hashBuf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
486
|
+
const hashHex = await safeDigestSha256Hex(combined);
|
|
352
487
|
nextLayer.push(hashHex);
|
|
353
488
|
}
|
|
354
489
|
tree.push(nextLayer);
|
|
@@ -397,12 +532,12 @@ export async function solveMemory(seed, difficulty) {
|
|
|
397
532
|
}
|
|
398
533
|
|
|
399
534
|
/**
|
|
400
|
-
*
|
|
401
|
-
* NOTE:
|
|
402
|
-
*
|
|
403
|
-
* @param {Array<{x: number, y: number}>} cities -
|
|
404
|
-
* @param {number} targetMaxDistance -
|
|
405
|
-
* @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.
|
|
406
541
|
*/
|
|
407
542
|
export async function solveTsp(cities, targetMaxDistance) {
|
|
408
543
|
// Utility function to calculate the distance between two cities
|
|
@@ -460,7 +595,7 @@ export async function solveTsp(cities, targetMaxDistance) {
|
|
|
460
595
|
return { path: solutionPath, distance: solutionDistance };
|
|
461
596
|
}
|
|
462
597
|
|
|
463
|
-
// ---
|
|
598
|
+
// --- Optimization functions adapted from library.js for client usage ---
|
|
464
599
|
|
|
465
600
|
function paretoDominates(objectivesA, objectivesB) {
|
|
466
601
|
let aIsBetterInOne = false;
|
|
@@ -781,7 +916,7 @@ const ClientOptimizers = {
|
|
|
781
916
|
|
|
782
917
|
const mutate = (solution) => {
|
|
783
918
|
const newSolution = [...solution];
|
|
784
|
-
const i = Math.floor(
|
|
919
|
+
const i = Math.floor(secureRandom() * 4);
|
|
785
920
|
const mutationFactors = [500, 1000, 0.1, 2];
|
|
786
921
|
newSolution[i] += (secureRandom() - 0.5) * mutationFactors[i];
|
|
787
922
|
return newSolution;
|
|
@@ -808,9 +943,9 @@ const ClientOptimizers = {
|
|
|
808
943
|
};
|
|
809
944
|
|
|
810
945
|
/**
|
|
811
|
-
*
|
|
812
|
-
* @param {object} task -
|
|
813
|
-
* @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.
|
|
814
949
|
*/
|
|
815
950
|
async function solveUsefulWorkTask(task) {
|
|
816
951
|
await new Promise(r => setTimeout(r, 10)); // Yield thread
|
|
@@ -885,7 +1020,7 @@ async function solveUsefulWorkTask(task) {
|
|
|
885
1020
|
const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
|
|
886
1021
|
const mutate = p => { const n = [...p], i = Math.floor(secureRandom() * n.length); n[i] += (secureRandom() - 0.5) * 0.2; return n.map(v => Math.max(0, v)); };
|
|
887
1022
|
|
|
888
|
-
//
|
|
1023
|
+
// The client must recreate the population if not provided
|
|
889
1024
|
const initialPopulation = task.initialPopulation || Array.from({ length: task.payload.options.populationSize }, () => ({ chromosome: createIndividual(), fitness: 0 }));
|
|
890
1025
|
initialPopulation.forEach(p => p.fitness = fitness(p.chromosome));
|
|
891
1026
|
|
|
@@ -894,33 +1029,31 @@ async function solveUsefulWorkTask(task) {
|
|
|
894
1029
|
}
|
|
895
1030
|
|
|
896
1031
|
case 'run_multiple_parallel':
|
|
897
|
-
//
|
|
898
|
-
//
|
|
899
|
-
//
|
|
1032
|
+
// On the client side, Web Workers are not nested for runMultipleParallel.
|
|
1033
|
+
// Run a single cycle of the requested solver.
|
|
1034
|
+
// This still represents non-trivial computational work for useful PoW.
|
|
900
1035
|
const { solverName, baseSolverArgs } = task;
|
|
901
1036
|
const clientSolver = ClientOptimizers[solverName];
|
|
902
1037
|
if (!clientSolver) throw new Error(`Solver ${solverName} not found on client.`);
|
|
903
1038
|
|
|
904
|
-
//
|
|
905
|
-
// Note: `baseSolverArgs`
|
|
1039
|
+
// Execute solver with base arguments.
|
|
1040
|
+
// Note: `baseSolverArgs` may contain solver options.
|
|
906
1041
|
return clientSolver(...baseSolverArgs);
|
|
907
1042
|
|
|
908
1043
|
case 'multi_objective_genetic_algorithm': {
|
|
909
|
-
//
|
|
910
|
-
// On cherche le solveur demandé (ex: 'cpc.solve') dans notre registre client.
|
|
1044
|
+
// Look up requested solver (e.g. 'cpc.solve') in the client registry.
|
|
911
1045
|
const solverFunction = ClientOptimizers[task.solverName];
|
|
912
1046
|
if (!solverFunction) {
|
|
913
1047
|
throw new Error(`Solver '${task.solverName}' not found on client.`);
|
|
914
1048
|
}
|
|
915
1049
|
// On appelle le solveur en lui passant le payload et les options.
|
|
916
|
-
//
|
|
1050
|
+
// Invoke solver with payload and options.
|
|
917
1051
|
return solverFunction(task.payload, { generations: task.generations, initialFront: task.initialFront });
|
|
918
1052
|
}
|
|
919
1053
|
|
|
920
1054
|
case 'pytorch_onnx_learning': {
|
|
921
|
-
//
|
|
922
|
-
//
|
|
923
|
-
// Ici, nous simulons le calcul de gradient mathématique équivalent pour la régression/classification.
|
|
1055
|
+
// In a production environment with ONNX Runtime Web, load .onnx and execute gradient backward pass.
|
|
1056
|
+
// Here, simulate the gradient backward pass for regression/classification.
|
|
924
1057
|
const { weights, payload } = task;
|
|
925
1058
|
const inputs = payload.inputs || [];
|
|
926
1059
|
const labels = payload.labels || [];
|
|
@@ -946,7 +1079,7 @@ async function solveUsefulWorkTask(task) {
|
|
|
946
1079
|
const labels = payload.labels || [];
|
|
947
1080
|
try {
|
|
948
1081
|
const tf = await loadTfjs();
|
|
949
|
-
//
|
|
1082
|
+
// Non-blocking model loading
|
|
950
1083
|
const model = await tf.loadLayersModel(modelPath);
|
|
951
1084
|
|
|
952
1085
|
const xs = tf.tensor2d(inputs);
|
|
@@ -958,7 +1091,7 @@ async function solveUsefulWorkTask(task) {
|
|
|
958
1091
|
return tf.losses.meanSquaredError(ys, preds);
|
|
959
1092
|
};
|
|
960
1093
|
|
|
961
|
-
//
|
|
1094
|
+
// Automatic differentiation for exact gradient computation
|
|
962
1095
|
const gFn = tf.grad(lossFn);
|
|
963
1096
|
const grads = gFn(trainableVars);
|
|
964
1097
|
|
|
@@ -989,15 +1122,15 @@ async function solveUsefulWorkTask(task) {
|
|
|
989
1122
|
}
|
|
990
1123
|
|
|
991
1124
|
/**
|
|
992
|
-
*
|
|
993
|
-
*
|
|
994
|
-
* NOTE:
|
|
995
|
-
* @param {Array<object>} initialPopulation -
|
|
996
|
-
* @param {number} generations -
|
|
997
|
-
* @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.
|
|
998
1131
|
*/
|
|
999
1132
|
export async function solveOptimizationTask(initialPopulation, generations) {
|
|
1000
|
-
//
|
|
1133
|
+
// Simplified GA operators (crossover, mutation)
|
|
1001
1134
|
const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
|
|
1002
1135
|
const mutate = (p) => {
|
|
1003
1136
|
const newP = [...p];
|
|
@@ -1009,20 +1142,20 @@ export async function solveOptimizationTask(initialPopulation, generations) {
|
|
|
1009
1142
|
let population = initialPopulation;
|
|
1010
1143
|
|
|
1011
1144
|
for (let gen = 0; gen < generations; gen++) {
|
|
1012
|
-
//
|
|
1145
|
+
// Simple selection: keep top 50%
|
|
1013
1146
|
const parents = population.sort((a, b) => a.fitness - b.fitness).slice(0, Math.ceil(population.length / 2));
|
|
1014
|
-
const newPopulation = [...parents]; //
|
|
1147
|
+
const newPopulation = [...parents]; // Elitism
|
|
1015
1148
|
|
|
1016
1149
|
while (newPopulation.length < population.length) {
|
|
1017
1150
|
const parent1 = parents[Math.floor(secureRandom() * parents.length)];
|
|
1018
1151
|
const parent2 = parents[Math.floor(secureRandom() * parents.length)];
|
|
1019
1152
|
let offspring = crossover(parent1.chromosome, parent2.chromosome);
|
|
1020
1153
|
if (secureRandom() < 0.1) offspring = mutate(offspring);
|
|
1021
|
-
//
|
|
1154
|
+
// Fitness is recalculated server-side for verification.
|
|
1022
1155
|
newPopulation.push({ chromosome: offspring, fitness: -1 });
|
|
1023
1156
|
}
|
|
1024
1157
|
population = newPopulation;
|
|
1025
|
-
//
|
|
1158
|
+
// Yield execution to prevent freezing the UI on long tasks
|
|
1026
1159
|
if (gen % 10 === 0) await new Promise(r => setTimeout(r, 0));
|
|
1027
1160
|
}
|
|
1028
1161
|
return population;
|
|
@@ -1030,7 +1163,7 @@ export async function solveOptimizationTask(initialPopulation, generations) {
|
|
|
1030
1163
|
|
|
1031
1164
|
/**
|
|
1032
1165
|
* @class ChallengeSolution
|
|
1033
|
-
* @description
|
|
1166
|
+
* @description Encapsulates a challenge solution and formatting helpers.
|
|
1034
1167
|
* @private
|
|
1035
1168
|
*/
|
|
1036
1169
|
class ChallengeSolution {
|
|
@@ -1041,14 +1174,14 @@ class ChallengeSolution {
|
|
|
1041
1174
|
}
|
|
1042
1175
|
|
|
1043
1176
|
/**
|
|
1044
|
-
*
|
|
1045
|
-
* @param {URL} url -
|
|
1177
|
+
* Applies solution parameters to a URL instance.
|
|
1178
|
+
* @param {URL} url - Target URL object to modify.
|
|
1046
1179
|
*/
|
|
1047
1180
|
applyToUrl(url) {
|
|
1048
1181
|
url.searchParams.set('pow_type', this.type);
|
|
1049
1182
|
url.searchParams.set('pow_nonce', this.nonce);
|
|
1050
1183
|
|
|
1051
|
-
//
|
|
1184
|
+
// Challenge-specific parameter formatting
|
|
1052
1185
|
if (this.type === 'cpu_mem' || this.type === 'cpu_mem_inline' || this.type === 'cpu_target') {
|
|
1053
1186
|
Object.entries(this.rawSolution).forEach(([key, value]) => {
|
|
1054
1187
|
if (typeof value === 'object' && value !== null) {
|
|
@@ -1061,17 +1194,17 @@ class ChallengeSolution {
|
|
|
1061
1194
|
url.searchParams.set('pow_solution_work_result', JSON.stringify(this.rawSolution.work_result));
|
|
1062
1195
|
url.searchParams.set('pow_problem_id', this.rawSolution.problem_id);
|
|
1063
1196
|
} else {
|
|
1064
|
-
//
|
|
1197
|
+
// Single-value solutions like TSP
|
|
1065
1198
|
url.searchParams.set('pow_solution', JSON.stringify(this.rawSolution));
|
|
1066
1199
|
}
|
|
1067
1200
|
}
|
|
1068
1201
|
}
|
|
1069
1202
|
|
|
1070
1203
|
/**
|
|
1071
|
-
*
|
|
1072
|
-
* @param {object} challenge -
|
|
1073
|
-
* @param {string} [fingerprint=''] -
|
|
1074
|
-
* @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.
|
|
1075
1208
|
*/
|
|
1076
1209
|
export async function solveChallenge(challenge, fingerprint = '') { // The fingerprint is now passed from the client library
|
|
1077
1210
|
const { type, nonce, clientSecret, cpuTarget, memDifficulty, cities, clientIp, targetMaxDistance, optimizationTask, usefulWorkTask, queries, sizeMb } = challenge;
|
|
@@ -1084,12 +1217,12 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
|
|
|
1084
1217
|
throw new Error("Challenge data is missing 'cpuTarget' property.");
|
|
1085
1218
|
}
|
|
1086
1219
|
const target = cpuTarget; // Keep variable name for consistency below
|
|
1087
|
-
//
|
|
1220
|
+
// For this simple challenge, baseBlock is the nonce.
|
|
1088
1221
|
const baseBlockBytes = new TextEncoder().encode(nonce + ":");
|
|
1089
1222
|
rawSolution.cpu = await solveCpuTargetInline(baseBlockBytes, target, null);
|
|
1090
1223
|
break;
|
|
1091
1224
|
case 'cpu_mem':
|
|
1092
|
-
//
|
|
1225
|
+
// For API calls, client IP is omitted from client-side message reconstruction
|
|
1093
1226
|
const baseMessageCombined = `:${nonce}:${clientSecret}`;
|
|
1094
1227
|
const memSeed = `:${nonce}:${clientSecret}`;
|
|
1095
1228
|
const [cpuSol, memSol] = await Promise.all([
|
|
@@ -1104,7 +1237,7 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
|
|
|
1104
1237
|
rawSolution.mem = memSol;
|
|
1105
1238
|
break;
|
|
1106
1239
|
case 'cpu_mem_inline':
|
|
1107
|
-
//
|
|
1240
|
+
// Inline version for HTML compatibility with IP
|
|
1108
1241
|
const memSeedInline = `:${nonce}:${clientSecret}`;
|
|
1109
1242
|
const [cpuSolInline, memSolInline] = await Promise.all([
|
|
1110
1243
|
(async () => {
|
|
@@ -1119,12 +1252,12 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
|
|
|
1119
1252
|
break;
|
|
1120
1253
|
case 'tsp':
|
|
1121
1254
|
const tspResult = await solveTsp(cities, targetMaxDistance);
|
|
1122
|
-
//
|
|
1255
|
+
// For TSP, solution is the tour path array
|
|
1123
1256
|
rawSolution = tspResult.path;
|
|
1124
1257
|
break;
|
|
1125
1258
|
case 'optimization_task':
|
|
1126
1259
|
const finalPopulation = await solveOptimizationTask(optimizationTask.population, optimizationTask.generations);
|
|
1127
|
-
rawSolution = finalPopulation.map(p => p.chromosome); //
|
|
1260
|
+
rawSolution = finalPopulation.map(p => p.chromosome); // Return chromosomes only
|
|
1128
1261
|
break;
|
|
1129
1262
|
case 'useful_work_task':
|
|
1130
1263
|
const workResult = await solveUsefulWorkTask(usefulWorkTask.task);
|
|
@@ -1142,11 +1275,12 @@ export async function solveChallenge(challenge, fingerprint = '') { // The finge
|
|
|
1142
1275
|
return new ChallengeSolution(type, nonce, rawSolution);
|
|
1143
1276
|
}
|
|
1144
1277
|
|
|
1145
|
-
// ---
|
|
1146
|
-
//
|
|
1278
|
+
// --- Direct HTML script injection compatibility ---
|
|
1279
|
+
// If loaded in browser context (window exists), expose methods on window.
|
|
1147
1280
|
if (typeof window !== 'undefined') {
|
|
1148
1281
|
window.solveCpuChallengeInline = solveCpuTargetInline;
|
|
1149
1282
|
window.solveMemoryChallenge = solveMemory;
|
|
1283
|
+
window.readSpaceBlock = readSpaceBlock;
|
|
1150
1284
|
window.solveTspChallenge = solveTsp;
|
|
1151
1285
|
window.solveChallenge = solveChallenge;
|
|
1152
1286
|
}
|