@anonympins/fingerprint 0.5.2 → 0.5.4

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 (35) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/package.json +1 -1
  3. package/src/js/fingerprint.client.js +1271 -1191
  4. package/src/js/fingerprint.js +202 -108
  5. package/src/js/fingerprint.utils.js +213 -213
  6. package/src/js/library.js +24 -8
  7. package/src/js/mongodb-store.js +165 -79
  8. package/src/js/pow.solver.inline.js +500 -14
  9. package/src/js/pow.solver.js +323 -14
  10. package/src/js/problem-manager.js +1 -1
  11. package/src/js/redis-store.js +114 -42
  12. package/src/js/tests/dns-circuit-breaker.test.js +55 -0
  13. package/src/js/tests/fingerprint.client.init.test.js +143 -143
  14. package/src/js/tests/fingerprint.client.test.js +128 -128
  15. package/src/js/tests/fingerprint.test.js +32 -2
  16. package/src/js/tests/ip-reputation.test.js +1 -1
  17. package/src/js/tests/pow.solver.test.js +5 -2
  18. package/src/js/tests/usefulWorkSolver.test.js +162 -0
  19. package/src/php/Challenge/ChallengeUtils.php +21 -8
  20. package/src/php/FingerprintClient.php +22 -5
  21. package/src/php/FingerprintEngine.php +232 -56
  22. package/src/php/Optimization/Optimization.php +23 -16
  23. package/src/php/Optimization/OptimizationOperators.php +27 -0
  24. package/src/php/ProblemManager.php +1 -1
  25. package/src/php/Store/MongoDbStore.php +184 -104
  26. package/src/php/Store/RedisStore.php +91 -11
  27. package/src/php/Tests/ChallengeUtilsTest.php +285 -285
  28. package/src/php/Tests/DnsCircuitBreakerTest.php +106 -0
  29. package/src/php/Tests/IpReputationTest.php +1 -1
  30. package/src/php/Tests/PowTest.php +1 -1
  31. package/src/php/Tests/RequestUtilsTest.php +29 -0
  32. package/src/php/Tests/TLSClientHelloParserTest.php +177 -117
  33. package/src/php/Utils/BigInt.php +202 -202
  34. package/src/php/Utils/RequestUtils.php +74 -21
  35. package/src/php/Utils/TLSClientHelloParser.php +212 -116
@@ -0,0 +1,162 @@
1
+ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
2
+ import { solveChallenge } from '../pow.solver.js'; // Assuming pow.solver.js is the ES module version
3
+
4
+ // Mock crypto.subtle for hashing operations
5
+ const mockSubtleDigest = vi.fn(async (algorithm, data) => {
6
+ // Return a consistent mock hash for testing purposes
7
+ const hash = new Uint8Array(32); // 32 bytes for SHA-256
8
+ for (let i = 0; i < hash.length; i++) {
9
+ hash[i] = i; // Simple predictable hash
10
+ }
11
+ return hash.buffer;
12
+ });
13
+
14
+ // Mock Math.random for deterministic optimization results
15
+ const mockMathRandom = vi.fn(() => 0.5);
16
+
17
+ // Mock IndexedDB for pospace challenges, even if not directly used by this test
18
+ const mockIndexedDB = {
19
+ open: vi.fn(() => ({
20
+ onupgradeneeded: null,
21
+ onsuccess: null,
22
+ onerror: null,
23
+ result: {
24
+ objectStoreNames: { contains: vi.fn(() => true) },
25
+ createObjectStore: vi.fn(),
26
+ transaction: vi.fn(() => ({
27
+ objectStore: vi.fn(() => ({
28
+ put: vi.fn(),
29
+ get: vi.fn(() => ({ onsuccess: null, result: null }))
30
+ }))
31
+ }))
32
+ }
33
+ }))
34
+ };
35
+
36
+ describe('Useful Work Solver for Optimization Challenges', () => {
37
+ let originalMathRandom;
38
+ let originalIndexedDB;
39
+
40
+ beforeEach(() => {
41
+ // Save original implementations
42
+ originalMathRandom = global.Math.random;
43
+ originalIndexedDB = global.indexedDB;
44
+
45
+ // Mock global objects
46
+ vi.stubGlobal('crypto', {
47
+ subtle: {
48
+ digest: mockSubtleDigest,
49
+ },
50
+ randomInt: vi.fn((min, max) => Math.floor(mockMathRandom() * (max - min) + min)), // Mock randomInt for crypto
51
+ randomBytes: vi.fn((length) => new Uint8Array(length).fill(0x01)), // Mock randomBytes for crypto
52
+ });
53
+ global.Math.random = mockMathRandom;
54
+ global.indexedDB = mockIndexedDB;
55
+
56
+ // Reset mocks
57
+ mockSubtleDigest.mockClear();
58
+ mockMathRandom.mockClear();
59
+ mockIndexedDB.open.mockClear();
60
+ });
61
+
62
+ afterEach(() => {
63
+ // Restore original implementations
64
+ vi.unstubAllGlobals();
65
+ global.Math.random = originalMathRandom;
66
+ global.indexedDB = originalIndexedDB;
67
+ });
68
+
69
+ it('should solve a "useful_work_task" with fraud.solve and return a Pareto front', async () => {
70
+ const mockChallenge = {
71
+ type: 'useful_work_task',
72
+ nonce: 'test-nonce-fraud',
73
+ usefulWorkTask: {
74
+ problemId: 'fraud_detection_challenge',
75
+ task: {
76
+ type: 'multi_objective_genetic_algorithm',
77
+ solverName: 'fraud.solve',
78
+ generations: 5, // Keep generations low for quick test
79
+ populationSize: 10, // Keep population low for quick test
80
+ payload: {
81
+ legitimateClicks: [
82
+ { fingerprint: 'fp1', timeToClick: 500, clickX: 10, clickY: 10, mouseEntropy: 0.8, scrollEvents: 5 },
83
+ { fingerprint: 'fp1', timeToClick: 600, clickX: 12, clickY: 12, mouseEntropy: 0.7, scrollEvents: 6 },
84
+ { fingerprint: 'fp2', timeToClick: 700, clickX: 20, clickY: 20, mouseEntropy: 0.9, scrollEvents: 7 },
85
+ ],
86
+ fraudulentClicks: [
87
+ { fingerprint: 'fp3', timeToClick: 50, clickX: 5, clickY: 5, mouseEntropy: 0.1, scrollEvents: 0 },
88
+ { fingerprint: 'fp3', timeToClick: 60, clickX: 6, clickY: 6, mouseEntropy: 0.2, scrollEvents: 1 },
89
+ { fingerprint: 'fp4', timeToClick: 100, clickX: 10, clickY: 10, mouseEntropy: 0.3, scrollEvents: 2 },
90
+ ],
91
+ },
92
+ },
93
+ },
94
+ };
95
+
96
+ const solution = await solveChallenge(mockChallenge);
97
+
98
+ // Assert the structure of the returned solution
99
+ expect(solution.type).toBe('useful_work_task');
100
+ expect(solution.nonce).toBe('test-nonce-fraud');
101
+ expect(solution.rawSolution).toHaveProperty('problem_id', 'fraud_detection_challenge');
102
+ expect(solution.rawSolution).toHaveProperty('work_result');
103
+
104
+ const workResult = solution.rawSolution.work_result;
105
+ expect(workResult).toHaveProperty('paretoFront');
106
+ expect(workResult.paretoFront).toBeInstanceOf(Array);
107
+ expect(workResult.paretoFront.length).toBeGreaterThan(0);
108
+
109
+ // Check the structure of a single solution in the Pareto front
110
+ const firstParetoSolution = workResult.paretoFront[0];
111
+ expect(firstParetoSolution).toHaveProperty('solution');
112
+ expect(firstParetoSolution.solution).toBeInstanceOf(Array); // fraud.solve returns an array of thresholds
113
+ expect(firstParetoSolution.solution.length).toBe(4); // [minTimeToClick, maxClickVariance, minMouseEntropy, minScrollEvents]
114
+ expect(firstParetoSolution).toHaveProperty('objectives');
115
+ expect(firstParetoSolution.objectives).toBeInstanceOf(Array); // [objective1, objective2]
116
+ expect(firstParetoSolution.objectives.length).toBe(2);
117
+ }, 10000); // Increase timeout for GA, even with low generations/population
118
+
119
+ it('should solve a "useful_work_task" with cpc.solve and return a Pareto front', async () => {
120
+ const mockChallenge = {
121
+ type: 'useful_work_task',
122
+ nonce: 'test-nonce-cpc',
123
+ usefulWorkTask: {
124
+ problemId: 'cpc_optimization_challenge',
125
+ task: {
126
+ type: 'multi_objective_genetic_algorithm',
127
+ solverName: 'cpc.solve',
128
+ generations: 5, // Keep generations low for quick test
129
+ populationSize: 10, // Keep population low for quick test
130
+ payload: {
131
+ platformParams: { optimalBaseCommission: 0.3, optimalBonusFactor: 0.1 },
132
+ website: { relevanceScore: 70 },
133
+ advertiser: { credits: 1000 },
134
+ estimatedImpressions: 500,
135
+ competingAds: [{ id: 'ad1' }, { id: 'ad2' }],
136
+ },
137
+ },
138
+ },
139
+ };
140
+
141
+ const solution = await solveChallenge(mockChallenge);
142
+
143
+ // Assert the structure of the returned solution
144
+ expect(solution.type).toBe('useful_work_task');
145
+ expect(solution.nonce).toBe('test-nonce-cpc');
146
+ expect(solution.rawSolution).toHaveProperty('problem_id', 'cpc_optimization_challenge');
147
+ expect(solution.rawSolution).toHaveProperty('work_result');
148
+
149
+ const workResult = solution.rawSolution.work_result;
150
+ expect(workResult).toHaveProperty('paretoFront');
151
+ expect(workResult.paretoFront).toBeInstanceOf(Array);
152
+ expect(workResult.paretoFront.length).toBeGreaterThan(0);
153
+
154
+ // Check the structure of a single solution in the Pareto front
155
+ const firstParetoSolution = workResult.paretoFront[0];
156
+ expect(firstParetoSolution).toHaveProperty('solution');
157
+ expect(typeof firstParetoSolution.solution).toBe('number'); // cpc.solve returns a single CPC value
158
+ expect(firstParetoSolution).toHaveProperty('objectives');
159
+ expect(firstParetoSolution.objectives).toBeInstanceOf(Array); // [platformRevenue, advertiserValue, marketImbalance]
160
+ expect(firstParetoSolution.objectives.length).toBe(3);
161
+ }, 10000);
162
+ });
@@ -839,6 +839,7 @@ class ChallengeUtils
839
839
 
840
840
  $solverCode = self::getPowSolverCode();
841
841
  $queriesJson = json_encode($queries);
842
+ $coopTimeout = $securityConfig['pospace']['coopTimeout'] ?? 15;
842
843
 
843
844
  $safePath = json_encode($path, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
844
845
  $safeNonce = json_encode($nonce, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
@@ -854,6 +855,7 @@ class ChallengeUtils
854
855
  const nodeId = "{$nodeId}";
855
856
  const peerId = "{$peerId}";
856
857
  const peerBlockIdx = {$peerBlockIdx};
858
+ const coopTimeout = {$coopTimeout};
857
859
 
858
860
  document.getElementById('loader').innerText = '⚙️ Checking persistent local storage...';
859
861
  await new Promise(r => setTimeout(r, 10));
@@ -889,7 +891,7 @@ class ChallengeUtils
889
891
  await fetch(window.location.pathname + "?coop_op=request_peer_block&node_id=" + nodeId + "&peer_id=" + peerId + "&block_idx=" + peerBlockIdx + "&req_id=" + reqId);
890
892
 
891
893
  let attempts = 0;
892
- while (attempts < 15) {
894
+ while (attempts < coopTimeout) {
893
895
  const res = await fetch(window.location.pathname + "?coop_op=poll_response&node_id=" + nodeId + "&req_id=" + reqId);
894
896
  const data = await res.json();
895
897
  if (data.status === 'ready') {
@@ -944,12 +946,23 @@ JS;
944
946
  $baseBlock = self::createCpuChallengeBaseBlock($nonce, $clientSecret, $originalFingerprint);
945
947
  $baseBlockBytes = '[' . implode(',', array_values(unpack('C*', $baseBlock))) . ']';
946
948
 
947
- $trapLinksHtml = implode(' ', array_map(
948
- fn($url, $index) => "<a href=\"{$url}\" tabindex=\"-1\"><span>&gt; " . ($index + 1) . "</span></a>",
949
- $trapUrls,
950
- array_keys($trapUrls)
951
- ));
952
- $trapContainerHtml = "<div style=\"position:absolute;left:-9999px;top:-9999px;transform:scale(0);pointer-events:none;\" aria-hidden=\"true\">{$trapLinksHtml}</div>";
949
+ $trapTags = ['div', 'span', 'p', 'section'];
950
+ $selectedTrapTag = $trapTags[array_rand($trapTags)];
951
+ $layoutProps = [
952
+ 'position:absolute;left:-9999px;top:-9999px;transform:scale(0);pointer-events:none;',
953
+ 'position:fixed;left:-8888px;top:-8888px;opacity:0;pointer-events:none;width:0;height:0;overflow:hidden;',
954
+ 'display:none;visibility:hidden;pointer-events:none;'
955
+ ];
956
+ $selectedLayout = $layoutProps[array_rand($layoutProps)];
957
+
958
+ $trapLinks = [];
959
+ foreach ($trapUrls as $index => $url) {
960
+ $nestingType = rand(0, 2);
961
+ $innerHtml = ($nestingType === 1) ? "<b>&gt; " . ($index + 1) . "</b>" : (($nestingType === 2) ? "<i>&gt; " . ($index + 1) . "</i>" : "<span>&gt; " . ($index + 1) . "</span>");
962
+ $trapLinks[] = "<a href=\"{$url}\" tabindex=\"-1\">{$innerHtml}</a>";
963
+ }
964
+ $trapLinksHtml = implode(' ', $trapLinks);
965
+ $trapContainerHtml = "<{$selectedTrapTag} style=\"{$selectedLayout}\" aria-hidden=\"true\">{$trapLinksHtml}</{$selectedTrapTag}>";
953
966
 
954
967
  $safePath = json_encode($path, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
955
968
  $safeNonce = json_encode($nonce, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
@@ -980,7 +993,7 @@ JS;
980
993
  return;
981
994
  }
982
995
 
983
- const finalUrl = path + "?pow_type=cpu_mem&pow_nonce=" + nonce + "&pow_solution_cpu=" + cpuSolution + "&pow_solution_mem=" + memSolution;
996
+ const finalUrl = path + "?pow_type=cpu_mem&pow_nonce=" + nonce + "&pow_solution_cpu=" + cpuSolution + "&pow_solution_mem=" + encodeURIComponent(JSON.stringify(memSolution));
984
997
  window.location.href = finalUrl;
985
998
  }
986
999
  solve();
@@ -77,13 +77,30 @@ class FingerprintClient
77
77
  $this->clientConfig['honeypots'][] = $fieldName;
78
78
  }
79
79
 
80
- // Styles CSS pour cacher le champ de manière robuste.
81
- $styles = 'position:absolute; left:-9999px; top:-9999px; transform:scale(0); opacity:0; pointer-events:none;';
80
+ // Styles CSS polymorphiques pour cacher le champ de manière robuste.
81
+ $styleOptions = [
82
+ 'position:absolute; left:-9999px; top:-9999px; transform:scale(0); opacity:0; pointer-events:none;',
83
+ 'position:fixed; left:-8888px; top:-8888px; width:0; height:0; overflow:hidden; opacity:0; pointer-events:none;',
84
+ 'display:none; visibility:hidden; pointer-events:none;'
85
+ ];
86
+ $styles = $styleOptions[array_rand($styleOptions)];
87
+
88
+ $containerTags = ['div', 'span', 'p', 'section'];
89
+ $tag = $containerTags[array_rand($containerTags)];
90
+
91
+ $nestingType = rand(0, 1);
92
+ if ($nestingType === 1) {
93
+ return '<' . $tag . ' style="' . $styles . '" aria-hidden="true">'
94
+ . '<label for="' . htmlspecialchars($fieldName) . '">' . htmlspecialchars($fieldName)
95
+ . '<input type="text" id="' . htmlspecialchars($fieldName) . '" name="' . htmlspecialchars($fieldName) . '" tabindex="-1" autocomplete="off">'
96
+ . '</label>'
97
+ . '</' . $tag . '>';
98
+ }
82
99
 
83
- return '<div style="' . $styles . '" aria-hidden="true">'
84
- . '<label for="' . htmlspecialchars($fieldName) . '">' . $fieldName . '</label>'
100
+ return '<' . $tag . ' style="' . $styles . '" aria-hidden="true">'
101
+ . '<label for="' . htmlspecialchars($fieldName) . '">' . htmlspecialchars($fieldName) . '</label>'
85
102
  . '<input type="text" id="' . htmlspecialchars($fieldName) . '" name="' . htmlspecialchars($fieldName) . '" tabindex="-1" autocomplete="off">'
86
- . '</div>';
103
+ . '</' . $tag . '>';
87
104
  }
88
105
 
89
106
  /**