Overview
compareFingerprints compares two FingerprintResult objects collected at different times or in different browser sessions. It applies hard constraint checking first, then per-signal fuzzy matching for signals that have dedicated fuzzy functions, then weighted similarity scoring to produce the final result.
The function never throws. On any uncaught error it returns a safe zero-score default with an empty signalComparison and a 15-element zero diffVector.
Comparison modes
| Mode | Signal set | Fuzzy matching | Best for |
|---|---|---|---|
| exact | All signals from both fingerprints | Enabled (unless overridden) | Default — all signals with graceful fuzzy tolerance |
| cross-browser | Hardware signals only (fonts, webgl_gpu_identity, platform, timezone, media_devices) | Enabled | Matching the same device across different browser engines |
| hardware-only | Hardware signals only | Enabled | Explicit hardware-only filter — same signal set as cross-browser, different semantic intent |
| engine-only | Engine signals only | Enabled | Detecting browser changes on the same device |
| strict | All signals | Disabled — hash equality only | Zero tolerance for any signal variation |
| lenient | All signals | Enabled | Maximum tolerance for expected drift |
Hard constraint checking
Before any signal scoring, compareFingerprints checks hard constraints. Currently one constraint is active: os_changed, which fires when the normalised platform value (the OS/platform string) differs between the two fingerprints. If checkConstraints is true (the default) and a violation is found, the function returns immediately with matchScore: 0 and match: false.
Even on an early return, hardwareMatch is still computed via string equality of the two hardwareFingerprint fields.
ComparisonOptions fields
| Field | Type | Default | Description |
|---|---|---|---|
| mode | ComparisonMode | 'exact' | Signal set and matching strategy preset. See modes table above. |
| includeSignals | string[] | — | Further narrows the mode's signal set to only these names. |
| excludeSignals | string[] | — | Removes these signal names from the comparison set. |
| fuzzyMatching | boolean | true | Enable per-signal fuzzy functions for signals that have them. |
| matchThreshold | number | 85 | Minimum matchScore (0–100) for match: true. |
| checkConstraints | boolean | true | Run hard constraint checks before comparison. |
| fuzzyThresholds | FuzzyThresholds | — | Override per-signal fuzzy thresholds for audio and screen signals. |
Per-signal fuzzy matching
Five signals have dedicated fuzzy functions that score partial matches rather than treating any hash mismatch as zero. For all other signals, a hash mismatch scores 0.0 — there is no fuzzy path. When mode is strict or fuzzyMatching is false, the fuzzy dispatch is bypassed entirely and every signal is scored as 1.0 (match) or 0.0 (mismatch).
Signals with fuzzy functions
| Signal | What is compared | Key tolerance |
|---|---|---|
| screen | Width, height, colorDepth, DPR as a 4-element array | Up to 10% dimension drift scores 0.6; DPR zoom tolerance of 0.5 |
| platform | OS string, hwConcurrency, deviceMemory, maxTouchPoints | OS must match exactly; each hardware spec adds 0.25 to the score |
| timezone | Timezone name string and UTC offset | Both must match exactly — returns 1.0 or 0.0 |
| media_devices | audioInputCount and videoInputCount (audioOutputCount excluded — Safari privacy) | Partial count match scores 0.5 |
| audio | Array of DSP samples compared per-element by percentage difference | Samples within 1% score 0.95; within 5% score 0.5 |
ComparisonResult fields
| Field | Type | Description |
|---|---|---|
| matchScore | number | Integer 0–100. Computed as Math.round(similarity * 100). |
| match | boolean | true when matchScore >= matchThreshold (default 85). |
| hardwareMatch | boolean | String equality of a.hardwareFingerprint and b.hardwareFingerprint. Always computed, even on early constraint return. |
| similarity | number | Weighted similarity 0.0–1.0. Hardware signals receive weight 2; engine signals weight 1. Absent signals are excluded. |
| hardwareSimilarity | number | Unweighted average of hardware-signal matchScores. 0.0–1.0. |
| constraintViolations | string[] | Array of violation strings, e.g. 'os_changed'. Empty when no violations found. |
| signalComparison | Record<string, SignalComparisonDetail> | Per-signal breakdown including matchScore and changeType (identical / implausible / plausible / one_absent). |
| diffVector | number[] | Fixed 15-element float array (0.0–1.0) in SIGNAL_ORDER. Suitable for ML feature input. Signals not in the comparison set are 0.0. |
import { getFingerprint, compareFingerprints } from 'doorman-benny';
const fpA = await getFingerprint();
// ... save fpA, restore later, or collect fpB on a different browser ...
const fpB = await getFingerprint();
// Default comparison — all signals, fuzzy matching, threshold 85.
const result = compareFingerprints(fpA, fpB);
console.log(result.matchScore); // 0–100
console.log(result.match); // true if matchScore >= 85
console.log(result.hardwareMatch); // independent string-equality check
console.log(result.similarity); // 0.0–1.0 weighted score
console.log(result.diffVector); // 15-element ML feature vector
// Cross-browser device matching — hardware signals only.
const crossResult = compareFingerprints(fpA, fpB, { mode: 'cross-browser' });
if (!crossResult.match) {
requireAdditionalVerification('Different device detected');
}
// Strict mode — any signal change fails the match.
const strictResult = compareFingerprints(fpA, fpB, { mode: 'strict' });
// Custom threshold and excluded signals.
const customResult = compareFingerprints(fpA, fpB, {
matchThreshold: 90,
excludeSignals: ['speech_voices', 'storage_quota'],
});compareFingerprints is synchronous — both inputs must already be collected FingerprintResult objects.

