@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.
- package/CHANGELOG.md +29 -0
- package/README.md +2 -2
- package/package.json +4 -1
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +315 -54
- package/src/js/gpu_pow.solver.js +37 -5
- package/src/js/library.js +188 -97
- package/src/js/pow.solver.inline.js +128 -2
- package/src/js/pow.solver.js +85 -0
- package/src/js/problem-manager.js +16 -1
- package/src/js/tests/cross_parity.test.js +2 -2
- package/src/js/tests/fingerprint.test.js +12 -3
- package/src/js/tests/gpu_pow.test.js +9 -7
- package/src/js/tests/ip-reputation.test.js +1 -1
- package/src/js/tests/quicFingerprint.test.js +21 -10
- package/src/js/upow-model-task.js +114 -0
- package/src/php/AutoTuner.php +41 -0
- package/src/php/Challenge/ChallengeUtils.php +39 -5
- package/src/php/Config/SecurityProfiles.php +6 -6
- package/src/php/FingerprintEngine.php +17 -6
- package/src/php/Optimization/FunctionRegistry.php +1 -1
- package/src/php/Optimization/OptimizationOperators.php +109 -28
- package/src/php/ProblemManager.php +22 -0
- package/src/php/Tests/ChallengeUtilsTest.php +9 -4
- package/src/php/Tests/IpReputationTest.php +1 -1
- package/src/php/Tests/ProblemManagerTest.php +1 -1
- package/src/php/Tests/QuicFingerprintTest.php +9 -9
- package/src/php/Utils/RequestUtils.php +102 -45
package/src/js/pow.solver.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
2512
|
-
calculateFinalScoreSpy.mockReturnValue(50); // Score between
|
|
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
|
|
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[
|
|
66
|
-
tamperedSolutions[
|
|
67
|
-
|
|
68
|
-
expect(
|
|
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}`,
|
|
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 {
|
|
4
|
+
const { getProtocolAnomalyScore } = __internal;
|
|
5
5
|
|
|
6
|
-
describe('
|
|
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 =
|
|
10
|
-
expect(score.
|
|
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 =
|
|
22
|
-
expect(score.
|
|
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 =
|
|
33
|
-
expect(score.
|
|
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
|
+
}
|
package/src/php/AutoTuner.php
CHANGED
|
@@ -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
|
|
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
|
-
|
|
207
|
-
$store->
|
|
208
|
-
|
|
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,
|
|
44
|
-
'
|
|
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
|
-
'
|
|
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
|
-
'
|
|
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
|
-
'
|
|
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
|
-
'
|
|
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
|
|
807
|
-
$
|
|
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
|
-
'
|
|
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
|
|
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
|
-
|
|
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, '
|
|
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"
|