Signal · signalsVersion 5

math

The exact results of 24 Math operations on fixed inputs, hashed. Their last digits differ between JavaScript engines and engine versions.

math at a glance (from the library code, signalsVersion 5)
Component namemath
In the visitorIdyes
structuredId groupbrowser (engine and build)
Hashingalways a digest (first 128 bits of SHA-256)
Excluded underno browser class
Inner timeout1000 ms
Added insignalsVersion 1
In plain words

Computers approximate mathematical functions such as sine or the exponential, and different browsers’ JavaScript engines do it in slightly different ways. For a few carefully chosen inputs, the very last digits of the answer differ. WhiskerJS computes 24 such results and keeps only a short hash of them. It needs no permission and draws nothing; it is pure arithmetic.

What it reads

The window’s Math object, called with fixed inputs:

  • acos, asin of 0.123124234234234242; acosh of 1e308; asinh of 1; atanh and atan of 0.5;
  • sin and tan of -1e300; cos of 10.000000000123; sinh, cosh, tanh, exp, expm1 of 1; log1p of 10;
  • “polyfill” forms of some of them written with log, sqrt and exp (for example log(1e154 + sqrt(1e154 * 1e154 - 1)) for acosh);
  • pow(PI, -100).

That is 24 named results in a fixed order.

How the technique works

  1. Each function is bound to the window’s Math. A function that is missing is replaced by one that returns 0, so a missing function changes the value instead of failing.
  2. Each result is written as name=<number> with JavaScript’s default number printing, and the 24 pairs are joined with ;.
  3. The string is always hashed: SHA-256, first 128 bits, 32 hex characters.

The operations, constants and polyfill expressions are adapted from FingerprintJS 5.3.0 (MIT); the notice is kept beside the code and in THIRD_PARTY_NOTICES.md.

Why it helps

The inputs sit where approximation errors show: huge arguments to sin and tan, values near the edge of the floating-point range, and expressions that lose precision. Different engines (V8, SpiderMonkey, JavaScriptCore), and sometimes different versions of one engine, round the last bits differently. The value says little about the device and a lot about the engine build, which is why it is in the browser group.

Stability

  • Stable within one browser version on one device: the same inputs always give the same outputs.
  • It can change with a browser update, when an engine changes its maths library. That moves the visitorId and the browser part of the structuredId.
  • It does not follow user settings.

Browser support and what blocks it

Every browser has Math, so it is ok everywhere: Chrome, Firefox, Safari, iOS browsers, Brave, Edge. No browser class excludes it. Because all iOS browsers share WebKit’s engine, they are expected to share the same math results with Safari on the same iOS version.

Privacy

The value is a property of the browser’s engine build, shared by everyone on that build. It contains nothing about the user and leaves the library only as a digest.

How WhiskerJS handles noise and failures

Status ruleId When
ok the digest of the 24 results
unavailable no_api there is no Math object or Math.exp is not a function
unavailable no_digest crypto.subtle is missing, so the value cannot be hashed (never sent raw)
blocked threw, security_error a patched Math function throws
excluded caller_excluded you excluded it
timed_out timeout, aborted budget or abort

There is no noise to detect: the results are deterministic.

Example value

In headless test browsers in a Linux container on an ARM host, Chromium and Firefox both gave 93c6779db9af8debbf63b6d97bbcbd97 and WebKit gave c5a6fcbb6b7bfc2c6aca4d4345d810bb. A small example, not a measurement of how often engines agree.

Questions and answers

Why would Math.sin give different answers in different browsers?

The standard allows small differences in how functions such as sin, tan or exp are approximated. Engines use different algorithms or libraries, so the last digits can differ for some inputs.

Is the math code taken from another library?

The list of operations, their input constants and the polyfill expressions are adapted from FingerprintJS 5.3.0 under the MIT licence, with the notice kept in THIRD_PARTY_NOTICES.md. The collector code around them is WhiskerJS's own.

Does math tell the CPU apart?

Mostly it tells engines and their versions apart. Two engines can also agree on the same machine: in a container run, headless Chromium and Firefox produced the same math digest while WebKit differed.