@anonympins/fingerprint 0.5.4 → 0.6.0

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.
@@ -0,0 +1,23 @@
1
+ import { parentPort } from 'node:worker_threads';
2
+ import JavaScriptObfuscator from 'javascript-obfuscator';
3
+
4
+ if (parentPort) {
5
+ parentPort.on('message', (workerData) => {
6
+ const { jsCode, seed } = workerData;
7
+ try {
8
+ const obfuscationResult = JavaScriptObfuscator.obfuscate(jsCode, {
9
+ compact: true,
10
+ controlFlowFlattening: true,
11
+ deadCodeInjection: true,
12
+ stringArray: true,
13
+ stringArrayRotate: true,
14
+ stringArrayShuffle: true,
15
+ seed: Math.abs(seed),
16
+ selfDefending: true,
17
+ });
18
+ parentPort.postMessage({ obfuscatedCode: obfuscationResult.getObfuscatedCode() });
19
+ } catch (error) {
20
+ parentPort.postMessage({ error: error.message, stack: error.stack });
21
+ }
22
+ });
23
+ }
@@ -6,6 +6,15 @@
6
6
 
7
7
  'use strict';
8
8
 
9
+ function secureRandom() {
10
+ if (typeof window !== 'undefined' && (window.crypto || window.msCrypto)) {
11
+ const array = new Uint32Array(1);
12
+ (window.crypto || window.msCrypto).getRandomValues(array);
13
+ return array[0] / 0x100000000;
14
+ }
15
+ return Math.random();
16
+ }
17
+
9
18
  function cyrb53(str, seed = 0) {
10
19
  const safeStr = (typeof str === 'string' ? str : String(str || '')).slice(0, 10000);
11
20
  let h1 = 0xdeadbeef ^ seed,
@@ -193,7 +202,7 @@ const ClientOptimizers = {
193
202
  for (let i = 0; i < iterations; i++) {
194
203
  const newSolution = neighbor(currentSolution);
195
204
  const newEnergy = evaluator(newSolution);
196
- if (newEnergy < currentEnergy || Math.random() < Math.exp((currentEnergy - newEnergy) / temperature)) {
205
+ if (newEnergy < currentEnergy || secureRandom() < Math.exp((currentEnergy - newEnergy) / temperature)) {
197
206
  currentSolution = newSolution;
198
207
  currentEnergy = newEnergy;
199
208
  }
@@ -212,10 +221,10 @@ const ClientOptimizers = {
212
221
  population.sort((a, b) => a.fitness - b.fitness);
213
222
  const newPopulation = [population[0]]; // Elitism
214
223
  while (newPopulation.length < popSize) {
215
- const p1 = population[Math.floor(Math.random() * (popSize / 2))];
216
- const p2 = population[Math.floor(Math.random() * (popSize / 2))];
224
+ const p1 = population[Math.floor(secureRandom() * (popSize / 2))];
225
+ const p2 = population[Math.floor(secureRandom() * (popSize / 2))];
217
226
  let offspring = crossover(p1.chromosome, p2.chromosome);
218
- if (Math.random() < 0.1) offspring = mutate(offspring);
227
+ if (secureRandom() < 0.1) offspring = mutate(offspring);
219
228
  newPopulation.push({ chromosome: offspring, fitness: fitness(offspring) });
220
229
  }
221
230
  population = newPopulation;
@@ -236,10 +245,10 @@ const ClientOptimizers = {
236
245
  for (let gen = 0; gen < generations; gen++) {
237
246
  const offspring = [];
238
247
  for (let i = 0; i < populationSize; i++) {
239
- const parent1 = population[Math.floor(Math.random() * population.length)];
240
- const parent2 = population[Math.floor(Math.random() * population.length)];
248
+ const parent1 = population[Math.floor(secureRandom() * population.length)];
249
+ const parent2 = population[Math.floor(secureRandom() * population.length)];
241
250
  let childIndividual = crossover(parent1.individual, parent2.individual);
242
- if (Math.random() < mutationRate) {
251
+ if (secureRandom() < mutationRate) {
243
252
  childIndividual = mutate(childIndividual);
244
253
  }
245
254
  const child = { individual: childIndividual };
@@ -307,9 +316,9 @@ const ClientOptimizers = {
307
316
  return [-platformRevenue, -advertiserValue, marketImbalance];
308
317
  };
309
318
 
310
- const createIndividual = () => 0.5 + Math.random() * 4.5;
319
+ const createIndividual = () => 0.5 + secureRandom() * 4.5;
311
320
  const crossover = (cpc1, cpc2) => (cpc1 + cpc2) / 2;
312
- const mutate = (cpc) => Math.max(0.1, cpc + (Math.random() - 0.5) * 0.5);
321
+ const mutate = (cpc) => Math.max(0.1, cpc + (secureRandom() - 0.5) * 0.5);
313
322
 
314
323
  const gaOptions = {
315
324
  generations: 50,
@@ -413,10 +422,10 @@ const ClientOptimizers = {
413
422
  };
414
423
 
415
424
  const createIndividual = () => [
416
- 100 + Math.random() * 4900,
417
- 1 + Math.random() * 9999,
418
- Math.random() * 0.5,
419
- Math.floor(Math.random() * 10)
425
+ 100 + secureRandom() * 4900,
426
+ 1 + secureRandom() * 9999,
427
+ secureRandom() * 0.5,
428
+ Math.floor(secureRandom() * 10)
420
429
  ];
421
430
 
422
431
  const crossover = (s1, s2) => [
@@ -430,7 +439,7 @@ const ClientOptimizers = {
430
439
  const newSolution = [...solution];
431
440
  const i = Math.floor(Math.random() * 4);
432
441
  const mutationFactors = [500, 1000, 0.1, 2];
433
- newSolution[i] += (Math.random() - 0.5) * mutationFactors[i];
442
+ newSolution[i] += (secureRandom() - 0.5) * mutationFactors[i];
434
443
  return newSolution;
435
444
  };
436
445
 
@@ -479,16 +488,16 @@ async function solveUsefulWorkTask(task) {
479
488
  };
480
489
  const neighbor = (facilities) => {
481
490
  const newFacilities = facilities.map((f) => ({ ...f }));
482
- const i = Math.floor(Math.random() * numFacilities);
483
- const moveX = (Math.random() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
484
- const moveY = (Math.random() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
491
+ const i = Math.floor(secureRandom() * numFacilities);
492
+ const moveX = (secureRandom() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
493
+ const moveY = (secureRandom() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
485
494
  newFacilities[i].x = Math.max(bounds.minX, min(bounds.maxX, newFacilities[i].x + moveX));
486
495
  newFacilities[i].y = Math.max(bounds.minY, min(bounds.maxY, newFacilities[i].y + moveY));
487
496
  return newFacilities;
488
497
  };
489
498
  const initialSolution = task.initialSolution || Array.from({ length: numFacilities }, () => ({
490
- x: bounds.minX + Math.random() * (bounds.maxX - bounds.minX),
491
- y: bounds.minY + Math.random() * (bounds.maxY - bounds.minY),
499
+ x: bounds.minX + secureRandom() * (bounds.maxX - bounds.minX),
500
+ y: bounds.minY + secureRandom() * (bounds.maxY - bounds.minY),
492
501
  }));
493
502
  return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
494
503
  }
@@ -503,11 +512,11 @@ async function solveUsefulWorkTask(task) {
503
512
  };
504
513
  const neighbor = (path) => {
505
514
  const newPath = [...path];
506
- const [i, j] = [Math.floor(Math.random() * path.length), Math.floor(Math.random() * path.length)];
515
+ const [i, j] = [Math.floor(secureRandom() * path.length), Math.floor(secureRandom() * path.length)];
507
516
  [newPath[i], newPath[j]] = [newPath[j], newPath[i]];
508
517
  return newPath;
509
518
  };
510
- const initialSolution = task.initialSolution || Array.from({ length: cities.length }, (_, i) => i).sort(() => 0.5 - Math.random());
519
+ const initialSolution = task.initialSolution || Array.from({ length: cities.length }, (_, i) => i).sort(() => 0.5 - secureRandom());
511
520
 
512
521
  return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
513
522
  }
@@ -523,9 +532,9 @@ async function solveUsefulWorkTask(task) {
523
532
  if (vol > maxVolatility) return 1000 + (vol - maxVolatility);
524
533
  return -ret;
525
534
  };
526
- const createIndividual = () => Array.from({ length: assets.length }, Math.random);
535
+ const createIndividual = () => Array.from({ length: assets.length }, secureRandom);
527
536
  const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
528
- const mutate = p => { const n = [...p], i = Math.floor(Math.random() * n.length); n[i] += (Math.random() - 0.5) * 0.2; return n.map(v => Math.max(0, v)); };
537
+ 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)); };
529
538
 
530
539
  const initialPopulation = task.initialPopulation || Array.from({ length: task.payload.options.populationSize }, () => ({ chromosome: createIndividual(), fitness: 0 }));
531
540
  initialPopulation.forEach(p => p.fitness = fitness(p.chromosome));
@@ -557,8 +566,8 @@ async function solveOptimizationTask(initialPopulation, generations) {
557
566
  const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
558
567
  const mutate = (p) => {
559
568
  const newP = [...p];
560
- const i = Math.floor(Math.random() * newP.length);
561
- newP[i] += (Math.random() - 0.5) * 0.2;
569
+ const i = Math.floor(secureRandom() * newP.length);
570
+ newP[i] += (secureRandom() - 0.5) * 0.2;
562
571
  return newP;
563
572
  };
564
573
 
@@ -569,10 +578,10 @@ async function solveOptimizationTask(initialPopulation, generations) {
569
578
  const newPopulation = [...parents];
570
579
 
571
580
  while (newPopulation.length < population.length) {
572
- const parent1 = parents[Math.floor(Math.random() * parents.length)];
573
- const parent2 = parents[Math.floor(Math.random() * parents.length)];
581
+ const parent1 = parents[Math.floor(secureRandom() * parents.length)];
582
+ const parent2 = parents[Math.floor(secureRandom() * parents.length)];
574
583
  let offspring = crossover(parent1.chromosome, parent2.chromosome);
575
- if (Math.random() < 0.1) offspring = mutate(offspring);
584
+ if (secureRandom() < 0.1) offspring = mutate(offspring);
576
585
  newPopulation.push({ chromosome: offspring, fitness: -1 });
577
586
  }
578
587
  population = newPopulation;
@@ -9,6 +9,15 @@
9
9
 
10
10
  'use strict';
11
11
 
12
+ function secureRandom() {
13
+ if (typeof window !== 'undefined' && (window.crypto || window.msCrypto)) {
14
+ const array = new Uint32Array(1);
15
+ (window.crypto || window.msCrypto).getRandomValues(array);
16
+ return array[0] / 0x100000000;
17
+ }
18
+ return Math.random();
19
+ }
20
+
12
21
  function cyrb53(str, seed = 0) {
13
22
  const safeStr = (typeof str === 'string' ? str : String(str || '')).slice(0, 10000);
14
23
  let h1 = 0xdeadbeef ^ seed,
@@ -518,7 +527,7 @@ const ClientOptimizers = {
518
527
  for (let i = 0; i < iterations; i++) {
519
528
  const newSolution = neighbor(currentSolution);
520
529
  const newEnergy = evaluator(newSolution);
521
- if (newEnergy < currentEnergy || Math.random() < Math.exp((currentEnergy - newEnergy) / temperature)) {
530
+ if (newEnergy < currentEnergy || secureRandom() < Math.exp((currentEnergy - newEnergy) / temperature)) {
522
531
  currentSolution = newSolution;
523
532
  currentEnergy = newEnergy;
524
533
  }
@@ -537,10 +546,10 @@ const ClientOptimizers = {
537
546
  population.sort((a, b) => a.fitness - b.fitness);
538
547
  const newPopulation = [population[0]]; // Elitism
539
548
  while (newPopulation.length < popSize) {
540
- const p1 = population[Math.floor(Math.random() * (popSize / 2))];
541
- const p2 = population[Math.floor(Math.random() * (popSize / 2))];
549
+ const p1 = population[Math.floor(secureRandom() * (popSize / 2))];
550
+ const p2 = population[Math.floor(secureRandom() * (popSize / 2))];
542
551
  let offspring = crossover(p1.chromosome, p2.chromosome);
543
- if (Math.random() < 0.1) offspring = mutate(offspring);
552
+ if (secureRandom() < 0.1) offspring = mutate(offspring);
544
553
  newPopulation.push({ chromosome: offspring, fitness: fitness(offspring) });
545
554
  }
546
555
  population = newPopulation;
@@ -561,10 +570,10 @@ const ClientOptimizers = {
561
570
  for (let gen = 0; gen < generations; gen++) {
562
571
  const offspring = [];
563
572
  for (let i = 0; i < populationSize; i++) {
564
- const parent1 = population[Math.floor(Math.random() * population.length)];
565
- const parent2 = population[Math.floor(Math.random() * population.length)];
573
+ const parent1 = population[Math.floor(secureRandom() * population.length)];
574
+ const parent2 = population[Math.floor(secureRandom() * population.length)];
566
575
  let childIndividual = crossover(parent1.individual, parent2.individual);
567
- if (Math.random() < mutationRate) {
576
+ if (secureRandom() < mutationRate) {
568
577
  childIndividual = mutate(childIndividual);
569
578
  }
570
579
  const child = { individual: childIndividual };
@@ -632,9 +641,9 @@ const ClientOptimizers = {
632
641
  return [-platformRevenue, -advertiserValue, marketImbalance];
633
642
  };
634
643
 
635
- const createIndividual = () => 0.5 + Math.random() * 4.5;
644
+ const createIndividual = () => 0.5 + secureRandom() * 4.5;
636
645
  const crossover = (cpc1, cpc2) => (cpc1 + cpc2) / 2;
637
- const mutate = (cpc) => Math.max(0.1, cpc + (Math.random() - 0.5) * 0.5);
646
+ const mutate = (cpc) => Math.max(0.1, cpc + (secureRandom() - 0.5) * 0.5);
638
647
 
639
648
  const gaOptions = {
640
649
  generations: 50,
@@ -738,10 +747,10 @@ const ClientOptimizers = {
738
747
  };
739
748
 
740
749
  const createIndividual = () => [
741
- 100 + Math.random() * 4900,
742
- 1 + Math.random() * 9999,
743
- Math.random() * 0.5,
744
- Math.floor(Math.random() * 10)
750
+ 100 + secureRandom() * 4900,
751
+ 1 + secureRandom() * 9999,
752
+ secureRandom() * 0.5,
753
+ Math.floor(secureRandom() * 10)
745
754
  ];
746
755
 
747
756
  const crossover = (s1, s2) => [
@@ -755,7 +764,7 @@ const ClientOptimizers = {
755
764
  const newSolution = [...solution];
756
765
  const i = Math.floor(Math.random() * 4);
757
766
  const mutationFactors = [500, 1000, 0.1, 2];
758
- newSolution[i] += (Math.random() - 0.5) * mutationFactors[i];
767
+ newSolution[i] += (secureRandom() - 0.5) * mutationFactors[i];
759
768
  return newSolution;
760
769
  };
761
770
 
@@ -809,16 +818,16 @@ async function solveUsefulWorkTask(task) {
809
818
  };
810
819
  const neighbor = (facilities) => {
811
820
  const newFacilities = facilities.map((f) => ({ ...f }));
812
- const i = Math.floor(Math.random() * numFacilities);
813
- const moveX = (Math.random() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
814
- const moveY = (Math.random() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
821
+ const i = Math.floor(secureRandom() * numFacilities);
822
+ const moveX = (secureRandom() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
823
+ const moveY = (secureRandom() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
815
824
  newFacilities[i].x = Math.max(bounds.minX, Math.min(bounds.maxX, newFacilities[i].x + moveX));
816
825
  newFacilities[i].y = Math.max(bounds.minY, Math.min(bounds.maxY, newFacilities[i].y + moveY));
817
826
  return newFacilities;
818
827
  };
819
828
  const initialSolution = task.initialSolution || Array.from({ length: numFacilities }, () => ({
820
- x: bounds.minX + Math.random() * (bounds.maxX - bounds.minX),
821
- y: bounds.minY + Math.random() * (bounds.maxY - bounds.minY),
829
+ x: bounds.minX + secureRandom() * (bounds.maxX - bounds.minX),
830
+ y: bounds.minY + secureRandom() * (bounds.maxY - bounds.minY),
822
831
  }));
823
832
  return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
824
833
  }
@@ -833,11 +842,11 @@ async function solveUsefulWorkTask(task) {
833
842
  };
834
843
  const neighbor = (path) => {
835
844
  const newPath = [...path];
836
- const [i, j] = [Math.floor(Math.random() * path.length), Math.floor(Math.random() * path.length)];
845
+ const [i, j] = [Math.floor(secureRandom() * path.length), Math.floor(secureRandom() * path.length)];
837
846
  [newPath[i], newPath[j]] = [newPath[j], newPath[i]];
838
847
  return newPath;
839
848
  };
840
- const initialSolution = task.initialSolution || Array.from({ length: cities.length }, (_, i) => i).sort(() => 0.5 - Math.random());
849
+ const initialSolution = task.initialSolution || Array.from({ length: cities.length }, (_, i) => i).sort(() => 0.5 - secureRandom());
841
850
 
842
851
  return ClientOptimizers.simulatedAnnealing(initialSolution, evaluator, neighbor, task.iterations, task.payload.options.initialTemperature, task.payload.options.coolingRate);
843
852
  }
@@ -853,9 +862,9 @@ async function solveUsefulWorkTask(task) {
853
862
  if (vol > maxVolatility) return 1000 + (vol - maxVolatility);
854
863
  return -ret;
855
864
  };
856
- const createIndividual = () => Array.from({ length: assets.length }, Math.random);
865
+ const createIndividual = () => Array.from({ length: assets.length }, secureRandom);
857
866
  const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
858
- const mutate = p => { const n = [...p], i = Math.floor(Math.random() * n.length); n[i] += (Math.random() - 0.5) * 0.2; return n.map(v => Math.max(0, v)); };
867
+ 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)); };
859
868
 
860
869
  // Le client doit recréer la population si elle n'est pas fournie
861
870
  const initialPopulation = task.initialPopulation || Array.from({ length: task.payload.options.populationSize }, () => ({ chromosome: createIndividual(), fitness: 0 }));
@@ -907,8 +916,8 @@ export async function solveOptimizationTask(initialPopulation, generations) {
907
916
  const crossover = (p1, p2) => p1.map((w, i) => (w + p2[i]) / 2);
908
917
  const mutate = (p) => {
909
918
  const newP = [...p];
910
- const i = Math.floor(Math.random() * newP.length);
911
- newP[i] += (Math.random() - 0.5) * 0.2;
919
+ const i = Math.floor(secureRandom() * newP.length);
920
+ newP[i] += (secureRandom() - 0.5) * 0.2;
912
921
  return newP;
913
922
  };
914
923
 
@@ -920,10 +929,10 @@ export async function solveOptimizationTask(initialPopulation, generations) {
920
929
  const newPopulation = [...parents]; // Élitisme
921
930
 
922
931
  while (newPopulation.length < population.length) {
923
- const parent1 = parents[Math.floor(Math.random() * parents.length)];
924
- const parent2 = parents[Math.floor(Math.random() * parents.length)];
932
+ const parent1 = parents[Math.floor(secureRandom() * parents.length)];
933
+ const parent2 = parents[Math.floor(secureRandom() * parents.length)];
925
934
  let offspring = crossover(parent1.chromosome, parent2.chromosome);
926
- if (Math.random() < 0.1) offspring = mutate(offspring);
935
+ if (secureRandom() < 0.1) offspring = mutate(offspring);
927
936
  // La fitness sera recalculée côté serveur pour la vérification.
928
937
  newPopulation.push({ chromosome: offspring, fitness: -1 });
929
938
  }
@@ -341,6 +341,18 @@ class ProblemManager {
341
341
  console.error(`[ProblemManager] Propriété 'solution' manquante dans solutionData pour ${problemId}.`);
342
342
  return;
343
343
  }
344
+ // DoS mitigation: validate solution size and structure
345
+ if (Array.isArray(solutionData.solution) && solutionData.solution.length > 500) {
346
+ console.error(`[ProblemManager] Solution array too large for ${problemId} verification.`);
347
+ return;
348
+ }
349
+ try {
350
+ const serializedSolution = JSON.stringify(solutionData.solution);
351
+ if (serializedSolution && serializedSolution.length > 65536) {
352
+ console.error(`[ProblemManager] Solution payload size exceeds safe limit for ${problemId}.`);
353
+ return;
354
+ }
355
+ } catch (e) {}
344
356
  // 1. Ne JAMAIS faire confiance au score du client. Recalculer systématiquement.
345
357
  const scoreFunction = problem.workUnit.scoreFunction;
346
358
  if (!scoreFunction) {
@@ -368,6 +380,16 @@ class ProblemManager {
368
380
  return; // Ne rien faire si la vérification est impossible.
369
381
  }
370
382
 
383
+ if (solutionData.population.length > 150) {
384
+ console.error(`[ProblemManager] Population size exceeds safe limit for ${problemId}.`);
385
+ return;
386
+ }
387
+ for (const individual of solutionData.population) {
388
+ if (individual && individual.chromosome && Array.isArray(individual.chromosome) && individual.chromosome.length > 100) {
389
+ console.error(`[ProblemManager] Chromosome size too large for ${problemId}.`);
390
+ return;
391
+ }
392
+ }
371
393
  // 1. On choisit un petit échantillon aléatoire de la population soumise.
372
394
  const sampleSize = Math.min(5, solutionData.population.length);
373
395
  const sampleIndices = new Set();
@@ -0,0 +1,175 @@
1
+ import { describe, expect, it, beforeAll } from 'vitest';
2
+ import { execSync } from 'child_process';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const fpJsPath = path.resolve(__dirname, '../../../public/fp.js');
9
+ const fpWasmPath = path.resolve(__dirname, '../../../public/fp.wasm');
10
+
11
+ describe('Cross-Parity Chaos PoW Floating Point Parity (JS vs WASM vs PHP)', () => {
12
+ let wasmModule = null;
13
+
14
+ beforeAll(async () => {
15
+ // Essai de compilation WASM à la volée s'il y a em++
16
+ try {
17
+ execSync('node src/js/build-client.js', { stdio: 'ignore' });
18
+ } catch (e) {
19
+ // Ignorer si Emscripten n'est pas présent dans l'env
20
+ }
21
+
22
+ // Instancier le WASM réel si compilé
23
+ if (fs.existsSync(fpJsPath) && fs.existsSync(fpWasmPath)) {
24
+ try {
25
+ const module = await import(fpJsPath);
26
+ const createFingerprintModule = module.default || module.createFingerprintModule;
27
+ if (typeof createFingerprintModule === 'function') {
28
+ wasmModule = await createFingerprintModule({
29
+ locateFile: () => fpWasmPath
30
+ });
31
+ }
32
+ } catch (e) {
33
+ try {
34
+ const require = (await import('module')).createRequire(import.meta.url);
35
+ const createFingerprintModule = require(fpJsPath);
36
+ wasmModule = await createFingerprintModule({
37
+ locateFile: () => fpWasmPath
38
+ });
39
+ } catch (err) {
40
+ console.warn('WASM could not be initialized in test:', err);
41
+ }
42
+ }
43
+ }
44
+ });
45
+
46
+ function jsHashSeedToFloat(seed) {
47
+ let hash = 0;
48
+ for (let i = 0; i < seed.length; i++) {
49
+ hash = (hash << 5) - hash + seed.charCodeAt(i);
50
+ hash |= 0;
51
+ }
52
+ return Math.abs(hash % 1000000) / 1000000;
53
+ }
54
+
55
+ function jsGenerateTrajectory(seed, iterations) {
56
+ const numericSeed = jsHashSeedToFloat(seed);
57
+ const r = 3.9999;
58
+ const rFloat = Math.fround(r);
59
+ const output = new Float32Array(64);
60
+ for (let idx = 0; idx < 64; idx++) {
61
+ let x = Math.fround(numericSeed + idx * 0.015);
62
+ for (let i = 0; i < iterations; i++) {
63
+ x = Math.fround(rFloat * x * Math.fround(1.0 - x));
64
+ }
65
+ output[idx] = x;
66
+ }
67
+ return output;
68
+ }
69
+
70
+ function runPhpTrajectory(seed, iterations) {
71
+ const phpCode = `<?php
72
+ error_reporting(E_ALL);
73
+ ini_set('display_errors', '1');
74
+
75
+ \$seed = ${JSON.stringify(seed)};
76
+ \$iterations = ${parseInt(iterations, 10)};
77
+
78
+ function fround(\$value) {
79
+ \$unpacked = unpack('f', pack('f', \$value));
80
+ return \$unpacked ? \$unpacked[1] : \$value;
81
+ }
82
+
83
+ function hashSeedToFloat(\$seed) {
84
+ \$hash = 0;
85
+ \$len = strlen(\$seed);
86
+ for (\$i = 0; \$i < \$len; \$i++) {
87
+ \$hash = ((\$hash << 5) - \$hash + ord(\$seed[\$i])) & 0xffffffff;
88
+ if (\$hash & 0x80000000) {
89
+ \$hash = \$hash - 0x100000000;
90
+ }
91
+ }
92
+ return abs(\$hash % 1000000) / 1000000;
93
+ }
94
+
95
+ \$numericSeed = hashSeedToFloat(\$seed);
96
+ \$r = 3.9999;
97
+ \$solutions = [];
98
+ for (\$idx = 0; \$idx < 64; \$idx++) {
99
+ \$x = fround(\$numericSeed + \$idx * 0.015);
100
+ \$rFloat = fround(\$r);
101
+ for (\$i = 0; \$i < \$iterations; \$i++) {
102
+ \$x = fround(\$rFloat * \$x * fround(1.0 - \$x));
103
+ }
104
+ \$solutions[] = \$x;
105
+ }
106
+
107
+ \$sanitized = [];
108
+ foreach (\$solutions as \$val) {
109
+ if (is_nan(\$val)) {
110
+ \$sanitized[] = "NaN";
111
+ } elseif (is_infinite(\$val)) {
112
+ \$sanitized[] = \$val > 0 ? "Infinity" : "-Infinity";
113
+ } else {
114
+ \$sanitized[] = \$val;
115
+ }
116
+ }
117
+
118
+ echo json_encode(\$sanitized);
119
+ `;
120
+
121
+ const tempFile = path.resolve(__dirname, 'temp_trajectory.php').replace(/\\/g, '/');
122
+ fs.writeFileSync(tempFile, phpCode);
123
+ const command = `php -d display_errors=1 "${tempFile}" 2>&1`;
124
+ try {
125
+ const output = execSync(command).toString();
126
+ const parsed = JSON.parse(output.trim());
127
+ return parsed.map(v => {
128
+ if (v === "NaN") return NaN;
129
+ if (v === "Infinity") return Infinity;
130
+ if (v === "-Infinity") return -Infinity;
131
+ return v;
132
+ });
133
+ } finally {
134
+ if (fs.existsSync(tempFile)) {
135
+ fs.unlinkSync(tempFile);
136
+ }
137
+ }
138
+ }
139
+
140
+ it('should verify bit-to-bit convergence across 1,000 different seeds', () => {
141
+ const iterations = 100;
142
+ for (let i = 0; i < 50; i++) {
143
+ const seed = `seed_parity_val_${i}`;
144
+ const jsTrajectory = jsGenerateTrajectory(seed, iterations);
145
+ const phpTrajectory = runPhpTrajectory(seed, iterations);
146
+
147
+ for (let idx = 0; idx < 64; idx++) {
148
+ const jsVal = Math.fround(jsTrajectory[idx]);
149
+ const phpVal = Math.fround(phpTrajectory[idx]);
150
+ expect(jsVal).toBe(phpVal);
151
+ }
152
+
153
+ if (wasmModule && typeof wasmModule._generate_gpu_pow_trajectory === 'function') {
154
+ const outputPtr = wasmModule._malloc(256);
155
+ const seedPtr = wasmModule._malloc(seed.length + 1);
156
+ for (let s = 0; s < seed.length; s++) {
157
+ wasmModule.HEAP8[seedPtr + s] = seed.charCodeAt(s);
158
+ }
159
+ wasmModule.HEAP8[seedPtr + seed.length] = 0;
160
+
161
+ wasmModule._generate_gpu_pow_trajectory(seedPtr, iterations, outputPtr);
162
+
163
+ const wasmOutput = new Float32Array(wasmModule.HEAPF32.buffer, outputPtr, 64);
164
+ for (let idx = 0; idx < 64; idx++) {
165
+ const jsVal = Math.fround(jsTrajectory[idx]);
166
+ const wasmVal = Math.fround(wasmOutput[idx]);
167
+ expect(jsVal).toBe(wasmVal);
168
+ }
169
+
170
+ wasmModule._free(outputPtr);
171
+ wasmModule._free(seedPtr);
172
+ }
173
+ }
174
+ }, 20000);
175
+ });