@anonympins/fingerprint 0.7.5 → 0.8.1

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 (68) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +1 -1
  3. package/package.json +3 -2
  4. package/public/anonympins-bot-mitigation-pow.zip +0 -0
  5. package/src/js/asn-lookup.js +223 -0
  6. package/src/js/fingerprint.client.js +1784 -1772
  7. package/src/js/fingerprint.client.obfuscated.js +1 -1
  8. package/src/js/fingerprint.js +236 -40
  9. package/src/js/fingerprint.utils.js +252 -2
  10. package/src/js/pow.worker.js +2 -2
  11. package/src/js/tests/asn-lookup.test.js +57 -0
  12. package/src/js/tests/cross_parity.test.js +3 -3
  13. package/src/js/tests/dns-circuit-breaker.test.js +6 -6
  14. package/src/js/tests/fingerprint.builder.test.js +76 -78
  15. package/src/js/tests/fingerprint.client.test.js +16 -17
  16. package/src/js/tests/fingerprint.test.js +19 -12
  17. package/src/js/tests/ja3AnomalyDetector.test.js +135 -136
  18. package/src/js/tests/library.test.js +94 -95
  19. package/src/js/tests/obfuscation.test.js +8 -8
  20. package/src/js/tests/quicFingerprint.test.js +47 -0
  21. package/src/js/tests/tcpFingerprint.test.js +4 -4
  22. package/src/js/upow-model-task.js +155 -112
  23. package/src/php/AsnLookupEngine.php +173 -0
  24. package/src/php/Challenge/ChallengeUtils.php +430 -117
  25. package/src/php/Config/SecurityProfiles.php +75 -62
  26. package/src/php/DirectFingerprint.php +80 -13
  27. package/src/php/FingerprintBuilder.php +13 -13
  28. package/src/php/FingerprintClient.php +6 -6
  29. package/src/php/FingerprintEngine.php +2141 -1837
  30. package/src/php/Ja3AnomalyDetector.php +22 -22
  31. package/src/php/NetworkProfile.php +68 -0
  32. package/src/php/Optimization/FunctionRegistry.php +4 -4
  33. package/src/php/Optimization/Optimization.php +6 -6
  34. package/src/php/ProblemManager.php +12 -2
  35. package/src/php/RequestContext.php +11 -11
  36. package/src/php/Store/InMemoryStore.php +96 -66
  37. package/src/php/Store/MongoDbStore.php +2 -2
  38. package/src/php/Store/RedisStore.php +1 -1
  39. package/src/php/Store/StoreManager.php +48 -35
  40. package/src/php/Tests/AsnLookupTest.php +61 -0
  41. package/src/php/Tests/ChallengeUtilsTest.php +31 -0
  42. package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
  43. package/src/php/Tests/FingerprintClientTest.php +70 -70
  44. package/src/php/Tests/FingerprintEngineTest.php +4 -2
  45. package/src/php/Tests/PowTest.php +37 -39
  46. package/src/php/Tests/QuicFingerprintTest.php +115 -53
  47. package/src/php/Tests/ThreatIntelTest.php +2 -2
  48. package/src/php/Utils/BigInt.php +202 -202
  49. package/src/php/Utils/BlockList.php +13 -13
  50. package/src/php/Utils/Env.php +7 -2
  51. package/src/php/Utils/Logger.php +19 -1
  52. package/src/php/Utils/MaliciousPatterns.php +6 -6
  53. package/src/php/Utils/MetricsManager.php +209 -209
  54. package/src/php/Utils/RequestUtils.php +348 -18
  55. package/src/php/Utils/TLSClientHelloParser.php +385 -369
  56. package/src/php/WordPress/WpDbStore.php +34 -7
  57. package/src/php/WordPress/anonympins-bot-mitigation-pow.php +1222 -0
  58. package/src/php/WordPress/fingerprint-anti-bot.php +0 -559
  59. package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.mo +0 -0
  60. package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.po +232 -0
  61. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
  62. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +297 -261
  63. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
  64. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +348 -267
  65. package/src/php/WordPress/package.php +88 -99
  66. package/src/php/WordPress/readme.txt +28 -6
  67. package/src/php/bin/auto-tune.php +1 -1
  68. package/public/fingerprint-anti-bot.zip +0 -0
@@ -1,54 +1,116 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint\Tests;
6
-
7
- use Anonympins\Fingerprint\RequestContext;
8
- use Anonympins\Fingerprint\Utils\RequestUtils;
9
- use PHPUnit\Framework\TestCase;
10
-
11
- class QuicFingerprintTest extends TestCase
12
- {
13
- public function testGetProtocolAnomalyScoreReturnsZeroIfNoFp(): void
14
- {
15
- $context = $this->createMock(RequestContext::class);
16
- $context->method('getHeader')->willReturn(null);
17
- $res = RequestUtils::getProtocolAnomalyScore($context);
18
- $this->assertEquals(0.0, $res['protocolAnomalyScore']);
19
- }
20
-
21
- public function testGetProtocolAnomalyScoreDetectsSpoofedChrome(): void
22
- {
23
- $context = $this->createMock(RequestContext::class);
24
- $context->method('getHeader')->willReturnCallback(function($name) {
25
- if ($name === 'user-agent') {
26
- return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
27
- }
28
- if ($name === 'x-quic-fp') {
29
- return '1;1=65536,4=50;i=0';
30
- }
31
- return null;
32
- });
33
-
34
- $res = RequestUtils::getProtocolAnomalyScore($context);
35
- $this->assertEquals(100.0, $res['protocolAnomalyScore']);
36
- }
37
-
38
- public function testGetProtocolAnomalyScoreAllowsLegitChrome(): void
39
- {
40
- $context = $this->createMock(RequestContext::class);
41
- $context->method('getHeader')->willReturnCallback(function($name) {
42
- if ($name === 'user-agent') {
43
- return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
44
- }
45
- if ($name === 'x-quic-fp') {
46
- return '1;1=1572864,4=100;u=2,i';
47
- }
48
- return null;
49
- });
50
-
51
- $res = RequestUtils::getProtocolAnomalyScore($context);
52
- $this->assertEquals(0.0, $res['protocolAnomalyScore']);
53
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Tests;
6
+
7
+ use Anonympins\Fingerprint\RequestContext;
8
+ use Anonympins\Fingerprint\Utils\RequestUtils;
9
+ use PHPUnit\Framework\TestCase;
10
+
11
+ class QuicFingerprintTest extends TestCase
12
+ {
13
+ public function testGetProtocolAnomalyScoreReturnsZeroIfNoFp(): void
14
+ {
15
+ $context = $this->createMock(RequestContext::class);
16
+ $context->method('getHeader')->willReturn(null);
17
+ $res = RequestUtils::getProtocolAnomalyScore($context);
18
+ $this->assertEquals(0.0, $res['protocolAnomalyScore']);
19
+ }
20
+
21
+ public function testGetProtocolAnomalyScoreDetectsSpoofedChrome(): void
22
+ {
23
+ $context = $this->createMock(RequestContext::class);
24
+ $context->method('getHeader')->willReturnCallback(function($name) {
25
+ if ($name === 'user-agent') {
26
+ return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
27
+ }
28
+ if ($name === 'x-quic-fp') {
29
+ return '1;1=65536,4=50;i=0';
30
+ }
31
+ return null;
32
+ });
33
+
34
+ $res = RequestUtils::getProtocolAnomalyScore($context);
35
+ $this->assertEquals(100.0, $res['protocolAnomalyScore']);
36
+ }
37
+
38
+ public function testGetProtocolAnomalyScoreAllowsLegitChrome(): void
39
+ {
40
+ $context = $this->createMock(RequestContext::class);
41
+ $context->method('getHeader')->willReturnCallback(function($name) {
42
+ if ($name === 'user-agent') {
43
+ return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
44
+ }
45
+ if ($name === 'x-quic-fp') {
46
+ return '1;1=1572864,4=100;u=2,i';
47
+ }
48
+ return null;
49
+ });
50
+
51
+ $res = RequestUtils::getProtocolAnomalyScore($context);
52
+ $this->assertEquals(0.0, $res['protocolAnomalyScore']);
53
+ }
54
+
55
+ public function testRfc9218PriorityWithoutIncrementalFlag(): void
56
+ {
57
+ $context = $this->createMock(RequestContext::class);
58
+ $context->method('getHeader')->willReturnCallback(function($name) {
59
+ if ($name === 'user-agent') return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
60
+ if ($name === 'x-quic-fp') return '1;1=1572864,4=100;u=2'; // Manque le drapeau 'i'
61
+ return null;
62
+ });
63
+ $res = RequestUtils::getProtocolAnomalyScore($context);
64
+ $this->assertEquals(40.0, $res['quicAnomalyScore']);
65
+ }
66
+
67
+ public function testCompressionRatioPenalty(): void
68
+ {
69
+ $context = $this->createMock(RequestContext::class);
70
+ $context->method('getHeader')->willReturnCallback(function($name) {
71
+ if ($name === 'user-agent') return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
72
+ if ($name === 'x-compression-info') return 'req_count:5,hpack_ratio:0.2';
73
+ return null;
74
+ });
75
+ $res = RequestUtils::getProtocolAnomalyScore($context);
76
+ $this->assertEquals(40.0, $res['http2AnomalyScore']);
77
+ }
78
+
79
+ public function testRfc9218UrgencyOutOfRange(): void
80
+ {
81
+ $context = $this->createMock(RequestContext::class);
82
+ $context->method('getHeader')->willReturnCallback(function($name) {
83
+ if ($name === 'user-agent') return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
84
+ if ($name === 'x-quic-fp') return '1;1=1572864,4=100;u=9,i';
85
+ return null;
86
+ });
87
+ $res = RequestUtils::getProtocolAnomalyScore($context);
88
+ $this->assertGreaterThanOrEqual(50.0, $res['quicAnomalyScore']);
89
+ }
90
+
91
+ public function testHttp2DefaultGoScraperDependencyTree(): void
92
+ {
93
+ $context = $this->createMock(RequestContext::class);
94
+ $context->method('getHeader')->willReturnCallback(function($name) {
95
+ if ($name === 'user-agent') return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
96
+ if ($name === 'x-http2-fingerprint') return 's:1:65536,2:0,3:1000,4:6291456,6:262144|15663105|1:0:0:16|m,a,s,p|p:3,w:4,c:1';
97
+ return null;
98
+ });
99
+ $context->httpVersion = '2.0';
100
+ $res = RequestUtils::getProtocolAnomalyScore($context);
101
+ $this->assertGreaterThanOrEqual(55.0, $res['http2AnomalyScore']);
102
+ }
103
+
104
+ public function testScraperWithZeroDynamicTableEntries(): void
105
+ {
106
+ $context = $this->createMock(RequestContext::class);
107
+ $context->method('getHeader')->willReturnCallback(function($name) {
108
+ if ($name === 'user-agent') return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
109
+ if ($name === 'x-compression-info') return 'req_count:5,dynamic_table_entries:0';
110
+ return null;
111
+ });
112
+ $context->httpVersion = '2.0';
113
+ $res = RequestUtils::getProtocolAnomalyScore($context);
114
+ $this->assertGreaterThanOrEqual(45.0, $res['http2AnomalyScore']);
115
+ }
54
116
  }
@@ -70,7 +70,7 @@ class ThreatIntelTest extends TestCase
70
70
  'differentialPrivacy' => [
71
71
  'enabled' => true,
72
72
  'epsilon' => 1.0,
73
- 'dummyRate' => 1.0 // Forcer l'injection d'un leurre pour le test
73
+ 'dummyRate' => 1.0 // Force decoy injection for test
74
74
  ]
75
75
  ];
76
76
 
@@ -93,6 +93,6 @@ class ThreatIntelTest extends TestCase
93
93
  $realZkpY = 'realzkpykey12345';
94
94
  $broadcastMethod->invoke($engineSub, $realZkpY);
95
95
 
96
- $this->assertCount(2, $postedCalls, 'Doit diffuser à la fois la clé réelle et la clé leurre');
96
+ $this->assertCount(2, $postedCalls, 'Should broadcast both real and decoy keys');
97
97
  }
98
98
  }
@@ -1,203 +1,203 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint\Utils;
6
-
7
- class BigInt
8
- {
9
- private static ?bool $useGmp = null;
10
- /** @var \GMP|string */
11
- private $value;
12
-
13
- private static function useGmp(): bool
14
- {
15
- if (self::$useGmp === null) {
16
- self::$useGmp = extension_loaded('gmp');
17
- }
18
- return self::$useGmp;
19
- }
20
-
21
- /**
22
- * @param \GMP|string|int $number Le nombre initial.
23
- */
24
- public function __construct($number)
25
- {
26
- if (self::useGmp()) {
27
- if ($number instanceof \GMP) {
28
- $this->value = $number;
29
- } else {
30
- $str = (string)$number;
31
- if (($dot = strpos($str, '.')) !== false) {
32
- $str = substr($str, 0, $dot);
33
- }
34
- $this->value = gmp_init($str);
35
- }
36
- } else {
37
- if ($number instanceof BigInt) {
38
- $this->value = $number->value;
39
- } else {
40
- $str = (string)$number;
41
- if (($dot = strpos($str, '.')) !== false) {
42
- $str = substr($str, 0, $dot);
43
- }
44
- $this->value = $str;
45
- }
46
- }
47
- }
48
-
49
- /**
50
- * Crée une instance à partir d'une chaîne hexadécimale.
51
- */
52
- public static function fromHex(string $hex): BigInt
53
- {
54
- $hex = trim(strtolower($hex));
55
- if (substr($hex, 0, 2) === '0x') {
56
- $hex = substr($hex, 2);
57
- }
58
- if (self::useGmp()) {
59
- return new self(gmp_init($hex, 16));
60
- } else {
61
- $dec = '0';
62
- $len = strlen($hex);
63
- for ($i = 0; $i < $len; $i++) {
64
- $dec = bcadd(bcmul($dec, '16', 0), (string)hexdec($hex[$i]), 0);
65
- }
66
- return new self($dec);
67
- }
68
- }
69
-
70
- /**
71
- * Compare cette instance avec une autre.
72
- * @return int < 0 si this < other, 0 si this == other, > 0 si this > other.
73
- */
74
- public function compareTo(BigInt $other): int
75
- {
76
- if (self::useGmp()) {
77
- return gmp_cmp($this->value, $other->value);
78
- } else {
79
- return bccomp($this->value, $other->value, 0);
80
- }
81
- }
82
-
83
- /**
84
- * Effectue un décalage de bits vers la gauche (<<).
85
- */
86
- public function shiftLeft(int $bits): BigInt
87
- {
88
- if (self::useGmp()) {
89
- return new self(gmp_mul($this->value, gmp_pow("2", $bits)));
90
- } else {
91
- return new self(bcmul($this->value, bcpow("2", (string)$bits, 0), 0));
92
- }
93
- }
94
-
95
- /**
96
- * Effectue un décalage de bits vers la droite (>>).
97
- */
98
- public function shiftRight(int $bits): BigInt
99
- {
100
- if (self::useGmp()) {
101
- return new self(gmp_div_q($this->value, gmp_pow("2", $bits)));
102
- } else {
103
- return new self(bcdiv($this->value, bcpow("2", (string)$bits, 0), 0));
104
- }
105
- }
106
-
107
- /**
108
- * Retourne la représentation en chaîne de caractères.
109
- */
110
- public function __toString(): string
111
- {
112
- if (self::useGmp()) {
113
- return gmp_strval($this->value);
114
- } else {
115
- return $this->value;
116
- }
117
- }
118
-
119
- /**
120
- * Retourne la représentation hexadécimale.
121
- */
122
- public function toHex(): string
123
- {
124
- if (self::useGmp()) {
125
- return gmp_strval($this->value, 16);
126
- } else {
127
- $hex = '';
128
- $dec = $this->value;
129
- while (bccomp($dec, '0', 0) > 0) {
130
- $rem = bcmod($dec, '16', 0);
131
- $hex = dechex((int)$rem) . $hex;
132
- $dec = bcdiv($dec, '16', 0);
133
- }
134
- return $hex ?: '0';
135
- }
136
- }
137
-
138
- /**
139
- * Crée une instance à partir d'une puissance de 2.
140
- */
141
- public static function pow(int $base, int $exp): BigInt
142
- {
143
- if (self::useGmp()) {
144
- return new self(gmp_pow((string)$base, $exp));
145
- } else {
146
- return new self(bcpow((string)$base, (string)$exp, 0));
147
- }
148
- }
149
-
150
- /**
151
- * Soustrait un autre BigInt.
152
- */
153
- public function sub(BigInt $other): BigInt
154
- {
155
- if (self::useGmp()) {
156
- return new self(gmp_sub($this->value, $other->value));
157
- } else {
158
- return new self(bcsub($this->value, $other->value, 0));
159
- }
160
- }
161
-
162
- /**
163
- * Computes ($this ^ $exponent) % $modulus
164
- */
165
- public function modPow(BigInt $exponent, BigInt $modulus): BigInt
166
- {
167
- if (self::useGmp()) {
168
- return new self(gmp_powm($this->value, $exponent->value, $modulus->value));
169
- } else {
170
- return new self(bcpowmod((string)$this->value, (string)$exponent->value, (string)$modulus->value, 0));
171
- }
172
- }
173
-
174
- /**
175
- * Computes $this % $modulus
176
- */
177
- public function mod(BigInt $modulus): BigInt
178
- {
179
- if (self::useGmp()) {
180
- return new self(gmp_mod($this->value, $modulus->value));
181
- } else {
182
- return new self(bcmod((string)$this->value, (string)$modulus->value, 0));
183
- }
184
- }
185
-
186
- public function add(BigInt $other): BigInt
187
- {
188
- if (self::useGmp()) {
189
- return new self(gmp_add($this->value, $other->value));
190
- } else {
191
- return new self(bcadd((string)$this->value, (string)$other->value, 0));
192
- }
193
- }
194
-
195
- public function mul(BigInt $other): BigInt
196
- {
197
- if (self::useGmp()) {
198
- return new self(gmp_mul($this->value, $other->value));
199
- } else {
200
- return new self(bcmul((string)$this->value, (string)$other->value, 0));
201
- }
202
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Utils;
6
+
7
+ class BigInt
8
+ {
9
+ private static ?bool $useGmp = null;
10
+ /** @var \GMP|string */
11
+ private $value;
12
+
13
+ private static function useGmp(): bool
14
+ {
15
+ if (self::$useGmp === null) {
16
+ self::$useGmp = extension_loaded('gmp');
17
+ }
18
+ return self::$useGmp;
19
+ }
20
+
21
+ /**
22
+ * @param \GMP|string|int $number Initial number.
23
+ */
24
+ public function __construct($number)
25
+ {
26
+ if (self::useGmp()) {
27
+ if ($number instanceof \GMP) {
28
+ $this->value = $number;
29
+ } else {
30
+ $str = (string)$number;
31
+ if (($dot = strpos($str, '.')) !== false) {
32
+ $str = substr($str, 0, $dot);
33
+ }
34
+ $this->value = gmp_init($str);
35
+ }
36
+ } else {
37
+ if ($number instanceof BigInt) {
38
+ $this->value = $number->value;
39
+ } else {
40
+ $str = (string)$number;
41
+ if (($dot = strpos($str, '.')) !== false) {
42
+ $str = substr($str, 0, $dot);
43
+ }
44
+ $this->value = $str;
45
+ }
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Creates an instance from a hexadecimal string.
51
+ */
52
+ public static function fromHex(string $hex): BigInt
53
+ {
54
+ $hex = trim(strtolower($hex));
55
+ if (substr($hex, 0, 2) === '0x') {
56
+ $hex = substr($hex, 2);
57
+ }
58
+ if (self::useGmp()) {
59
+ return new self(gmp_init($hex, 16));
60
+ } else {
61
+ $dec = '0';
62
+ $len = strlen($hex);
63
+ for ($i = 0; $i < $len; $i++) {
64
+ $dec = bcadd(bcmul($dec, '16', 0), (string)hexdec($hex[$i]), 0);
65
+ }
66
+ return new self($dec);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Compares this instance with another.
72
+ * @return int < 0 if this < other, 0 if this == other, > 0 if this > other.
73
+ */
74
+ public function compareTo(BigInt $other): int
75
+ {
76
+ if (self::useGmp()) {
77
+ return gmp_cmp($this->value, $other->value);
78
+ } else {
79
+ return bccomp($this->value, $other->value, 0);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Performs bitwise left shift (<<).
85
+ */
86
+ public function shiftLeft(int $bits): BigInt
87
+ {
88
+ if (self::useGmp()) {
89
+ return new self(gmp_mul($this->value, gmp_pow("2", $bits)));
90
+ } else {
91
+ return new self(bcmul($this->value, bcpow("2", (string)$bits, 0), 0));
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Performs bitwise right shift (>>).
97
+ */
98
+ public function shiftRight(int $bits): BigInt
99
+ {
100
+ if (self::useGmp()) {
101
+ return new self(gmp_div_q($this->value, gmp_pow("2", $bits)));
102
+ } else {
103
+ return new self(bcdiv($this->value, bcpow("2", (string)$bits, 0), 0));
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Returns the string representation.
109
+ */
110
+ public function __toString(): string
111
+ {
112
+ if (self::useGmp()) {
113
+ return gmp_strval($this->value);
114
+ } else {
115
+ return $this->value;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Returns the hexadecimal representation.
121
+ */
122
+ public function toHex(): string
123
+ {
124
+ if (self::useGmp()) {
125
+ return gmp_strval($this->value, 16);
126
+ } else {
127
+ $hex = '';
128
+ $dec = $this->value;
129
+ while (bccomp($dec, '0', 0) > 0) {
130
+ $rem = bcmod($dec, '16', 0);
131
+ $hex = dechex((int)$rem) . $hex;
132
+ $dec = bcdiv($dec, '16', 0);
133
+ }
134
+ return $hex ?: '0';
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Creates an instance from a power of two.
140
+ */
141
+ public static function pow(int $base, int $exp): BigInt
142
+ {
143
+ if (self::useGmp()) {
144
+ return new self(gmp_pow((string)$base, $exp));
145
+ } else {
146
+ return new self(bcpow((string)$base, (string)$exp, 0));
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Subtracts another BigInt.
152
+ */
153
+ public function sub(BigInt $other): BigInt
154
+ {
155
+ if (self::useGmp()) {
156
+ return new self(gmp_sub($this->value, $other->value));
157
+ } else {
158
+ return new self(bcsub($this->value, $other->value, 0));
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Computes ($this ^ $exponent) % $modulus
164
+ */
165
+ public function modPow(BigInt $exponent, BigInt $modulus): BigInt
166
+ {
167
+ if (self::useGmp()) {
168
+ return new self(gmp_powm($this->value, $exponent->value, $modulus->value));
169
+ } else {
170
+ return new self(bcpowmod((string)$this->value, (string)$exponent->value, (string)$modulus->value, 0));
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Computes $this % $modulus
176
+ */
177
+ public function mod(BigInt $modulus): BigInt
178
+ {
179
+ if (self::useGmp()) {
180
+ return new self(gmp_mod($this->value, $modulus->value));
181
+ } else {
182
+ return new self(bcmod((string)$this->value, (string)$modulus->value, 0));
183
+ }
184
+ }
185
+
186
+ public function add(BigInt $other): BigInt
187
+ {
188
+ if (self::useGmp()) {
189
+ return new self(gmp_add($this->value, $other->value));
190
+ } else {
191
+ return new self(bcadd((string)$this->value, (string)$other->value, 0));
192
+ }
193
+ }
194
+
195
+ public function mul(BigInt $other): BigInt
196
+ {
197
+ if (self::useGmp()) {
198
+ return new self(gmp_mul($this->value, $other->value));
199
+ } else {
200
+ return new self(bcmul((string)$this->value, (string)$other->value, 0));
201
+ }
202
+ }
203
203
  }
@@ -5,18 +5,18 @@ declare(strict_types=1);
5
5
  namespace Anonympins\Fingerprint\Utils;
6
6
 
7
7
  /**
8
- * Implémentation PHP d'une liste de blocage IP/CIDR, similaire à Node.js `net.BlockList`.
8
+ * PHP implementation of an IP/CIDR blocklist, similar to Node.js `net.BlockList`.
9
9
  */
10
10
  class BlockList
11
11
  {
12
12
  /**
13
- * @var array<string> Liste des IPs ou CIDR à bloquer.
13
+ * @var array<string> List of IPs or CIDR blocks to filter.
14
14
  */
15
15
  private array $entries = [];
16
16
 
17
17
  /**
18
- * Ajoute une adresse IP ou une plage CIDR à la liste.
19
- * @param string $entry Une adresse IP (ex: '192.168.1.1') ou une plage CIDR (ex: '192.168.1.0/24').
18
+ * Adds an IP address or CIDR range to the list.
19
+ * @param string $entry An IP address (e.g. '192.168.1.1') or CIDR range (e.g. '192.168.1.0/24').
20
20
  * @return void
21
21
  */
22
22
  public function add(string $entry): void
@@ -25,20 +25,20 @@ class BlockList
25
25
  }
26
26
 
27
27
  /**
28
- * Vérifie si une adresse IP est présente dans la liste de blocage.
29
- * @param string $ip L'adresse IP à vérifier.
30
- * @return bool True si l'IP est bloquée, false sinon.
28
+ * Checks if an IP address exists in the blocklist.
29
+ * @param string $ip The IP address to verify.
30
+ * @return bool True if the IP matches, false otherwise.
31
31
  */
32
32
  public function check(string $ip): bool
33
33
  {
34
34
  foreach ($this->entries as $entry) {
35
35
  if (str_contains($entry, '/')) {
36
- // C'est une plage CIDR
36
+ // CIDR range
37
37
  if ($this->ipInCidr($ip, $entry)) {
38
38
  return true;
39
39
  }
40
40
  } else {
41
- // C'est une IP directe
41
+ // Direct IP match
42
42
  if ($ip === $entry) {
43
43
  return true;
44
44
  }
@@ -48,10 +48,10 @@ class BlockList
48
48
  }
49
49
 
50
50
  /**
51
- * Vérifie si une adresse IP se trouve dans une plage CIDR donnée.
52
- * @param string $ip L'adresse IP à vérifier.
53
- * @param string $cidr La plage CIDR (ex: '192.168.1.0/24').
54
- * @return bool True si l'IP est dans la plage, false sinon.
51
+ * Checks if an IP address belongs to a given CIDR range.
52
+ * @param string $ip The IP address to verify.
53
+ * @param string $cidr The CIDR range (e.g. '192.168.1.0/24').
54
+ * @return bool True if the IP is within the range, false otherwise.
55
55
  */
56
56
  private function ipInCidr(string $ip, string $cidr): bool
57
57
  {