Skip to content

Math Fingerprinting ​

Math fingerprinting exploits tiny precision differences in how JavaScript engines compute transcendental mathematical functions. These differences come from the CPU architecture, not the browser.

How it works ​

Neoprint computes 22 mathematical operations and stores the full-precision results:

ts
const data = {
  acos: Math.acos(0.123456789),
  acosh: Math.acosh(1e10),
  asin: Math.asin(0.123456789),
  asinh: Math.asinh(1),
  atan: Math.atan(2),
  atanh: Math.atanh(0.5),
  atan2: Math.atan2(0.04, -0.04),
  cbrt: Math.cbrt(100),
  cos: Math.cos(21 * Math.LN2),
  cosh: Math.cosh(1),
  exp: Math.exp(1),
  expm1: Math.expm1(1),
  log: Math.log(10),
  log1p: Math.log1p(10),
  log2: Math.log2(7),
  log10: Math.log10(7),
  sin: Math.sin(1e300),
  sinh: Math.sinh(1),
  sqrt: Math.sqrt(2),
  tan: Math.tan(-1e300),
  tanh: Math.tanh(1),
  pow: Math.pow(Math.PI, -100),
}

Why results differ across devices ​

IEEE 754 floating-point standard allows implementations to differ in the last 1-2 bits of precision for transcendental functions. Different JS engines (V8, JavaScriptCore, SpiderMonkey) and CPU architectures (x86, ARM) produce slightly different results.

Example: Math.atan(2) might return:

  • 1.1071487177940904 on Chrome/V8 (x86)
  • 1.1071487177940906 on Safari/JSC (ARM)

Entropy and stability ​

PropertyValue
Entropy~6 bits
Stability0.95
Typical duration<1ms

Math is the most stable collector alongside WebGL. Values only change when the user switches CPU architecture or JS engine version.

Cross-browser normalization ​

For crossBrowserId, math values are rounded to 8 significant digits to absorb V8 vs JavaScriptCore last-digit differences while preserving architecture-level differences (x86 vs ARM).

Web Worker offloading ​

Math runs in a Web Worker by default since it requires no DOM access.

Usage ​

ts
const fp = await neoprint.get({ collectors: ['math'] })
const math = fp.components.math.value

console.log(math.sin)  // 1e300 produces different results per platform
console.log(math.tan)  // -1e300 is another strong differentiator

Released under the MIT License.