@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.
@@ -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 < str.length; i++) {
16
- ch = str.charCodeAt(i);
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
- if (cpuSolution % 100000 === 0) {
170
- await new Promise(r => setTimeout(r, 0));
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 seedPtr = wasmModule._malloc(seed.length + 1);
227
- for (let i = 0; i < seed.length; i++) {
228
- wasmModule.HEAP8[seedPtr + i] = seed.charCodeAt(i);
229
- }
230
- wasmModule.HEAP8[seedPtr + seed.length] = 0;
231
- const solution = wasmModule._solve_memory_challenge(seedPtr, difficulty);
232
- wasmModule._free(seedPtr);
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
- let h = new TextEncoder().encode(seed).reduce((acc, v) => acc + v, 0);
283
+ const size = difficulty * 1024 * 1024;
284
+ const numBlocks = difficulty * 256;
285
+ if (numBlocks === 0) return { solution: 0, merkleRoot: '', proofs: {} };
237
286
 
238
- for (let i = 0; i < buffer.length; i++) {
239
- buffer[i] = (h = Math.imul(h ^ i, 1597334677));
240
- if (i % YIELD_THRESHOLD === 0) {
241
- await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
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 = size / 16;
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 = buffer[addr] % buffer.length;
339
+ addr = readBuffer(blocks, addr) % totalElements;
250
340
  solution ^= addr;
251
- if (i % YIELD_THRESHOLD === 0) {
252
- await new Promise(r => setTimeout(r, 0)); // Respiration pour ne pas geler l'UI
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
- return solution;
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
- url.searchParams.set(`pow_solution_${key}`, String(value));
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', () => {