@anonympins/fingerprint 0.4.6 → 0.5.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.
- package/CHANGELOG.md +43 -0
- package/README.md +56 -11
- package/package.json +2 -1
- package/src/js/fingerprint.client.js +360 -23
- package/src/js/fingerprint.js +508 -200
- package/src/js/fingerprint.utils.js +184 -0
- package/src/js/gpu_pow.solver.js +217 -0
- package/src/js/library.js +1 -1
- package/src/js/pow.solver.inline.js +12 -4
- package/src/js/pow.solver.js +12 -4
- package/src/js/tests/fingerprint.client.init.test.js +5 -2
- package/src/js/tests/fingerprint.isMalicious.test.js +234 -116
- package/src/js/tests/fingerprint.test.js +195 -1
- package/src/js/tests/gpu_pow.test.js +118 -0
- package/src/js/tests/ja3AnomalyDetector.test.js +1 -0
- package/src/js/tests/quicFingerprint.test.js +35 -0
- package/src/php/AutoTuner.php +71 -0
- package/src/php/Challenge/ChallengeUtils.php +344 -9
- package/src/php/Config/SecurityProfiles.php +9 -0
- package/src/php/FingerprintEngine.php +61 -2
- package/src/php/RequestContext.php +2 -0
- package/src/php/Tests/ChallengeUtilsTest.php +285 -81
- package/src/php/Tests/MaliciousPatternsTest.php +104 -0
- package/src/php/Tests/QuicFingerprintTest.php +54 -0
- package/src/php/Tests/RequestUtilsTest.php +43 -0
- package/src/php/Utils/BigInt.php +202 -144
- package/src/php/Utils/MaliciousPatterns.php +74 -58
- package/src/php/Utils/RequestUtils.php +120 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import {cyrb53} from "./fingerprint.builder.js";
|
|
2
|
+
|
|
3
|
+
export function verifyZkpProof(yStr, tStr, sStr) {
|
|
4
|
+
try {
|
|
5
|
+
const y = BigInt('0x' + yStr);
|
|
6
|
+
const t = BigInt('0x' + tStr);
|
|
7
|
+
const s = BigInt('0x' + sStr);
|
|
8
|
+
|
|
9
|
+
const ZKP_P = 115792089237316195423570985008687907853269984665640564039457584007908834671663n;
|
|
10
|
+
const ZKP_G = 2n;
|
|
11
|
+
|
|
12
|
+
const cStr = ZKP_G.toString() + y.toString() + t.toString();
|
|
13
|
+
const hashHex = crypto.createHash('sha256').update(cStr).digest('hex');
|
|
14
|
+
const c = BigInt('0x' + hashHex) % ZKP_P;
|
|
15
|
+
|
|
16
|
+
return modPow(ZKP_G, s, ZKP_P) === (t * modPow(y, c, ZKP_P)) % ZKP_P;
|
|
17
|
+
} catch (e) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function decodePolymorphicFingerprint(fpString, mapping) {
|
|
23
|
+
if (!fpString || !mapping || !mapping.keys) return fpString;
|
|
24
|
+
const reverseKeys = {};
|
|
25
|
+
for (const [orig, rand] of Object.entries(mapping.keys)) {
|
|
26
|
+
reverseKeys[rand] = orig;
|
|
27
|
+
}
|
|
28
|
+
const parts = fpString.split('|');
|
|
29
|
+
const mappedParts = parts.map(part => {
|
|
30
|
+
const pair = part.split(':');
|
|
31
|
+
if (pair.length === 2) {
|
|
32
|
+
const origKey = reverseKeys[pair[0]] || pair[0];
|
|
33
|
+
return `${origKey}:${pair[1]}`;
|
|
34
|
+
}
|
|
35
|
+
return part;
|
|
36
|
+
});
|
|
37
|
+
return mappedParts.join('|');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @private
|
|
43
|
+
* Deep merges two objects. The `source` object's properties overwrite the `target`'s.
|
|
44
|
+
* @param {object} target - The target object.
|
|
45
|
+
* @param {object} source - The source object.
|
|
46
|
+
* @returns {object} The merged object.
|
|
47
|
+
*/
|
|
48
|
+
export function deepMerge(target, source) {
|
|
49
|
+
const output = { ...target };
|
|
50
|
+
if (target && typeof target === 'object' && source && typeof source === 'object') {
|
|
51
|
+
Object.keys(source).forEach(key => {
|
|
52
|
+
if (source[key] && typeof source[key] === 'object' && key in target) {
|
|
53
|
+
output[key] = deepMerge(target[key], source[key]);
|
|
54
|
+
} else {
|
|
55
|
+
output[key] = source[key];
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return output;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates a stable hash based on device characteristics, independent of the IP.
|
|
64
|
+
* This is our "level 2 fingerprint".
|
|
65
|
+
* @param {object} context - The request context.
|
|
66
|
+
* @returns {string} A hash representing the device.
|
|
67
|
+
*/
|
|
68
|
+
export function getHeaderSignature(context) {
|
|
69
|
+
if (!context.rawHeaders) return '';
|
|
70
|
+
const headerKeys = [];
|
|
71
|
+
for (let i = 0; i < context.rawHeaders.length; i += 2) {
|
|
72
|
+
headerKeys.push(context.rawHeaders[i]);
|
|
73
|
+
}
|
|
74
|
+
return cyrb53(headerKeys.sort().join(','));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Analyses a raw JA3 string.
|
|
79
|
+
* Format: "TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurveFormats"
|
|
80
|
+
* @param {string} ja3String
|
|
81
|
+
* @returns {object|null}
|
|
82
|
+
*/
|
|
83
|
+
export function parseJa3(ja3String) {
|
|
84
|
+
if (!ja3String || typeof ja3String !== 'string') {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const parts = ja3String.split(',');
|
|
88
|
+
if (parts.length !== 5) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
tlsVersion: parseInt(parts[0], 10),
|
|
93
|
+
ciphers: parts[1] !== '' ? parts[1].split('-').map(Number) : [],
|
|
94
|
+
extensions: parts[2] !== '' ? parts[2].split('-').map(Number) : [],
|
|
95
|
+
curves: parts[3] !== '' ? parts[3].split('-').map(Number) : [],
|
|
96
|
+
points: parts[4] !== '' ? parts[4].split('-').map(Number) : []
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function modPow(base, exponent, modulus) {
|
|
101
|
+
if (modulus === 1n) return 0n;
|
|
102
|
+
let result = 1n;
|
|
103
|
+
base = base % modulus;
|
|
104
|
+
while (exponent > 0n) {
|
|
105
|
+
if (exponent % 2n === 1n) {
|
|
106
|
+
result = (result * base) % modulus;
|
|
107
|
+
}
|
|
108
|
+
exponent = exponent >> 1n;
|
|
109
|
+
base = (base * base) % modulus;
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
export function hashNetwork(ip, prefix = 24) {
|
|
116
|
+
// Hash du réseau (masque /24 ou /16)
|
|
117
|
+
const parts = ip.split('.');
|
|
118
|
+
if (parts.length !== 4) return null;
|
|
119
|
+
const maskBytes = prefix / 8;
|
|
120
|
+
const network = parts.slice(0, maskBytes).join('.');
|
|
121
|
+
// Hash simple
|
|
122
|
+
let hash = 0;
|
|
123
|
+
for (let i = 0; i < network.length; i++) {
|
|
124
|
+
const char = network.charCodeAt(i);
|
|
125
|
+
hash = ((hash << 5) - hash) + char;
|
|
126
|
+
hash = hash & hash;
|
|
127
|
+
}
|
|
128
|
+
return hash.toString(16);
|
|
129
|
+
}
|
|
130
|
+
export function normalizeReferer(referer) {
|
|
131
|
+
try {
|
|
132
|
+
const url = new URL(referer);
|
|
133
|
+
return `${url.protocol}//${url.hostname}`;
|
|
134
|
+
} catch {
|
|
135
|
+
return referer;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function isPrivateIp(ip) {
|
|
140
|
+
// Vérifier si l'IP est privée
|
|
141
|
+
const parts = ip.split('.');
|
|
142
|
+
if (parts.length !== 4) return false;
|
|
143
|
+
const first = parseInt(parts[0]);
|
|
144
|
+
return (first === 10) || (first === 172 && parseInt(parts[1]) >= 16 && parseInt(parts[1]) <= 31) || (first === 192 && parseInt(parts[1]) === 168);
|
|
145
|
+
}
|
|
146
|
+
// Fonctions utilitaires
|
|
147
|
+
export function parseUserAgent(ua) {
|
|
148
|
+
// Parser basique du User-Agent
|
|
149
|
+
const result = {};
|
|
150
|
+
|
|
151
|
+
// Détection du navigateur
|
|
152
|
+
if (ua.includes('Chrome') && !ua.includes('Edg')) {
|
|
153
|
+
result.browser = 'Chrome';
|
|
154
|
+
const match = ua.match(/Chrome\/(\d+)/);
|
|
155
|
+
if (match) result.browser += `/${match[1]}`;
|
|
156
|
+
} else if (ua.includes('Firefox')) {
|
|
157
|
+
result.browser = 'Firefox';
|
|
158
|
+
const match = ua.match(/Firefox\/(\d+)/);
|
|
159
|
+
if (match) result.browser += `/${match[1]}`;
|
|
160
|
+
} else if (ua.includes('Safari') && !ua.includes('Chrome')) {
|
|
161
|
+
result.browser = 'Safari';
|
|
162
|
+
const match = ua.match(/Version\/(\d+)/);
|
|
163
|
+
if (match) result.browser += `/${match[1]}`;
|
|
164
|
+
} else if (ua.includes('Edg')) {
|
|
165
|
+
result.browser = 'Edge';
|
|
166
|
+
const match = ua.match(/Edg\/(\d+)/);
|
|
167
|
+
if (match) result.browser += `/${match[1]}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Détection de l'OS
|
|
171
|
+
if (ua.includes('Windows NT 10.0')) result.os = 'Windows 10';
|
|
172
|
+
else if (ua.includes('Windows NT 6.1')) result.os = 'Windows 7';
|
|
173
|
+
else if (ua.includes('Mac OS X')) result.os = 'macOS';
|
|
174
|
+
else if (ua.includes('Linux') && !ua.includes('Android')) result.os = 'Linux';
|
|
175
|
+
else if (ua.includes('Android')) result.os = 'Android';
|
|
176
|
+
else if (ua.includes('iPhone') || ua.includes('iPad')) result.os = 'iOS';
|
|
177
|
+
|
|
178
|
+
// Détection du type d'appareil
|
|
179
|
+
if (ua.includes('Mobile')) result.device = 'mobile';
|
|
180
|
+
else if (ua.includes('Tablet')) result.device = 'tablet';
|
|
181
|
+
else result.device = 'desktop';
|
|
182
|
+
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GPU Proof-of-Work Solver (WebGPU with WebGL2 Fallback)
|
|
3
|
+
* Forces massive parallel floats computation to exhaust CPU emulators (SwiftShader).
|
|
4
|
+
*/
|
|
5
|
+
export class GpuPowSolver {
|
|
6
|
+
/**
|
|
7
|
+
* Solve the GPU challenge.
|
|
8
|
+
* @param {string} seed - The hexadecimal/string challenge seed from the server.
|
|
9
|
+
* @param {number} iterations - Number of chaotic map iterations per thread.
|
|
10
|
+
* @returns {Promise<{solution: string, platform: string, duration: number}>}
|
|
11
|
+
*/
|
|
12
|
+
static async solve(seed, iterations = 200000) {
|
|
13
|
+
const start = performance.now();
|
|
14
|
+
const numericSeed = this._hashSeedToFloat(seed);
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
if (navigator.gpu) {
|
|
18
|
+
const result = await this._solveWebGPU(numericSeed, iterations);
|
|
19
|
+
return {
|
|
20
|
+
solution: result,
|
|
21
|
+
platform: 'webgpu',
|
|
22
|
+
duration: performance.now() - start
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.warn('[GPU-PoW] WebGPU failed or disabled, falling back to WebGL2:', e);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Fallback to WebGL2
|
|
30
|
+
try {
|
|
31
|
+
const result = await this._solveWebGL2(numericSeed, iterations);
|
|
32
|
+
return {
|
|
33
|
+
solution: result,
|
|
34
|
+
platform: 'webgl2',
|
|
35
|
+
duration: performance.now() - start
|
|
36
|
+
};
|
|
37
|
+
} catch (e) {
|
|
38
|
+
throw new Error(`[GPU-PoW] Both WebGPU and WebGL2 solvers failed: ${e.message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Verifies a GPU PoW solution.
|
|
44
|
+
* To prevent server-side DoS, it verifies a sample of the 64 channels.
|
|
45
|
+
* @param {string} seed - The challenge seed.
|
|
46
|
+
* @param {number} iterations - Number of iterations.
|
|
47
|
+
* @param {string} solution - The comma-separated solution string.
|
|
48
|
+
* @param {Array<number>} [sampleIndices=[0, 12, 35, 57]] - Indices to verify.
|
|
49
|
+
* @returns {boolean} True if the solution is valid.
|
|
50
|
+
*/
|
|
51
|
+
static verify(seed, iterations, solution, sampleIndices = [0, 12, 35, 57]) {
|
|
52
|
+
if (!solution || typeof solution !== 'string') return false;
|
|
53
|
+
const values = solution.split(',');
|
|
54
|
+
if (values.length !== 64) return false;
|
|
55
|
+
|
|
56
|
+
const numericSeed = this._hashSeedToFloat(seed);
|
|
57
|
+
const r = 3.9999;
|
|
58
|
+
|
|
59
|
+
for (const idx of sampleIndices) {
|
|
60
|
+
if (idx < 0 || idx >= 64) return false;
|
|
61
|
+
let x = Math.fround(numericSeed + idx * 0.015);
|
|
62
|
+
const rFloat = Math.fround(r);
|
|
63
|
+
for (let i = 0; i < iterations; i++) {
|
|
64
|
+
x = Math.fround(rFloat * x * Math.fround(1.0 - x));
|
|
65
|
+
}
|
|
66
|
+
const clientVal = parseFloat(values[idx]);
|
|
67
|
+
if (isNaN(clientVal) || Math.abs(clientVal - x) > 1e-4) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static _hashSeedToFloat(seed) {
|
|
75
|
+
let hash = 0;
|
|
76
|
+
for (let i = 0; i < seed.length; i++) {
|
|
77
|
+
hash = (hash << 5) - hash + seed.charCodeAt(i);
|
|
78
|
+
hash |= 0;
|
|
79
|
+
}
|
|
80
|
+
return Math.abs(hash % 1000000) / 1000000;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static async _solveWebGPU(seed, iterations) {
|
|
84
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
85
|
+
if (!adapter) throw new Error('No compatible GPU adapter found.');
|
|
86
|
+
const device = await adapter.requestDevice();
|
|
87
|
+
|
|
88
|
+
// Compute shader performing a chaotic logistic map iteration
|
|
89
|
+
const shaderCode = `
|
|
90
|
+
@group(0) @binding(0) var<storage, read_write> data: array<f32>;
|
|
91
|
+
@compute @workgroup_size(64)
|
|
92
|
+
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
|
93
|
+
let index = global_id.x;
|
|
94
|
+
if (index >= 64) { return; }
|
|
95
|
+
|
|
96
|
+
var x: f32 = data[index];
|
|
97
|
+
let r: f32 = 3.9999; // Chaotic regime
|
|
98
|
+
|
|
99
|
+
for (var i: u32 = 0u; i < ${iterations}u; i = i + 1u) {
|
|
100
|
+
x = r * x * (1.0 - x);
|
|
101
|
+
}
|
|
102
|
+
data[index] = x;
|
|
103
|
+
}
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
const shaderModule = device.createShaderModule({ code: shaderCode });
|
|
107
|
+
const pipeline = device.createComputePipeline({
|
|
108
|
+
layout: 'auto',
|
|
109
|
+
compute: { module: shaderModule, entryPoint: 'main' }
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const inputData = new Float32Array(64);
|
|
113
|
+
for (let i = 0; i < 64; i++) {
|
|
114
|
+
inputData[i] = seed + (i * 0.015);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const gpuBuffer = device.createBuffer({
|
|
118
|
+
size: inputData.byteLength,
|
|
119
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
|
|
120
|
+
mappedAtCreation: true
|
|
121
|
+
});
|
|
122
|
+
new Float32Array(gpuBuffer.getMappedRange()).set(inputData);
|
|
123
|
+
gpuBuffer.unmap();
|
|
124
|
+
|
|
125
|
+
const readBuffer = device.createBuffer({
|
|
126
|
+
size: inputData.byteLength,
|
|
127
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const bindGroup = device.createBindGroup({
|
|
131
|
+
layout: pipeline.getBindGroupLayout(0),
|
|
132
|
+
entries: [{ binding: 0, resource: { buffer: gpuBuffer } }]
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const commandEncoder = device.createCommandEncoder();
|
|
136
|
+
const passEncoder = commandEncoder.beginComputePass();
|
|
137
|
+
passEncoder.setPipeline(pipeline);
|
|
138
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
139
|
+
passEncoder.dispatchWorkgroups(1);
|
|
140
|
+
passEncoder.end();
|
|
141
|
+
|
|
142
|
+
commandEncoder.copyBufferToBuffer(gpuBuffer, 0, readBuffer, 0, inputData.byteLength);
|
|
143
|
+
device.queue.submit([commandEncoder.finish()]);
|
|
144
|
+
|
|
145
|
+
await readBuffer.mapAsync(GPUMapMode.READ);
|
|
146
|
+
const result = new Float32Array(readBuffer.getMappedRange());
|
|
147
|
+
const solutionHash = Array.from(result).map(v => v.toFixed(6)).join(',');
|
|
148
|
+
readBuffer.unmap();
|
|
149
|
+
|
|
150
|
+
return solutionHash;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static async _solveWebGL2(seed, iterations) {
|
|
154
|
+
const canvas = document.createElement('canvas');
|
|
155
|
+
canvas.width = 8;
|
|
156
|
+
canvas.height = 8; // 64 pixels total matching WebGPU size
|
|
157
|
+
const gl = canvas.getContext('webgl2');
|
|
158
|
+
if (!gl) throw new Error('WebGL2 context not supported.');
|
|
159
|
+
|
|
160
|
+
const vs = `#version 300 es\nin vec4 pos; void main() { gl_Position = pos; }`;
|
|
161
|
+
const fs = `#version 300 es
|
|
162
|
+
precision highp float;
|
|
163
|
+
out vec4 outColor;
|
|
164
|
+
uniform float uSeed;
|
|
165
|
+
uniform int uIterations;
|
|
166
|
+
void main() {
|
|
167
|
+
float index = gl_FragCoord.x + (gl_FragCoord.y * 8.0);
|
|
168
|
+
float x = uSeed + (index * 0.015);
|
|
169
|
+
float r = 3.9999;
|
|
170
|
+
for(int i = 0; i < uIterations; i++) {
|
|
171
|
+
x = r * x * (1.0 - x);
|
|
172
|
+
}
|
|
173
|
+
outColor = vec4(x, 0.0, 0.0, 1.0);
|
|
174
|
+
}`;
|
|
175
|
+
|
|
176
|
+
// Setup programs, draw fullscreen quad, etc.
|
|
177
|
+
const program = this._createProgram(gl, vs, fs);
|
|
178
|
+
gl.useProgram(program);
|
|
179
|
+
|
|
180
|
+
const posAttr = gl.getAttribLocation(program, 'pos');
|
|
181
|
+
const buffer = gl.createBuffer();
|
|
182
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
183
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, -1,1, 1,-1, 1,1]), gl.STATIC_DRAW);
|
|
184
|
+
gl.enableVertexAttribArray(posAttr);
|
|
185
|
+
gl.vertexAttribPointer(posAttr, 2, gl.FLOAT, false, 0, 0);
|
|
186
|
+
|
|
187
|
+
gl.uniform1f(gl.getUniformLocation(program, 'uSeed'), seed);
|
|
188
|
+
gl.uniform1i(gl.getUniformLocation(program, 'uIterations'), iterations);
|
|
189
|
+
|
|
190
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
191
|
+
|
|
192
|
+
const pixels = new Float32Array(8 * 8 * 4);
|
|
193
|
+
gl.readPixels(0, 0, 8, 8, gl.RGBA, gl.FLOAT, pixels);
|
|
194
|
+
|
|
195
|
+
const result = [];
|
|
196
|
+
for (let i = 0; i < 64; i++) {
|
|
197
|
+
result.push(pixels[i * 4]);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return result.map(v => v.toFixed(6)).join(',');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
static _createProgram(gl, vsSource, fsSource) {
|
|
204
|
+
const vs = gl.createShader(gl.VERTEX_SHADER);
|
|
205
|
+
gl.shaderSource(vs, vsSource);
|
|
206
|
+
gl.compileShader(vs);
|
|
207
|
+
const fs = gl.createShader(gl.FRAGMENT_SHADER);
|
|
208
|
+
gl.shaderSource(fs, fsSource);
|
|
209
|
+
gl.compileShader(fs);
|
|
210
|
+
|
|
211
|
+
const program = gl.createProgram();
|
|
212
|
+
gl.attachShader(program, vs);
|
|
213
|
+
gl.attachShader(program, fs);
|
|
214
|
+
gl.linkProgram(program);
|
|
215
|
+
return program;
|
|
216
|
+
}
|
|
217
|
+
}
|
package/src/js/library.js
CHANGED
|
@@ -1551,7 +1551,7 @@ Optimization.Operators.createFullSecurityConfigEvaluator = ({ trafficData }) =>
|
|
|
1551
1551
|
// Pour cet exemple, nous utilisons une version simplifiée.
|
|
1552
1552
|
let score = 0;
|
|
1553
1553
|
for (const key in config.weights) {
|
|
1554
|
-
score += (log.vector[key] || 0) * config.weights[key];
|
|
1554
|
+
score += (log.vector?.[key] || 0) * config.weights[key];
|
|
1555
1555
|
}
|
|
1556
1556
|
return score;
|
|
1557
1557
|
};
|
|
@@ -70,7 +70,7 @@ async function initializeSpace(seed, sizeMb) {
|
|
|
70
70
|
store.put({ sizeMb, seed }, metadataKey);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
async function solveSpaceChallenge(seed, queries, nonce, clientSecret) {
|
|
73
|
+
async function solveSpaceChallenge(seed, queries, nonce, clientSecret, peerBlock = '') {
|
|
74
74
|
const db = await openDb();
|
|
75
75
|
const transaction = db.transaction("blocks", "readonly");
|
|
76
76
|
const store = transaction.objectStore("blocks");
|
|
@@ -88,11 +88,19 @@ async function solveSpaceChallenge(seed, queries, nonce, clientSecret) {
|
|
|
88
88
|
combined.set(block, i * 1024);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
let finalCombined = combined;
|
|
92
|
+
if (peerBlock) {
|
|
93
|
+
const peerBytes = new Uint8Array(peerBlock.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
|
|
94
|
+
finalCombined = new Uint8Array(combined.length + peerBytes.length);
|
|
95
|
+
finalCombined.set(combined);
|
|
96
|
+
finalCombined.set(peerBytes, combined.length);
|
|
97
|
+
}
|
|
98
|
+
|
|
91
99
|
const encoder = new TextEncoder();
|
|
92
100
|
const nonceBytes = encoder.encode(nonce + ":" + clientSecret);
|
|
93
|
-
const finalBlock = new Uint8Array(
|
|
94
|
-
finalBlock.set(
|
|
95
|
-
finalBlock.set(nonceBytes,
|
|
101
|
+
const finalBlock = new Uint8Array(finalCombined.length + nonceBytes.length);
|
|
102
|
+
finalBlock.set(finalCombined);
|
|
103
|
+
finalBlock.set(nonceBytes, finalCombined.length);
|
|
96
104
|
|
|
97
105
|
const buf = await crypto.subtle.digest("SHA-256", finalBlock);
|
|
98
106
|
return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
package/src/js/pow.solver.js
CHANGED
|
@@ -73,7 +73,7 @@ export async function initializeSpace(seed, sizeMb) {
|
|
|
73
73
|
store.put({ sizeMb, seed }, metadataKey);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
export async function solveSpaceChallenge(seed, queries, nonce, clientSecret) {
|
|
76
|
+
export async function solveSpaceChallenge(seed, queries, nonce, clientSecret, peerBlock = '') {
|
|
77
77
|
const db = await openDb();
|
|
78
78
|
const transaction = db.transaction("blocks", "readonly");
|
|
79
79
|
const store = transaction.objectStore("blocks");
|
|
@@ -91,11 +91,19 @@ export async function solveSpaceChallenge(seed, queries, nonce, clientSecret) {
|
|
|
91
91
|
combined.set(block, i * 1024);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
let finalCombined = combined;
|
|
95
|
+
if (peerBlock) {
|
|
96
|
+
const peerBytes = new Uint8Array(peerBlock.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
|
|
97
|
+
finalCombined = new Uint8Array(combined.length + peerBytes.length);
|
|
98
|
+
finalCombined.set(combined);
|
|
99
|
+
finalCombined.set(peerBytes, combined.length);
|
|
100
|
+
}
|
|
101
|
+
|
|
94
102
|
const encoder = new TextEncoder();
|
|
95
103
|
const nonceBytes = encoder.encode(nonce + ":" + clientSecret);
|
|
96
|
-
const finalBlock = new Uint8Array(
|
|
97
|
-
finalBlock.set(
|
|
98
|
-
finalBlock.set(nonceBytes,
|
|
104
|
+
const finalBlock = new Uint8Array(finalCombined.length + nonceBytes.length);
|
|
105
|
+
finalBlock.set(finalCombined);
|
|
106
|
+
finalBlock.set(nonceBytes, finalCombined.length);
|
|
99
107
|
|
|
100
108
|
const buf = await crypto.subtle.digest("SHA-256", finalBlock);
|
|
101
109
|
return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
@@ -28,7 +28,7 @@ global.TextEncoder = dom.window.TextEncoder;
|
|
|
28
28
|
describe('ClientLibrary.initializeClient', () => {
|
|
29
29
|
|
|
30
30
|
// On utilise des espions (spies) pour vérifier si les méthodes internes sont appelées.
|
|
31
|
-
let startMouseSpy, startKeystrokeSpy, startClickSpy, startTouchSpy, initWasmSpy, initHoneypotsSpy, injectTrapsSpy, initFetchSpy, injectPhantomTrapsSpy;
|
|
31
|
+
let startMouseSpy, startKeystrokeSpy, startClickSpy, startTouchSpy, startRenderingSpy, initWasmSpy, initHoneypotsSpy, injectTrapsSpy, initFetchSpy, injectPhantomTrapsSpy;
|
|
32
32
|
|
|
33
33
|
beforeEach(() => {
|
|
34
34
|
// Réinitialiser l'état du client avant chaque test
|
|
@@ -39,6 +39,7 @@ describe('ClientLibrary.initializeClient', () => {
|
|
|
39
39
|
startKeystrokeSpy = vi.spyOn(ClientLibrary, 'startKeystrokeDynamicsTracker');
|
|
40
40
|
startClickSpy = vi.spyOn(ClientLibrary, 'startClickTracker');
|
|
41
41
|
startTouchSpy = vi.spyOn(ClientLibrary, 'startTouchEventTracker');
|
|
42
|
+
startRenderingSpy = vi.spyOn(ClientLibrary, 'startRenderingTracker');
|
|
42
43
|
initWasmSpy = vi.spyOn(ClientLibrary, 'initializeWasm').mockResolvedValue(undefined);
|
|
43
44
|
initHoneypotsSpy = vi.spyOn(ClientLibrary, 'initializeHoneypots');
|
|
44
45
|
injectTrapsSpy = vi.spyOn(ClientLibrary, 'injectTrapLinks');
|
|
@@ -58,16 +59,18 @@ describe('ClientLibrary.initializeClient', () => {
|
|
|
58
59
|
expect(startKeystrokeSpy).toHaveBeenCalled();
|
|
59
60
|
expect(startClickSpy).toHaveBeenCalled();
|
|
60
61
|
expect(startTouchSpy).toHaveBeenCalled();
|
|
62
|
+
expect(startRenderingSpy).toHaveBeenCalled();
|
|
61
63
|
expect(injectPhantomTrapsSpy).toHaveBeenCalled();
|
|
62
64
|
});
|
|
63
65
|
|
|
64
66
|
it('should disable trackers when configured', () => {
|
|
65
|
-
ClientLibrary.initializeClient({ mouse: false, keystrokes: false, clicks: false, touches: false });
|
|
67
|
+
ClientLibrary.initializeClient({ mouse: false, keystrokes: false, clicks: false, touches: false, rendering: false });
|
|
66
68
|
|
|
67
69
|
expect(startMouseSpy).not.toHaveBeenCalled();
|
|
68
70
|
expect(startKeystrokeSpy).not.toHaveBeenCalled();
|
|
69
71
|
expect(startClickSpy).not.toHaveBeenCalled();
|
|
70
72
|
expect(startTouchSpy).not.toHaveBeenCalled();
|
|
73
|
+
expect(startRenderingSpy).not.toHaveBeenCalled();
|
|
71
74
|
});
|
|
72
75
|
|
|
73
76
|
it('should initialize WASM if wasmPath is configured', () => {
|