@anonympins/fingerprint 0.5.0 → 0.5.2
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 +31 -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 +224 -24
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +382 -63
- package/src/js/fingerprint.utils.js +213 -183
- package/src/js/gpu_pow.solver.js +312 -0
- package/src/js/library.js +16 -13
- package/src/js/pow.solver.inline.js +138 -27
- package/src/js/pow.solver.js +141 -28
- package/src/js/tests/fingerprint.client.init.test.js +5 -2
- package/src/js/tests/fingerprint.engine.test.js +370 -370
- package/src/js/tests/fingerprint.test.js +150 -3
- package/src/js/tests/gpu_pow.test.js +199 -0
- package/src/js/tests/pow.solver.test.js +4 -4
- package/src/js/tests/quicFingerprint.test.js +35 -0
- package/src/php/Challenge/ChallengeUtils.php +158 -27
- package/src/php/Config/SecurityProfiles.php +9 -0
- package/src/php/FingerprintEngine.php +124 -2
- package/src/php/RequestContext.php +2 -0
- package/src/php/Tests/ChallengeUtilsTest.php +77 -0
- package/src/php/Tests/QuicFingerprintTest.php +54 -0
- package/src/php/Tests/RequestUtilsTest.php +43 -0
- package/src/php/Utils/RequestUtils.php +120 -0
|
@@ -0,0 +1,312 @@
|
|
|
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
|
+
// Fallthrough to WebGL2
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Fallback to WebGL2
|
|
31
|
+
try {
|
|
32
|
+
const result = await this._solveWebGL2(numericSeed, iterations);
|
|
33
|
+
return {
|
|
34
|
+
solution: result,
|
|
35
|
+
platform: 'webgl2',
|
|
36
|
+
duration: performance.now() - start
|
|
37
|
+
};
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.warn('[GPU-PoW] WebGL2 failed or disabled, falling back to WebGL1:', e);
|
|
40
|
+
// Fallthrough to WebGL1
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Fallback to WebGL1
|
|
44
|
+
try {
|
|
45
|
+
const result = await this._solveWebGL1(numericSeed, iterations);
|
|
46
|
+
return {
|
|
47
|
+
solution: result,
|
|
48
|
+
platform: 'webgl1',
|
|
49
|
+
duration: performance.now() - start
|
|
50
|
+
};
|
|
51
|
+
} catch (e) {
|
|
52
|
+
throw new Error(`[GPU-PoW] All GPU solvers (WebGPU, WebGL2, WebGL1) failed: ${e.message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Verifies a GPU PoW solution.
|
|
58
|
+
* To prevent server-side DoS, it verifies a sample of the 64 channels.
|
|
59
|
+
* @param {string} seed - The challenge seed.
|
|
60
|
+
* @param {number} iterations - Number of iterations.
|
|
61
|
+
* @param {string} solution - The comma-separated solution string.
|
|
62
|
+
* @param {Array<number>} [sampleIndices=[0, 12, 35, 57]] - Indices to verify.
|
|
63
|
+
* @returns {boolean} True if the solution is valid.
|
|
64
|
+
*/
|
|
65
|
+
static verify(seed, iterations, solution, sampleIndices = [0, 12, 35, 57]) {
|
|
66
|
+
if (!solution || typeof solution !== 'string') return false;
|
|
67
|
+
const values = solution.split(',');
|
|
68
|
+
if (values.length !== 64) return false;
|
|
69
|
+
|
|
70
|
+
const numericSeed = this._hashSeedToFloat(seed);
|
|
71
|
+
const r = 3.9999;
|
|
72
|
+
|
|
73
|
+
for (const idx of sampleIndices) {
|
|
74
|
+
if (idx < 0 || idx >= 64) return false;
|
|
75
|
+
let x = Math.fround(numericSeed + idx * 0.015);
|
|
76
|
+
const rFloat = Math.fround(r);
|
|
77
|
+
for (let i = 0; i < iterations; i++) {
|
|
78
|
+
x = Math.fround(rFloat * x * Math.fround(1.0 - x));
|
|
79
|
+
}
|
|
80
|
+
const clientVal = parseFloat(values[idx]);
|
|
81
|
+
if (isNaN(clientVal) || Math.abs(clientVal - x) > 1e-4) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static _hashSeedToFloat(seed) {
|
|
89
|
+
let hash = 0;
|
|
90
|
+
for (let i = 0; i < seed.length; i++) {
|
|
91
|
+
hash = (hash << 5) - hash + seed.charCodeAt(i);
|
|
92
|
+
hash |= 0;
|
|
93
|
+
}
|
|
94
|
+
return Math.abs(hash % 1000000) / 1000000;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static async _solveWebGPU(seed, iterations) {
|
|
98
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
99
|
+
if (!adapter) throw new Error('No compatible GPU adapter found.');
|
|
100
|
+
const device = await adapter.requestDevice();
|
|
101
|
+
|
|
102
|
+
// Compute shader performing a chaotic logistic map iteration
|
|
103
|
+
const shaderCode = `
|
|
104
|
+
@group(0) @binding(0) var<storage, read_write> data: array<f32>;
|
|
105
|
+
@compute @workgroup_size(64)
|
|
106
|
+
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
|
107
|
+
let index = global_id.x;
|
|
108
|
+
if (index >= 64) { return; }
|
|
109
|
+
|
|
110
|
+
var x: f32 = data[index];
|
|
111
|
+
let r: f32 = 3.9999; // Chaotic regime
|
|
112
|
+
|
|
113
|
+
for (var i: u32 = 0u; i < ${iterations}u; i = i + 1u) {
|
|
114
|
+
x = r * x * (1.0 - x);
|
|
115
|
+
}
|
|
116
|
+
data[index] = x;
|
|
117
|
+
}
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
const shaderModule = device.createShaderModule({ code: shaderCode });
|
|
121
|
+
const pipeline = device.createComputePipeline({
|
|
122
|
+
layout: 'auto',
|
|
123
|
+
compute: { module: shaderModule, entryPoint: 'main' }
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const inputData = new Float32Array(64);
|
|
127
|
+
for (let i = 0; i < 64; i++) {
|
|
128
|
+
inputData[i] = seed + (i * 0.015);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const gpuBuffer = device.createBuffer({
|
|
132
|
+
size: inputData.byteLength,
|
|
133
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
|
|
134
|
+
mappedAtCreation: true
|
|
135
|
+
});
|
|
136
|
+
new Float32Array(gpuBuffer.getMappedRange()).set(inputData);
|
|
137
|
+
gpuBuffer.unmap();
|
|
138
|
+
|
|
139
|
+
const readBuffer = device.createBuffer({
|
|
140
|
+
size: inputData.byteLength,
|
|
141
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const bindGroup = device.createBindGroup({
|
|
145
|
+
layout: pipeline.getBindGroupLayout(0),
|
|
146
|
+
entries: [{ binding: 0, resource: { buffer: gpuBuffer } }]
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const commandEncoder = device.createCommandEncoder();
|
|
150
|
+
const passEncoder = commandEncoder.beginComputePass();
|
|
151
|
+
passEncoder.setPipeline(pipeline);
|
|
152
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
153
|
+
passEncoder.dispatchWorkgroups(1);
|
|
154
|
+
passEncoder.end();
|
|
155
|
+
|
|
156
|
+
commandEncoder.copyBufferToBuffer(gpuBuffer, 0, readBuffer, 0, inputData.byteLength);
|
|
157
|
+
device.queue.submit([commandEncoder.finish()]);
|
|
158
|
+
|
|
159
|
+
await readBuffer.mapAsync(GPUMapMode.READ);
|
|
160
|
+
const result = new Float32Array(readBuffer.getMappedRange());
|
|
161
|
+
const solutionHash = Array.from(result).map(v => v.toFixed(6)).join(',');
|
|
162
|
+
readBuffer.unmap();
|
|
163
|
+
|
|
164
|
+
return solutionHash;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static async _solveWebGL2(seed, iterations) {
|
|
168
|
+
const canvas = document.createElement('canvas');
|
|
169
|
+
canvas.width = 8;
|
|
170
|
+
canvas.height = 8; // 64 pixels total matching WebGPU size
|
|
171
|
+
const gl = canvas.getContext('webgl2');
|
|
172
|
+
if (!gl) throw new Error('WebGL2 context not supported.');
|
|
173
|
+
|
|
174
|
+
const vs = `#version 300 es\nin vec4 pos; void main() { gl_Position = pos; }`;
|
|
175
|
+
const fs = `#version 300 es
|
|
176
|
+
precision highp float;
|
|
177
|
+
out vec4 outColor;
|
|
178
|
+
uniform float uSeed;
|
|
179
|
+
uniform int uIterations;
|
|
180
|
+
void main() {
|
|
181
|
+
float index = gl_FragCoord.x + (gl_FragCoord.y * 8.0);
|
|
182
|
+
float x = uSeed + (index * 0.015);
|
|
183
|
+
float r = 3.9999;
|
|
184
|
+
for(int i = 0; i < uIterations; i++) {
|
|
185
|
+
x = r * x * (1.0 - x);
|
|
186
|
+
}
|
|
187
|
+
outColor = vec4(x, 0.0, 0.0, 1.0);
|
|
188
|
+
}`;
|
|
189
|
+
|
|
190
|
+
// Setup programs, draw fullscreen quad, etc.
|
|
191
|
+
const program = this._createProgram(gl, vs, fs);
|
|
192
|
+
gl.useProgram(program);
|
|
193
|
+
|
|
194
|
+
const posAttr = gl.getAttribLocation(program, 'pos');
|
|
195
|
+
const buffer = gl.createBuffer();
|
|
196
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
197
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, -1,1, 1,-1, 1,1]), gl.STATIC_DRAW);
|
|
198
|
+
gl.enableVertexAttribArray(posAttr);
|
|
199
|
+
gl.vertexAttribPointer(posAttr, 2, gl.FLOAT, false, 0, 0);
|
|
200
|
+
|
|
201
|
+
gl.uniform1f(gl.getUniformLocation(program, 'uSeed'), seed);
|
|
202
|
+
gl.uniform1i(gl.getUniformLocation(program, 'uIterations'), iterations);
|
|
203
|
+
|
|
204
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
205
|
+
|
|
206
|
+
const pixels = new Float32Array(8 * 8 * 4);
|
|
207
|
+
gl.readPixels(0, 0, 8, 8, gl.RGBA, gl.FLOAT, pixels);
|
|
208
|
+
|
|
209
|
+
const result = [];
|
|
210
|
+
for (let i = 0; i < 64; i++) {
|
|
211
|
+
result.push(pixels[i * 4]);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return result.map(v => v.toFixed(6)).join(',');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
static async _solveWebGL1(seed, iterations) {
|
|
218
|
+
const canvas = document.createElement('canvas');
|
|
219
|
+
canvas.width = 8;
|
|
220
|
+
canvas.height = 8; // 64 pixels total matching WebGPU size
|
|
221
|
+
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); // Get WebGL1 context with legacy fallback
|
|
222
|
+
if (!gl) throw new Error('WebGL1 context not supported.');
|
|
223
|
+
|
|
224
|
+
// WebGL1 Vertex Shader
|
|
225
|
+
const vs = `
|
|
226
|
+
attribute vec4 pos;
|
|
227
|
+
void main() {
|
|
228
|
+
gl_Position = pos;
|
|
229
|
+
}`;
|
|
230
|
+
// WebGL1 Fragment Shader
|
|
231
|
+
const fs = `
|
|
232
|
+
precision highp float; // highp float is an extension in WebGL1, but generally available
|
|
233
|
+
uniform float uSeed;
|
|
234
|
+
uniform int uIterations;
|
|
235
|
+
void main() {
|
|
236
|
+
float index = gl_FragCoord.x + (gl_FragCoord.y * 8.0);
|
|
237
|
+
float x = uSeed + (index * 0.015);
|
|
238
|
+
float r = 3.9999;
|
|
239
|
+
for(int i = 0; i < uIterations; i++) {
|
|
240
|
+
x = r * x * (1.0 - x);
|
|
241
|
+
}
|
|
242
|
+
gl_FragColor = vec4(x, 0.0, 0.0, 1.0); // Use gl_FragColor for WebGL1
|
|
243
|
+
}`;
|
|
244
|
+
|
|
245
|
+
const program = this._createProgram(gl, vs, fs);
|
|
246
|
+
gl.useProgram(program);
|
|
247
|
+
|
|
248
|
+
const posAttr = gl.getAttribLocation(program, 'pos');
|
|
249
|
+
const buffer = gl.createBuffer();
|
|
250
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
251
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, -1,1, 1,-1, 1,1]), gl.STATIC_DRAW);
|
|
252
|
+
gl.enableVertexAttribArray(posAttr);
|
|
253
|
+
gl.vertexAttribPointer(posAttr, 2, gl.FLOAT, false, 0, 0);
|
|
254
|
+
|
|
255
|
+
gl.uniform1f(gl.getUniformLocation(program, 'uSeed'), seed);
|
|
256
|
+
gl.uniform1i(gl.getUniformLocation(program, 'uIterations'), iterations);
|
|
257
|
+
|
|
258
|
+
// Check for OES_texture_float and WEBGL_color_buffer_float extensions for float textures and readPixels
|
|
259
|
+
const floatTextureExt = gl.getExtension('OES_texture_float');
|
|
260
|
+
const floatColorBufferExt = gl.getExtension('WEBGL_color_buffer_float');
|
|
261
|
+
|
|
262
|
+
if (!floatTextureExt || !floatColorBufferExt) {
|
|
263
|
+
throw new Error('WebGL1 float texture/color buffer extensions not supported. Cannot read float pixels.');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Create a framebuffer to render to a float texture
|
|
267
|
+
const texture = gl.createTexture();
|
|
268
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
269
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, canvas.width, canvas.height, 0, gl.RGBA, gl.FLOAT, null);
|
|
270
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
271
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
272
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
273
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
274
|
+
|
|
275
|
+
const fb = gl.createFramebuffer();
|
|
276
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
|
|
277
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
|
|
278
|
+
|
|
279
|
+
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
|
|
280
|
+
if (status !== gl.FRAMEBUFFER_COMPLETE) {
|
|
281
|
+
throw new Error('WebGL1 framebuffer not complete: ' + status);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
285
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
286
|
+
|
|
287
|
+
const pixels = new Float32Array(canvas.width * canvas.height * 4);
|
|
288
|
+
gl.readPixels(0, 0, canvas.width, canvas.height, gl.RGBA, gl.FLOAT, pixels);
|
|
289
|
+
|
|
290
|
+
const result = [];
|
|
291
|
+
for (let i = 0; i < 64; i++) {
|
|
292
|
+
result.push(pixels[i * 4]); // Only take the R component
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return result.map(v => v.toFixed(6)).join(',');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
static _createProgram(gl, vsSource, fsSource) {
|
|
299
|
+
const vs = gl.createShader(gl.VERTEX_SHADER);
|
|
300
|
+
gl.shaderSource(vs, vsSource);
|
|
301
|
+
gl.compileShader(vs);
|
|
302
|
+
const fs = gl.createShader(gl.FRAGMENT_SHADER);
|
|
303
|
+
gl.shaderSource(fs, fsSource);
|
|
304
|
+
gl.compileShader(fs);
|
|
305
|
+
|
|
306
|
+
const program = gl.createProgram();
|
|
307
|
+
gl.attachShader(program, vs);
|
|
308
|
+
gl.attachShader(program, fs);
|
|
309
|
+
gl.linkProgram(program);
|
|
310
|
+
return program;
|
|
311
|
+
}
|
|
312
|
+
}
|
package/src/js/library.js
CHANGED
|
@@ -521,8 +521,8 @@ Optimization.geneticAlgorithmMultiObjective = function (
|
|
|
521
521
|
const offspring = [];
|
|
522
522
|
for (let i = 0; i < populationSize; i++) {
|
|
523
523
|
// Sélection simple pour l'exemple
|
|
524
|
-
|
|
525
|
-
|
|
524
|
+
const parent1 = population[crypto.randomInt(0, population.length)];
|
|
525
|
+
const parent2 = population[crypto.randomInt(0, population.length)];
|
|
526
526
|
let childIndividual = crossover(parent1.individual, parent2.individual);
|
|
527
527
|
if (secureRandom() < mutationRate) {
|
|
528
528
|
childIndividual = mutate(childIndividual, currentConfig); // mutate doit maintenant utiliser currentConfig
|
|
@@ -749,7 +749,7 @@ Optimization.Operators.createTournamentSelection = (options = {}) => {
|
|
|
749
749
|
|
|
750
750
|
for (let i = 0; i < tournamentSize; i++) {
|
|
751
751
|
const individual =
|
|
752
|
-
population[
|
|
752
|
+
population[crypto.randomInt(0, population.length)];
|
|
753
753
|
if (!best || individual.fitness < best.fitness) {
|
|
754
754
|
best = individual;
|
|
755
755
|
}
|
|
@@ -757,7 +757,7 @@ Optimization.Operators.createTournamentSelection = (options = {}) => {
|
|
|
757
757
|
// Retourne le meilleur trouvé. Dans le pire des cas (tous les scores sont Infinity),
|
|
758
758
|
// on retourne le premier candidat sélectionné au lieu de null.
|
|
759
759
|
if (!best) {
|
|
760
|
-
return population[
|
|
760
|
+
return population[crypto.randomInt(0, population.length)];
|
|
761
761
|
}
|
|
762
762
|
return best;
|
|
763
763
|
};
|
|
@@ -911,9 +911,12 @@ Optimization.Operators.solveTSP = (cities, options = {}) => {
|
|
|
911
911
|
// Voisinage : génère un chemin voisin en inversant une sous-séquence (heuristique 2-opt).
|
|
912
912
|
const pathNeighbor = (path) => {
|
|
913
913
|
const newPath = [...path];
|
|
914
|
-
|
|
915
|
-
let
|
|
916
|
-
|
|
914
|
+
if (newPath.length <= 1) return newPath;
|
|
915
|
+
let i = crypto.randomInt(0, newPath.length);
|
|
916
|
+
let j = crypto.randomInt(0, newPath.length);
|
|
917
|
+
while (i === j) {
|
|
918
|
+
j = crypto.randomInt(0, newPath.length);
|
|
919
|
+
}
|
|
917
920
|
const [start, end] = [Math.min(i, j), Math.max(i, j)];
|
|
918
921
|
|
|
919
922
|
const segment = newPath.slice(start, end + 1).reverse();
|
|
@@ -971,7 +974,7 @@ Optimization.Operators.solvePortfolio = (
|
|
|
971
974
|
|
|
972
975
|
const mutate = (p) => {
|
|
973
976
|
const newP = [...p];
|
|
974
|
-
|
|
977
|
+
const i = crypto.randomInt(0, newP.length);
|
|
975
978
|
newP[i] += (secureRandom() - 0.5) * 0.2; // Mutation douce
|
|
976
979
|
newP[i] = Math.max(0, newP[i]); // Les poids ne peuvent être négatifs
|
|
977
980
|
return newP;
|
|
@@ -1105,7 +1108,7 @@ Optimization.Operators.solveFacilityLocation = (
|
|
|
1105
1108
|
// Voisinage : déplace légèrement une infrastructure au hasard.
|
|
1106
1109
|
const facilityNeighbor = (facilities) => {
|
|
1107
1110
|
const newFacilities = facilities.map((f) => ({ ...f }));
|
|
1108
|
-
|
|
1111
|
+
const i = crypto.randomInt(0, numFacilities);
|
|
1109
1112
|
const moveX = (secureRandom() - 0.5) * (bounds.maxX - bounds.minX) * 0.1;
|
|
1110
1113
|
const moveY = (secureRandom() - 0.5) * (bounds.maxY - bounds.minY) * 0.1;
|
|
1111
1114
|
|
|
@@ -1485,7 +1488,7 @@ Optimization.Operators.solveFraudDetection = (context, options = {}) => {
|
|
|
1485
1488
|
const minTimeToClick = 100 + secureRandom() * 4900; // entre 100ms et 5s
|
|
1486
1489
|
const maxClickVariance = 1 + secureRandom() * 9999; // entre 1 et 10000
|
|
1487
1490
|
const minMouseEntropy = secureRandom() * 0.5; // entre 0 et 0.5
|
|
1488
|
-
const minScrollEvents =
|
|
1491
|
+
const minScrollEvents = crypto.randomInt(0, 10); // entre 0 et 10
|
|
1489
1492
|
return [minTimeToClick, maxClickVariance, minMouseEntropy, minScrollEvents];
|
|
1490
1493
|
};
|
|
1491
1494
|
|
|
@@ -1502,7 +1505,7 @@ Optimization.Operators.solveFraudDetection = (context, options = {}) => {
|
|
|
1502
1505
|
// Mutation : légère variation aléatoire d'un des seuils
|
|
1503
1506
|
const mutate = (solution) => {
|
|
1504
1507
|
const newSolution = [...solution];
|
|
1505
|
-
const i =
|
|
1508
|
+
const i = crypto.randomInt(0, 4);
|
|
1506
1509
|
// Amplitudes de mutation différentes pour chaque seuil
|
|
1507
1510
|
const mutationFactors = [500, 1000, 0.1, 2];
|
|
1508
1511
|
const mutationFactor = mutationFactors[i];
|
|
@@ -1646,7 +1649,7 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
|
|
|
1646
1649
|
burstWeight: 20 + secureRandom() * 40,
|
|
1647
1650
|
scrapeThreshold: 500 + secureRandom() * 1000,
|
|
1648
1651
|
scrapeWeight: 15 + secureRandom() * 35,
|
|
1649
|
-
sequenceLength: 3 +
|
|
1652
|
+
sequenceLength: 3 + crypto.randomInt(0, 3),
|
|
1650
1653
|
sequenceWeight: 20 + secureRandom() * 50,
|
|
1651
1654
|
regularityThreshold: 50 + secureRandom() * 200,
|
|
1652
1655
|
regularityWeight: 20 + secureRandom() * 40,
|
|
@@ -1696,7 +1699,7 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
|
|
|
1696
1699
|
}
|
|
1697
1700
|
|
|
1698
1701
|
const keys = Object.keys(newConfig[sectionToMutate]);
|
|
1699
|
-
const keyToMutate = keys[
|
|
1702
|
+
const keyToMutate = keys[crypto.randomInt(0, keys.length)];
|
|
1700
1703
|
|
|
1701
1704
|
if (keyToMutate === 'honeypotScore') return newConfig; // Ne pas muter le poids du honeypot
|
|
1702
1705
|
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
9
|
function cyrb53(str, seed = 0) {
|
|
10
|
+
const safeStr = (typeof str === 'string' ? str : String(str || '')).slice(0, 10000);
|
|
10
11
|
let h1 = 0xdeadbeef ^ seed,
|
|
11
12
|
h2 = 0x41c6ce57 ^ seed;
|
|
12
|
-
for (let i = 0, ch; i <
|
|
13
|
-
ch =
|
|
13
|
+
for (let i = 0, ch; i < safeStr.length; i++) {
|
|
14
|
+
ch = safeStr.charCodeAt(i);
|
|
14
15
|
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
15
16
|
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
16
17
|
}
|
|
@@ -140,6 +141,53 @@ async function solveCpuTargetInline(baseBlock, target, progressCallback) {
|
|
|
140
141
|
return solution;
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
// Try Web Worker execution first if supported and not blocked by CSP
|
|
145
|
+
if (typeof window !== 'undefined' && typeof Worker !== 'undefined') {
|
|
146
|
+
try {
|
|
147
|
+
return await new Promise((resolve, reject) => {
|
|
148
|
+
const workerCode = `
|
|
149
|
+
self.onmessage = async (e) => {
|
|
150
|
+
const { baseBlock, target } = e.data;
|
|
151
|
+
const cpuTarget = BigInt(target);
|
|
152
|
+
const encoder = new TextEncoder();
|
|
153
|
+
let cpuSolution = 0;
|
|
154
|
+
while (true) {
|
|
155
|
+
const solutionBytes = encoder.encode(String(cpuSolution));
|
|
156
|
+
const finalBlock = new Uint8Array(baseBlock.length + solutionBytes.length);
|
|
157
|
+
finalBlock.set(baseBlock);
|
|
158
|
+
finalBlock.set(solutionBytes, baseBlock.length);
|
|
159
|
+
const buf = await crypto.subtle.digest("SHA-256", finalBlock);
|
|
160
|
+
const hashHex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
161
|
+
if (BigInt('0x' + hashHex) < cpuTarget) break;
|
|
162
|
+
cpuSolution++;
|
|
163
|
+
if (cpuSolution % 25000 === 0) {
|
|
164
|
+
self.postMessage({ type: 'progress', solution: cpuSolution });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
self.postMessage({ type: 'success', solution: cpuSolution });
|
|
168
|
+
};
|
|
169
|
+
`;
|
|
170
|
+
const blob = new Blob([workerCode], { type: 'application/javascript' });
|
|
171
|
+
const worker = new Worker(URL.createObjectURL(blob));
|
|
172
|
+
worker.onmessage = (event) => {
|
|
173
|
+
if (event.data.type === 'progress') {
|
|
174
|
+
if (progressCallback) progressCallback(event.data.solution);
|
|
175
|
+
} else if (event.data.type === 'success') {
|
|
176
|
+
resolve(event.data.solution);
|
|
177
|
+
worker.terminate();
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
worker.onerror = (err) => {
|
|
181
|
+
worker.terminate();
|
|
182
|
+
reject(err);
|
|
183
|
+
};
|
|
184
|
+
worker.postMessage({ baseBlock, target: cpuTarget.toString() });
|
|
185
|
+
});
|
|
186
|
+
} catch (workerError) {
|
|
187
|
+
console.warn("Web Worker creation failed (possibly due to CSP). Falling back to main thread with scheduler/setTimeout yielding.");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
143
191
|
let cpuSolution = 0;
|
|
144
192
|
|
|
145
193
|
while (true) {
|
|
@@ -165,8 +213,12 @@ async function solveCpuTargetInline(baseBlock, target, progressCallback) {
|
|
|
165
213
|
// --- FIN DES LOGS ---
|
|
166
214
|
if (BigInt('0x' + hashHex) < cpuTarget) break;
|
|
167
215
|
cpuSolution++;
|
|
168
|
-
|
|
169
|
-
|
|
216
|
+
if (cpuSolution % 50000 === 0) {
|
|
217
|
+
if (typeof scheduler !== 'undefined' && typeof scheduler.yield === 'function') {
|
|
218
|
+
await scheduler.yield();
|
|
219
|
+
} else {
|
|
220
|
+
await new Promise(r => setTimeout(r, 0));
|
|
221
|
+
}
|
|
170
222
|
if (progressCallback) progressCallback(cpuSolution);
|
|
171
223
|
}
|
|
172
224
|
}
|
|
@@ -215,41 +267,100 @@ async function solveCpuTarget(message, target) {
|
|
|
215
267
|
* @returns {Promise<number>} La solution (nombre entier).
|
|
216
268
|
*/
|
|
217
269
|
async function solveMemory(seed, difficulty) {
|
|
218
|
-
// On définit un seuil pour savoir quand faire une pause, afin de ne pas bloquer le thread UI.
|
|
219
|
-
const YIELD_THRESHOLD = 100000;
|
|
220
|
-
const size = difficulty * 1024 * 1024;
|
|
221
|
-
const buffer = new Uint32Array(size / 4);
|
|
222
|
-
|
|
223
270
|
const wasmModule = typeof window !== 'undefined' ? (window.wasmModule || (window.ClientLibrary && window.ClientLibrary.wasmModule)) : null;
|
|
224
271
|
if (wasmModule && typeof wasmModule._solve_memory_challenge === 'function') {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
wasmModule.
|
|
230
|
-
const solution = wasmModule._solve_memory_challenge(
|
|
231
|
-
wasmModule._free(
|
|
272
|
+
const encoder = new TextEncoder();
|
|
273
|
+
const seedBytes = encoder.encode(seed);
|
|
274
|
+
const ptr = wasmModule._malloc(seedBytes.length + 1);
|
|
275
|
+
wasmModule.HEAPU8.set(seedBytes, ptr);
|
|
276
|
+
wasmModule.HEAPU8[ptr + seedBytes.length] = 0; // Null-terminator
|
|
277
|
+
const solution = wasmModule._solve_memory_challenge(ptr, difficulty);
|
|
278
|
+
wasmModule._free(ptr);
|
|
232
279
|
return solution;
|
|
233
280
|
}
|
|
234
281
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
282
|
+
const size = difficulty * 1024 * 1024;
|
|
283
|
+
const numBlocks = difficulty * 256;
|
|
284
|
+
if (numBlocks === 0) return { solution: 0, merkleRoot: '', proofs: {} };
|
|
285
|
+
|
|
286
|
+
async function hashBlock(block) {
|
|
287
|
+
const buf = await crypto.subtle.digest("SHA-256", block.buffer);
|
|
288
|
+
return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function hexToBytes(hex) {
|
|
292
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
293
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
294
|
+
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
295
|
+
}
|
|
296
|
+
return bytes;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const leaves = [];
|
|
300
|
+
const blocks = [];
|
|
301
|
+
for (let b = 0; b < numBlocks; b++) {
|
|
302
|
+
const block = new Uint32Array(1024);
|
|
303
|
+
let h = cyrb53(seed + ":" + b);
|
|
304
|
+
for (let i = 0; i < 1024; i++) {
|
|
305
|
+
block[i] = (h = Math.imul(h ^ i, 1597334677));
|
|
240
306
|
}
|
|
307
|
+
blocks.push(block);
|
|
308
|
+
leaves.push(await hashBlock(block));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const tree = [leaves];
|
|
312
|
+
while (tree[tree.length - 1].length > 1) {
|
|
313
|
+
const currentLayer = tree[tree.length - 1];
|
|
314
|
+
const nextLayer = [];
|
|
315
|
+
for (let i = 0; i < currentLayer.length; i += 2) {
|
|
316
|
+
const left = currentLayer[i];
|
|
317
|
+
const right = currentLayer[i + 1] || left;
|
|
318
|
+
const combined = hexToBytes(left + right);
|
|
319
|
+
const hashBuf = await crypto.subtle.digest("SHA-256", combined);
|
|
320
|
+
const hashHex = Array.from(new Uint8Array(hashBuf)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
321
|
+
nextLayer.push(hashHex);
|
|
322
|
+
}
|
|
323
|
+
tree.push(nextLayer);
|
|
324
|
+
}
|
|
325
|
+
const merkleRoot = tree[tree.length - 1][0];
|
|
326
|
+
|
|
327
|
+
function readBuffer(blocks, addr) {
|
|
328
|
+
const blockIdx = Math.floor(addr / 1024);
|
|
329
|
+
const elementIdx = addr % 1024;
|
|
330
|
+
return blocks[blockIdx][elementIdx];
|
|
241
331
|
}
|
|
332
|
+
|
|
333
|
+
const totalElements = numBlocks * 1024;
|
|
334
|
+
let addr = totalElements > 0 ? readBuffer(blocks, 0) % totalElements : 0;
|
|
242
335
|
let solution = 0;
|
|
243
|
-
const iterations =
|
|
244
|
-
let addr = buffer.length > 0 ? buffer[0] % buffer.length : 0;
|
|
336
|
+
const iterations = 1024;
|
|
245
337
|
for (let i = 0; i < iterations; i++) {
|
|
246
|
-
addr =
|
|
338
|
+
addr = readBuffer(blocks, addr) % totalElements;
|
|
247
339
|
solution ^= addr;
|
|
248
|
-
|
|
249
|
-
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const challengedIndices = [];
|
|
343
|
+
let h_idx = cyrb53(seed + ":" + solution);
|
|
344
|
+
for (let i = 0; i < 4; i++) {
|
|
345
|
+
h_idx = Math.imul(h_idx ^ i, 1597334677);
|
|
346
|
+
challengedIndices.push(Math.abs(h_idx) % numBlocks);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const proofs = {};
|
|
350
|
+
for (const index of challengedIndices) {
|
|
351
|
+
const proof = [];
|
|
352
|
+
let idx = index;
|
|
353
|
+
for (let layer = 0; layer < tree.length - 1; layer++) {
|
|
354
|
+
const isRight = idx % 2 === 1;
|
|
355
|
+
const siblingIdx = isRight ? idx - 1 : idx + 1;
|
|
356
|
+
const sibling = tree[layer][siblingIdx] || tree[layer][idx];
|
|
357
|
+
proof.push(sibling);
|
|
358
|
+
idx = Math.floor(idx / 2);
|
|
250
359
|
}
|
|
360
|
+
proofs[index] = proof;
|
|
251
361
|
}
|
|
252
|
-
|
|
362
|
+
|
|
363
|
+
return { solution, merkleRoot, proofs };
|
|
253
364
|
}
|
|
254
365
|
|
|
255
366
|
/**
|