@anonympins/fingerprint 0.5.1 → 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.
@@ -1,184 +1,214 @@
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;
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 safeJsonStringify(val) {
23
+ return JSON.stringify(val)
24
+ .replace(/</g, '\\u003c')
25
+ .replace(/>/g, '\\u003e')
26
+ .replace(/\u2028/g, '\\u2028')
27
+ .replace(/\u2029/g, '\\u2029');
28
+ }
29
+
30
+ export function sanitizeRedirectPath(p) {
31
+ if (typeof p !== 'string') return '/';
32
+ let sanitized = p.replace(/[^a-zA-Z0-9\/.\-_~%?&=:@+,;]/g, '');
33
+
34
+ // Prevent protocol-relative redirects (e.g. //evil.com)
35
+ if (sanitized.startsWith('//')) {
36
+ sanitized = '/' + sanitized.replace(/^\/+/g, '');
37
+ }
38
+ // Prevent absolute redirects (e.g. http://evil.com)
39
+ if (/^https?:\/\//i.test(sanitized)) {
40
+ try {
41
+ const parsed = new URL(sanitized);
42
+ sanitized = parsed.pathname + parsed.search + parsed.hash;
43
+ } catch (e) {
44
+ sanitized = '/';
45
+ }
46
+ }
47
+ if (!sanitized.startsWith('/')) {
48
+ sanitized = '/' + sanitized;
49
+ }
50
+ return sanitized.replace(/^\/+/g, '/');
51
+ }
52
+ export function decodePolymorphicFingerprint(fpString, mapping) {
53
+ if (!fpString || !mapping || !mapping.keys) return fpString;
54
+ const reverseKeys = {};
55
+ for (const [orig, rand] of Object.entries(mapping.keys)) {
56
+ reverseKeys[rand] = orig;
57
+ }
58
+ const parts = fpString.split('|');
59
+ const mappedParts = parts.map(part => {
60
+ const pair = part.split(':');
61
+ if (pair.length === 2) {
62
+ const origKey = reverseKeys[pair[0]] || pair[0];
63
+ return `${origKey}:${pair[1]}`;
64
+ }
65
+ return part;
66
+ });
67
+ return mappedParts.join('|');
68
+ }
69
+
70
+
71
+ /**
72
+ * @private
73
+ * Deep merges two objects. The `source` object's properties overwrite the `target`'s.
74
+ * @param {object} target - The target object.
75
+ * @param {object} source - The source object.
76
+ * @returns {object} The merged object.
77
+ */
78
+ export function deepMerge(target, source) {
79
+ const output = { ...target };
80
+ if (target && typeof target === 'object' && source && typeof source === 'object') {
81
+ Object.keys(source).forEach(key => {
82
+ if (source[key] && typeof source[key] === 'object' && key in target) {
83
+ output[key] = deepMerge(target[key], source[key]);
84
+ } else {
85
+ output[key] = source[key];
86
+ }
87
+ });
88
+ }
89
+ return output;
90
+ }
91
+
92
+ /**
93
+ * Creates a stable hash based on device characteristics, independent of the IP.
94
+ * This is our "level 2 fingerprint".
95
+ * @param {object} context - The request context.
96
+ * @returns {string} A hash representing the device.
97
+ */
98
+ export function getHeaderSignature(context) {
99
+ if (!context.rawHeaders) return '';
100
+ const headerKeys = [];
101
+ for (let i = 0; i < context.rawHeaders.length; i += 2) {
102
+ headerKeys.push(context.rawHeaders[i]);
103
+ }
104
+ return cyrb53(headerKeys.sort().join(','));
105
+ }
106
+
107
+ /**
108
+ * Analyses a raw JA3 string.
109
+ * Format: "TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurveFormats"
110
+ * @param {string} ja3String
111
+ * @returns {object|null}
112
+ */
113
+ export function parseJa3(ja3String) {
114
+ if (!ja3String || typeof ja3String !== 'string') {
115
+ return null;
116
+ }
117
+ const parts = ja3String.split(',');
118
+ if (parts.length !== 5) {
119
+ return null;
120
+ }
121
+ return {
122
+ tlsVersion: parseInt(parts[0], 10),
123
+ ciphers: parts[1] !== '' ? parts[1].split('-').map(Number) : [],
124
+ extensions: parts[2] !== '' ? parts[2].split('-').map(Number) : [],
125
+ curves: parts[3] !== '' ? parts[3].split('-').map(Number) : [],
126
+ points: parts[4] !== '' ? parts[4].split('-').map(Number) : []
127
+ };
128
+ }
129
+
130
+ export function modPow(base, exponent, modulus) {
131
+ if (modulus === 1n) return 0n;
132
+ let result = 1n;
133
+ base = base % modulus;
134
+ while (exponent > 0n) {
135
+ if (exponent % 2n === 1n) {
136
+ result = (result * base) % modulus;
137
+ }
138
+ exponent = exponent >> 1n;
139
+ base = (base * base) % modulus;
140
+ }
141
+ return result;
142
+ }
143
+
144
+
145
+ export function hashNetwork(ip, prefix = 24) {
146
+ // Hash du réseau (masque /24 ou /16)
147
+ const parts = ip.split('.');
148
+ if (parts.length !== 4) return null;
149
+ const maskBytes = prefix / 8;
150
+ const network = parts.slice(0, maskBytes).join('.');
151
+ // Hash simple
152
+ let hash = 0;
153
+ for (let i = 0; i < network.length; i++) {
154
+ const char = network.charCodeAt(i);
155
+ hash = ((hash << 5) - hash) + char;
156
+ hash = hash & hash;
157
+ }
158
+ return hash.toString(16);
159
+ }
160
+ export function normalizeReferer(referer) {
161
+ try {
162
+ const url = new URL(referer);
163
+ return `${url.protocol}//${url.hostname}`;
164
+ } catch {
165
+ return referer;
166
+ }
167
+ }
168
+
169
+ export function isPrivateIp(ip) {
170
+ // Vérifier si l'IP est privée
171
+ const parts = ip.split('.');
172
+ if (parts.length !== 4) return false;
173
+ const first = parseInt(parts[0]);
174
+ return (first === 10) || (first === 172 && parseInt(parts[1]) >= 16 && parseInt(parts[1]) <= 31) || (first === 192 && parseInt(parts[1]) === 168);
175
+ }
176
+ // Fonctions utilitaires
177
+ export function parseUserAgent(ua) {
178
+ // Parser basique du User-Agent
179
+ const result = {};
180
+
181
+ // Détection du navigateur
182
+ if (ua.includes('Chrome') && !ua.includes('Edg')) {
183
+ result.browser = 'Chrome';
184
+ const match = ua.match(/Chrome\/(\d+)/);
185
+ if (match) result.browser += `/${match[1]}`;
186
+ } else if (ua.includes('Firefox')) {
187
+ result.browser = 'Firefox';
188
+ const match = ua.match(/Firefox\/(\d+)/);
189
+ if (match) result.browser += `/${match[1]}`;
190
+ } else if (ua.includes('Safari') && !ua.includes('Chrome')) {
191
+ result.browser = 'Safari';
192
+ const match = ua.match(/Version\/(\d+)/);
193
+ if (match) result.browser += `/${match[1]}`;
194
+ } else if (ua.includes('Edg')) {
195
+ result.browser = 'Edge';
196
+ const match = ua.match(/Edg\/(\d+)/);
197
+ if (match) result.browser += `/${match[1]}`;
198
+ }
199
+
200
+ // Détection de l'OS
201
+ if (ua.includes('Windows NT 10.0')) result.os = 'Windows 10';
202
+ else if (ua.includes('Windows NT 6.1')) result.os = 'Windows 7';
203
+ else if (ua.includes('Mac OS X')) result.os = 'macOS';
204
+ else if (ua.includes('Linux') && !ua.includes('Android')) result.os = 'Linux';
205
+ else if (ua.includes('Android')) result.os = 'Android';
206
+ else if (ua.includes('iPhone') || ua.includes('iPad')) result.os = 'iOS';
207
+
208
+ // Détection du type d'appareil
209
+ if (ua.includes('Mobile')) result.device = 'mobile';
210
+ else if (ua.includes('Tablet')) result.device = 'tablet';
211
+ else result.device = 'desktop';
212
+
213
+ return result;
184
214
  }
@@ -24,6 +24,7 @@ export class GpuPowSolver {
24
24
  }
25
25
  } catch (e) {
26
26
  console.warn('[GPU-PoW] WebGPU failed or disabled, falling back to WebGL2:', e);
27
+ // Fallthrough to WebGL2
27
28
  }
28
29
 
29
30
  // Fallback to WebGL2
@@ -35,7 +36,20 @@ export class GpuPowSolver {
35
36
  duration: performance.now() - start
36
37
  };
37
38
  } catch (e) {
38
- throw new Error(`[GPU-PoW] Both WebGPU and WebGL2 solvers failed: ${e.message}`);
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}`);
39
53
  }
40
54
  }
41
55
 
@@ -200,6 +214,87 @@ export class GpuPowSolver {
200
214
  return result.map(v => v.toFixed(6)).join(',');
201
215
  }
202
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
+
203
298
  static _createProgram(gl, vsSource, fsSource) {
204
299
  const vs = gl.createShader(gl.VERTEX_SHADER);
205
300
  gl.shaderSource(vs, vsSource);
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
- const parent1 = population[Math.floor(secureRandom() * population.length)];
525
- const parent2 = population[Math.floor(secureRandom() * population.length)];
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[Math.floor(secureRandom() * population.length)];
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[Math.floor(secureRandom() * population.length)];
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
- let i = Math.floor(secureRandom() * newPath.length);
915
- let j = Math.floor(secureRandom() * newPath.length);
916
- if (i === j) j = (j + 1) % newPath.length;
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
- const i = Math.floor(secureRandom() * newP.length);
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
- const i = Math.floor(secureRandom() * numFacilities);
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 = Math.floor(secureRandom() * 10); // entre 0 et 10
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 = Math.floor(secureRandom() * 4);
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 + Math.floor(secureRandom() * 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[Math.floor(secureRandom() * keys.length)];
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