@cloudglides/veil 0.1.1 → 1.0.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/dist/index.d.ts +103 -0
- package/dist/index.js +1000 -0
- package/dist/veil_core_bg.wasm +0 -0
- package/package.json +24 -10
- package/.envrc +0 -1
- package/.github/workflows/build-on-tag.yml +0 -82
- package/.github/workflows/deploy-pages.yml +0 -73
- package/example/index.html +0 -220
- package/example/veil_core.d.ts +0 -71
- package/example/veil_core_bg.wasm +0 -0
- package/example/veil_core_bg.wasm.d.ts +0 -20
- package/flake.nix +0 -68
- package/scripts/patch-wasm.js +0 -12
- package/src/cpu.ts +0 -67
- package/src/entropy/adblock.ts +0 -16
- package/src/entropy/approximate.ts +0 -9
- package/src/entropy/audio.ts +0 -17
- package/src/entropy/battery.ts +0 -10
- package/src/entropy/browser.ts +0 -7
- package/src/entropy/canvas.ts +0 -17
- package/src/entropy/complexity.ts +0 -14
- package/src/entropy/connection.ts +0 -14
- package/src/entropy/distribution.ts +0 -9
- package/src/entropy/fonts.ts +0 -4
- package/src/entropy/hardware.ts +0 -10
- package/src/entropy/language.ts +0 -14
- package/src/entropy/os.ts +0 -12
- package/src/entropy/osVersion.ts +0 -6
- package/src/entropy/performance.ts +0 -14
- package/src/entropy/permissions.ts +0 -15
- package/src/entropy/plugins.ts +0 -14
- package/src/entropy/preferences.ts +0 -12
- package/src/entropy/probabilistic.ts +0 -20
- package/src/entropy/screen.ts +0 -12
- package/src/entropy/screenInfo.ts +0 -8
- package/src/entropy/spectral.ts +0 -9
- package/src/entropy/statistical.ts +0 -15
- package/src/entropy/storage.ts +0 -22
- package/src/entropy/timezone.ts +0 -10
- package/src/entropy/userAgent.ts +0 -16
- package/src/entropy/webFeatures.ts +0 -21
- package/src/entropy/webgl.ts +0 -11
- package/src/gpu.ts +0 -132
- package/src/index.test.ts +0 -26
- package/src/index.ts +0 -248
- package/src/normalize/index.ts +0 -31
- package/src/probability.ts +0 -11
- package/src/scoring.ts +0 -106
- package/src/seeded-rng.ts +0 -14
- package/src/stability.ts +0 -405
- package/src/tamper.ts +0 -207
- package/src/types/index.ts +0 -11
- package/src/types.ts +0 -56
- package/src/veil_core.d.ts +0 -4
- package/src/wasm-loader.ts +0 -44
- package/tsconfig.json +0 -12
- package/tsup.config.ts +0 -41
- package/veil-core/Cargo.lock +0 -114
- package/veil-core/Cargo.toml +0 -12
- package/veil-core/src/entropy.rs +0 -132
- package/veil-core/src/lib.rs +0 -93
- package/veil-core/src/similarity.rs +0 -67
- package/vitest.config.ts +0 -15
- /package/{example → dist}/veil_core.js +0 -0
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { getWasmModule } from "../wasm-loader";
|
|
2
|
-
import { seededRng } from "../seeded-rng";
|
|
3
|
-
|
|
4
|
-
export async function getApproximateEntropy(seed: string): Promise<string> {
|
|
5
|
-
const samples = seededRng(seed, 256);
|
|
6
|
-
const wasm = await getWasmModule();
|
|
7
|
-
const apen = wasm.approx_entropy(new Float64Array(samples), 2);
|
|
8
|
-
return `apen:${apen.toFixed(6)}`;
|
|
9
|
-
}
|
package/src/entropy/audio.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export async function getAudioEntropy(): Promise<string> {
|
|
2
|
-
try {
|
|
3
|
-
const ctx = new (window.AudioContext || (window as any).webkitAudioContext)();
|
|
4
|
-
const state = ctx.state;
|
|
5
|
-
const sampleRate = ctx.sampleRate;
|
|
6
|
-
|
|
7
|
-
const bitDepth = 32;
|
|
8
|
-
const nyquistFreq = sampleRate / 2;
|
|
9
|
-
const frequencyBits = Math.log2(nyquistFreq + 1);
|
|
10
|
-
const audioCapacity = bitDepth * sampleRate;
|
|
11
|
-
|
|
12
|
-
ctx.close();
|
|
13
|
-
return `state:${state}|sr:${sampleRate}|f_nyquist:${nyquistFreq}|H(audio)=${frequencyBits.toFixed(3)}`;
|
|
14
|
-
} catch {
|
|
15
|
-
return "audio:unavailable";
|
|
16
|
-
}
|
|
17
|
-
}
|
package/src/entropy/battery.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export async function getBatteryEntropy(): Promise<string> {
|
|
2
|
-
const nav = navigator as any;
|
|
3
|
-
if (!nav.getBattery) return "battery:unavailable";
|
|
4
|
-
try {
|
|
5
|
-
const battery = await nav.getBattery();
|
|
6
|
-
return `level:${battery.level}|charging:${battery.charging}`;
|
|
7
|
-
} catch {
|
|
8
|
-
return "battery:unavailable";
|
|
9
|
-
}
|
|
10
|
-
}
|
package/src/entropy/browser.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export async function getBrowserEntropy(): Promise<string> {
|
|
2
|
-
const ua = navigator.userAgent;
|
|
3
|
-
const vendor = navigator.vendor;
|
|
4
|
-
const cookieEnabled = navigator.cookieEnabled;
|
|
5
|
-
const doNotTrack = navigator.doNotTrack;
|
|
6
|
-
return `ua:${ua}|vendor:${vendor}|cookies:${cookieEnabled}|dnt:${doNotTrack}`;
|
|
7
|
-
}
|
package/src/entropy/canvas.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export async function getCanvasEntropy(): Promise<string> {
|
|
2
|
-
try {
|
|
3
|
-
const canvas = document.createElement("canvas");
|
|
4
|
-
if (canvas.getContext === undefined) return "canvas:unavailable";
|
|
5
|
-
|
|
6
|
-
const ctx = canvas.getContext("2d");
|
|
7
|
-
if (!ctx) return "canvas:unavailable";
|
|
8
|
-
|
|
9
|
-
const support = ctx.fillText ? "fillText" : "none";
|
|
10
|
-
const textMetrics = ctx.measureText ? "measureText" : "none";
|
|
11
|
-
const imageDataSupport = ctx.getImageData ? "getImageData" : "none";
|
|
12
|
-
|
|
13
|
-
return `support:${support}|metrics:${textMetrics}|imageData:${imageDataSupport}`;
|
|
14
|
-
} catch {
|
|
15
|
-
return "canvas:unavailable";
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { getWasmModule } from "../wasm-loader";
|
|
2
|
-
|
|
3
|
-
export async function getComplexityEntropy(): Promise<string> {
|
|
4
|
-
const ua = navigator.userAgent;
|
|
5
|
-
const bytes = new Uint8Array(ua.length);
|
|
6
|
-
|
|
7
|
-
for (let i = 0; i < ua.length; i++) {
|
|
8
|
-
bytes[i] = ua.charCodeAt(i) % 256;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const wasm = await getWasmModule();
|
|
12
|
-
const complexity = wasm.lz_complexity(bytes);
|
|
13
|
-
return `lz:${complexity}`;
|
|
14
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export async function getConnectionEntropy(): Promise<string> {
|
|
2
|
-
const nav = navigator as any;
|
|
3
|
-
const conn = nav.connection || nav.mozConnection || nav.webkitConnection;
|
|
4
|
-
if (!conn) return "connection:unknown";
|
|
5
|
-
|
|
6
|
-
const rtt = conn.rtt || 0;
|
|
7
|
-
const downlink = conn.downlink || 0;
|
|
8
|
-
|
|
9
|
-
const bandwidth = Math.log2(downlink * 1000 + 1);
|
|
10
|
-
const latencyEntropy = Math.log2(rtt + 2);
|
|
11
|
-
const connectionScore = (downlink / rtt) * 1000;
|
|
12
|
-
|
|
13
|
-
return `type:${conn.effectiveType}|bw:${bandwidth.toFixed(3)}|H(RTT)=${latencyEntropy.toFixed(3)}|Q=${connectionScore.toFixed(2)}`;
|
|
14
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { getWasmModule } from "../wasm-loader";
|
|
2
|
-
import { seededRng } from "../seeded-rng";
|
|
3
|
-
|
|
4
|
-
export async function getDistributionEntropy(seed: string): Promise<string> {
|
|
5
|
-
const samples = seededRng(seed, 1000);
|
|
6
|
-
const wasm = await getWasmModule();
|
|
7
|
-
const ksStatistic = wasm.ks_test(new Float64Array(samples));
|
|
8
|
-
return `ks:${ksStatistic.toFixed(6)}`;
|
|
9
|
-
}
|
package/src/entropy/fonts.ts
DELETED
package/src/entropy/hardware.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export async function getHardwareEntropy(): Promise<string> {
|
|
2
|
-
const cores = navigator.hardwareConcurrency || 1;
|
|
3
|
-
const memory = (navigator as any).deviceMemory || 4;
|
|
4
|
-
|
|
5
|
-
const computePower = Math.log2(cores) * Math.log2(memory);
|
|
6
|
-
const memoryBits = Math.ceil(Math.log2(memory * 1024));
|
|
7
|
-
const coreEntropy = cores > 0 ? Math.log2(cores) : 0;
|
|
8
|
-
|
|
9
|
-
return `cores:${cores}|mem:${memory}GB|P(compute)=${computePower.toFixed(3)}|H(cores)=${coreEntropy.toFixed(3)}`;
|
|
10
|
-
}
|
package/src/entropy/language.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export async function getLanguageEntropy(): Promise<string> {
|
|
2
|
-
const language = navigator.language;
|
|
3
|
-
const languages = navigator.languages;
|
|
4
|
-
|
|
5
|
-
const langCount = languages.length;
|
|
6
|
-
const langDiversity = Math.log2(langCount + 1);
|
|
7
|
-
const primaryEntropy = -Array.from(language).reduce((h, c) => {
|
|
8
|
-
const p = 1 / language.length;
|
|
9
|
-
return h + p * Math.log2(p);
|
|
10
|
-
}, 0);
|
|
11
|
-
|
|
12
|
-
const combined = languages.join("|");
|
|
13
|
-
return `primary:${language}|langs:${langCount}|H(L)=${primaryEntropy.toFixed(3)}|D=${langDiversity.toFixed(3)}`;
|
|
14
|
-
}
|
package/src/entropy/os.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export async function getOSEntropy(): Promise<string> {
|
|
2
|
-
const platform = navigator.platform || "unknown";
|
|
3
|
-
const oscpu = (navigator as any).oscpu || "unknown";
|
|
4
|
-
const combined = platform + oscpu;
|
|
5
|
-
|
|
6
|
-
const uniqueChars = new Set(combined).size;
|
|
7
|
-
const totalChars = combined.length;
|
|
8
|
-
const charDiversity = uniqueChars / totalChars;
|
|
9
|
-
const surprisal = -Math.log2(1 / uniqueChars);
|
|
10
|
-
|
|
11
|
-
return `OS:${platform}|CPU:${oscpu}|δ=${charDiversity.toFixed(4)}|I=${surprisal.toFixed(2)}`;
|
|
12
|
-
}
|
package/src/entropy/osVersion.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export async function getPerformanceEntropy(): Promise<string> {
|
|
2
|
-
if (!performance.memory) return "perf:unavailable";
|
|
3
|
-
const mem = (performance as any).memory;
|
|
4
|
-
|
|
5
|
-
const used = mem.usedJSHeapSize;
|
|
6
|
-
const total = mem.totalJSHeapSize;
|
|
7
|
-
const limit = mem.jsHeapSizeLimit;
|
|
8
|
-
|
|
9
|
-
const utilizationRatio = used / limit;
|
|
10
|
-
const fragmentation = (total - used) / total;
|
|
11
|
-
const headroomBits = Math.log2(limit - used + 1);
|
|
12
|
-
|
|
13
|
-
return `used:${used}|limit:${limit}|ρ=${utilizationRatio.toFixed(4)}|frag=${fragmentation.toFixed(4)}|H(mem)=${headroomBits.toFixed(2)}`;
|
|
14
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export async function getPermissionsEntropy(): Promise<string> {
|
|
2
|
-
const perms = [];
|
|
3
|
-
const checks = ["geolocation", "notifications", "camera", "microphone"];
|
|
4
|
-
|
|
5
|
-
for (const perm of checks) {
|
|
6
|
-
try {
|
|
7
|
-
const result = await navigator.permissions.query({ name: perm as any });
|
|
8
|
-
perms.push(`${perm}:${result.state}`);
|
|
9
|
-
} catch {
|
|
10
|
-
perms.push(`${perm}:unknown`);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return perms.join("|");
|
|
15
|
-
}
|
package/src/entropy/plugins.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export async function getPluginsEntropy(): Promise<string> {
|
|
2
|
-
const plugins = Array.from(navigator.plugins).map((p) => p.name);
|
|
3
|
-
const pluginStr = plugins.join("|");
|
|
4
|
-
|
|
5
|
-
const pluginCount = plugins.length;
|
|
6
|
-
const pluginEntropy = pluginCount > 0
|
|
7
|
-
? -plugins.reduce((h, p) => {
|
|
8
|
-
const p_freq = 1 / pluginCount;
|
|
9
|
-
return h + p_freq * Math.log2(p_freq);
|
|
10
|
-
}, 0)
|
|
11
|
-
: 0;
|
|
12
|
-
|
|
13
|
-
return `count:${pluginCount}|H(plugins)=${pluginEntropy.toFixed(3)}|plugins:${pluginStr || "none"}`;
|
|
14
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export async function getPreferencesEntropy(): Promise<string> {
|
|
2
|
-
const darkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
3
|
-
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
4
|
-
const highContrast = window.matchMedia("(prefers-contrast: more)").matches;
|
|
5
|
-
|
|
6
|
-
const prefCount = [darkMode, reducedMotion, highContrast].filter(Boolean).length;
|
|
7
|
-
const maxCombinations = Math.pow(2, 3);
|
|
8
|
-
const preferenceBits = Math.log2(maxCombinations);
|
|
9
|
-
const actualEntropy = prefCount > 0 ? -((prefCount / 3) * Math.log2(prefCount / 3) + ((3 - prefCount) / 3) * Math.log2((3 - prefCount) / 3)) : 0;
|
|
10
|
-
|
|
11
|
-
return `dark:${darkMode}|motion:${reducedMotion}|contrast:${highContrast}|enabled:${prefCount}|H(pref)=${actualEntropy.toFixed(3)}`;
|
|
12
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export async function getProbabilisticEntropy(): Promise<string> {
|
|
2
|
-
const metrics = [];
|
|
3
|
-
|
|
4
|
-
const screen_values = [screen.width, screen.height, screen.colorDepth];
|
|
5
|
-
const hw_values = [navigator.hardwareConcurrency || 1, (navigator as any).deviceMemory || 4];
|
|
6
|
-
const all_values = [...screen_values, ...hw_values];
|
|
7
|
-
|
|
8
|
-
const mean = all_values.reduce((a, b) => a + b) / all_values.length;
|
|
9
|
-
const sorted = [...all_values].sort((a, b) => a - b);
|
|
10
|
-
const median = sorted[Math.floor(sorted.length / 2)];
|
|
11
|
-
const q1 = sorted[Math.floor(sorted.length / 4)];
|
|
12
|
-
const q3 = sorted[Math.floor(sorted.length * 3 / 4)];
|
|
13
|
-
const iqr = q3 - q1;
|
|
14
|
-
|
|
15
|
-
metrics.push(`mean:${mean.toFixed(2)}`);
|
|
16
|
-
metrics.push(`median:${median.toFixed(2)}`);
|
|
17
|
-
metrics.push(`iqr:${iqr.toFixed(2)}`);
|
|
18
|
-
|
|
19
|
-
return metrics.join("|");
|
|
20
|
-
}
|
package/src/entropy/screen.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export async function getScreenEntropy(): Promise<string> {
|
|
2
|
-
const w = screen.width;
|
|
3
|
-
const h = screen.height;
|
|
4
|
-
const d = screen.colorDepth;
|
|
5
|
-
|
|
6
|
-
const pixelCount = w * h;
|
|
7
|
-
const totalColors = Math.pow(2, d);
|
|
8
|
-
const colorSpace = Math.log2(totalColors);
|
|
9
|
-
const screenSurface = Math.log2(pixelCount);
|
|
10
|
-
|
|
11
|
-
return `${w}x${h}|colors:${d}|log₂(A)=${screenSurface.toFixed(2)}|log₂(C)=${colorSpace.toFixed(2)}`;
|
|
12
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export async function getScreenInfoEntropy(): Promise<string> {
|
|
2
|
-
const width = screen.width;
|
|
3
|
-
const height = screen.height;
|
|
4
|
-
const depth = screen.colorDepth;
|
|
5
|
-
const pixelDepth = screen.pixelDepth;
|
|
6
|
-
const devicePixelRatio = window.devicePixelRatio;
|
|
7
|
-
return `${width}x${height}|depth:${depth}|px:${pixelDepth}|ratio:${devicePixelRatio}`;
|
|
8
|
-
}
|
package/src/entropy/spectral.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { getWasmModule } from "../wasm-loader";
|
|
2
|
-
import { seededRng } from "../seeded-rng";
|
|
3
|
-
|
|
4
|
-
export async function getSpectralEntropy(seed: string): Promise<string> {
|
|
5
|
-
const samples = seededRng(seed, 128);
|
|
6
|
-
const wasm = await getWasmModule();
|
|
7
|
-
const entropy = wasm.spectral(new Float64Array(samples));
|
|
8
|
-
return `spectral:${entropy.toFixed(6)}`;
|
|
9
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export async function getStatisticalEntropy(): Promise<string> {
|
|
2
|
-
const ua = navigator.userAgent;
|
|
3
|
-
const freq: Record<string, number> = {};
|
|
4
|
-
|
|
5
|
-
for (const char of ua) {
|
|
6
|
-
freq[char] = (freq[char] || 0) + 1;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const values = Object.values(freq);
|
|
10
|
-
const mean = values.reduce((a, b) => a + b) / values.length;
|
|
11
|
-
const variance = values.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / values.length;
|
|
12
|
-
const stdDev = Math.sqrt(variance);
|
|
13
|
-
|
|
14
|
-
return `mean:${mean.toFixed(4)}|var:${variance.toFixed(4)}|std:${stdDev.toFixed(4)}`;
|
|
15
|
-
}
|
package/src/entropy/storage.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export async function getStorageEntropy(): Promise<string> {
|
|
2
|
-
let ls = false;
|
|
3
|
-
let idb = false;
|
|
4
|
-
|
|
5
|
-
try {
|
|
6
|
-
const test = "__storage_test__";
|
|
7
|
-
window.localStorage.setItem(test, test);
|
|
8
|
-
window.localStorage.removeItem(test);
|
|
9
|
-
ls = true;
|
|
10
|
-
} catch {
|
|
11
|
-
ls = false;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const request = window.indexedDB.open("__idb_test__");
|
|
16
|
-
idb = true;
|
|
17
|
-
} catch {
|
|
18
|
-
idb = false;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return `localStorage:${ls}|indexedDB:${idb}`;
|
|
22
|
-
}
|
package/src/entropy/timezone.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export async function getTimezoneEntropy(): Promise<string> {
|
|
2
|
-
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
3
|
-
const offset = new Date().getTimezoneOffset();
|
|
4
|
-
|
|
5
|
-
const tzEntropy = Math.log2(Math.abs(offset) + 1);
|
|
6
|
-
const offsetBits = offset.toString().length * 8;
|
|
7
|
-
const tzUniqueness = Math.log2(24 * 60 / Math.max(Math.abs(offset), 1));
|
|
8
|
-
|
|
9
|
-
return `TZ:${tz}|offset:${offset}|H(TZ)=${tzEntropy.toFixed(3)}|U=${tzUniqueness.toFixed(3)}`;
|
|
10
|
-
}
|
package/src/entropy/userAgent.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export async function getUserAgentEntropy(): Promise<string> {
|
|
2
|
-
const ua = navigator.userAgent;
|
|
3
|
-
|
|
4
|
-
let entropy = 0;
|
|
5
|
-
const freq: Record<string, number> = {};
|
|
6
|
-
for (const char of ua) {
|
|
7
|
-
freq[char] = (freq[char] || 0) + 1;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
for (const count of Object.values(freq)) {
|
|
11
|
-
const p = count / ua.length;
|
|
12
|
-
entropy -= p * Math.log2(p);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return `${ua}|H(UA)=${entropy.toFixed(4)}`;
|
|
16
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export async function getWebFeaturesEntropy(): Promise<string> {
|
|
2
|
-
const features = {
|
|
3
|
-
localStorage: !!window.localStorage,
|
|
4
|
-
sessionStorage: !!window.sessionStorage,
|
|
5
|
-
indexedDB: !!window.indexedDB,
|
|
6
|
-
openDatabase: !!(window as any).openDatabase,
|
|
7
|
-
serviceWorker: !!navigator.serviceWorker,
|
|
8
|
-
webWorker: typeof Worker !== "undefined",
|
|
9
|
-
geolocation: !!navigator.geolocation,
|
|
10
|
-
notifications: !!window.Notification,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const enabled = Object.values(features).filter(Boolean).length;
|
|
14
|
-
const total = Object.keys(features).length;
|
|
15
|
-
const supportRatio = enabled / total;
|
|
16
|
-
const supportEntropy = Math.log2(total) * (1 - Math.abs(supportRatio - 0.5) * 2);
|
|
17
|
-
|
|
18
|
-
return `enabled:${enabled}/${total}|σ=${supportRatio.toFixed(3)}|H(features)=${supportEntropy.toFixed(3)}|${Object.entries(features)
|
|
19
|
-
.map(([k, v]) => `${k}:${v}`)
|
|
20
|
-
.join("|")}`;
|
|
21
|
-
}
|
package/src/entropy/webgl.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export async function getWebGLEntropy(): Promise<string> {
|
|
2
|
-
const canvas = document.createElement("canvas");
|
|
3
|
-
const gl = canvas.getContext("webgl") || canvas.getContext("webgl2");
|
|
4
|
-
if (!gl) return "webgl:unavailable";
|
|
5
|
-
|
|
6
|
-
const vendor = gl.getParameter(gl.VENDOR) || "unknown";
|
|
7
|
-
const renderer = gl.getParameter(gl.RENDERER) || "unknown";
|
|
8
|
-
const version = gl.getParameter(gl.VERSION) || "unknown";
|
|
9
|
-
|
|
10
|
-
return `vendor:${vendor}|renderer:${renderer}|version:${version}`;
|
|
11
|
-
}
|
package/src/gpu.ts
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
export async function runGPUBenchmark(): Promise<{
|
|
2
|
-
renderTime: number;
|
|
3
|
-
textureOps: number;
|
|
4
|
-
shaderPerformance: number;
|
|
5
|
-
}> {
|
|
6
|
-
const canvas = document.createElement("canvas");
|
|
7
|
-
canvas.width = 512;
|
|
8
|
-
canvas.height = 512;
|
|
9
|
-
|
|
10
|
-
const gl = canvas.getContext("webgl2") || canvas.getContext("webgl");
|
|
11
|
-
if (!gl) {
|
|
12
|
-
return { renderTime: 0, textureOps: 0, shaderPerformance: 0 };
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const start = performance.now();
|
|
16
|
-
|
|
17
|
-
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
|
|
18
|
-
if (!vertexShader) return { renderTime: 0, textureOps: 0, shaderPerformance: 0 };
|
|
19
|
-
|
|
20
|
-
gl.shaderSource(
|
|
21
|
-
vertexShader,
|
|
22
|
-
`
|
|
23
|
-
attribute vec2 position;
|
|
24
|
-
void main() {
|
|
25
|
-
gl_Position = vec4(position, 0.0, 1.0);
|
|
26
|
-
}
|
|
27
|
-
`,
|
|
28
|
-
);
|
|
29
|
-
gl.compileShader(vertexShader);
|
|
30
|
-
|
|
31
|
-
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
|
|
32
|
-
if (!fragmentShader) return { renderTime: 0, textureOps: 0, shaderPerformance: 0 };
|
|
33
|
-
|
|
34
|
-
gl.shaderSource(
|
|
35
|
-
fragmentShader,
|
|
36
|
-
`
|
|
37
|
-
precision highp float;
|
|
38
|
-
uniform sampler2D tex;
|
|
39
|
-
void main() {
|
|
40
|
-
gl_FragColor = texture2D(tex, vec2(0.5, 0.5));
|
|
41
|
-
}
|
|
42
|
-
`,
|
|
43
|
-
);
|
|
44
|
-
gl.compileShader(fragmentShader);
|
|
45
|
-
|
|
46
|
-
const program = gl.createProgram();
|
|
47
|
-
if (!program) return { renderTime: 0, textureOps: 0, shaderPerformance: 0 };
|
|
48
|
-
|
|
49
|
-
gl.attachShader(program, vertexShader);
|
|
50
|
-
gl.attachShader(program, fragmentShader);
|
|
51
|
-
gl.linkProgram(program);
|
|
52
|
-
gl.useProgram(program);
|
|
53
|
-
|
|
54
|
-
const positionBuffer = gl.createBuffer();
|
|
55
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
56
|
-
gl.bufferData(
|
|
57
|
-
gl.ARRAY_BUFFER,
|
|
58
|
-
new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
|
|
59
|
-
gl.STATIC_DRAW,
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
const texture = gl.createTexture();
|
|
63
|
-
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
64
|
-
gl.texImage2D(
|
|
65
|
-
gl.TEXTURE_2D,
|
|
66
|
-
0,
|
|
67
|
-
gl.RGBA,
|
|
68
|
-
256,
|
|
69
|
-
256,
|
|
70
|
-
0,
|
|
71
|
-
gl.RGBA,
|
|
72
|
-
gl.UNSIGNED_BYTE,
|
|
73
|
-
new Uint8Array(256 * 256 * 4).fill(128),
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
for (let i = 0; i < 100; i++) {
|
|
77
|
-
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
78
|
-
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));
|
|
82
|
-
|
|
83
|
-
const renderTime = performance.now() - start;
|
|
84
|
-
|
|
85
|
-
const textureOps = 100;
|
|
86
|
-
const shaderPerformance = 512 * 512 * 100 / (renderTime || 1);
|
|
87
|
-
|
|
88
|
-
gl.deleteShader(vertexShader);
|
|
89
|
-
gl.deleteShader(fragmentShader);
|
|
90
|
-
gl.deleteProgram(program);
|
|
91
|
-
gl.deleteBuffer(positionBuffer);
|
|
92
|
-
gl.deleteTexture(texture);
|
|
93
|
-
|
|
94
|
-
canvas.remove();
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
renderTime,
|
|
98
|
-
textureOps,
|
|
99
|
-
shaderPerformance,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export async function getGPUHash(): Promise<string> {
|
|
104
|
-
const benchmark = await runGPUBenchmark();
|
|
105
|
-
const canvas = document.createElement("canvas");
|
|
106
|
-
canvas.width = 256;
|
|
107
|
-
canvas.height = 256;
|
|
108
|
-
|
|
109
|
-
const ctx = canvas.getContext("2d");
|
|
110
|
-
if (!ctx) return "";
|
|
111
|
-
|
|
112
|
-
for (let i = 0; i < 256; i++) {
|
|
113
|
-
for (let j = 0; j < 256; j++) {
|
|
114
|
-
const hue = (benchmark.renderTime * (i + j)) % 360;
|
|
115
|
-
const sat = (benchmark.shaderPerformance * i) % 100;
|
|
116
|
-
const lum = (benchmark.textureOps * j) % 100;
|
|
117
|
-
|
|
118
|
-
ctx.fillStyle = `hsl(${hue}, ${sat}%, ${lum}%)`;
|
|
119
|
-
ctx.fillRect(i, j, 1, 1);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const imageData = ctx.getImageData(0, 0, 256, 256);
|
|
124
|
-
const data = imageData.data;
|
|
125
|
-
|
|
126
|
-
let hash = 0;
|
|
127
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
128
|
-
hash ^= ((data[i] + data[i + 1] + data[i + 2]) * (i * 1)) >> 0;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return hash.toString(16);
|
|
132
|
-
}
|
package/src/index.test.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { getFingerprint } from "./index";
|
|
3
|
-
|
|
4
|
-
describe("veil fingerprinting", () => {
|
|
5
|
-
it("should generate consistent fingerprint", async () => {
|
|
6
|
-
const fp1 = await getFingerprint();
|
|
7
|
-
const fp2 = await getFingerprint();
|
|
8
|
-
expect(fp1).toBe(fp2);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("should generate different fingerprints with different options", async () => {
|
|
12
|
-
const fp1 = await getFingerprint({ entropy: { canvas: true } });
|
|
13
|
-
const fp2 = await getFingerprint({ entropy: { canvas: false } });
|
|
14
|
-
expect(fp1).not.toBe(fp2);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("should support SHA-512", async () => {
|
|
18
|
-
const fp = await getFingerprint({ hash: "sha512" });
|
|
19
|
-
expect(fp.length).toBeGreaterThan(64);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("should return hex string", async () => {
|
|
23
|
-
const fp = await getFingerprint();
|
|
24
|
-
expect(/^[a-f0-9]+$/.test(fp)).toBe(true);
|
|
25
|
-
});
|
|
26
|
-
});
|