@anonympins/fingerprint 0.4.2 → 0.4.3
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 +17 -0
- package/README.md +66 -60
- package/package.json +1 -1
- package/src/js/fingerprint.js +10 -2
- package/src/js/pow.solver.js +531 -497
- package/src/js/problem-manager.js +24 -0
- package/src/js/tests/fingerprint.test.js +2351 -2319
- package/src/js/tests/pow.solver.test.js +233 -197
- package/src/js/tests/problem-manager.test.js +358 -322
- package/src/php/FingerprintEngine.php +10 -2
- package/src/php/Optimization/FunctionRegistry.php +63 -62
- package/src/php/Optimization/OptimizationOperators.php +401 -304
- package/src/php/ProblemManager.php +29 -0
- package/src/php/Tests/FingerprintEngineTest.php +330 -299
- package/src/php/Tests/ProblemManagerTest.php +376 -296
- package/src/php/Tests/RequestUtilsTest.php +256 -253
- package/src/php/Tests/problems.config.json +3 -3
- package/src/php/Utils/RequestUtils.php +1 -1
|
@@ -1,305 +1,402 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Optimization;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Opérateurs pour les problèmes d'optimisation.
|
|
9
|
-
*/
|
|
10
|
-
class OptimizationOperators
|
|
11
|
-
{
|
|
12
|
-
/**
|
|
13
|
-
* Génère un nombre flottant aléatoire cryptographiquement sûr entre 0 (inclus) et 1 (exclus).
|
|
14
|
-
*/
|
|
15
|
-
private static function secureRandom(): float
|
|
16
|
-
{
|
|
17
|
-
try {
|
|
18
|
-
return random_int(0, PHP_INT_MAX - 1) / PHP_INT_MAX;
|
|
19
|
-
} catch (\Exception $e) {
|
|
20
|
-
return (float)mt_rand() / (float)mt_getrandmax(); // Fallback
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Crée un évaluateur pour l'optimisation de portefeuille.
|
|
26
|
-
* @param array $config
|
|
27
|
-
* @return callable
|
|
28
|
-
*/
|
|
29
|
-
public static function createPortfolioAllocator(array $config): callable
|
|
30
|
-
{
|
|
31
|
-
// Cette fonction est un placeholder. Une implémentation complète nécessiterait
|
|
32
|
-
// une logique de calcul de rendement et de volatilité de portefeuille.
|
|
33
|
-
return function (array $weights) use ($config): float {
|
|
34
|
-
// Minimiser le rendement négatif (donc maximiser le rendement)
|
|
35
|
-
return -array_sum($weights);
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Crée un évaluateur pour l'auto-tuning complet de la configuration de sécurité.
|
|
41
|
-
* @param array $context
|
|
42
|
-
* @return callable
|
|
43
|
-
*/
|
|
44
|
-
public static function createFullSecurityConfigEvaluator(array $context): callable
|
|
45
|
-
{
|
|
46
|
-
$trafficData = $context['trafficData'];
|
|
47
|
-
|
|
48
|
-
return function (array $config) use ($trafficData): array {
|
|
49
|
-
$falsePositives = 0;
|
|
50
|
-
$falseNegatives = 0;
|
|
51
|
-
$totalHumans = 0;
|
|
52
|
-
$totalBots = 0;
|
|
53
|
-
|
|
54
|
-
$calculateScore = function (array $log) use ($config): float {
|
|
55
|
-
$score = 0.0;
|
|
56
|
-
foreach ($config['weights'] as $key => $weight) {
|
|
57
|
-
$score += ($log['vector'][$key] ?? 0) * $weight;
|
|
58
|
-
}
|
|
59
|
-
return $score;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
$confidenceWeights = [
|
|
63
|
-
'request_passed' => 0.7,
|
|
64
|
-
'challenge_issued' => 1.0,
|
|
65
|
-
'request_blocked' => 1.0,
|
|
66
|
-
'challenge_solved' => 1.5,
|
|
67
|
-
'trap_triggered' => 2.0,
|
|
68
|
-
];
|
|
69
|
-
|
|
70
|
-
foreach ($trafficData as $log) {
|
|
71
|
-
$confidence = $confidenceWeights[$log['type']] ?? 1.0;
|
|
72
|
-
$isLikelyBot = in_array($log['type'], ['challenge_issued', 'request_blocked', 'trap_triggered']);
|
|
73
|
-
$isLikelyHuman = in_array($log['type'], ['request_passed', 'challenge_solved']);
|
|
74
|
-
|
|
75
|
-
if ($isLikelyBot) {
|
|
76
|
-
$totalBots += $confidence;
|
|
77
|
-
$score = $calculateScore($log);
|
|
78
|
-
if ($score < $config['thresholds']['low']) {
|
|
79
|
-
$falseNegatives += $confidence;
|
|
80
|
-
}
|
|
81
|
-
} elseif ($isLikelyHuman) {
|
|
82
|
-
$totalHumans += $confidence;
|
|
83
|
-
$score = $calculateScore($log);
|
|
84
|
-
if ($score >= $config['thresholds']['low']) {
|
|
85
|
-
$falsePositives += $confidence;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
$falsePositiveRate = $totalHumans > 0 ? $falsePositives / $totalHumans : 0;
|
|
91
|
-
$falseNegativeRate = $totalBots > 0 ? $falseNegatives / $totalBots : 0;
|
|
92
|
-
|
|
93
|
-
return [$falsePositiveRate, $falseNegativeRate];
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Résout le problème de l'auto-tuning complet de la configuration de sécurité.
|
|
99
|
-
* @param array $context
|
|
100
|
-
* @param array $options
|
|
101
|
-
* @return array
|
|
102
|
-
*/
|
|
103
|
-
public static function solveFullSecurityTuning(array $context, array $options = []): array
|
|
104
|
-
{
|
|
105
|
-
$fitnessFunction = self::createFullSecurityConfigEvaluator($context);
|
|
106
|
-
|
|
107
|
-
$createIndividual = function (): array {
|
|
108
|
-
return [
|
|
109
|
-
'thresholds' => [
|
|
110
|
-
'low' => 15 + self::secureRandom() * 20,
|
|
111
|
-
'medium' => 40 + self::secureRandom() * 25,
|
|
112
|
-
'high' => 70 + self::secureRandom() * 20,
|
|
113
|
-
],
|
|
114
|
-
'weights' => [
|
|
115
|
-
'historyScore' => self::secureRandom(),
|
|
116
|
-
'rotationScore' => self::secureRandom(),
|
|
117
|
-
'headerAnomalyScore' => self::secureRandom(),
|
|
118
|
-
'requestPatternScore' => 0.5 + self::secureRandom(),
|
|
119
|
-
'inconsistencyScore' => self::secureRandom(),
|
|
120
|
-
'honeypotScore' => 1.0,
|
|
121
|
-
'behaviorScore' => self::secureRandom(),
|
|
122
|
-
'crossLayerInconsistencyScore' => self::secureRandom(),
|
|
123
|
-
'timeInconsistencyScore' => self::secureRandom(),
|
|
124
|
-
'tlsSpoofingScore' => self::secureRandom(),
|
|
125
|
-
'botScore' => self::secureRandom(),
|
|
126
|
-
'cookieDroppingScore' => self::secureRandom(),
|
|
127
|
-
'threatIntelScore' => self::secureRandom(),
|
|
128
|
-
],
|
|
129
|
-
'patterns' => [
|
|
130
|
-
'velocityThreshold' => 100 + self::secureRandom() * 400,
|
|
131
|
-
'burstThreshold' => 300 + self::secureRandom() * 700,
|
|
132
|
-
'scrapeThreshold' => 500 + self::secureRandom() * 1000,
|
|
133
|
-
'regularityThreshold' => 50 + self::secureRandom() * 200,
|
|
134
|
-
'decayFactor' => 0.85 + self::secureRandom() * 0.14,
|
|
135
|
-
'inactivityReset' => 15000 + self::secureRandom() * 45000,
|
|
136
|
-
]
|
|
137
|
-
];
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
$crossover = function (array $c1, array $c2): array {
|
|
141
|
-
$child = $c1;
|
|
142
|
-
foreach (['thresholds', 'weights', 'patterns'] as $section) {
|
|
143
|
-
foreach ($child[$section] as $key => $value) {
|
|
144
|
-
if ($key !== 'honeypotScore') {
|
|
145
|
-
$child[$section][$key] = ($c1[$section][$key] + $c2[$section][$key]) / 2;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return $child;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
$mutate = function (array $c): array {
|
|
153
|
-
$newConfig = $c;
|
|
154
|
-
$sections = [
|
|
155
|
-
['name' => 'patterns', 'weight' => 0.5],
|
|
156
|
-
['name' => 'weights', 'weight' => 0.35],
|
|
157
|
-
['name' => 'thresholds', 'weight' => 0.15]
|
|
158
|
-
];
|
|
159
|
-
$rand = self::secureRandom();
|
|
160
|
-
$cumulativeWeight = 0;
|
|
161
|
-
$sectionToMutate = 'patterns';
|
|
162
|
-
foreach ($sections as $section) {
|
|
163
|
-
$cumulativeWeight += $section['weight'];
|
|
164
|
-
if ($rand < $cumulativeWeight) {
|
|
165
|
-
$sectionToMutate = $section['name'];
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
$keys = array_keys($newConfig[$sectionToMutate]);
|
|
171
|
-
$keyToMutate = $keys[random_int(0, count($keys) - 1)];
|
|
172
|
-
|
|
173
|
-
if ($keyToMutate === 'honeypotScore') return $newConfig;
|
|
174
|
-
|
|
175
|
-
$mutationAmount = (self::secureRandom() - 0.5) * 0.4;
|
|
176
|
-
$newConfig[$sectionToMutate][$keyToMutate] *= (1 + $mutationAmount);
|
|
177
|
-
|
|
178
|
-
if ($sectionToMutate === 'weights') {
|
|
179
|
-
$newConfig[$sectionToMutate][$keyToMutate] = max(0, min(1.5, $newConfig[$sectionToMutate][$keyToMutate]));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return $newConfig;
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
return Optimization::geneticAlgorithmMultiObjective(
|
|
186
|
-
$createIndividual,
|
|
187
|
-
$fitnessFunction,
|
|
188
|
-
$crossover,
|
|
189
|
-
$mutate,
|
|
190
|
-
array_merge(['generations' => 50, 'populationSize' => 50], $options)
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Crée un évaluateur pour trouver les seuils de détection de fraude optimaux.
|
|
196
|
-
* @param array $context
|
|
197
|
-
* @return callable
|
|
198
|
-
*/
|
|
199
|
-
public static function createFraudThresholdEvaluator(array $context): callable
|
|
200
|
-
{
|
|
201
|
-
$legitimateClicks = $context['legitimateClicks'] ?? [];
|
|
202
|
-
$fraudulentClicks = $context['fraudulentClicks'] ?? [];
|
|
203
|
-
|
|
204
|
-
return function (array $solution) use ($legitimateClicks, $fraudulentClicks): array {
|
|
205
|
-
[$minTimeToClick, $maxClickVariance, $minMouseEntropy, $minScrollEvents] = $solution;
|
|
206
|
-
|
|
207
|
-
$truePositives = 0;
|
|
208
|
-
$falsePositives = 0;
|
|
209
|
-
|
|
210
|
-
foreach ($fraudulentClicks as $click) {
|
|
211
|
-
if (
|
|
212
|
-
($click['timeToClick'] < $minTimeToClick) ||
|
|
213
|
-
($click['mouseEntropy'] < $minMouseEntropy)
|
|
214
|
-
) {
|
|
215
|
-
$truePositives++;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
foreach ($legitimateClicks as $click) {
|
|
220
|
-
if (
|
|
221
|
-
($click['timeToClick'] < $minTimeToClick) ||
|
|
222
|
-
($click['mouseEntropy'] < $minMouseEntropy)
|
|
223
|
-
) {
|
|
224
|
-
$falsePositives++;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
$totalFraudulent = count($fraudulentClicks) ?: 1;
|
|
229
|
-
$totalLegitimate = count($legitimateClicks) ?: 1;
|
|
230
|
-
|
|
231
|
-
$objective1 = 1 - ($truePositives / $totalFraudulent);
|
|
232
|
-
$objective2 = $falsePositives / $totalLegitimate;
|
|
233
|
-
|
|
234
|
-
return [$objective1, $objective2];
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Résout le problème de la détection de fraude.
|
|
240
|
-
* @param array $context
|
|
241
|
-
* @param array $options
|
|
242
|
-
* @return array
|
|
243
|
-
*/
|
|
244
|
-
public static function solveFraudDetection(array $context, array $options = []): array
|
|
245
|
-
{
|
|
246
|
-
$fitnessFunction = self::createFraudThresholdEvaluator($context);
|
|
247
|
-
|
|
248
|
-
$createIndividual = function (): array {
|
|
249
|
-
return [
|
|
250
|
-
100 + self::secureRandom() * 4900, // minTimeToClick
|
|
251
|
-
1 + self::secureRandom() * 9999, // maxClickVariance
|
|
252
|
-
self::secureRandom() * 0.5, // minMouseEntropy
|
|
253
|
-
floor(self::secureRandom() * 10) // minScrollEvents
|
|
254
|
-
];
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
$crossover = fn ($s1, $s2) => array_map(fn ($a, $b) => ($a + $b) / 2, $s1, $s2);
|
|
258
|
-
|
|
259
|
-
$mutate = function (array $solution): array {
|
|
260
|
-
$i = random_int(0, 3);
|
|
261
|
-
$mutationFactors = [500, 1000, 0.1, 2];
|
|
262
|
-
$solution[$i] += (self::secureRandom() - 0.5) * $mutationFactors[$i];
|
|
263
|
-
return $solution;
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
return Optimization::geneticAlgorithmMultiObjective(
|
|
267
|
-
$createIndividual,
|
|
268
|
-
$fitnessFunction,
|
|
269
|
-
$crossover,
|
|
270
|
-
$mutate,
|
|
271
|
-
array_merge(['generations' => 80, 'populationSize' => 60], $options)
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Placeholder pour le solveur TSP.
|
|
277
|
-
* @param array $cities
|
|
278
|
-
* @param array $options
|
|
279
|
-
* @return array
|
|
280
|
-
*/
|
|
281
|
-
public static function solveTSP(array $cities, array $options = []): array
|
|
282
|
-
{
|
|
283
|
-
// Implémentation factice pour la complétude
|
|
284
|
-
return ['solution' => array_keys($cities), 'energy' => 100];
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Placeholder pour le solveur de portefeuille.
|
|
289
|
-
* @param array $assets
|
|
290
|
-
* @param float $maxVolatility
|
|
291
|
-
* @param array $options
|
|
292
|
-
* @return array
|
|
293
|
-
*/
|
|
294
|
-
public static function solvePortfolio(array $assets, float $maxVolatility, array $options = []): array
|
|
295
|
-
{
|
|
296
|
-
// Implémentation factice pour la complétude
|
|
297
|
-
return ['solution' => array_fill(0, count($assets), 1 / count($assets)), 'fitness' => -0.1];
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
public static function solveFacilityLocation(array $customers, int $numFacilities, array $bounds, array $options = []): array
|
|
301
|
-
{
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Optimization;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Opérateurs pour les problèmes d'optimisation.
|
|
9
|
+
*/
|
|
10
|
+
class OptimizationOperators
|
|
11
|
+
{
|
|
12
|
+
/**
|
|
13
|
+
* Génère un nombre flottant aléatoire cryptographiquement sûr entre 0 (inclus) et 1 (exclus).
|
|
14
|
+
*/
|
|
15
|
+
private static function secureRandom(): float
|
|
16
|
+
{
|
|
17
|
+
try {
|
|
18
|
+
return random_int(0, PHP_INT_MAX - 1) / PHP_INT_MAX;
|
|
19
|
+
} catch (\Exception $e) {
|
|
20
|
+
return (float)mt_rand() / (float)mt_getrandmax(); // Fallback
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Crée un évaluateur pour l'optimisation de portefeuille.
|
|
26
|
+
* @param array $config
|
|
27
|
+
* @return callable
|
|
28
|
+
*/
|
|
29
|
+
public static function createPortfolioAllocator(array $config): callable
|
|
30
|
+
{
|
|
31
|
+
// Cette fonction est un placeholder. Une implémentation complète nécessiterait
|
|
32
|
+
// une logique de calcul de rendement et de volatilité de portefeuille.
|
|
33
|
+
return function (array $weights) use ($config): float {
|
|
34
|
+
// Minimiser le rendement négatif (donc maximiser le rendement)
|
|
35
|
+
return -array_sum($weights);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Crée un évaluateur pour l'auto-tuning complet de la configuration de sécurité.
|
|
41
|
+
* @param array $context
|
|
42
|
+
* @return callable
|
|
43
|
+
*/
|
|
44
|
+
public static function createFullSecurityConfigEvaluator(array $context): callable
|
|
45
|
+
{
|
|
46
|
+
$trafficData = $context['trafficData'];
|
|
47
|
+
|
|
48
|
+
return function (array $config) use ($trafficData): array {
|
|
49
|
+
$falsePositives = 0;
|
|
50
|
+
$falseNegatives = 0;
|
|
51
|
+
$totalHumans = 0;
|
|
52
|
+
$totalBots = 0;
|
|
53
|
+
|
|
54
|
+
$calculateScore = function (array $log) use ($config): float {
|
|
55
|
+
$score = 0.0;
|
|
56
|
+
foreach ($config['weights'] as $key => $weight) {
|
|
57
|
+
$score += ($log['vector'][$key] ?? 0) * $weight;
|
|
58
|
+
}
|
|
59
|
+
return $score;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
$confidenceWeights = [
|
|
63
|
+
'request_passed' => 0.7,
|
|
64
|
+
'challenge_issued' => 1.0,
|
|
65
|
+
'request_blocked' => 1.0,
|
|
66
|
+
'challenge_solved' => 1.5,
|
|
67
|
+
'trap_triggered' => 2.0,
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
foreach ($trafficData as $log) {
|
|
71
|
+
$confidence = $confidenceWeights[$log['type']] ?? 1.0;
|
|
72
|
+
$isLikelyBot = in_array($log['type'], ['challenge_issued', 'request_blocked', 'trap_triggered']);
|
|
73
|
+
$isLikelyHuman = in_array($log['type'], ['request_passed', 'challenge_solved']);
|
|
74
|
+
|
|
75
|
+
if ($isLikelyBot) {
|
|
76
|
+
$totalBots += $confidence;
|
|
77
|
+
$score = $calculateScore($log);
|
|
78
|
+
if ($score < $config['thresholds']['low']) {
|
|
79
|
+
$falseNegatives += $confidence;
|
|
80
|
+
}
|
|
81
|
+
} elseif ($isLikelyHuman) {
|
|
82
|
+
$totalHumans += $confidence;
|
|
83
|
+
$score = $calculateScore($log);
|
|
84
|
+
if ($score >= $config['thresholds']['low']) {
|
|
85
|
+
$falsePositives += $confidence;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
$falsePositiveRate = $totalHumans > 0 ? $falsePositives / $totalHumans : 0;
|
|
91
|
+
$falseNegativeRate = $totalBots > 0 ? $falseNegatives / $totalBots : 0;
|
|
92
|
+
|
|
93
|
+
return [$falsePositiveRate, $falseNegativeRate];
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Résout le problème de l'auto-tuning complet de la configuration de sécurité.
|
|
99
|
+
* @param array $context
|
|
100
|
+
* @param array $options
|
|
101
|
+
* @return array
|
|
102
|
+
*/
|
|
103
|
+
public static function solveFullSecurityTuning(array $context, array $options = []): array
|
|
104
|
+
{
|
|
105
|
+
$fitnessFunction = self::createFullSecurityConfigEvaluator($context);
|
|
106
|
+
|
|
107
|
+
$createIndividual = function (): array {
|
|
108
|
+
return [
|
|
109
|
+
'thresholds' => [
|
|
110
|
+
'low' => 15 + self::secureRandom() * 20,
|
|
111
|
+
'medium' => 40 + self::secureRandom() * 25,
|
|
112
|
+
'high' => 70 + self::secureRandom() * 20,
|
|
113
|
+
],
|
|
114
|
+
'weights' => [
|
|
115
|
+
'historyScore' => self::secureRandom(),
|
|
116
|
+
'rotationScore' => self::secureRandom(),
|
|
117
|
+
'headerAnomalyScore' => self::secureRandom(),
|
|
118
|
+
'requestPatternScore' => 0.5 + self::secureRandom(),
|
|
119
|
+
'inconsistencyScore' => self::secureRandom(),
|
|
120
|
+
'honeypotScore' => 1.0,
|
|
121
|
+
'behaviorScore' => self::secureRandom(),
|
|
122
|
+
'crossLayerInconsistencyScore' => self::secureRandom(),
|
|
123
|
+
'timeInconsistencyScore' => self::secureRandom(),
|
|
124
|
+
'tlsSpoofingScore' => self::secureRandom(),
|
|
125
|
+
'botScore' => self::secureRandom(),
|
|
126
|
+
'cookieDroppingScore' => self::secureRandom(),
|
|
127
|
+
'threatIntelScore' => self::secureRandom(),
|
|
128
|
+
],
|
|
129
|
+
'patterns' => [
|
|
130
|
+
'velocityThreshold' => 100 + self::secureRandom() * 400,
|
|
131
|
+
'burstThreshold' => 300 + self::secureRandom() * 700,
|
|
132
|
+
'scrapeThreshold' => 500 + self::secureRandom() * 1000,
|
|
133
|
+
'regularityThreshold' => 50 + self::secureRandom() * 200,
|
|
134
|
+
'decayFactor' => 0.85 + self::secureRandom() * 0.14,
|
|
135
|
+
'inactivityReset' => 15000 + self::secureRandom() * 45000,
|
|
136
|
+
]
|
|
137
|
+
];
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
$crossover = function (array $c1, array $c2): array {
|
|
141
|
+
$child = $c1;
|
|
142
|
+
foreach (['thresholds', 'weights', 'patterns'] as $section) {
|
|
143
|
+
foreach ($child[$section] as $key => $value) {
|
|
144
|
+
if ($key !== 'honeypotScore') {
|
|
145
|
+
$child[$section][$key] = ($c1[$section][$key] + $c2[$section][$key]) / 2;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return $child;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
$mutate = function (array $c): array {
|
|
153
|
+
$newConfig = $c;
|
|
154
|
+
$sections = [
|
|
155
|
+
['name' => 'patterns', 'weight' => 0.5],
|
|
156
|
+
['name' => 'weights', 'weight' => 0.35],
|
|
157
|
+
['name' => 'thresholds', 'weight' => 0.15]
|
|
158
|
+
];
|
|
159
|
+
$rand = self::secureRandom();
|
|
160
|
+
$cumulativeWeight = 0;
|
|
161
|
+
$sectionToMutate = 'patterns';
|
|
162
|
+
foreach ($sections as $section) {
|
|
163
|
+
$cumulativeWeight += $section['weight'];
|
|
164
|
+
if ($rand < $cumulativeWeight) {
|
|
165
|
+
$sectionToMutate = $section['name'];
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
$keys = array_keys($newConfig[$sectionToMutate]);
|
|
171
|
+
$keyToMutate = $keys[random_int(0, count($keys) - 1)];
|
|
172
|
+
|
|
173
|
+
if ($keyToMutate === 'honeypotScore') return $newConfig;
|
|
174
|
+
|
|
175
|
+
$mutationAmount = (self::secureRandom() - 0.5) * 0.4;
|
|
176
|
+
$newConfig[$sectionToMutate][$keyToMutate] *= (1 + $mutationAmount);
|
|
177
|
+
|
|
178
|
+
if ($sectionToMutate === 'weights') {
|
|
179
|
+
$newConfig[$sectionToMutate][$keyToMutate] = max(0, min(1.5, $newConfig[$sectionToMutate][$keyToMutate]));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return $newConfig;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return Optimization::geneticAlgorithmMultiObjective(
|
|
186
|
+
$createIndividual,
|
|
187
|
+
$fitnessFunction,
|
|
188
|
+
$crossover,
|
|
189
|
+
$mutate,
|
|
190
|
+
array_merge(['generations' => 50, 'populationSize' => 50], $options)
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Crée un évaluateur pour trouver les seuils de détection de fraude optimaux.
|
|
196
|
+
* @param array $context
|
|
197
|
+
* @return callable
|
|
198
|
+
*/
|
|
199
|
+
public static function createFraudThresholdEvaluator(array $context): callable
|
|
200
|
+
{
|
|
201
|
+
$legitimateClicks = $context['legitimateClicks'] ?? [];
|
|
202
|
+
$fraudulentClicks = $context['fraudulentClicks'] ?? [];
|
|
203
|
+
|
|
204
|
+
return function (array $solution) use ($legitimateClicks, $fraudulentClicks): array {
|
|
205
|
+
[$minTimeToClick, $maxClickVariance, $minMouseEntropy, $minScrollEvents] = $solution;
|
|
206
|
+
|
|
207
|
+
$truePositives = 0;
|
|
208
|
+
$falsePositives = 0;
|
|
209
|
+
|
|
210
|
+
foreach ($fraudulentClicks as $click) {
|
|
211
|
+
if (
|
|
212
|
+
($click['timeToClick'] < $minTimeToClick) ||
|
|
213
|
+
($click['mouseEntropy'] < $minMouseEntropy)
|
|
214
|
+
) {
|
|
215
|
+
$truePositives++;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
foreach ($legitimateClicks as $click) {
|
|
220
|
+
if (
|
|
221
|
+
($click['timeToClick'] < $minTimeToClick) ||
|
|
222
|
+
($click['mouseEntropy'] < $minMouseEntropy)
|
|
223
|
+
) {
|
|
224
|
+
$falsePositives++;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
$totalFraudulent = count($fraudulentClicks) ?: 1;
|
|
229
|
+
$totalLegitimate = count($legitimateClicks) ?: 1;
|
|
230
|
+
|
|
231
|
+
$objective1 = 1 - ($truePositives / $totalFraudulent);
|
|
232
|
+
$objective2 = $falsePositives / $totalLegitimate;
|
|
233
|
+
|
|
234
|
+
return [$objective1, $objective2];
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Résout le problème de la détection de fraude.
|
|
240
|
+
* @param array $context
|
|
241
|
+
* @param array $options
|
|
242
|
+
* @return array
|
|
243
|
+
*/
|
|
244
|
+
public static function solveFraudDetection(array $context, array $options = []): array
|
|
245
|
+
{
|
|
246
|
+
$fitnessFunction = self::createFraudThresholdEvaluator($context);
|
|
247
|
+
|
|
248
|
+
$createIndividual = function (): array {
|
|
249
|
+
return [
|
|
250
|
+
100 + self::secureRandom() * 4900, // minTimeToClick
|
|
251
|
+
1 + self::secureRandom() * 9999, // maxClickVariance
|
|
252
|
+
self::secureRandom() * 0.5, // minMouseEntropy
|
|
253
|
+
floor(self::secureRandom() * 10) // minScrollEvents
|
|
254
|
+
];
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
$crossover = fn ($s1, $s2) => array_map(fn ($a, $b) => ($a + $b) / 2, $s1, $s2);
|
|
258
|
+
|
|
259
|
+
$mutate = function (array $solution): array {
|
|
260
|
+
$i = random_int(0, 3);
|
|
261
|
+
$mutationFactors = [500, 1000, 0.1, 2];
|
|
262
|
+
$solution[$i] += (self::secureRandom() - 0.5) * $mutationFactors[$i];
|
|
263
|
+
return $solution;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
return Optimization::geneticAlgorithmMultiObjective(
|
|
267
|
+
$createIndividual,
|
|
268
|
+
$fitnessFunction,
|
|
269
|
+
$crossover,
|
|
270
|
+
$mutate,
|
|
271
|
+
array_merge(['generations' => 80, 'populationSize' => 60], $options)
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Placeholder pour le solveur TSP.
|
|
277
|
+
* @param array $cities
|
|
278
|
+
* @param array $options
|
|
279
|
+
* @return array
|
|
280
|
+
*/
|
|
281
|
+
public static function solveTSP(array $cities, array $options = []): array
|
|
282
|
+
{
|
|
283
|
+
// Implémentation factice pour la complétude
|
|
284
|
+
return ['solution' => array_keys($cities), 'energy' => 100];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Placeholder pour le solveur de portefeuille.
|
|
289
|
+
* @param array $assets
|
|
290
|
+
* @param float $maxVolatility
|
|
291
|
+
* @param array $options
|
|
292
|
+
* @return array
|
|
293
|
+
*/
|
|
294
|
+
public static function solvePortfolio(array $assets, float $maxVolatility, array $options = []): array
|
|
295
|
+
{
|
|
296
|
+
// Implémentation factice pour la complétude
|
|
297
|
+
return ['solution' => array_fill(0, count($assets), 1 / count($assets)), 'fitness' => -0.1];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
public static function solveFacilityLocation(array $customers, int $numFacilities, array $bounds, array $options = []): array
|
|
301
|
+
{
|
|
302
|
+
$fixedCostPerFacility = $options['fixedCostPerFacility'] ?? 0;
|
|
303
|
+
$initialTemperature = $options['initialTemperature'] ?? 100000.0;
|
|
304
|
+
$coolingRate = $options['coolingRate'] ?? 0.999;
|
|
305
|
+
$maxIterations = $options['maxIterations'] ?? 15000; // Borne raisonnable pour PHP
|
|
306
|
+
|
|
307
|
+
$evaluator = function (array $facilities) use ($customers, $fixedCostPerFacility): float {
|
|
308
|
+
$totalConnectionCost = 0.0;
|
|
309
|
+
foreach ($customers as $customer) {
|
|
310
|
+
$minDistanceSq = INF;
|
|
311
|
+
foreach ($facilities as $facility) {
|
|
312
|
+
$dx = $customer['x'] - $facility['x'];
|
|
313
|
+
$dy = $customer['y'] - $facility['y'];
|
|
314
|
+
$dSq = $dx * $dx + $dy * $dy;
|
|
315
|
+
if ($dSq < $minDistanceSq) {
|
|
316
|
+
$minDistanceSq = $dSq;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
$totalConnectionCost += sqrt($minDistanceSq);
|
|
320
|
+
}
|
|
321
|
+
return $totalConnectionCost + count($facilities) * $fixedCostPerFacility;
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
$neighbor = function (array $facilities) use ($bounds, $numFacilities): array {
|
|
325
|
+
$newFacilities = $facilities;
|
|
326
|
+
$i = random_int(0, $numFacilities - 1);
|
|
327
|
+
|
|
328
|
+
$moveX = (self::secureRandom() - 0.5) * ($bounds['maxX'] - $bounds['minX']) * 0.1;
|
|
329
|
+
$moveY = (self::secureRandom() - 0.5) * ($bounds['maxY'] - $bounds['minY']) * 0.1;
|
|
330
|
+
|
|
331
|
+
$newFacilities[$i]['x'] = max($bounds['minX'], min($bounds['maxX'], $newFacilities[$i]['x'] + $moveX));
|
|
332
|
+
$newFacilities[$i]['y'] = max($bounds['minY'], min($bounds['maxY'], $newFacilities[$i]['y'] + $moveY));
|
|
333
|
+
|
|
334
|
+
return $newFacilities;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// Génération d'une solution initiale aléatoire
|
|
338
|
+
$currentSolution = [];
|
|
339
|
+
for ($i = 0; $i < $numFacilities; $i++) {
|
|
340
|
+
$currentSolution[] = [
|
|
341
|
+
'x' => $bounds['minX'] + self::secureRandom() * ($bounds['maxX'] - $bounds['minX']),
|
|
342
|
+
'y' => $bounds['minY'] + self::secureRandom() * ($bounds['maxY'] - $bounds['minY']),
|
|
343
|
+
];
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
$currentEnergy = $evaluator($currentSolution);
|
|
347
|
+
$bestSolution = $currentSolution;
|
|
348
|
+
$bestEnergy = $currentEnergy;
|
|
349
|
+
$temperature = $initialTemperature;
|
|
350
|
+
|
|
351
|
+
for ($step = 0; $step < $maxIterations; $step++) {
|
|
352
|
+
$newSolution = $neighbor($currentSolution);
|
|
353
|
+
$newEnergy = $evaluator($newSolution);
|
|
354
|
+
|
|
355
|
+
$acceptanceProbability = exp(($currentEnergy - $newEnergy) / $temperature);
|
|
356
|
+
|
|
357
|
+
if ($newEnergy < $currentEnergy || self::secureRandom() < $acceptanceProbability) {
|
|
358
|
+
$currentSolution = $newSolution;
|
|
359
|
+
$currentEnergy = $newEnergy;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if ($currentEnergy < $bestEnergy) {
|
|
363
|
+
$bestSolution = $currentSolution;
|
|
364
|
+
$bestEnergy = $currentEnergy;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
$temperature *= $coolingRate;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return ['solution' => $bestSolution, 'energy' => $bestEnergy];
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Évalue de manière indépendante l'énergie d'une solution de placement d'infrastructures.
|
|
375
|
+
* Appelée par le serveur pour valider les calculs soumis par le client.
|
|
376
|
+
*
|
|
377
|
+
* @param array $facilities Liste des positions proposées par le client.
|
|
378
|
+
* @param array $payload Configuration initiale contenant les clients et les coûts fixes.
|
|
379
|
+
* @return float Le coût total vérifié.
|
|
380
|
+
*/
|
|
381
|
+
public static function evaluateFacilityLocation(array $facilities, array $payload): float
|
|
382
|
+
{
|
|
383
|
+
$customers = $payload['customers'] ?? [];
|
|
384
|
+
$fixedCostPerFacility = $payload['options']['fixedCostPerFacility'] ?? 0;
|
|
385
|
+
$totalConnectionCost = 0.0;
|
|
386
|
+
|
|
387
|
+
foreach ($customers as $customer) {
|
|
388
|
+
$minDistanceSq = INF;
|
|
389
|
+
foreach ($facilities as $facility) {
|
|
390
|
+
$dx = $customer['x'] - $facility['x'];
|
|
391
|
+
$dy = $customer['y'] - $facility['y'];
|
|
392
|
+
$dSq = $dx * $dx + $dy * $dy;
|
|
393
|
+
if ($dSq < $minDistanceSq) {
|
|
394
|
+
$minDistanceSq = $dSq;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
$totalConnectionCost += sqrt($minDistanceSq);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return $totalConnectionCost + count($facilities) * $fixedCostPerFacility;
|
|
401
|
+
}
|
|
305
402
|
}
|