@anonympins/fingerprint 0.6.4 → 0.6.6

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.
@@ -32,6 +32,25 @@ function cyrb53(str, seed = 0) {
32
32
  return 4294967296 * (2097151 & h2) + (h1 >>> 0);
33
33
  }
34
34
 
35
+ async function loadTfjs() {
36
+ if (typeof tf !== 'undefined') return tf;
37
+ if (typeof window !== 'undefined') {
38
+ if (window.tf) return window.tf;
39
+ return new Promise((resolve, reject) => {
40
+ const script = document.createElement('script');
41
+ script.src = 'https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js';
42
+ script.onload = () => resolve(window.tf);
43
+ script.onerror = () => reject(new Error('Failed to load TensorFlow.js'));
44
+ document.head.appendChild(script);
45
+ });
46
+ }
47
+ try {
48
+ return await import('@tensorflow/tfjs');
49
+ } catch (e) {
50
+ throw new Error("TensorFlow.js is not available.");
51
+ }
52
+ }
53
+
35
54
  function openDb() {
36
55
  return new Promise((resolve, reject) => {
37
56
  const request = indexedDB.open("pospace-db", 1);
@@ -898,6 +917,72 @@ async function solveUsefulWorkTask(task) {
898
917
  return solverFunction(task.payload, { generations: task.generations, initialFront: task.initialFront });
899
918
  }
900
919
 
920
+ case 'pytorch_onnx_learning': {
921
+ // Dans un environnement de production réel avec ONNX Runtime Web, on chargerait le modèle .onnx
922
+ // et on exécuterait un calcul de gradient (backward pass).
923
+ // Ici, nous simulons le calcul de gradient mathématique équivalent pour la régression/classification.
924
+ const { weights, payload } = task;
925
+ const inputs = payload.inputs || [];
926
+ const labels = payload.labels || [];
927
+ const gradients = new Array(weights.length).fill(0);
928
+ for (let i = 0; i < inputs.length; i++) {
929
+ const x = inputs[i];
930
+ const y = labels[i];
931
+ let pred = 0;
932
+ for (let j = 0; j < weights.length; j++) {
933
+ pred += x[j] * weights[j];
934
+ }
935
+ const error = pred - y;
936
+ for (let j = 0; j < weights.length; j++) {
937
+ gradients[j] += error * x[j] / inputs.length;
938
+ }
939
+ }
940
+ return { gradients };
941
+ }
942
+
943
+ case 'tfjs_learning': {
944
+ const { weights, payload, modelPath } = task;
945
+ const inputs = payload.inputs || [];
946
+ const labels = payload.labels || [];
947
+ try {
948
+ const tf = await loadTfjs();
949
+ // Chargement non-bloquant du modèle au format JSON
950
+ const model = await tf.loadLayersModel(modelPath);
951
+
952
+ const xs = tf.tensor2d(inputs);
953
+ const ys = tf.tensor2d(labels);
954
+
955
+ const trainableVars = model.trainableWeights.map(w => w.read());
956
+ const lossFn = () => {
957
+ const preds = model.predict(xs);
958
+ return tf.losses.meanSquaredError(ys, preds);
959
+ };
960
+
961
+ // Dérivation automatique pour le calcul exact des gradients
962
+ const gFn = tf.grad(lossFn);
963
+ const grads = gFn(trainableVars);
964
+
965
+ const flatGradients = [];
966
+ const arr = await grads.array();
967
+ flatGradients.push(...arr.flat());
968
+
969
+ tf.dispose([xs, ys, grads]);
970
+ return { gradients: flatGradients };
971
+ } catch (err) {
972
+ console.warn('[Useful Work Solver] Real TFJS loading failed, using fallback simulation:', err);
973
+ const gradients = new Array(weights.length).fill(0);
974
+ for (let i = 0; i < inputs.length; i++) {
975
+ const x = inputs[i];
976
+ const y = labels[i][0] !== undefined ? labels[i][0] : labels[i];
977
+ let pred = 0;
978
+ for (let j = 0; j < weights.length; j++) pred += x[j] * weights[j];
979
+ const error = pred - y;
980
+ for (let j = 0; j < weights.length; j++) gradients[j] += error * x[j] / inputs.length;
981
+ }
982
+ return { gradients };
983
+ }
984
+ }
985
+
901
986
  default:
902
987
  throw new Error(`Unknown useful work type: ${task.type}`);
903
988
  }
@@ -318,6 +318,12 @@ class ProblemManager {
318
318
  task.initialFront = problem.state.paretoFront;
319
319
  task.solverName = problem.workUnit.solverName; // Le nom du solveur à utiliser (ex: 'cpc.solve')
320
320
  break;
321
+
322
+ case 'tfjs_learning':
323
+ task.modelPath = problem.workUnit.modelPath;
324
+ task.payload = problem.payload;
325
+ task.weights = problem.state.weights || [];
326
+ break;
321
327
  }
322
328
 
323
329
  return { problemId: problem.id, task };
@@ -408,7 +414,16 @@ class ProblemManager {
408
414
  if (!individual || !individual.chromosome) {
409
415
  throw new Error("Structure d'individu ou chromosome invalide dans la population.");
410
416
  }
411
- totalRecalculatedFitness += fitnessFunction(individual.chromosome, problem.payload);
417
+ const recalculated = fitnessFunction(individual.chromosome, problem.payload);
418
+
419
+ if (individual.fitness !== undefined && individual.fitness !== -1) {
420
+ if (Math.abs(individual.fitness - recalculated) > 1e-4) {
421
+ console.error(`[ProblemManager] Triche détectée pour ${problemId}! Fitness déclaré: ${individual.fitness}, recalculé: ${recalculated}`);
422
+ return; // Rejeter immédiatement toute la population
423
+ }
424
+ }
425
+ individual.fitness = recalculated; // Forcer la valeur exacte recalculée
426
+ totalRecalculatedFitness += recalculated;
412
427
  }
413
428
 
414
429
  problem.state.population = solutionData.population; // On accepte la population
@@ -58,7 +58,7 @@ describe('Cross-Parity Chaos PoW Floating Point Parity (JS vs WASM vs PHP)', ()
58
58
  const rFloat = Math.fround(r);
59
59
  const output = new Float32Array(64);
60
60
  for (let idx = 0; idx < 64; idx++) {
61
- let x = Math.fround(numericSeed + idx * 0.015);
61
+ let x = Math.fround((numericSeed + idx * 0.015) % 1);
62
62
  for (let i = 0; i < iterations; i++) {
63
63
  x = Math.fround(rFloat * x * Math.fround(1.0 - x));
64
64
  }
@@ -96,7 +96,7 @@ describe('Cross-Parity Chaos PoW Floating Point Parity (JS vs WASM vs PHP)', ()
96
96
  \$r = 3.9999;
97
97
  \$solutions = [];
98
98
  for (\$idx = 0; \$idx < 64; \$idx++) {
99
- \$x = fround(\$numericSeed + \$idx * 0.015);
99
+ \$x = fround(fmod(\$numericSeed + \$idx * 0.015, 1.0));
100
100
  \$rFloat = fround(\$r);
101
101
  for (\$i = 0; \$i < \$iterations; \$i++) {
102
102
  \$x = fround(\$rFloat * \$x * fround(1.0 - \$x));
@@ -2487,7 +2487,7 @@ describe('FingerprintEngine.processRequest - updateSubnetMetrics call logic', ()
2487
2487
  beforeEach(() => {
2488
2488
  inMemoryStore._map.clear();
2489
2489
  configureStore(inMemoryStore);
2490
-
2490
+ // Add a default mock for getTlsFingerprint to stabilize these tests.
2491
2491
  securityConfig = {
2492
2492
  weights: { historyScore: 1.0 },
2493
2493
  thresholds: { low: 20, medium: 45, high: 75, block: 95 }, // Default blockThreshold
@@ -2508,8 +2508,8 @@ describe('FingerprintEngine.processRequest - updateSubnetMetrics call logic', ()
2508
2508
  calculateFinalScoreSpy.mockRestore();
2509
2509
  });
2510
2510
 
2511
- it('should call updateSubnetMetrics if finalScore is between lowThreshold and blockThreshold', async () => {
2512
- calculateFinalScoreSpy.mockReturnValue(50); // Score between 20 and 95
2511
+ it('should call updateSubnetMetrics if finalScore is between mediumThreshold and blockThreshold', async () => {
2512
+ calculateFinalScoreSpy.mockReturnValue(50); // Score between 45 and 95
2513
2513
  const requestContext = { clientIp: '192.168.1.1', cookies: {}, query: {}, headers: {} };
2514
2514
 
2515
2515
  await engine.processRequest(requestContext);
@@ -2518,6 +2518,15 @@ describe('FingerprintEngine.processRequest - updateSubnetMetrics call logic', ()
2518
2518
  expect(updateSubnetMetricsSpy).toHaveBeenCalledWith(requestContext, expect.any(String), 50);
2519
2519
  });
2520
2520
 
2521
+ it('should NOT call updateSubnetMetrics if finalScore is below mediumThreshold', async () => {
2522
+ calculateFinalScoreSpy.mockReturnValue(30); // Score below 45
2523
+ const requestContext = { clientIp: '192.168.1.1', cookies: {}, query: {}, headers: {} };
2524
+
2525
+ await engine.processRequest(requestContext);
2526
+
2527
+ expect(updateSubnetMetricsSpy).not.toHaveBeenCalled();
2528
+ });
2529
+
2521
2530
  it('should NOT call updateSubnetMetrics if finalScore is equal to or above blockThreshold', async () => {
2522
2531
  calculateFinalScoreSpy.mockReturnValue(95); // Score equals blockThreshold
2523
2532
  const requestContext = { clientIp: '192.168.1.1', cookies: {}, query: {}, headers: {} };
@@ -27,7 +27,7 @@ describe('GpuPowSolver - Chaotic Logistic Map PoW', () => {
27
27
  const solutions = [];
28
28
 
29
29
  for (let idx = 0; idx < 64; idx++) {
30
- let x = Math.fround(numericSeed + idx * 0.015);
30
+ let x = Math.fround((numericSeed + idx * 0.015) % 1); // Ensure x starts in [0, 1)
31
31
  for (let i = 0; i < iterations; i++) {
32
32
  x = Math.fround(rFloat * x * Math.fround(1.0 - x));
33
33
  }
@@ -53,19 +53,21 @@ describe('GpuPowSolver - Chaotic Logistic Map PoW', () => {
53
53
  const solutions = [];
54
54
 
55
55
  for (let idx = 0; idx < 64; idx++) {
56
- let x = Math.fround(numericSeed + idx * 0.015);
56
+ let x = Math.fround((numericSeed + idx * 0.015) % 1); // Ensure x starts in [0, 1)
57
57
  for (let i = 0; i < iterations; i++) {
58
58
  x = Math.fround(rFloat * x * Math.fround(1.0 - x));
59
59
  }
60
60
  solutions.push(x.toFixed(6));
61
61
  }
62
62
 
63
- // Tamper with the 12th channel (one of the default sample indices [0, 12, 35, 57])
63
+ // Tamper with one of the dynamically derived sample indices
64
+ const sampleIndices = GpuPowSolver.deriveSampleIndices('127.0.0.1', 'gpu-pow-salt');
65
+ const tamperedIndex = sampleIndices[0];
64
66
  const tamperedSolutions = [...solutions];
65
- const originalVal = parseFloat(tamperedSolutions[12]);
66
- tamperedSolutions[12] = (originalVal + 0.0002).toFixed(6); // exceeding 1e-4 tolerance
67
-
68
- expect(GpuPowSolver.verify(seed, iterations, tamperedSolutions.join(','))).toBe(false);
67
+ const originalVal = parseFloat(tamperedSolutions[tamperedIndex]);
68
+ tamperedSolutions[tamperedIndex] = (originalVal + 0.1).toFixed(6); // exceeding tolerance
69
+ const verificationResult = GpuPowSolver.verify(seed, iterations, tamperedSolutions.join(','), '127.0.0.1', 'gpu-pow-salt');
70
+ expect(verificationResult).toBe(false);
69
71
  });
70
72
 
71
73
  it('should fallback to WebGL2 if WebGPU is not supported', async () => {
@@ -134,7 +134,7 @@ describe('IP Reputation Local System (Node.js)', () => {
134
134
  let score = await __internal.getSubnetScore(mockContext);
135
135
  expect(score.subnetScore).toBe(0);
136
136
  for (let i = 1; i <= 12; i++) {
137
- await __internal.updateSubnetMetrics(mockContext, `dev-${i}`, 40);
137
+ await __internal.updateSubnetMetrics(mockContext, `dev-${i}`, 50);
138
138
  }
139
139
  score = await __internal.getSubnetScore(mockContext);
140
140
  expect(score.subnetScore).toBeGreaterThan(0);
@@ -1,16 +1,16 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import { __internal } from '../fingerprint.js';
3
3
 
4
- const { getQuicAnomalyScore } = __internal;
4
+ const { getProtocolAnomalyScore } = __internal;
5
5
 
6
- describe('QUIC / HTTP/3 Flow Profiling', () => {
7
- it('should return 0.0 if no QUIC fingerprint is present', () => {
6
+ describe('Protocol Anomaly Flow Profiling', () => {
7
+ it('should return 0.0 if no QUIC or HTTP/2 fingerprint is present', () => {
8
8
  const context = { headers: {} };
9
- const score = getQuicAnomalyScore(context);
10
- expect(score.quicAnomalyScore).toBe(0.0);
9
+ const score = getProtocolAnomalyScore(context);
10
+ expect(score.protocolAnomalyScore).toBe(0.0);
11
11
  });
12
12
 
13
- it('should detect Chrome spoofed with non-default stream/priority values', () => {
13
+ it('should detect Chrome spoofed with non-default stream/priority values (QUIC)', () => {
14
14
  // Chrome attend un initial_max_data (1) >= 1MB, initial_max_streams_bidi (4) == 100, et extensible Priority (contient u=)
15
15
  const context = {
16
16
  headers: {
@@ -18,8 +18,8 @@ describe('QUIC / HTTP/3 Flow Profiling', () => {
18
18
  'x-quic-fp': '1;1=65536,4=50;i=0' // max_data trop bas, max_streams != 100, pas de priorité u=
19
19
  }
20
20
  };
21
- const score = getQuicAnomalyScore(context);
22
- expect(score.quicAnomalyScore).toBe(100.0);
21
+ const score = getProtocolAnomalyScore(context);
22
+ expect(score.protocolAnomalyScore).toBe(100.0);
23
23
  });
24
24
 
25
25
  it('should return 0.0 for a legitimate Chrome QUIC flow profile', () => {
@@ -29,7 +29,18 @@ describe('QUIC / HTTP/3 Flow Profiling', () => {
29
29
  'x-quic-fp': '1;1=1572864,4=100;u=2,i'
30
30
  }
31
31
  };
32
- const score = getQuicAnomalyScore(context);
33
- expect(score.quicAnomalyScore).toBe(0.0);
32
+ const score = getProtocolAnomalyScore(context);
33
+ expect(score.protocolAnomalyScore).toBe(0.0);
34
+ });
35
+
36
+ it('should detect Chromium HTTP/2 header order anomalies', () => {
37
+ const context = {
38
+ headers: {
39
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
40
+ 'x-http2-fingerprint': '1|65535|0|invalid_order'
41
+ }
42
+ };
43
+ const score = getProtocolAnomalyScore(context);
44
+ expect(score.protocolAnomalyScore).toBe(100.0);
34
45
  });
35
46
  });
@@ -0,0 +1,114 @@
1
+ import crypto from "node:crypto";
2
+
3
+ /**
4
+ * Classe de base abstraite pour définir des tâches uPoW basées sur l'entraînement
5
+ * ou l'évaluation de modèles IA (PyTorch exportés au format ONNX).
6
+ */
7
+ export class UpowModelTask {
8
+ constructor(problemId, modelPath, config = {}) {
9
+ if (new.target === UpowModelTask) {
10
+ throw new TypeError("Cannot construct UpowModelTask instances directly");
11
+ }
12
+ this.problemId = problemId;
13
+ this.modelPath = modelPath;
14
+ this.config = {
15
+ batchSize: 32,
16
+ learningRate: 0.01,
17
+ validationTolerance: 0.15,
18
+ ...config
19
+ };
20
+ }
21
+
22
+ /**
23
+ * Récupère l'identifiant du problème.
24
+ * @returns {string}
25
+ */
26
+ getProblemId() {
27
+ return this.problemId;
28
+ }
29
+
30
+ /**
31
+ * Récupère le chemin réseau du modèle ONNX.
32
+ * @returns {string}
33
+ */
34
+ getModelAssetPath() {
35
+ return this.modelPath;
36
+ }
37
+
38
+ /**
39
+ * Génère la charge utile de travail pour le client.
40
+ * Envoie l'état actuel des poids du modèle et un lot de données (mini-batch) à traiter.
41
+ * @param {number} suspicionFactor
42
+ * @returns {object} Données à envoyer au client
43
+ */
44
+ dispatchTask(suspicionFactor) {
45
+ throw new Error("Method 'dispatchTask(suspicionFactor)' must be implemented.");
46
+ }
47
+
48
+ /**
49
+ * Vérifie la validité de la solution retournée par le client.
50
+ * Implémente des gardes-fous contre l'empoisonnement (ex: vérification des gradients).
51
+ * @param {object} taskContext
52
+ * @param {object} solution
53
+ * @returns {boolean}
54
+ */
55
+ verifySolution(taskContext, solution) {
56
+ throw new Error("Method 'verifySolution(taskContext, solution)' must be implemented.");
57
+ }
58
+
59
+ /**
60
+ * Intègre les gradients ou résultats validés dans le modèle global.
61
+ * Par exemple : algorithme FedAvg (Federated Averaging).
62
+ * @param {object} solution
63
+ */
64
+ integrateSolution(solution) {
65
+ throw new Error("Method 'integrateSolution(solution)' must be implemented.");
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Exemple d'implémentation d'une tâche d'entraînement (Classifieur de requêtes suspectes)
71
+ */
72
+ export class RequestClassifierTask extends UpowModelTask {
73
+ constructor(modelPath, config) {
74
+ super("request_classifier_nn", modelPath, config);
75
+ // Poids initiaux du modèle (Réseau de neurones linéaire simple)
76
+ this.weights = [0.1, -0.2, 0.8, 0.5];
77
+ }
78
+
79
+ dispatchTask(suspicionFactor) {
80
+ // Génération de données d'entraînement anonymisées pour le client
81
+ const inputs = [
82
+ [1.0, 0.5, 0.0, 1.2],
83
+ [0.0, 1.0, -0.5, 0.8]
84
+ ];
85
+ return {
86
+ weights: [...this.weights],
87
+ inputs: inputs,
88
+ learningRate: this.config.learningRate,
89
+ batchSize: this.config.batchSize
90
+ };
91
+ }
92
+
93
+ verifySolution(taskContext, solution) {
94
+ const gradients = solution?.gradients;
95
+ if (!Array.isArray(gradients) || gradients.length !== this.weights.length) {
96
+ return false;
97
+ }
98
+ // Anti-poisoning : s'assurer que les gradients ne contiennent pas de valeurs aberrantes (clipping)
99
+ const maxGradientNorm = 10.0;
100
+ for (const grad of gradients) {
101
+ if (typeof grad !== 'number' || isNaN(grad) || Math.abs(grad) > maxGradientNorm) {
102
+ return false;
103
+ }
104
+ }
105
+ return true;
106
+ }
107
+
108
+ integrateSolution(solution) {
109
+ const gradients = solution.gradients;
110
+ for (let i = 0; i < this.weights.length; i++) {
111
+ this.weights[i] -= this.config.learningRate * gradients[i];
112
+ }
113
+ }
114
+ }
@@ -53,6 +53,47 @@ class AutoTuner
53
53
  $this->onCleanup = $options['onCleanup'] ?? null;
54
54
  $this->savePath = $options['savePath'] ?? null;
55
55
  $this->validationTolerance = $options['validationTolerance'] ?? 0.15;
56
+
57
+ if (isset($this->securityConfig['logger']) && is_callable($this->securityConfig['logger']) && !isset($this->securityConfig['logger_wrapped'])) {
58
+ $originalLogger = $this->securityConfig['logger'];
59
+ $maxPercentage = $this->securityConfig['autotuning']['maxDensityPercentage'] ?? 0.02;
60
+
61
+ $this->securityConfig['logger'] = function (array $log) use ($originalLogger, $maxPercentage) {
62
+ $ip = $log['clientIp'] ?? $log['ip'] ?? null;
63
+ $subnet = $ip ? RequestUtils::getIpSubnet($ip, 24, 48) : null;
64
+ $fp = $log['deviceHash'] ?? $log['fingerprint'] ?? $log['deviceFingerprint'] ?? null;
65
+ $stableFp = $fp ? RequestUtils::extractStablePart($fp) : null;
66
+
67
+ if (count($this->trafficData) > 0) {
68
+ $total = count($this->trafficData);
69
+ $ipMatchCount = 0;
70
+ $fpMatchCount = 0;
71
+ $matchedKey = null;
72
+
73
+ foreach ($this->trafficData as $key => $existingLog) {
74
+ $logIp = $existingLog['clientIp'] ?? $existingLog['ip'] ?? null;
75
+ $logSubnet = $logIp ? RequestUtils::getIpSubnet($logIp, 24, 48) : null;
76
+ $logFp = $existingLog['deviceHash'] ?? $existingLog['fingerprint'] ?? $existingLog['deviceFingerprint'] ?? null;
77
+ $logStableFp = $logFp ? RequestUtils::extractStablePart($logFp) : null;
78
+
79
+ $isIpMatch = $subnet && $logSubnet === $subnet;
80
+ $isFpMatch = $stableFp && $logStableFp === $stableFp;
81
+ if ($isIpMatch) $ipMatchCount++;
82
+ if ($isFpMatch) $fpMatchCount++;
83
+ if ($isIpMatch || $isFpMatch) $matchedKey = $key;
84
+ }
85
+ if (($subnet && ($ipMatchCount / $total) > $maxPercentage) || ($stableFp && ($fpMatchCount / $total) > $maxPercentage)) {
86
+ if ($matchedKey !== null) {
87
+ $this->trafficData[$matchedKey]['instancesCount'] = ($this->trafficData[$matchedKey]['instancesCount'] ?? 1) + 1;
88
+ $this->trafficData[$matchedKey]['weight'] = ($this->trafficData[$matchedKey]['weight'] ?? 1.0) + 1.0;
89
+ }
90
+ return;
91
+ }
92
+ }
93
+ $originalLogger($log);
94
+ };
95
+ $this->securityConfig['logger_wrapped'] = true;
96
+ }
56
97
  }
57
98
 
58
99
  /**
@@ -32,19 +32,40 @@ class ChallengeUtils
32
32
  return abs($hash % 1000000) / 1000000;
33
33
  }
34
34
 
35
- public static function verifyGpuPow(string $seed, int $iterations, string $solution, array $sampleIndices = [0, 12, 35, 57]): bool
35
+ public static function deriveSampleIndices(string $clientIp, string $secret): array
36
+ {
37
+ $timeWindow = (int)floor(time() / (60 * 5));
38
+ $message = "{$clientIp}:{$timeWindow}";
39
+ $hmacHex = hash_hmac('sha256', $message, $secret ?: 'gpu-pow-salt');
40
+ $hmacBytes = array_values(unpack('C*', hex2bin($hmacHex)));
41
+
42
+ $indices = [];
43
+ for ($i = 0; $i < 4; $i++) {
44
+ $offset = ($i * 2) % count($hmacBytes);
45
+ $value = ($hmacBytes[$offset] << 8) | $hmacBytes[($offset + 1) % count($hmacBytes)];
46
+ $idx = $value % 64;
47
+ while (in_array($idx, $indices, true)) {
48
+ $idx = ($idx + 1) % 64;
49
+ }
50
+ $indices[] = $idx;
51
+ }
52
+ return $indices;
53
+ }
54
+
55
+ public static function verifyGpuPow(string $seed, int $iterations, string $solution, string $clientIp = '127.0.0.1', string $secret = 'gpu-pow-salt'): bool
36
56
  {
37
57
  $values = explode(',', $solution);
38
58
  if (count($values) !== 64) {
39
59
  return false;
40
60
  }
61
+ $sampleIndices = self::deriveSampleIndices($clientIp, $secret);
41
62
  $numericSeed = self::hashSeedToFloat($seed);
42
63
  $r = 3.9999;
43
64
  foreach ($sampleIndices as $idx) {
44
65
  if ($idx < 0 || $idx >= 64) {
45
66
  return false;
46
67
  }
47
- $x = self::fround($numericSeed + $idx * 0.015);
68
+ $x = self::fround(fmod($numericSeed + $idx * 0.015, 1.0));
48
69
  $rFloat = self::fround($r);
49
70
  for ($i = 0; $i < $iterations; $i++) {
50
71
  $x = self::fround($rFloat * $x * self::fround(1.0 - $x));
@@ -203,9 +224,22 @@ class ChallengeUtils
203
224
  return ['error' => 'Missing signature'];
204
225
  }
205
226
 
206
- // Ban the ZKP public key for 30 days
207
- $store->set("banned-zkp-y:{$zkpY}", true, 86400 * 30);
208
- return ['status' => 'synchronized'];
227
+ $peersKey = "fed-peers:{$zkpY}";
228
+ $reportedPeers = $store->get($peersKey) ?: [];
229
+ if (!is_array($reportedPeers)) {
230
+ $reportedPeers = [];
231
+ }
232
+ if (!in_array($clientIp, $reportedPeers, true)) {
233
+ $reportedPeers[] = $clientIp;
234
+ $store->set($peersKey, $reportedPeers, 86400 * 30);
235
+ }
236
+
237
+ $threshold = $config['federationConsensusThreshold'] ?? 3;
238
+ if (count($reportedPeers) >= $threshold) {
239
+ $store->set("banned-zkp-y:{$zkpY}", true, 86400 * 30);
240
+ return ['status' => 'synchronized', 'banned' => true];
241
+ }
242
+ return ['status' => 'synchronized', 'banned' => false, 'reportsCount' => count($reportedPeers)];
209
243
  }
210
244
 
211
245
  $nodeId = $params['node_id'] ?? '';
@@ -40,8 +40,8 @@ class SecurityProfiles
40
40
  'clickVarianceScore' => 0.6, // Poids pour la variance des clics
41
41
  'subnetScore' => 0.5, // Pénalise les sous-réseaux IP avec une activité suspecte agrégée
42
42
  'botnetClusterScore' => 0.6, // NOUVEAU: Poids pour le clustering botnet
43
- 'tcpAnomalyScore' => 0.8, // NEW: Anomalie de pile TCP/IP
44
- 'quicAnomalyScore' => 0.8, // NEW: Anomalie QUIC
43
+ 'tcpAnomalyScore' => 0.8,
44
+ 'protocolAnomalyScore' => 0.8, // NEW: Anomalie de protocole (HTTP/2 et QUIC)
45
45
  'renderingAnomalyScore' => 0.8, // NEW: Anomalie de rendu
46
46
  'ipReputationScore' => 0.5, // NOUVEAU: Poids pour la réputation IP
47
47
  ],
@@ -90,7 +90,7 @@ class SecurityProfiles
90
90
  'subnetScore' => 0.7, // Poids plus élevé en mode strict
91
91
  'botnetClusterScore' => 0.8, // NOUVEAU: Poids pour le clustering botnet
92
92
  'tcpAnomalyScore' => 1.0, // NEW: Anomalie de pile TCP/IP
93
- 'quicAnomalyScore' => 1.0, // NEW: Anomalie QUIC
93
+ 'protocolAnomalyScore' => 1.0, // NEW: Anomalie de protocole (HTTP/2 et QUIC)
94
94
  'renderingAnomalyScore' => 1.0, // NEW: Anomalie de rendu
95
95
 
96
96
  ],
@@ -139,7 +139,7 @@ class SecurityProfiles
139
139
  'subnetScore' => 0.8, // Très important pour les API pour détecter les botnets
140
140
  'botnetClusterScore' => 0.7, // NOUVEAU: Poids pour le clustering botnet
141
141
  'tcpAnomalyScore' => 0.8, // NEW: Anomalie de pile TCP/IP
142
- 'quicAnomalyScore' => 0.8, // NEW: Anomalie QUIC
142
+ 'protocolAnomalyScore' => 0.8, // NEW: Anomalie de protocole (HTTP/2 et QUIC)
143
143
 
144
144
  ],
145
145
  'thresholds' => ['low' => 25, 'medium' => 50, 'high' => 80, 'block' => 95],
@@ -190,7 +190,7 @@ class SecurityProfiles
190
190
  'ipReputationScore' => 0.3, // NOUVEAU: Poids pour la réputation IP
191
191
  'botnetClusterScore' => 0.5, // NOUVEAU: Poids pour le clustering botnet
192
192
  'tcpAnomalyScore' => 0.5, // NEW: Anomalie de pile TCP/IP
193
- 'quicAnomalyScore' => 0.5, // NEW: Anomalie QUIC
193
+ 'protocolAnomalyScore' => 0.5, // NEW: Anomalie de protocole (HTTP/2 et QUIC)
194
194
  'renderingAnomalyScore' => 0.5, // NEW: Anomalie de rendu
195
195
 
196
196
  ],
@@ -240,7 +240,7 @@ class SecurityProfiles
240
240
  'subnetScore' => 0.9, // Crucial contre les attaques de scalping distribuées
241
241
  'botnetClusterScore' => 0.9, // NOUVEAU: Poids pour le clustering botnet
242
242
  'tcpAnomalyScore' => 0.9, // NEW: Anomalie de pile TCP/IP
243
- 'quicAnomalyScore' => 0.9, // NEW: Anomalie QUIC
243
+ 'protocolAnomalyScore' => 0.9, // NEW: Anomalie de protocole (HTTP/2 et QUIC)
244
244
  'renderingAnomalyScore' => 0.9, // NEW: Anomalie de rendu
245
245
 
246
246
  ],
@@ -803,8 +803,8 @@
803
803
  // Score d'anomalie de pile TCP/IP
804
804
  $tcpAnomaly = RequestUtils::getTcpAnomalyScore($context);
805
805
 
806
- // Score d'anomalie de flux QUIC/HTTP3
807
- $quicAnomaly = RequestUtils::getQuicAnomalyScore($context);
806
+ // Score d'anomalie de protocole (HTTP/2 et QUIC)
807
+ $protocolAnomaly = RequestUtils::getProtocolAnomalyScore($context);
808
808
 
809
809
  // Score d'anomalie de rendu d'affichage (V-Sync)
810
810
  $renderingAnomaly = RequestUtils::getRenderingAnomalyScore($context);
@@ -828,7 +828,7 @@
828
828
  'subnetScore' => $subnetScore['subnetScore'],
829
829
  'botnetClusterScore' => $botnetCluster['botnetClusterScore'],
830
830
  'tcpAnomalyScore' => $tcpAnomaly['tcpAnomalyScore'],
831
- 'quicAnomalyScore' => $quicAnomaly['quicAnomalyScore'],
831
+ 'protocolAnomalyScore' => $protocolAnomaly['protocolAnomalyScore'],
832
832
  'renderingAnomalyScore' => $renderingAnomaly['renderingAnomalyScore'],
833
833
  ]);
834
834
 
@@ -1197,7 +1197,7 @@
1197
1197
  ]);
1198
1198
 
1199
1199
  // Mettre à jour les métriques du sous-réseau après le calcul du score final
1200
- if ($finalScore > ($thresholds['low'] ?? 20)) {
1200
+ if ($finalScore >= ($thresholds['medium'] ?? 45)) {
1201
1201
  RequestUtils::updateSubnetMetrics($context, $deviceId, $finalScore);
1202
1202
  MetricsManager::observeValue('suspicion_score', $finalScore, ['action' => 'high_score_subnet_update']);
1203
1203
  }
@@ -1249,10 +1249,21 @@
1249
1249
  // 1. Valider cryptographiquement la preuve avant de bannir/diffuser
1250
1250
  if (ChallengeUtils::verifyZkpProof($zkpY, $zkpT, $zkpS)) {
1251
1251
  // 2. Dédoublonner : Ne diffuser que si la clé n'est pas déjà bannie
1252
- if (!$store->has("banned-zkp-y:{$zkpY}")) {
1252
+ $peersKey = "fed-peers:{$zkpY}";
1253
+ $reportedPeers = $store->get($peersKey) ?: [];
1254
+ if (!is_array($reportedPeers)) {
1255
+ $reportedPeers = [];
1256
+ }
1257
+ if (!in_array('local', $reportedPeers, true)) {
1258
+ $reportedPeers[] = 'local';
1259
+ $store->set($peersKey, $reportedPeers, 86400 * 30);
1260
+ }
1261
+
1262
+ $threshold = $this->securityConfig['federationConsensusThreshold'] ?? 3;
1263
+ if (count($reportedPeers) >= $threshold) {
1253
1264
  $store->set("banned-zkp-y:{$zkpY}", true, 86400 * 30);
1254
- $this->broadcastBannedZkp($zkpY);
1255
1265
  }
1266
+ $this->broadcastBannedZkp($zkpY);
1256
1267
  }
1257
1268
  }
1258
1269
 
@@ -20,7 +20,7 @@ class FunctionRegistry
20
20
  if (empty(self::$functions)) {
21
21
  // Fonctions de "Scoring"
22
22
  self::$functions['tsp.calculateEnergy'] = [OptimizationUtils::class, 'evaluatePathDistance'];
23
- self::$functions['portfolio.calculateMetrics'] = [OptimizationOperators::class, 'createPortfolioAllocator'];
23
+ self::$functions['portfolio.calculateMetrics'] = [OptimizationOperators::class, 'calculatePortfolioMetrics'];
24
24
  self::$functions['facility.calculateEnergy'] = [OptimizationOperators::class, 'evaluateFacilityLocation'];
25
25
 
26
26
  // Fonctions de "Résolution"