@anonympins/fingerprint 0.6.5 → 0.6.7

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.
@@ -53,6 +53,44 @@ function generateBlock(seed, blockIndex, blockSize = 1024) {
53
53
  return block;
54
54
  }
55
55
 
56
+ async function loadTfjs() {
57
+ if (typeof tf !== 'undefined') return tf;
58
+ if (typeof window !== 'undefined') {
59
+ if (window.tf) return window.tf;
60
+ return new Promise((resolve, reject) => {
61
+ const script = document.createElement('script');
62
+ script.src = 'https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js';
63
+ script.onload = () => resolve(window.tf);
64
+ script.onerror = () => reject(new Error('Failed to load TensorFlow.js'));
65
+ document.head.appendChild(script);
66
+ });
67
+ }
68
+ try {
69
+ return await import('@tensorflow/tfjs');
70
+ } catch (e) {
71
+ throw new Error("TensorFlow.js is not available.");
72
+ }
73
+ }
74
+
75
+ async function loadOnnxRuntime() {
76
+ if (typeof ort !== 'undefined') return ort;
77
+ if (typeof window !== 'undefined') {
78
+ if (window.ort) return window.ort;
79
+ return new Promise((resolve, reject) => {
80
+ const script = document.createElement('script');
81
+ script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
82
+ script.onload = () => resolve(window.ort);
83
+ script.onerror = () => reject(new Error('Failed to load ONNX Runtime Web'));
84
+ document.head.appendChild(script);
85
+ });
86
+ }
87
+ try {
88
+ return await import('onnxruntime-node');
89
+ } catch (e) {
90
+ throw new Error("ONNX Runtime is not available.");
91
+ }
92
+ }
93
+
56
94
  async function initializeSpace(seed, sizeMb) {
57
95
  const db = await openDb();
58
96
  const transaction = db.transaction("blocks", "readwrite");
@@ -491,8 +529,8 @@ async function solveUsefulWorkTask(task) {
491
529
  const i = Math.floor(secureRandom() * numFacilities);
492
530
  const moveX = (secureRandom() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
493
531
  const moveY = (secureRandom() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
494
- newFacilities[i].x = Math.max(bounds.minX, min(bounds.maxX, newFacilities[i].x + moveX));
495
- newFacilities[i].y = Math.max(bounds.minY, min(bounds.maxY, newFacilities[i].y + moveY));
532
+ newFacilities[i].x = Math.max(bounds.minX, Math.min(bounds.maxX, newFacilities[i].x + moveX));
533
+ newFacilities[i].y = Math.max(bounds.minY, Math.min(bounds.maxY, newFacilities[i].y + moveY));
496
534
  return newFacilities;
497
535
  };
498
536
  const initialSolution = task.initialSolution || Array.from({ length: numFacilities }, () => ({
@@ -557,6 +595,94 @@ async function solveUsefulWorkTask(task) {
557
595
  return solverFunction(task.payload, { generations: task.generations, initialFront: task.initialFront });
558
596
  }
559
597
 
598
+ case 'pytorch_onnx_learning': {
599
+ const { weights, payload, modelPath } = task;
600
+ const inputs = payload.inputs || [];
601
+ const labels = payload.labels || [];
602
+
603
+ try {
604
+ const ort = await loadOnnxRuntime();
605
+ const session = await ort.InferenceSession.create(modelPath);
606
+ const inputName = session.inputNames[0];
607
+ const outputName = session.outputNames[0];
608
+
609
+ const dims = [inputs.length, inputs[0].length];
610
+ const inputTensor = new ort.Tensor('float32', Float32Array.from(inputs.flat()), dims);
611
+ const results = await session.run({ [inputName]: inputTensor });
612
+ const outputTensor = results[outputName];
613
+ const predictions = Array.from(outputTensor.data);
614
+
615
+ const gradients = new Array(weights.length).fill(0);
616
+ for (let i = 0; i < inputs.length; i++) {
617
+ const x = inputs[i];
618
+ const y = labels[i];
619
+ const pred = predictions[i];
620
+ const error = pred - y;
621
+ for (let j = 0; j < weights.length; j++) {
622
+ gradients[j] += error * x[j] / inputs.length;
623
+ }
624
+ }
625
+ return { gradients, predictions };
626
+ } catch (err) {
627
+ console.warn('[Useful Work Solver] Real ONNX loading failed, using fallback simulation:', err);
628
+ const gradients = new Array(weights.length).fill(0);
629
+ for (let i = 0; i < inputs.length; i++) {
630
+ const x = inputs[i];
631
+ const y = labels[i];
632
+ let pred = 0;
633
+ for (let j = 0; j < weights.length; j++) {
634
+ pred += x[j] * weights[j];
635
+ }
636
+ const error = pred - y;
637
+ for (let j = 0; j < weights.length; j++) {
638
+ gradients[j] += error * x[j] / inputs.length;
639
+ }
640
+ }
641
+ return { gradients };
642
+ }
643
+ }
644
+
645
+ case 'tfjs_learning': {
646
+ const { weights, payload, modelPath } = task;
647
+ const inputs = payload.inputs || [];
648
+ const labels = payload.labels || [];
649
+ try {
650
+ const tf = await loadTfjs();
651
+ const model = await tf.loadLayersModel(modelPath);
652
+
653
+ const xs = tf.tensor2d(inputs);
654
+ const ys = tf.tensor2d(labels);
655
+
656
+ const trainableVars = model.trainableWeights.map(w => w.read());
657
+ const lossFn = () => {
658
+ const preds = model.predict(xs);
659
+ return tf.losses.meanSquaredError(ys, preds);
660
+ };
661
+
662
+ const gFn = tf.grad(lossFn);
663
+ const grads = gFn(trainableVars);
664
+
665
+ const flatGradients = [];
666
+ const arr = await grads.array();
667
+ flatGradients.push(...arr.flat());
668
+
669
+ tf.dispose([xs, ys, ...grads]);
670
+ return { gradients: flatGradients };
671
+ } catch (err) {
672
+ console.warn('[Useful Work Solver] Real TFJS loading failed, using fallback simulation:', err);
673
+ const gradients = new Array(weights.length).fill(0);
674
+ for (let i = 0; i < inputs.length; i++) {
675
+ const x = inputs[i];
676
+ const y = labels[i][0] !== undefined ? labels[i][0] : labels[i];
677
+ let pred = 0;
678
+ for (let j = 0; j < weights.length; j++) pred += x[j] * weights[j];
679
+ const error = pred - y;
680
+ for (let j = 0; j < weights.length; j++) gradients[j] += error * x[j] / inputs.length;
681
+ }
682
+ return { gradients };
683
+ }
684
+ }
685
+
560
686
  default:
561
687
  throw new Error(`Unknown useful work type: ${task.type}`);
562
688
  }
@@ -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 };
@@ -379,9 +385,16 @@ class ProblemManager {
379
385
  case 'genetic_algorithm_generations':
380
386
  // VÉRIFICATION PAR ÉCHANTILLONNAGE pour équilibrer sécurité et performance.
381
387
  const fitnessFunction = FunctionRegistry['portfolio.calculateMetrics']; // Ou une fonction plus générique
382
- if (!fitnessFunction || !Array.isArray(solutionData.population) || solutionData.population.length === 0) {
383
- console.error(`[ProblemManager] Impossible de vérifier la population pour ${problemId}.`);
384
- return; // Ne rien faire si la vérification est impossible.
388
+ if (!solutionData || !Array.isArray(solutionData.population)) {
389
+ console.error(`[ProblemManager] Propriété 'population' manquante ou invalide dans solutionData pour ${problemId}.`);
390
+ return;
391
+ }
392
+ if (solutionData.population.length === 0) {
393
+ return; // Retour gracieux si la population est vide (ex: fallback de redirection)
394
+ }
395
+ if (!fitnessFunction) {
396
+ console.error(`[ProblemManager] Impossible de vérifier la population pour ${problemId} (fonction de fitness manquante).`);
397
+ return;
385
398
  }
386
399
 
387
400
  if (solutionData.population.length > 150) {
@@ -408,7 +421,16 @@ class ProblemManager {
408
421
  if (!individual || !individual.chromosome) {
409
422
  throw new Error("Structure d'individu ou chromosome invalide dans la population.");
410
423
  }
411
- totalRecalculatedFitness += fitnessFunction(individual.chromosome, problem.payload);
424
+ const recalculated = fitnessFunction(individual.chromosome, problem.payload);
425
+
426
+ if (individual.fitness !== undefined && individual.fitness !== -1) {
427
+ if (Math.abs(individual.fitness - recalculated) > 1e-4) {
428
+ console.error(`[ProblemManager] Triche détectée pour ${problemId}! Fitness déclaré: ${individual.fitness}, recalculé: ${recalculated}`);
429
+ return; // Rejeter immédiatement toute la population
430
+ }
431
+ }
432
+ individual.fitness = recalculated; // Forcer la valeur exacte recalculée
433
+ totalRecalculatedFitness += recalculated;
412
434
  }
413
435
 
414
436
  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));
@@ -130,6 +130,31 @@ describe('FingerprintEngine Challenge Validation', () => {
130
130
  expect(await inMemoryStore.has(`secret:${nonce}`)).toBe(false);
131
131
  }, 20000);
132
132
 
133
+ it('should generate a combined challenge page containing the correct prefix for the memory seed', async () => {
134
+ const originalRequestContext = {
135
+ clientIp: '127.0.0.1',
136
+ path: '/sensitive-data',
137
+ cookies: {},
138
+ query: {},
139
+ headers: {
140
+ 'user-agent': 'A-Legit-Browser/1.0',
141
+ 'x-device-fingerprint': 'fingerprint-A'
142
+ },
143
+ isStatic: false,
144
+ rawReq: { headers: { accept: 'text/html' } }
145
+ };
146
+
147
+ // Force a high score to trigger a challenge
148
+ vi.spyOn(engine, 'calculateFinalScore').mockReturnValueOnce(20);
149
+
150
+ const challengeDecision = await engine.processRequest(originalRequestContext);
151
+
152
+ expect(challengeDecision.action).toBe('challenge');
153
+ const body = challengeDecision.body;
154
+ // Verify that the memory seed is constructed with the correct leading colon
155
+ expect(body).toContain('const memSeed = ":" + nonce + ":" + clientSecret;');
156
+ });
157
+
133
158
  it('should reject a challenge solution with a mismatched fingerprint', async () => {
134
159
  // --- 1. First request: Challenge is issued to "Machine A" ---
135
160
  const originalRequestContext = {
@@ -2414,7 +2414,7 @@ describe('Subnet Scoring (Node.js)', () => {
2414
2414
  deviceIds: deviceIds, // count = 20. score += (20-10)*5 = 50
2415
2415
  });
2416
2416
  ({ subnetScore } = await __internal.getSubnetScore(context, 'device-1'));
2417
- expect(subnetScore).toBe(100);
2417
+ expect(subnetScore).toBe(94.4);
2418
2418
  });
2419
2419
  });
2420
2420
 
@@ -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: {} };
@@ -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
+ }
@@ -65,7 +65,7 @@ class ChallengeUtils
65
65
  if ($idx < 0 || $idx >= 64) {
66
66
  return false;
67
67
  }
68
- $x = self::fround($numericSeed + $idx * 0.015);
68
+ $x = self::fround(fmod($numericSeed + $idx * 0.015, 1.0));
69
69
  $rFloat = self::fround($r);
70
70
  for ($i = 0; $i < $iterations; $i++) {
71
71
  $x = self::fround($rFloat * $x * self::fround(1.0 - $x));
@@ -1134,7 +1134,7 @@ JS;
1134
1134
  }
1135
1135
  let memSolution = 0;
1136
1136
  try {
1137
- const memSeed = nonce + ":" + clientSecret;
1137
+ const memSeed = ":" + nonce + ":" + clientSecret;
1138
1138
  memSolution = await window.solveMemoryChallenge(memSeed, memDifficulty);
1139
1139
  } catch(e) {
1140
1140
  document.getElementById('loader').innerText = "Error: Insufficient memory. Please refresh.";
@@ -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
  ],