@anonympins/fingerprint 0.3.2 → 0.3.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +172 -0
  2. package/README.md +276 -34
  3. package/composer.json +38 -0
  4. package/index.js +5 -0
  5. package/package.json +23 -18
  6. package/phpunit.xml +20 -0
  7. package/public/fp.js +2 -0
  8. package/src/js/build-client.js +69 -0
  9. package/{fingerprint.builder.js → src/js/fingerprint.builder.js} +168 -175
  10. package/{fingerprint.client.js → src/js/fingerprint.client.js} +634 -538
  11. package/src/js/fingerprint.client.obfuscated.js +1 -0
  12. package/{fingerprint.js → src/js/fingerprint.js} +255 -101
  13. package/{library.js → src/js/library.js} +1729 -1729
  14. package/{problem-manager.js → src/js/problem-manager.js} +539 -522
  15. package/src/php/AutoTuner.php +155 -0
  16. package/src/php/Challenge/ChallengeUtils.php +306 -0
  17. package/src/php/Config/SecurityProfiles.php +257 -0
  18. package/src/php/DirectFingerprint.php +81 -0
  19. package/src/php/FingerprintBuilder.php +185 -0
  20. package/src/php/FingerprintClient.php +118 -0
  21. package/src/php/FingerprintEngine.php +850 -0
  22. package/src/php/Optimization/FunctionRegistry.php +63 -0
  23. package/src/php/Optimization/Optimization.php +256 -0
  24. package/src/php/Optimization/OptimizationOperators.php +305 -0
  25. package/src/php/Optimization/ProblemInitializers.php +53 -0
  26. package/src/php/ProblemManager.php +255 -0
  27. package/src/php/RequestContext.php +87 -0
  28. package/src/php/Store/IStore.php +42 -0
  29. package/src/php/Store/InMemoryStore.php +67 -0
  30. package/src/php/Store/StoreManager.php +26 -0
  31. package/src/php/Tests/ChallengeUtilsTest.php +82 -0
  32. package/src/php/Tests/FingerprintBuilderTest.php +58 -0
  33. package/src/php/Tests/FingerprintEngineTest.php +219 -0
  34. package/src/php/Tests/PowTest.php +40 -0
  35. package/src/php/Tests/ProblemManagerTest.php +295 -0
  36. package/src/php/Tests/RequestUtilsTest.php +81 -0
  37. package/src/php/Tests/problems.config.json +9 -0
  38. package/src/php/Utils/BigInt.php +102 -0
  39. package/src/php/Utils/BlockList.php +100 -0
  40. package/src/php/Utils/Logger.php +30 -0
  41. package/src/php/Utils/MaliciousPatterns.php +59 -0
  42. package/src/php/Utils/RequestUtils.php +673 -0
  43. package/fingerprint.client.obfuscated.js +0 -1
  44. /package/{mongodb-store.js → src/js/mongodb-store.js} +0 -0
  45. /package/{optimization.worker.js → src/js/optimization.worker.js} +0 -0
  46. /package/{pow.solver.inline.js → src/js/pow.solver.inline.js} +0 -0
  47. /package/{pow.solver.js → src/js/pow.solver.js} +0 -0
  48. /package/{pow.worker.js → src/js/pow.worker.js} +0 -0
  49. /package/{redis-store.js → src/js/redis-store.js} +0 -0
  50. /package/{sql-store.js → src/js/sql-store.js} +0 -0
@@ -0,0 +1,63 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Optimization;
6
+
7
+ /**
8
+ * Registre pour exposer de manière contrôlée les fonctions de la bibliothèque d'optimisation.
9
+ */
10
+ class FunctionRegistry
11
+ {
12
+ /** @var array<string, callable> */
13
+ private static array $functions = [];
14
+
15
+ /**
16
+ * Initialise le registre avec les fonctions disponibles.
17
+ */
18
+ private static function initialize(): void
19
+ {
20
+ if (empty(self::$functions)) {
21
+ // Fonctions de "Scoring"
22
+ self::$functions['tsp.calculateEnergy'] = [OptimizationUtils::class, 'evaluatePathDistance'];
23
+ self::$functions['portfolio.calculateMetrics'] = [OptimizationOperators::class, 'createPortfolioAllocator'];
24
+
25
+ // Fonctions de "Résolution"
26
+ self::$functions['tsp.solve'] = [OptimizationOperators::class, 'solveTSP'];
27
+ self::$functions['portfolio.solve'] = [OptimizationOperators::class, 'solvePortfolio'];
28
+ self::$functions['fraud.solve'] = [OptimizationOperators::class, 'solveFraudDetection'];
29
+ self::$functions['facility.solve'] = [OptimizationOperators::class, 'solveFacilityLocation'];
30
+ self::$functions['security.tune'] = [OptimizationOperators::class, 'solveFullSecurityTuning'];
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Récupère une fonction depuis le registre.
36
+ */
37
+ public static function get(string $name): ?callable
38
+ {
39
+ self::initialize();
40
+ return self::$functions[$name] ?? null;
41
+ }
42
+ /**
43
+ * Enregistre une nouvelle fonction. Principalement pour les tests.
44
+ * @internal
45
+ * @param string $name
46
+ * @param callable $function
47
+ * @return void
48
+ */
49
+ public static function register(string $name, callable $function): void
50
+ {
51
+ self::initialize();
52
+ self::$functions[$name] = $function;
53
+ }
54
+
55
+ /**
56
+ * Réinitialise le registre. Uniquement pour les tests.
57
+ * @internal
58
+ */
59
+ public static function __internal_resetRegistry(): void
60
+ {
61
+ self::$functions = [];
62
+ }
63
+ }
@@ -0,0 +1,256 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Optimization;
6
+
7
+ /**
8
+ * Bibliothèque d'algorithmes d'optimisation.
9
+ */
10
+ class Optimization
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
+ * Algorithme génétique multi-objectifs (inspiré de NSGA-II).
26
+ * @param callable $createIndividual
27
+ * @param callable $fitnessFunction
28
+ * @param callable $crossover
29
+ * @param callable $mutate
30
+ * @param array $options
31
+ * @return array<int, array{solution: mixed, objectives: array<float>}>
32
+ */
33
+ public static function geneticAlgorithmMultiObjective(
34
+ callable $createIndividual,
35
+ callable $fitnessFunction,
36
+ callable $crossover,
37
+ callable $mutate,
38
+ array $options = []
39
+ ): array {
40
+ $generations = $options['generations'] ?? 150;
41
+ $populationSize = $options['populationSize'] ?? 60;
42
+ $mutationRate = $options['mutationRate'] ?? 0.1;
43
+
44
+ $population = [];
45
+ for ($i = 0; $i < $populationSize; $i++) {
46
+ $individual = $createIndividual();
47
+ $population[] = [
48
+ 'individual' => $individual,
49
+ 'objectives' => $fitnessFunction($individual)
50
+ ];
51
+ }
52
+
53
+ for ($gen = 0; $gen < $generations; $gen++) {
54
+ // 1. Créer une population d'enfants
55
+ $offspring = [];
56
+ for ($i = 0; $i < $populationSize; $i++) {
57
+ $parent1 = $population[random_int(0, count($population) - 1)];
58
+ $parent2 = $population[random_int(0, count($population) - 1)];
59
+ $childIndividual = $crossover($parent1['individual'], $parent2['individual']);
60
+ if (self::secureRandom() < $mutationRate) {
61
+ $childIndividual = $mutate($childIndividual);
62
+ }
63
+ $offspring[] = [
64
+ 'individual' => $childIndividual,
65
+ 'objectives' => $fitnessFunction($childIndividual)
66
+ ];
67
+ }
68
+
69
+ // 2. Combiner parents et enfants
70
+ $combinedPopulation = array_merge($population, $offspring);
71
+
72
+ // 3. Trier la population combinée en fronts
73
+ $fronts = self::nonDominatedSort($combinedPopulation);
74
+
75
+ // 4. Construire la nouvelle population
76
+ $newPopulation = [];
77
+ foreach ($fronts as $front) {
78
+ if (count($newPopulation) + count($front) <= $populationSize) {
79
+ $newPopulation = array_merge($newPopulation, $front);
80
+ } else {
81
+ self::calculateCrowdingDistance($front);
82
+ // Trier par distance décroissante
83
+ usort($front, fn ($a, $b) => $b['crowdingDistance'] <=> $a['crowdingDistance']);
84
+ $remaining = $populationSize - count($newPopulation);
85
+ $newPopulation = array_merge($newPopulation, array_slice($front, 0, $remaining));
86
+ break;
87
+ }
88
+ }
89
+ $population = $newPopulation;
90
+ }
91
+
92
+ // Retourner le premier front de la population finale
93
+ $finalFronts = self::nonDominatedSort($population);
94
+ $bestFront = $finalFronts[0] ?? [];
95
+
96
+ // Filtrer pour ne garder que les solutions avec des objectifs uniques
97
+ $uniqueSolutionsMap = [];
98
+ foreach ($bestFront as $p) {
99
+ $key = json_encode($p['objectives']);
100
+ if (!isset($uniqueSolutionsMap[$key])) {
101
+ $uniqueSolutionsMap[$key] = [
102
+ 'solution' => $p['individual'],
103
+ 'objectives' => $p['objectives'],
104
+ ];
105
+ }
106
+ }
107
+ return array_values($uniqueSolutionsMap);
108
+ }
109
+
110
+ /**
111
+ * Détermine si la solution A domine la solution B.
112
+ * @param array<float> $objectivesA
113
+ * @param array<float> $objectivesB
114
+ */
115
+ private static function paretoDominates(array $objectivesA, array $objectivesB): bool
116
+ {
117
+ $aIsBetterInOne = false;
118
+ for ($i = 0; $i < count($objectivesA); $i++) {
119
+ if ($objectivesA[$i] > $objectivesB[$i]) {
120
+ return false; // A est pire sur au moins un objectif
121
+ }
122
+ if ($objectivesA[$i] < $objectivesB[$i]) {
123
+ $aIsBetterInOne = true; // A est strictement meilleur sur au moins un
124
+ }
125
+ }
126
+ return $aIsBetterInOne;
127
+ }
128
+
129
+ /**
130
+ * Trie une population en fronts de Pareto non-dominés.
131
+ * @param array<int, array> &$populationWithObjectives
132
+ * @return array<int, array>
133
+ */
134
+ private static function nonDominatedSort(array &$populationWithObjectives): array
135
+ {
136
+ $fronts = [[]];
137
+ $n = count($populationWithObjectives);
138
+
139
+ for ($i = 0; $i < $n; $i++) {
140
+ $p1 = &$populationWithObjectives[$i];
141
+ $p1['dominationCount'] = 0;
142
+ $p1['dominatedSolutions'] = [];
143
+
144
+ for ($j = 0; $j < $n; $j++) {
145
+ if ($i === $j) continue;
146
+ $p2 = &$populationWithObjectives[$j];
147
+
148
+ if (self::paretoDominates($p1['objectives'], $p2['objectives'])) {
149
+ $p1['dominatedSolutions'][] = $j;
150
+ } elseif (self::paretoDominates($p2['objectives'], $p1['objectives'])) {
151
+ $p1['dominationCount']++;
152
+ }
153
+ }
154
+
155
+ if ($p1['dominationCount'] === 0) {
156
+ $p1['rank'] = 0;
157
+ $fronts[0][] = $p1;
158
+ }
159
+ }
160
+
161
+ $i = 0;
162
+ while (!empty($fronts[$i])) {
163
+ $nextFront = [];
164
+ foreach ($fronts[$i] as $p1) {
165
+ foreach ($p1['dominatedSolutions'] as $p2_idx) {
166
+ $p2 = &$populationWithObjectives[$p2_idx];
167
+ $p2['dominationCount']--;
168
+ if ($p2['dominationCount'] === 0) {
169
+ $p2['rank'] = $i + 1;
170
+ $nextFront[] = $p2;
171
+ }
172
+ }
173
+ }
174
+ $i++;
175
+ if (!empty($nextFront)) {
176
+ $fronts[$i] = $nextFront;
177
+ }
178
+ }
179
+ return $fronts;
180
+ }
181
+
182
+ /**
183
+ * Calcule la distance de promiscuité (crowding distance) pour un front.
184
+ * @param array<int, array> &$front
185
+ */
186
+ private static function calculateCrowdingDistance(array &$front): void
187
+ {
188
+ if (empty($front)) return;
189
+
190
+ $numObjectives = count($front[0]['objectives']);
191
+ $l = count($front);
192
+
193
+ foreach ($front as &$p) {
194
+ $p['crowdingDistance'] = 0;
195
+ }
196
+
197
+ for ($i = 0; $i < $numObjectives; $i++) {
198
+ // Trier le front par l'objectif courant
199
+ usort($front, fn ($a, $b) => $a['objectives'][$i] <=> $b['objectives'][$i]);
200
+
201
+ $minObj = $front[0]['objectives'][$i];
202
+ $maxObj = $front[$l - 1]['objectives'][$i];
203
+
204
+ // Les solutions aux extrémités ont une distance infinie
205
+ $front[0]['crowdingDistance'] = INF;
206
+ $front[$l - 1]['crowdingDistance'] = INF;
207
+
208
+ if ($maxObj === $minObj) continue;
209
+
210
+ for ($j = 1; $j < $l - 1; $j++) {
211
+ $front[$j]['crowdingDistance'] +=
212
+ ($front[$j + 1]['objectives'][$i] - $front[$j - 1]['objectives'][$i]) /
213
+ ($maxObj - $minObj);
214
+ }
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Calcule la déviation d'une série de chiffres par rapport à la loi de Benford.
220
+ * @public
221
+ * @param array<int|float> $numbers
222
+ */
223
+ public static function benfordTest(array $numbers): float // Rendre la méthode publique et statique
224
+ {
225
+ if (count($numbers) < 10) {
226
+ return 0.0; // Pas assez de données
227
+ }
228
+
229
+ $leadingDigits = array_map(function ($n) {
230
+ $str = ltrim((string)$n, '0.');
231
+ return $str[0] ?? '';
232
+ }, $numbers);
233
+
234
+ $leadingDigits = array_filter($leadingDigits, fn ($d) => $d >= '1' && $d <= '9');
235
+
236
+ if (count($leadingDigits) < 10) {
237
+ return 0.0;
238
+ }
239
+
240
+ $counts = array_fill(1, 9, 0);
241
+ foreach ($leadingDigits as $digit) {
242
+ $counts[(int)$digit]++;
243
+ }
244
+
245
+ $benfordDistribution = [1 => 30.1, 2 => 17.6, 3 => 12.5, 4 => 9.7, 5 => 7.9, 6 => 6.7, 7 => 5.8, 8 => 5.1, 9 => 4.6];
246
+
247
+ $totalDeviation = 0.0;
248
+ for ($i = 1; $i <= 9; $i++) {
249
+ $observedFrequency = ($counts[$i] / count($leadingDigits)) * 100;
250
+ $expectedFrequency = $benfordDistribution[$i];
251
+ $totalDeviation += pow($observedFrequency - $expectedFrequency, 2);
252
+ }
253
+
254
+ return sqrt($totalDeviation) / 50.0;
255
+ }
256
+ }
@@ -0,0 +1,305 @@
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
+ // Implémentation factice pour la complétude
303
+ return ['solution' => [], 'energy' => 0];
304
+ }
305
+ }
@@ -0,0 +1,53 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Optimization;
6
+
7
+ /**
8
+ * Fonctions pour générer dynamiquement les données d'un problème.
9
+ */
10
+ class ProblemInitializers
11
+ {
12
+ /** @var array<string, callable> */
13
+ private static array $initializers = [];
14
+
15
+ private static function initialize(): void
16
+ {
17
+ if (empty(self::$initializers)) {
18
+ self::$initializers['generate:randomPoints'] = function (array $params): array {
19
+ $count = $params['count'] ?? 0;
20
+ $bounds = $params['bounds'] ?? ['x' => 1000, 'y' => 1000];
21
+ if (!is_numeric($count)) return [];
22
+ $points = [];
23
+ for ($i = 0; $i < $count; $i++) {
24
+ $points[] = [
25
+ 'x' => mt_rand() / mt_getrandmax() * $bounds['x'],
26
+ 'y' => mt_rand() / mt_getrandmax() * $bounds['y']
27
+ ];
28
+ }
29
+ return $points;
30
+ };
31
+
32
+ self::$initializers['generate:randomAssets'] = function (array $params): array {
33
+ $count = $params['count'] ?? 0;
34
+ if (!is_numeric($count)) return [];
35
+ $assets = [];
36
+ for ($i = 0; $i < $count; $i++) {
37
+ $assets[] = [
38
+ 'name' => 'Asset ' . ($i + 1),
39
+ 'expectedReturn' => mt_rand() / mt_getrandmax() * 0.2,
40
+ 'volatility' => 0.1 + mt_rand() / mt_getrandmax() * 0.3
41
+ ];
42
+ }
43
+ return $assets;
44
+ };
45
+ }
46
+ }
47
+
48
+ public static function get(string $name): ?callable
49
+ {
50
+ self::initialize();
51
+ return self::$initializers[$name] ?? null;
52
+ }
53
+ }