@anonympins/fingerprint 0.5.1 → 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.
- package/CHANGELOG.md +27 -0
- package/README.md +118 -115
- package/package.json +1 -1
- package/src/js/dynamic-wasm.js +52 -13
- package/src/js/fingerprint.builder.js +169 -168
- package/src/js/fingerprint.client.js +1271 -1189
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +436 -162
- package/src/js/fingerprint.utils.js +30 -0
- package/src/js/gpu_pow.solver.js +96 -1
- package/src/js/library.js +40 -21
- package/src/js/mongodb-store.js +165 -79
- package/src/js/pow.solver.inline.js +627 -30
- package/src/js/pow.solver.js +453 -31
- package/src/js/problem-manager.js +1 -1
- package/src/js/redis-store.js +114 -42
- package/src/js/tests/dns-circuit-breaker.test.js +55 -0
- package/src/js/tests/fingerprint.client.init.test.js +143 -143
- package/src/js/tests/fingerprint.client.test.js +128 -128
- package/src/js/tests/fingerprint.engine.test.js +370 -370
- package/src/js/tests/fingerprint.test.js +119 -5
- package/src/js/tests/gpu_pow.test.js +81 -0
- package/src/js/tests/ip-reputation.test.js +1 -1
- package/src/js/tests/pow.solver.test.js +9 -6
- package/src/js/tests/usefulWorkSolver.test.js +162 -0
- package/src/php/Challenge/ChallengeUtils.php +137 -35
- package/src/php/FingerprintClient.php +22 -5
- package/src/php/FingerprintEngine.php +335 -58
- package/src/php/Optimization/Optimization.php +23 -16
- package/src/php/Optimization/OptimizationOperators.php +27 -0
- package/src/php/ProblemManager.php +1 -1
- package/src/php/Store/MongoDbStore.php +184 -104
- package/src/php/Store/RedisStore.php +91 -11
- package/src/php/Tests/ChallengeUtilsTest.php +285 -285
- package/src/php/Tests/DnsCircuitBreakerTest.php +106 -0
- package/src/php/Tests/IpReputationTest.php +1 -1
- package/src/php/Tests/PowTest.php +1 -1
- package/src/php/Tests/RequestUtilsTest.php +29 -0
- package/src/php/Tests/TLSClientHelloParserTest.php +177 -117
- package/src/php/Utils/BigInt.php +202 -202
- package/src/php/Utils/RequestUtils.php +74 -21
- package/src/php/Utils/TLSClientHelloParser.php +212 -116
|
@@ -638,6 +638,55 @@ class ChallengeUtils
|
|
|
638
638
|
/**
|
|
639
639
|
* Vérifie une solution de PoW mémoire.
|
|
640
640
|
*/
|
|
641
|
+
private static function getChallengedIndices(string $seed, int $solution, int $numBlocks, int $k = 4): array
|
|
642
|
+
{
|
|
643
|
+
$indices = [];
|
|
644
|
+
$h = (int)bcmod(\Anonympins\Fingerprint\FingerprintBuilder::cyrb53($seed . ":" . $solution), '4294967296');
|
|
645
|
+
for ($i = 0; $i < $k; $i++) {
|
|
646
|
+
$h = self::gmp_imul($h ^ $i, 1597334677);
|
|
647
|
+
$indices[] = abs($h) % $numBlocks;
|
|
648
|
+
}
|
|
649
|
+
return $indices;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
private static function verifyMerkleProof(string $leafHash, int $index, array $proof, string $root): bool
|
|
653
|
+
{
|
|
654
|
+
$currentHash = $leafHash;
|
|
655
|
+
$idx = $index;
|
|
656
|
+
foreach ($proof as $sibling) {
|
|
657
|
+
$combined = ($idx % 2 === 0) ? $currentHash . $sibling : $sibling . $currentHash;
|
|
658
|
+
$currentHash = hash('sha256', hex2bin($combined));
|
|
659
|
+
$idx = (int)floor($idx / 2);
|
|
660
|
+
}
|
|
661
|
+
return $currentHash === $root;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
private static function verifyMemoryPoWLegacy(string $nonce, int $solution, int $difficulty, string $clientSecret): bool
|
|
665
|
+
{
|
|
666
|
+
$size = $difficulty * 1024 * 1024;
|
|
667
|
+
$iterations = (int)floor($size / 16);
|
|
668
|
+
$buffer = new \SplFixedArray((int)floor($size / 4));
|
|
669
|
+
|
|
670
|
+
$seed = ":{$nonce}:{$clientSecret}";
|
|
671
|
+
$h = 0;
|
|
672
|
+
foreach (unpack('C*', $seed) as $byte) {
|
|
673
|
+
$h += $byte;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
for ($i = 0; $i < count($buffer); $i++) {
|
|
677
|
+
$buffer[$i] = $h = self::gmp_imul($h ^ $i, 1597334677);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
$finalHash = 0;
|
|
681
|
+
$addr = count($buffer) > 0 ? $buffer[0] % count($buffer) : 0;
|
|
682
|
+
for ($i = 0; $i < $iterations; $i++) {
|
|
683
|
+
$addr = $buffer[$addr] % count($buffer);
|
|
684
|
+
$finalHash ^= $addr;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return $finalHash === $solution;
|
|
688
|
+
}
|
|
689
|
+
|
|
641
690
|
public static function verifyMemoryPoW(
|
|
642
691
|
string $nonce,
|
|
643
692
|
string $solution,
|
|
@@ -654,37 +703,69 @@ class ChallengeUtils
|
|
|
654
703
|
return false;
|
|
655
704
|
}
|
|
656
705
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
706
|
+
$data = json_decode($solution, true);
|
|
707
|
+
if (json_last_error() !== JSON_ERROR_NONE || !isset($data['solution']) || !isset($data['merkleRoot']) || !isset($data['proofs'])) {
|
|
708
|
+
if ($difficulty <= 4 && preg_match('/^\d+$/', $solution)) {
|
|
709
|
+
return self::verifyMemoryPoWLegacy($nonce, (int)$solution, $difficulty, $clientSecret);
|
|
710
|
+
}
|
|
711
|
+
return false;
|
|
661
712
|
}
|
|
662
713
|
|
|
663
|
-
$
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
667
|
-
$iterations = (int)floor($size / 16);
|
|
668
|
-
$buffer = new \SplFixedArray((int)floor($size / 4));
|
|
714
|
+
$sol = $data['solution'];
|
|
715
|
+
$merkleRoot = $data['merkleRoot'];
|
|
716
|
+
$proofs = $data['proofs'];
|
|
669
717
|
|
|
718
|
+
$numBlocks = $difficulty * 256;
|
|
670
719
|
$seed = ":{$nonce}:{$clientSecret}";
|
|
671
|
-
$h = 0;
|
|
672
|
-
foreach (unpack('C*', $seed) as $byte) {
|
|
673
|
-
$h += $byte;
|
|
674
|
-
}
|
|
675
720
|
|
|
676
|
-
|
|
677
|
-
|
|
721
|
+
$challengedIndices = self::getChallengedIndices($seed, (int)$sol, $numBlocks, 4);
|
|
722
|
+
|
|
723
|
+
foreach ($challengedIndices as $b) {
|
|
724
|
+
$proof = $proofs[$b] ?? $proofs[(string)$b] ?? null;
|
|
725
|
+
if ($proof === null) return false;
|
|
726
|
+
|
|
727
|
+
$blockBytes = '';
|
|
728
|
+
$h = (int)bcmod(\Anonympins\Fingerprint\FingerprintBuilder::cyrb53($seed . ":" . $b), '4294967296');
|
|
729
|
+
for ($i = 0; $i < 1024; $i++) {
|
|
730
|
+
$h = self::gmp_imul($h ^ $i, 1597334677);
|
|
731
|
+
$blockBytes .= pack('V', $h);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
$expectedLeaf = hash('sha256', $blockBytes);
|
|
735
|
+
|
|
736
|
+
if (!self::verifyMerkleProof($expectedLeaf, $b, $proof, $merkleRoot)) {
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
678
739
|
}
|
|
679
740
|
|
|
680
|
-
$
|
|
681
|
-
$
|
|
741
|
+
$blockCache = [];
|
|
742
|
+
$getBlockElement = function (int $blockIdx, int $elementIdx) use (&$blockCache, $seed): int {
|
|
743
|
+
if (!isset($blockCache[$blockIdx])) {
|
|
744
|
+
$block = [];
|
|
745
|
+
$h = (int)bcmod(\Anonympins\Fingerprint\FingerprintBuilder::cyrb53($seed . ":" . $blockIdx), '4294967296');
|
|
746
|
+
for ($i = 0; $i < 1024; $i++) {
|
|
747
|
+
$h = self::gmp_imul($h ^ $i, 1597334677);
|
|
748
|
+
$block[$i] = $h;
|
|
749
|
+
}
|
|
750
|
+
$blockCache[$blockIdx] = $block;
|
|
751
|
+
}
|
|
752
|
+
return $blockCache[$blockIdx][$elementIdx];
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
$totalElements = $numBlocks * 1024;
|
|
756
|
+
$addr = $totalElements > 0 ? $getBlockElement(0, 0) % $totalElements : 0;
|
|
757
|
+
$addr = $addr & 0xffffffff;
|
|
758
|
+
$expectedSolution = 0;
|
|
759
|
+
$iterations = 1024;
|
|
682
760
|
for ($i = 0; $i < $iterations; $i++) {
|
|
683
|
-
$
|
|
684
|
-
$
|
|
761
|
+
$blockIdx = (int)floor($addr / 1024);
|
|
762
|
+
$elementIdx = $addr % 1024;
|
|
763
|
+
$addr = $getBlockElement($blockIdx, $elementIdx) % $totalElements;
|
|
764
|
+
$addr = $addr & 0xffffffff;
|
|
765
|
+
$expectedSolution ^= $addr;
|
|
685
766
|
}
|
|
686
767
|
|
|
687
|
-
return $
|
|
768
|
+
return $expectedSolution === (int)$sol;
|
|
688
769
|
}
|
|
689
770
|
|
|
690
771
|
/**
|
|
@@ -758,17 +839,23 @@ class ChallengeUtils
|
|
|
758
839
|
|
|
759
840
|
$solverCode = self::getPowSolverCode();
|
|
760
841
|
$queriesJson = json_encode($queries);
|
|
842
|
+
$coopTimeout = $securityConfig['pospace']['coopTimeout'] ?? 15;
|
|
843
|
+
|
|
844
|
+
$safePath = json_encode($path, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
|
|
845
|
+
$safeNonce = json_encode($nonce, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
|
|
846
|
+
$safeClientSecret = json_encode($clientSecret, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
|
|
761
847
|
|
|
762
848
|
$challengeScript = <<<JS
|
|
763
849
|
async function solve() {
|
|
764
|
-
const nonce =
|
|
765
|
-
const path =
|
|
766
|
-
const clientSecret =
|
|
850
|
+
const nonce = {$safeNonce};
|
|
851
|
+
const path = {$safePath};
|
|
852
|
+
const clientSecret = {$safeClientSecret};
|
|
767
853
|
const queries = {$queriesJson};
|
|
768
854
|
const sizeMb = {$sizeMb};
|
|
769
855
|
const nodeId = "{$nodeId}";
|
|
770
856
|
const peerId = "{$peerId}";
|
|
771
857
|
const peerBlockIdx = {$peerBlockIdx};
|
|
858
|
+
const coopTimeout = {$coopTimeout};
|
|
772
859
|
|
|
773
860
|
document.getElementById('loader').innerText = '⚙️ Checking persistent local storage...';
|
|
774
861
|
await new Promise(r => setTimeout(r, 10));
|
|
@@ -804,7 +891,7 @@ class ChallengeUtils
|
|
|
804
891
|
await fetch(window.location.pathname + "?coop_op=request_peer_block&node_id=" + nodeId + "&peer_id=" + peerId + "&block_idx=" + peerBlockIdx + "&req_id=" + reqId);
|
|
805
892
|
|
|
806
893
|
let attempts = 0;
|
|
807
|
-
while (attempts <
|
|
894
|
+
while (attempts < coopTimeout) {
|
|
808
895
|
const res = await fetch(window.location.pathname + "?coop_op=poll_response&node_id=" + nodeId + "&req_id=" + reqId);
|
|
809
896
|
const data = await res.json();
|
|
810
897
|
if (data.status === 'ready') {
|
|
@@ -859,18 +946,33 @@ JS;
|
|
|
859
946
|
$baseBlock = self::createCpuChallengeBaseBlock($nonce, $clientSecret, $originalFingerprint);
|
|
860
947
|
$baseBlockBytes = '[' . implode(',', array_values(unpack('C*', $baseBlock))) . ']';
|
|
861
948
|
|
|
862
|
-
$
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
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>> " . ($index + 1) . "</b>" : (($nestingType === 2) ? "<i>> " . ($index + 1) . "</i>" : "<span>> " . ($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}>";
|
|
966
|
+
|
|
967
|
+
$safePath = json_encode($path, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
|
|
968
|
+
$safeNonce = json_encode($nonce, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
|
|
969
|
+
$safeClientSecret = json_encode($clientSecret, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
|
|
868
970
|
|
|
869
971
|
$challengeScript = <<<JS
|
|
870
972
|
async function solve() {
|
|
871
|
-
const nonce =
|
|
872
|
-
const path =
|
|
873
|
-
const clientSecret =
|
|
973
|
+
const nonce = {$safeNonce};
|
|
974
|
+
const path = {$safePath};
|
|
975
|
+
const clientSecret = {$safeClientSecret};
|
|
874
976
|
const cpuTarget = BigInt("0x" + "{$target}");
|
|
875
977
|
const memDifficulty = {$memoryDifficulty};
|
|
876
978
|
const baseBlock = new Uint8Array({$baseBlockBytes});
|
|
@@ -891,7 +993,7 @@ JS;
|
|
|
891
993
|
return;
|
|
892
994
|
}
|
|
893
995
|
|
|
894
|
-
|
|
996
|
+
const finalUrl = path + "?pow_type=cpu_mem&pow_nonce=" + nonce + "&pow_solution_cpu=" + cpuSolution + "&pow_solution_mem=" + encodeURIComponent(JSON.stringify(memSolution));
|
|
895
997
|
window.location.href = finalUrl;
|
|
896
998
|
}
|
|
897
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
|
-
$
|
|
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 '<
|
|
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
|
-
. '</
|
|
103
|
+
. '</' . $tag . '>';
|
|
87
104
|
}
|
|
88
105
|
|
|
89
106
|
/**
|