Case study

Room or Radio?

I built a system that identified Wi-Fi devices at 99.7%. The controls broke the identity claim—and opened a more interesting security question about the physical link.

69.5Mclean beacon frames replayed with a deterministic, unwrap-free estimator

99.7%the original chronological holdout result, three transmitters
6.8 SDcross-receiver double difference — the same beacon pair, seen by two receivers in the same instants, disagreeing by this much at every bin scale. A property of the transmitter cannot do that.
0.2σbetween two different boards after they were co-located
95.8%median blind identification of three co-located transmitters, held-out 30-minute window, chance 33.3%
53–64%best channel-feature recognition on a held-out night — the same signature, one night later

54.92 GiB screened across 69,688,145 raw rows in sixteen canonical capture files.Eight paired nights, three ESP32 beacons, and two receivers. Every row was screened; 69,510,186 clean beacon frames entered the replay. The new security interpretation is a hypothesis, not a measured spoof-resistance claim.

Watch the original captures

Choose a capture, switch receivers, and watch the three beacon traces move through the room. This is the measurement that first looked like device identity—and then moved when the floor plan changed.

Sampling-clock offset over time

B1B2B3

The number I believed

The first classifier identified three transmitters at 99.7% accuracy on a chronological holdout. It used sampling-frequency offset inferred from raw Wi-Fi subcarrier phase: a plausible hardware feature produced by imperfect quartz clocks.

The number was real. It was not measuring what it was named after.

Then I drew a floor plan

Each beacon lived in a different room. Every between-device comparison was also a between-path comparison. “Which radio is this?” and “where is this radio?” had been the same label from the beginning.

I put the transmitters on one surface, four feet apart, and repeated the measurement. Two boards that had read 7.9σ apart became 0.2σ apart. The same board moved between rooms changed by 19.6σ, even after stating the weaker 2.5–4.9σ drift floor beside it. Two receivers could report opposite slope signs for the same transmitter at the same instant.

A property of one crystal cannot depend on which receiver is listening.

Eight nights later

The follow-up was larger than the experiment that broke the original claim: three ESP32 beacons, one D0WD receiver, one S3 receiver, and eight paired overnight captures. Every one of the 69,688,145 raw rows was screened. A deterministic, unwrap-free estimator replayed 69,510,186 clean beacon frames and produced exact 1-second, 10-second, 1-minute, and 5-minute summaries.

Whole nights were held out. No neighboring window from a test night was allowed into its training templates.

The fixed links were recognizable later in the same session: 88–96% depending on receiver and feature family. Across an unseen night, the best channel representation fell to 53% on D0WD and 64% on S3. At verification rather than identification, the best equal-error rate was 30.9%. Simple two-receiver concatenation did not help.

That is not an authentication system. It is evidence of a strong but short-lived physical state.

The more interesting security question

A MAC address is cheap to copy. A radio path is not carried in the packet header. An attacker would have to reproduce what multiple receivers observe from the authorized position, hardware, antenna, environment, and instant—or defeat the measurement some other way.

The eight nights do not prove that is hard. They prove there is something concrete enough to challenge.

The next experiment is therefore not another device classifier. It is a randomized link-authentication trial: legitimate beacon fixed; cloned transmitter moved through preregistered distances and bearings; same and different boards crossed with same and different positions; receiver positions swapped; thresholds frozen before attacker trials begin.

The primary result will be false-accept rate versus attacker displacement, reported beside legitimate false rejects as the enrolled signature ages. Replay and relay need unpredictable receiver challenges or channel hopping; passive CSI recognition alone does not provide freshness.

What this project demonstrates

The transferable work is not fitting a line to phase. It is building an instrument, discovering that its best number answers the wrong question, replaying every frame with a corrected estimator, and keeping the new claim smaller than the available evidence.

The rejection ledger records 35 independent tests collapsing to 29 distinct ideas. Thirteen of those ideas were my own working results, retired when their controls contradicted them. Fifteen of the 35 tests used a threshold or decision rule frozen before the run.

Thirteen earlier working results were retired by controls designed to let them fail. The eight-night link-security prerequisite failed too. The project became more useful each time the answer got narrower.

Demo

Eight-night link-signature feasibility

The contrast is the result: the same simple features are highly discriminative later in one session, but unreliable when the entire test night is held out.

Experimental method

Split time honestly

Whole nights—not randomly mixed frames—were held out. Nearby CSI windows are strongly correlated; random splitting would reward interpolation and call it tomorrow.

Replay every eligible frame

The final pass screened every raw row, then computed deterministic unwrap-free slope, OLS, coherence, amplitude, RSSI, and CIR features for every clean B1/B2/B3 frame.

Freeze the bar before the result

Representations, train/test units, metrics, and pass/fail thresholds were written down before the eight-night link-signature classification was run. None of the frozen security prerequisites passed.

Keep the claim smaller than the evidence

These captures test repeatability and separability. With no cloned transmitter, displaced attacker, relay, replay, or receiver swap, they cannot test spoofing cost.

Results

  • The original device-ID result did not survive physical controls. Two boards previously 7.9σ apart became 0.2σ apart when co-located; the same board moved 19.6σ between rooms, against a 2.5–4.9σ drift floor.
  • What it measures is the link, not the device, and the double difference is the test that settles it. If a beacon pair's separation were a property of the transmitters, two receivers watching the same pair in the same instants should agree. They disagree by a median of 6.79 between-unit SD at 10-second, 1-minute and 5-minute bins, with level correlation of essentially zero. Smoothing does not remove it.
  • It still separates co-located hardware. In a held-out 30-minute window that had never been analysed, blind identification of three transmitters sitting four feet apart on one surface ran to a median of 95.8% across fourteen receiver-nights, against 33.3% chance — with the signature moving only 0.07σ across a 39-minute gap. The capability is real; the attribution was wrong.
  • All 69,688,145 raw rows were accounted for. The full replay retained 69,510,186 clean beacon frames, found 1,258 corrupt rows on D0WD and none on S3, and conserved every frame through four aggregation scales.
  • Channel features were the best cross-night representation, reaching 53.2% balanced accuracy on D0WD and 64.3% on S3 against 33.3% chance. The frozen prerequisite still failed because performance varied sharply by night.
  • Fresh within-night enrollment was much stronger: 88.4% on D0WD, 91.7% on S3, and 94.3% with both receivers for channel features. This was a post-registration stability analysis, so it is evidence for a short-horizon hypothesis rather than a confirmed authentication result.
  • Claimed-link verification was not security quality. The best representation reached AUC 0.70 and an equal-error rate of 30.9%. Simple two-receiver concatenation did not improve held-out-night recognition.

Honest limitations

  • No attacker was recorded

    There are no examples of a cloned MAC, substitute ESP32, displaced transmitter, directional antenna, channel emulator, replay, or relay. False-accept rate against those attacks remains unknown.

  • Device and location are still confounded

    Each beacon stayed associated with its own hardware, antenna, transmit power, and location. Cross-receiver structure may be caused by any combination of those variables.

  • The strong result is short-horizon and exploratory

    The early-40%-to-late-40% test used a 20% time gap and reached 88–96%, but it was designed after the frozen cross-night tests. It must be predicted and repeated prospectively.

  • Three transmitters, two receivers

    Every accuracy uses three classes with chance at 33.3%. More devices, receiver geometries, homes, channels, and environmental conditions are needed before generalization.

  • The raw corpus stays private

    Promiscuous captures include third-party radios and indirectly encode home occupancy. Only aggregate results and sanitized tooling are publishable.

Implementation stack

Firmware

C on ESP-IDF · ESP-NOW beacons · two receiver architectures · binary telemetry · RF-liveness watchdogs

Signal processing

Python · NumPy · unwrap-free circular slope arbitration · OLS controls · CIR metrics · robust multiscale aggregation

Experimental design

whole-night holdouts · preregistered thresholds · receiver double differences · change-point tests · block-shuffled periodicity · night bootstrap

Scale and validation

69.5M-frame memory-mapped cache · exact frame conservation · structural validators · deterministic reruns

Case study — the instrument

Building the Instrument — Crystal-Clock Fingerprinting, V1

How the sensor array was designed and what it measured before the physical controls ran. Every number here is real and reproducible; what they turned out to be measuring is the subject of Room or Radio, which supersedes the identity claim below.

99.7%blind-holdout accuracy over 7,497 test windows on a single receiver across 11 sessions — as measured in July, before co-location and cross-receiver controls established that the separation belongs to the link rather than the device

95.7%same task, 14,234 windows, once a second receiver's captures of those sessions join the population
12.7σ / 10.4σcross-manufacturer device separation from the reference beacon and from the other ambient device (6/6 holdout, correctly classified)
2.36MCSI frames in the session set backing these results — 11 sessions
$ fewcost per sensor node — commodity ESP32 hardware

99.5 GiB raw CSI captured across 118 session files.Counted from disk. The sixteen canonical eight-night files account for 54.92 GiB and exactly 69,688,145 rows, which puts the full corpus in the region of 126 million rows by the same bytes-per-row — an extrapolation, not a count, and quoted as such. The 99.7% figure above rests on a separate 2.36M-frame, 11-session blind holdout; the two numbers answer different questions and must not be combined.

Every WiFi transmitter is driven by a quartz crystal oscillator, and no two crystals are cut identically — manufacturing tolerances leave each radio with a slightly different carrier frequency and sampling clock. Those imperfections are physically baked into the silicon and stay stable across reboots, across MAC-address rotation, and across software. A MAC address is something a spoofer can fake in software in seconds; a crystal’s timing signature is not.

This project measures that signature end to end on hardware that costs a few dollars a node: an ESP32-based WiFi Channel State Information (CSI) sensing array that extracts Carrier Frequency Offset (CFO) and Sampling Frequency Offset (SFO) from raw subcarrier phase, models each device’s clock signature statistically, and classifies live traffic as a known device or a stranger. It has been running continuously as my own apartment’s home-security system, not a benchtop experiment.

It’s a complete embedded-plus-DSP system built from first principles: custom firmware on constrained hardware, a signal-processing pipeline where every stage defeats a specific measured failure mode, statistically honest evaluation against blind holdouts — chronological on a single receiver, cross-receiver when both are pooled — and the operational discipline to run it 24/7 and fix the bugs that only autonomy surfaces — like discovering that pyserial asserts DTR/RTS on port open, silently power-cycling every node a diagnostic tool touched, until every host tool was fixed to open ports with those lines deasserted.

Demo

CFO/SFO monitor — interface mockup
CFO/SFO monitor — interface mockup

Interface mockup — shows the live CFO/SFO monitor UI (representative visualization, not live captured data).

How the instrument was built

A dedicated reference beacon, not router traffic

A TX beacon broadcasts identical ESP-NOW packets at a fixed 100 Hz on a fixed channel — a clean, steady stream instead of bursty, unpredictable router traffic. RX nodes run custom ESP-IDF firmware that streams compact binary CSI frames over USB serial to a host PC, which does all the science. Dumb, robust capture nodes; a smart host — so the DSP can evolve without ever reflashing hardware.

Vectorized RANSAC line fits, not least squares

Per-frame phase-vs-subcarrier slope (the SFO proxy) is recovered with RANSAC instead of a plain line fit, so multipath bumps and junk subcarriers become outliers that get ignored rather than error that biases the result. It runs vectorized as a single broadcast operation because it has to keep up with 100 Hz capture.

Reference-beacon drift subtraction

The receiver's own crystal drifts too, and that drift is common-mode across every source it measures. Subtracting the beacon's own Kalman-smoothed drift track cancels the receiver-side thermal wander — measured impact: device separation more than doubled, from ~1.0σ to 2.6σ on the same data.

Mahalanobis discrimination with real probabilistic meaning

Each known device keeps a running 2D Gaussian model of its (CFO, SFO) signature. New observations are scored by Mahalanobis distance, which under the model is χ²-distributed — so the accept/reject thresholds correspond to actual confidence levels, not tuned magic numbers. Anomalous windows never update the model, so a spoofer can't teach the system its own signature by flooding it.

Results

  • Headline: 99.7% blind-holdout accuracy at device ID over 7,497 test windows on a single receiver, measured on a chronological split — the model is tested on windows later in time than it trained on, so the number reflects real thermal drift, not interpolation.
  • Adding a second receiver's captures of those same sessions puts it at 95.7% over 14,234 windows. That split is by receiver rather than by time — the replay walks the files in sorted order, so the desk receiver's captures train the model and the second node's are classified blind. It's cross-hardware generalization: train on one radio, test on another.
  • The security-relevant regime — a stranger's device vs. a known fleet — is the easy case for this method: cross-manufacturer separation lands at 12.7σ from the reference beacon and 10.4σ from the other ambient device, with 6/6 holdout windows classified correctly.
  • A second pipeline reuses the same captured amplitude data for cameraless occupancy sensing: motion detection and respiration detection (7.0–16.0 bpm) from the identical CSI stream, with multi-link consensus required before it alarms. The occupancy arm is deliberately quoted without an accuracy figure — a labelled ground-truth session was captured, and when it was finally scored the labels turned out to be zone-calibration labels rather than the detection protocol's, so the numbers that would validate it cannot be computed from it. That session needs recapturing before any occupancy accuracy claim is made.

Honest limitations

  • Same-model discrimination is the weak point

    Telling one ESP32 apart from an identical ESP32 is the adversarial worst case, not the operational common case — distinct units land 1.8–2.7σ apart. A single session scored around 77%, but that was a best case and the class count behind it was never recorded, so it isn't a general performance figure and isn't quoted as one. Well above chance; the honest ceiling today is unquantified.

  • True clock twins are the current frontier

    Two of the reference beacons are effectively identical crystals, ~0.3σ apart — a coin flip for two-feature discrimination. Closing this is the top item on the roadmap, not a hidden gap.

  • Thermal drift is real and only partially modeled

    Clock signatures wander with temperature; one device pair moved from 2.7σ to 1.3σ over an hours-long session. Reference-beacon subtraction cancels receiver-side drift across sessions, but not within-session same-model wander.

  • Ambient devices are measured far worse than beacons, and the gap is not closable by capturing longer

    A dedicated beacon transmits constantly; a household device does not. Over a nine-hour two-receiver capture, 38 ambient addresses were seen and exactly one cleared the 2,000-frame line the estimator needs — and it was a duplicate of another address. Loosening the frame-admission gate raises the admitted-frame count by up to 500× but adds no usable sources: measured against a criterion frozen before the test, each newly-admitted device scatters across 10–65× the entire spread that separates one ESP32 from another. The cross-manufacturer separation figures above rest on a small number of windows from well-heard devices and should be read as an existence proof, not a population result.

  • One candidate feature turned out to measure the receiver, not the transmitter

    The conjugate-image coefficient from IQ imbalance looked like a promising second fingerprint. A pre-registered test found it receiver-dominated: two receivers disagree about the same transmitter by 5.7× the entire spread between transmitters, and that disagreement is not a constant that can be calibrated away. With two receivers only the receiver difference is identifiable, so 5.7× is a lower bound. Reported because the result is negative and the alternative was to quietly stop mentioning it.

Implementation stack

Firmware

C on ESP-IDF v5.5 · Custom components (node_hal, telemetry, calibration) · ESP-NOW transport · NVS-persisted identity · Custom binary wire protocol

Signal processing / host

Python + NumPy · Hand-rolled RANSAC, phase unwrap, Welford covariance, 1D Kalman · pyserial · SQLite · Matplotlib

Deliberately not used

Deep learning — the discriminator is a statistically interpretable Gaussian/Mahalanobis model with χ²-calibrated thresholds, explainable in terms of an ellipse and a probability

What's next

Landing soon

  • Adaptive link authentication — V2

    V1 asked whether a clock identifies a device. It does not — it identifies a link: a transmitter, a receiver and the path between them, measured as one object. V2 asks whether that link can authenticate.

    Find the link. Measure it. Find out whether it can be tracked. Three questions that can fail independently, which is why they are three and not one.

    The eight nights already settled three design decisions. Enrollment has to be recent and adaptive, not a permanent template — a freshly enrolled signature holds through the rest of that session at 88–96%, and a template carried to another night averages 38–49%. Channel features lead, not raw clock slope — log CIR spread transfers at 57.6% and RSSI at 55.1%, against 38.4% for the slope everything was originally built around. And two receivers have to be independent evidence under a calibrated rule, because naive concatenation came in 0.6 points below the better single receiver.

    The measurement that matters is the one this corpus cannot make: false-accept rate against attacker displacement. Eight nights contain no cloned MAC, no substitute board, no relay and no replay — they measure repeatability, not spoof resistance. That needs randomized trials with the legitimate beacon fixed and an attacker transmitting cloned packets from pre-registered distances and bearings, device and location crossed factorially, receiver positions swapped midway, thresholds frozen, and every attack trial scored blind.

    Until that runs, “a link signature is harder to spoof than a device signature” is a hypothesis. It is not a finding and it will not be quoted as one.

Cracking the clock-twin frontier

  • Thermal compensation from the transmitter itself

    Every ESP32 has an internal die-temperature sensor. Embedding TX die temperature in the beacon payload lets the host correlate and compensate the thermal drift that currently smears same-model centroids together — zero added hardware cost, and the single highest-value improvement on the board.

  • Higher-dimensional features

    The current model uses two features (CFO, SFO) and discards the per-subcarrier phase residual shape, which carries multipath and hardware-nonlinearity signature. Adding a residual-vector feature to a higher-dimensional Mahalanobis model is the path past the same-model ceiling — and it can be validated against the existing 2.36M-frame dataset with no new captures required.

  • Live match and library — retired, and why

    This was going to mature the live match/library workflow so any capture was scored against a learned device library in real time, returning one of four verdicts: known device, same radio on a rotated MAC, trusted MAC but wrong radio, or stranger.

    Its premise did not survive the controls. That workflow assumes a device carries a portable signature — that a library learned on one receiver means something on another, and next week. The eight-night series measured the opposite: the cross-receiver double difference is 6.79 between-unit SD at every bin scale, and a template carried to a different night averages 38–49% against 33.3% chance. There is no library to match against, because the thing being learned belongs to the transmitter, the receiver and the path together.

    Kept on the roadmap as a retired item rather than deleted, because the reasoning is more useful than the plan was. What replaces it is Adaptive link authentication.

Hardware & deployment

  • Custom 3D-printed enclosures

    Turning the breadboard prototype into a finished, mountable sensor product — designed in OpenSCAD, printed in-house. Hardware photos land here as soon as the first enclosure run is off the plate.

  • Raspberry Pi host migration

    The collector is pure Python and pyserial, and runs unchanged on a Pi — enabling a permanent headless deployment with home-automation (MQTT / Home Assistant) publishing, and the eventual live embed for the demo slot above.