Sign in

BeaconTruth

Authenticated Bluetooth location beacons. Every advertisement is one link of a hash chain — verifiable by anyone, forgeable by nobody — so a position report can be trusted, not just received.

The service

Generate

A beacon makes its own identity the first time it builds a chain: a root drawn from the die's own noise, sealed into a slot it can never rewrite or read back. No secret is programmed in at the factory, so there is nothing in a factory to steal.

Register

Commissioning is hearing one advertisement. The anchor is computed from the packet — never accepted from the caller — so registering a beacon proves it holds a chain, and nothing has to be read out of the device to do it.

Resolve

Each packet carries the next link backwards along that chain, and verifying one is hashing it forward until the anchor appears. Forgeries and replays are refused; what survives becomes trusted location data.

One standard packet

Thirty-one bytes of advertising data, in two standard AD structures: the 16-bit service UUID list, then service data carrying a 23-byte payload. The UUID appears in both because iOS hands a background scan no service data unless it also appears in the list.

03 03 d907
1a 16 d907 01e240 08a43d8f66d9e0c9c0fa095885bfc3541134 2a17

AD 1 — complete list of 16-bit service UUIDs, 4 bytes
FieldBytesValueWhat it is
Length 1 03 How much of this AD structure follows: the type byte and the UUID.
Type 1 03 AD type 0x03, “complete list of 16-bit service UUIDs”. Standard, so a scanner can filter on it without knowing anything about this service.
UUID 2 d907 0x07D9, little-endian on the wire. Nothing is carried here — the list exists so iOS will hand the service data below to a backgrounded app.
AD 2 — service data, 27 bytes
FieldBytesValueWhat it is
Length 1 1a Twenty-six bytes follow: the type, the UUID, and the 23-byte payload.
Type 1 16 AD type 0x16, “service data, 16-bit UUID” — everything after the UUID belongs to whoever owns it.
UUID 2 d907 0x07D9 again, this time as the key the payload is filed under.
Index 3 01e240 Which link of the chain this is — 123 456 here, big-endian. Not a counter to be trusted on its own: the token only verifies against the index it claims, so a wrong one fails.
Token 18 08a43d8f66d9e0c9c0fa095885bfc3541134 The chain link itself: SHA-256 truncated to eighteen bytes, one new link a minute. The only field that proves anything — hash it index + 1 times and it lands on the beacon’s anchor.
Health 2 2a17 Battery and die temperature, outside the chain. 0x2a is a six-bit battery code in 24 mV steps from 2000 mV — 3008 mV here — and 0x17 is 23 °C. Neither is authenticated: both can be rewritten onto a replayed packet.

The token is one link of a hash chain. The beacon walks 223 links forward from a seed it never reveals, publishes only where that walk ended, and then broadcasts the chain backwards, one link a minute for about sixteen years — so hearing today's token tells you nothing about tomorrow's. The index says which link it is.

Verifying is hashing forward. Hash the token index + 1 times and you reach the beacon's anchor, which is public: a hash that proves a beacon without letting anyone impersonate it. A wrong index lands somewhere else and fails, so the token authenticates the moment it claims as well as itself. The two health bytes — battery and die temperature — sit outside the chain and are not authenticated at all. Anyone can forge them onto a replayed packet, and nothing should be built on them.

No device ID on the wire, and no stable address either: the beacon advertises from a private address it redraws on the same sixty-second boundary the token turns over. Every beacon still has an ID — ten bytes, written as sixteen Crockford symbols and printed on the hardware — and its anchor is registered against that at commissioning. Binding the two once, there, is what lets the packet stop repeating it.

One advertisement, and a token you can check for yourself. It sits at index 123 456; SHA-256 it 123 457 times, truncating to eighteen bytes each round, and you land on a08130e65feae4a6b2870c6ecda4f5104835 — the anchor. No key was involved in that, which is the point.

Nothing to steal

There is no shared secret anywhere in this system. No secret is programmed into a beacon at manufacture and nothing is read out of one to register it; the chain root never leaves the die it was generated on; and we hold no key that would let us — or anyone who compromised us — produce a token for a beacon we do not have. Every anchor we store is a hash that authorizes nothing: leaking the lot would let an attacker verify beacons, which is exactly what we publish them for.

Open by design

The packet format, the chain construction, and the registration protocol are fixed and fully specified — nothing here is meant to be reverse-engineered from a scanner. Every beacon's anchor is published in a public registry, rebuilt daily and signed with a key that never leaves its hardware module, so you can verify a packet offline and never have to take our word for one.