@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.
- package/CHANGELOG.md +16 -0
- package/README.md +7 -6
- package/package.json +1 -1
- package/src/js/build-client.js +67 -67
- package/src/js/fingerprint.client.js +1445 -1271
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +499 -116
- package/src/js/obfuscation.worker.js +23 -0
- package/src/js/pow.solver.inline.js +37 -28
- package/src/js/pow.solver.js +37 -28
- package/src/js/problem-manager.js +22 -0
- package/src/js/tests/cross_parity.test.js +175 -0
- package/src/js/tests/fingerprint.client.test.js +130 -128
- package/src/js/tests/fingerprint.test.js +42 -2
- package/src/js/tests/obfuscation.test.js +48 -0
- package/src/js/ttl-optimization.worker.js +51 -0
- package/src/php/AutoTuner.php +42 -0
- package/src/php/Challenge/ChallengeUtils.php +57 -6
- package/src/php/FingerprintBuilder.php +193 -185
- package/src/php/FingerprintEngine.php +147 -1
- package/src/php/ProblemManager.php +20 -0
- package/src/php/Tests/ChallengeUtilsTest.php +446 -285
- package/src/php/Utils/RequestUtils.php +19 -0
|
@@ -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 ||
|
|
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(
|
|
216
|
-
const p2 = population[Math.floor(
|
|
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 (
|
|
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(
|
|
240
|
-
const parent2 = population[Math.floor(
|
|
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 (
|
|
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 +
|
|
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 + (
|
|
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 +
|
|
417
|
-
1 +
|
|
418
|
-
|
|
419
|
-
Math.floor(
|
|
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] += (
|
|
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(
|
|
483
|
-
const moveX = (
|
|
484
|
-
const moveY = (
|
|
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 +
|
|
491
|
-
y: 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(
|
|
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 -
|
|
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 },
|
|
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(
|
|
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(
|
|
561
|
-
newP[i] += (
|
|
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(
|
|
573
|
-
const parent2 = parents[Math.floor(
|
|
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 (
|
|
584
|
+
if (secureRandom() < 0.1) offspring = mutate(offspring);
|
|
576
585
|
newPopulation.push({ chromosome: offspring, fitness: -1 });
|
|
577
586
|
}
|
|
578
587
|
population = newPopulation;
|
package/src/js/pow.solver.js
CHANGED
|
@@ -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 ||
|
|
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(
|
|
541
|
-
const p2 = population[Math.floor(
|
|
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 (
|
|
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(
|
|
565
|
-
const parent2 = population[Math.floor(
|
|
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 (
|
|
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 +
|
|
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 + (
|
|
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 +
|
|
742
|
-
1 +
|
|
743
|
-
|
|
744
|
-
Math.floor(
|
|
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] += (
|
|
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(
|
|
813
|
-
const moveX = (
|
|
814
|
-
const moveY = (
|
|
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 +
|
|
821
|
-
y: 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(
|
|
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 -
|
|
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 },
|
|
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(
|
|
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(
|
|
911
|
-
newP[i] += (
|
|
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(
|
|
924
|
-
const parent2 = parents[Math.floor(
|
|
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 (
|
|
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
|
+
});
|