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 26-element zero diffVector.
The comparison surface covers all signals in the fusion order. Engine-bound signals are weighted differently from hardware-bound signals in the similarity calculation; the exact weight ratio is part of the internal scoring policy and not part of the public contract.
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-bound signals only | 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[] | (none) | Further narrows the mode's signal set to only these names. |
| excludeSignals | string[] | (none) | Removes these signal names from the comparison set. |
| fuzzyMatching | boolean | true | Enable per-signal fuzzy functions for signals that have them. |
| matchThreshold | number | (tuned) | Minimum matchScore (0 to 100) for match: true. The default is tuned for typical re-identification workloads; consumers can override. |
| checkConstraints | boolean | true | Run hard constraint checks before comparison. |
| fuzzyThresholds | FuzzyThresholds | (none) | Override per-signal fuzzy thresholds for audio and screen signals. |
Per-signal fuzzy matching
Several 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 | Dimension drift is scored in tolerance tiers; DPR zoom is given a tolerance window. The exact cut-offs are part of the internal scoring policy and not part of the public contract. |
| platform | OS string, hwConcurrency, deviceMemory, maxTouchPoints | OS must match exactly; each matching hardware spec contributes additional weight 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 for Safari privacy) | Partial count match scores at a reduced level; both counts matching scores fully |
| audio | Reads value.waveforms (the four per-waveform mode values) and compares per-element by percentage difference. Pre-1.24.0 callers passed the bare number[] directly; from 1.24.0 the call site unwraps value.waveforms from the new { waveforms, divergence } shape before invoking the per-signal fuzzy comparison for audio (with a legacy number[] fallback). | Near-identical samples score very highly; samples with moderate divergence score partially. The exact tier cut-offs are part of the internal scoring policy and not part of the public contract. |
ComparisonResult fields
| Field | Type | Description |
|---|---|---|
| matchScore | number | Integer 0 to 100. Computed as Math.round(similarity * 100). |
| match | boolean | true when matchScore meets matchThreshold. The default is tuned for typical re-identification workloads; consumers can override. |
| hardwareMatch | boolean | String equality of a.hardwareFingerprint and b.hardwareFingerprint. Always computed, even on early constraint return. |
| similarity | number | Weighted similarity 0.0 to 1.0. Hardware-bound signals contribute more weight than engine-bound signals; absent signals are excluded. Exact weights are part of the internal scoring policy and not part of the public contract. |
| hardwareSimilarity | number | Unweighted average of hardware-signal matchScores, 0.0 to 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, or one_absent). |
| diffVector | number[] | Fixed 26-element float array (0.0 to 1.0) in the fusion 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, default threshold.
const result = compareFingerprints(fpA, fpB);
console.log(result.matchScore); // 0 to 100
console.log(result.match); // true if matchScore meets the threshold
console.log(result.hardwareMatch); // independent string-equality check
console.log(result.similarity); // 0.0 to 1.0 weighted score
console.log(result.diffVector); // fixed-length ML feature vector
// Cross-browser device matching: hardware-bound 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.
Last reviewed 2026-06-04

