@anonympins/fingerprint 0.5.0 → 0.5.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 +31 -0
- package/README.md +118 -115
- package/package.json +1 -1
- package/src/js/dynamic-wasm.js +52 -13
- package/src/js/fingerprint.builder.js +169 -168
- package/src/js/fingerprint.client.js +224 -24
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +382 -63
- package/src/js/fingerprint.utils.js +213 -183
- package/src/js/gpu_pow.solver.js +312 -0
- package/src/js/library.js +16 -13
- package/src/js/pow.solver.inline.js +138 -27
- package/src/js/pow.solver.js +141 -28
- package/src/js/tests/fingerprint.client.init.test.js +5 -2
- package/src/js/tests/fingerprint.engine.test.js +370 -370
- package/src/js/tests/fingerprint.test.js +150 -3
- package/src/js/tests/gpu_pow.test.js +199 -0
- package/src/js/tests/pow.solver.test.js +4 -4
- package/src/js/tests/quicFingerprint.test.js +35 -0
- package/src/php/Challenge/ChallengeUtils.php +158 -27
- package/src/php/Config/SecurityProfiles.php +9 -0
- package/src/php/FingerprintEngine.php +124 -2
- package/src/php/RequestContext.php +2 -0
- package/src/php/Tests/ChallengeUtilsTest.php +77 -0
- package/src/php/Tests/QuicFingerprintTest.php +54 -0
- package/src/php/Tests/RequestUtilsTest.php +43 -0
- package/src/php/Utils/RequestUtils.php +120 -0
package/src/js/pow.solver.js
CHANGED
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
12
|
function cyrb53(str, seed = 0) {
|
|
13
|
+
const safeStr = (typeof str === 'string' ? str : String(str || '')).slice(0, 10000);
|
|
13
14
|
let h1 = 0xdeadbeef ^ seed,
|
|
14
15
|
h2 = 0x41c6ce57 ^ seed;
|
|
15
|
-
for (let i = 0, ch; i <
|
|
16
|
-
ch =
|
|
16
|
+
for (let i = 0, ch; i < safeStr.length; i++) {
|
|
17
|
+
ch = safeStr.charCodeAt(i);
|
|
17
18
|
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
18
19
|
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
19
20
|
}
|
|
@@ -143,6 +144,53 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
|
|
|
143
144
|
return solution;
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
// Try Web Worker execution first if supported and not blocked by CSP
|
|
148
|
+
if (typeof window !== 'undefined' && typeof Worker !== 'undefined') {
|
|
149
|
+
try {
|
|
150
|
+
return await new Promise((resolve, reject) => {
|
|
151
|
+
const workerCode = `
|
|
152
|
+
self.onmessage = async (e) => {
|
|
153
|
+
const { baseBlock, target } = e.data;
|
|
154
|
+
const cpuTarget = BigInt(target);
|
|
155
|
+
const encoder = new TextEncoder();
|
|
156
|
+
let cpuSolution = 0;
|
|
157
|
+
while (true) {
|
|
158
|
+
const solutionBytes = encoder.encode(String(cpuSolution));
|
|
159
|
+
const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
|
|
160
|
+
finalBlock.set(baseBlock);
|
|
161
|
+
finalBlock.set(solutionBytes, baseBlock.length);
|
|
162
|
+
const buf = await crypto.subtle.digest("SHA-256", finalBlock);
|
|
163
|
+
const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
164
|
+
if (BigInt('0x' + hashHex) < cpuTarget) break;
|
|
165
|
+
cpuSolution++;
|
|
166
|
+
if (cpuSolution % 25000 === 0) {
|
|
167
|
+
self.postMessage({ type: 'progress', solution: cpuSolution });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
self.postMessage({ type: 'success', solution: cpuSolution });
|
|
171
|
+
};
|
|
172
|
+
`;
|
|
173
|
+
const blob = new Blob([workerCode], { type: 'application/javascript' });
|
|
174
|
+
const worker = new Worker(URL.createObjectURL(blob));
|
|
175
|
+
worker.onmessage = (event) => {
|
|
176
|
+
if (event.data.type === 'progress') {
|
|
177
|
+
if (progressCallback) progressCallback(event.data.solution);
|
|
178
|
+
} else if (event.data.type === 'success') {
|
|
179
|
+
resolve(event.data.solution);
|
|
180
|
+
worker.terminate();
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
worker.onerror = (err) => {
|
|
184
|
+
worker.terminate();
|
|
185
|
+
reject(err);
|
|
186
|
+
};
|
|
187
|
+
worker.postMessage({ baseBlock, target: cpuTarget.toString() });
|
|
188
|
+
});
|
|
189
|
+
} catch (workerError) {
|
|
190
|
+
console.warn("Web Worker creation failed (possibly due to CSP). Falling back to main thread with scheduler/setTimeout yielding.");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
146
194
|
let cpuSolution = 0;
|
|
147
195
|
|
|
148
196
|
while (true) {
|
|
@@ -166,8 +214,12 @@ export async function solveCpuTargetInline(baseBlock, target, progressCallback)
|
|
|
166
214
|
// --- FIN DES LOGS ---
|
|
167
215
|
if (BigInt('0x' + hashHex) < cpuTarget) break;
|
|
168
216
|
cpuSolution++;
|
|
169
|
-
|
|
170
|
-
|
|
217
|
+
if (cpuSolution % 50000 === 0) {
|
|
218
|
+
if (typeof scheduler !== 'undefined' && typeof scheduler.yield === 'function') {
|
|
219
|
+
await scheduler.yield();
|
|
220
|
+
} else {
|
|
221
|
+
await new Promise(r => setTimeout(r, 0));
|
|
222
|
+
}
|
|
171
223
|
if (progressCallback) progressCallback(cpuSolution);
|
|
172
224
|
}
|
|
173
225
|
}
|
|
@@ -216,43 +268,100 @@ export async function solveCpuTarget(message, target) {
|
|
|
216
268
|
* @returns {Promise<number>} La solution (nombre entier).
|
|
217
269
|
*/
|
|
218
270
|
export async function solveMemory(seed, difficulty) {
|
|
219
|
-
// On définit un seuil pour savoir quand faire une pause, afin de ne pas bloquer le thread UI.
|
|
220
|
-
const YIELD_THRESHOLD = 100000;
|
|
221
|
-
const size = difficulty * 1024 * 1024;
|
|
222
|
-
const buffer = new Uint32Array(size / 4);
|
|
223
|
-
|
|
224
271
|
const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
|
|
225
272
|
if (wasmModule && typeof wasmModule._solve_memory_challenge === 'function') {
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
wasmModule.
|
|
231
|
-
const solution = wasmModule._solve_memory_challenge(
|
|
232
|
-
wasmModule._free(
|
|
273
|
+
const encoder = new TextEncoder();
|
|
274
|
+
const seedBytes = encoder.encode(seed);
|
|
275
|
+
const ptr = wasmModule._malloc(seedBytes.length + 1);
|
|
276
|
+
wasmModule.HEAPU8.set(seedBytes, ptr);
|
|
277
|
+
wasmModule.HEAPU8[ptr + seedBytes.length] = 0; // Null-terminator
|
|
278
|
+
const solution = wasmModule._solve_memory_challenge(ptr, difficulty);
|
|
279
|
+
wasmModule._free(ptr);
|
|
233
280
|
return solution;
|
|
234
281
|
}
|
|
235
282
|
|
|
236
|
-
|
|
283
|
+
const size = difficulty * 1024 * 1024;
|
|
284
|
+
const numBlocks = difficulty * 256;
|
|
285
|
+
if (numBlocks === 0) return { solution: 0, merkleRoot: '', proofs: {} };
|
|
237
286
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
287
|
+
async function hashBlock(block) {
|
|
288
|
+
const buf = await crypto.subtle.digest("SHA-256", block.buffer);
|
|
289
|
+
return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function hexToBytes(hex) {
|
|
293
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
294
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
295
|
+
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
242
296
|
}
|
|
297
|
+
return bytes;
|
|
243
298
|
}
|
|
244
299
|
|
|
300
|
+
const leaves = [];
|
|
301
|
+
const blocks = [];
|
|
302
|
+
for (let b = 0; b < numBlocks; b++) {
|
|
303
|
+
const block = new Uint32Array(1024);
|
|
304
|
+
let h = cyrb53(seed + ":" + b);
|
|
305
|
+
for (let i = 0; i < 1024; i++) {
|
|
306
|
+
block[i] = (h = Math.imul(h ^ i, 1597334677));
|
|
307
|
+
}
|
|
308
|
+
blocks.push(block);
|
|
309
|
+
leaves.push(await hashBlock(block));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const tree = [leaves];
|
|
313
|
+
while (tree[tree.length - 1].length > 1) {
|
|
314
|
+
const currentLayer = tree[tree.length - 1];
|
|
315
|
+
const nextLayer = [];
|
|
316
|
+
for (let i = 0; i < currentLayer.length; i += 2) {
|
|
317
|
+
const left = currentLayer[i];
|
|
318
|
+
const right = currentLayer[i + 1] || left;
|
|
319
|
+
const combined = hexToBytes(left + right);
|
|
320
|
+
const hashBuf = await crypto.subtle.digest("SHA-256", combined);
|
|
321
|
+
const hashHex = Array.from(new Uint8Array(hashBuf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
322
|
+
nextLayer.push(hashHex);
|
|
323
|
+
}
|
|
324
|
+
tree.push(nextLayer);
|
|
325
|
+
}
|
|
326
|
+
const merkleRoot = tree[tree.length - 1][0];
|
|
327
|
+
|
|
328
|
+
function readBuffer(blocks, addr) {
|
|
329
|
+
const blockIdx = Math.floor(addr / 1024);
|
|
330
|
+
const elementIdx = addr % 1024;
|
|
331
|
+
return blocks[blockIdx][elementIdx];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const totalElements = numBlocks * 1024;
|
|
335
|
+
let addr = totalElements > 0 ? readBuffer(blocks, 0) % totalElements : 0;
|
|
245
336
|
let solution = 0;
|
|
246
|
-
const iterations =
|
|
247
|
-
let addr = buffer.length > 0 ? buffer[0] % buffer.length : 0;
|
|
337
|
+
const iterations = 1024;
|
|
248
338
|
for (let i = 0; i < iterations; i++) {
|
|
249
|
-
addr =
|
|
339
|
+
addr = readBuffer(blocks, addr) % totalElements;
|
|
250
340
|
solution ^= addr;
|
|
251
|
-
|
|
252
|
-
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const challengedIndices = [];
|
|
344
|
+
let h_idx = cyrb53(seed + ":" + solution);
|
|
345
|
+
for (let i = 0; i < 4; i++) {
|
|
346
|
+
h_idx = Math.imul(h_idx ^ i, 1597334677);
|
|
347
|
+
challengedIndices.push(Math.abs(h_idx) % numBlocks);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const proofs = {};
|
|
351
|
+
for (const index of challengedIndices) {
|
|
352
|
+
const proof = [];
|
|
353
|
+
let idx = index;
|
|
354
|
+
for (let layer = 0; layer < tree.length - 1; layer++) {
|
|
355
|
+
const isRight = idx % 2 === 1;
|
|
356
|
+
const siblingIdx = isRight ? idx - 1 : idx + 1;
|
|
357
|
+
const sibling = tree[layer][siblingIdx] || tree[layer][idx];
|
|
358
|
+
proof.push(sibling);
|
|
359
|
+
idx = Math.floor(idx / 2);
|
|
253
360
|
}
|
|
361
|
+
proofs[index] = proof;
|
|
254
362
|
}
|
|
255
|
-
|
|
363
|
+
|
|
364
|
+
return { solution, merkleRoot, proofs };
|
|
256
365
|
}
|
|
257
366
|
|
|
258
367
|
/**
|
|
@@ -539,7 +648,11 @@ class ChallengeSolution {
|
|
|
539
648
|
// Logique de formatage spécifique à chaque type de challenge
|
|
540
649
|
if (this.type === 'cpu_mem' || this.type === 'cpu_mem_inline' || this.type === 'cpu_target') {
|
|
541
650
|
Object.entries(this.rawSolution).forEach(([key, value]) => {
|
|
542
|
-
|
|
651
|
+
if (typeof value === 'object' && value !== null) {
|
|
652
|
+
url.searchParams.set(`pow_solution_${key}`, JSON.stringify(value));
|
|
653
|
+
} else {
|
|
654
|
+
url.searchParams.set(`pow_solution_${key}`, String(value));
|
|
655
|
+
}
|
|
543
656
|
});
|
|
544
657
|
} else if (this.type === 'useful_work_task') {
|
|
545
658
|
url.searchParams.set('pow_solution_work_result', JSON.stringify(this.rawSolution.work_result));
|
|
@@ -28,7 +28,7 @@ global.TextEncoder = dom.window.TextEncoder;
|
|
|
28
28
|
describe('ClientLibrary.initializeClient', () => {
|
|
29
29
|
|
|
30
30
|
// On utilise des espions (spies) pour vérifier si les méthodes internes sont appelées.
|
|
31
|
-
let startMouseSpy, startKeystrokeSpy, startClickSpy, startTouchSpy, initWasmSpy, initHoneypotsSpy, injectTrapsSpy, initFetchSpy, injectPhantomTrapsSpy;
|
|
31
|
+
let startMouseSpy, startKeystrokeSpy, startClickSpy, startTouchSpy, startRenderingSpy, initWasmSpy, initHoneypotsSpy, injectTrapsSpy, initFetchSpy, injectPhantomTrapsSpy;
|
|
32
32
|
|
|
33
33
|
beforeEach(() => {
|
|
34
34
|
// Réinitialiser l'état du client avant chaque test
|
|
@@ -39,6 +39,7 @@ describe('ClientLibrary.initializeClient', () => {
|
|
|
39
39
|
startKeystrokeSpy = vi.spyOn(ClientLibrary, 'startKeystrokeDynamicsTracker');
|
|
40
40
|
startClickSpy = vi.spyOn(ClientLibrary, 'startClickTracker');
|
|
41
41
|
startTouchSpy = vi.spyOn(ClientLibrary, 'startTouchEventTracker');
|
|
42
|
+
startRenderingSpy = vi.spyOn(ClientLibrary, 'startRenderingTracker');
|
|
42
43
|
initWasmSpy = vi.spyOn(ClientLibrary, 'initializeWasm').mockResolvedValue(undefined);
|
|
43
44
|
initHoneypotsSpy = vi.spyOn(ClientLibrary, 'initializeHoneypots');
|
|
44
45
|
injectTrapsSpy = vi.spyOn(ClientLibrary, 'injectTrapLinks');
|
|
@@ -58,16 +59,18 @@ describe('ClientLibrary.initializeClient', () => {
|
|
|
58
59
|
expect(startKeystrokeSpy).toHaveBeenCalled();
|
|
59
60
|
expect(startClickSpy).toHaveBeenCalled();
|
|
60
61
|
expect(startTouchSpy).toHaveBeenCalled();
|
|
62
|
+
expect(startRenderingSpy).toHaveBeenCalled();
|
|
61
63
|
expect(injectPhantomTrapsSpy).toHaveBeenCalled();
|
|
62
64
|
});
|
|
63
65
|
|
|
64
66
|
it('should disable trackers when configured', () => {
|
|
65
|
-
ClientLibrary.initializeClient({ mouse: false, keystrokes: false, clicks: false, touches: false });
|
|
67
|
+
ClientLibrary.initializeClient({ mouse: false, keystrokes: false, clicks: false, touches: false, rendering: false });
|
|
66
68
|
|
|
67
69
|
expect(startMouseSpy).not.toHaveBeenCalled();
|
|
68
70
|
expect(startKeystrokeSpy).not.toHaveBeenCalled();
|
|
69
71
|
expect(startClickSpy).not.toHaveBeenCalled();
|
|
70
72
|
expect(startTouchSpy).not.toHaveBeenCalled();
|
|
73
|
+
expect(startRenderingSpy).not.toHaveBeenCalled();
|
|
71
74
|
});
|
|
72
75
|
|
|
73
76
|
it('should initialize WASM if wasmPath is configured', () => {
|