Whisper · Docs
Embedded · Recipes

Embedded recipes

Three copy-paste recipes put a forge-proof, owner-revocable network identity under an OTA pipeline, a fleet gateway, and a genuine part. Each one runs today on shipped primitives, and each names exactly where it stops.

They share one primitive: a routable IPv6 /128 that a device derives from a key it already holds, DNSSEC-signed and DANE-pinned so anyone can verify it and nobody can copy it. Where a step is a pattern you assemble rather than a single command, or an SDK still on the roadmap, it says so in plain words.

What's shipped, stated honestly. The device /128 derivation, the keyless verify / RDAP surface, WireGuard egress + policy + logs on Linux-class devices, and one-call revoke are live. These recipes run from a Linux-class device, gateway, or workstation today; the keyless HTTPS checks additionally run from any embedded client with a TLS stack (ESP32-class and up). The MCU-native SDKs (C, ESP-IDF, Arduino, Zephyr, Yocto, pico-sdk) and the packaged MQTT-DANCE / manufacture-line recipes are roadmap, phase-2 deliverables: nothing below depends on them, and nothing below presents them as installable. Whisper anchors the device/cloud IP boundary: not secure boot, not the RTOS, not a radio protocol's own security layer.

The shared primitive: an identity a device derives, not one you flash into it

A device already carries a hardware key from the factory: the 802.1AR IDevID key in its secure element or OTP. Whisper takes only its public SubjectPublicKeyInfo (SPKI) and, optionally with a canonical device identifier as the domain separator, deterministically derives a /128 under 2a04:2a01::/32 (AS219419). The private key never leaves the device.

Provision one with your API key. The device identifier is optional; today it rides the live device_id argument (17-character canonical form; a free-form serial/EUI-64 argument is roadmap):

# Provision a device's identity from the key it already holds.
# Re-running with the same key+id returns the SAME /128 (idempotent).
curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  --data @- <<'JSON' | jq .
{"query":"CALL whisper.agents({op:'connect', args:{tier:'wireguard', identity_public_key:'<base64 SPKI of the device key>', device_id:'GW00427A310B55EF2'}}) YIELD op, ok, status, result, error RETURN op, ok, status, result, error"}
JSON

# For a specific sub-module or part, add ecu_serial as the component separator for a distinct /128:
#   args:{tier:'wireguard', identity_public_key:'…', device_id:'GW00427A310B55EF2', ecu_serial:'MOD-7F31'}
i

A supplied identifier is checked against a reused identity: the same device key with a different identifier on your tenant is a 409, and a non-string identifier is a clear 400. Liberal in what it accepts, strict and unambiguous in what it commits. The server only ever derives a public address; it never sees or reconstructs the device's private key.

Once provisioned, every check in the three recipes below is keyless: no account, no API key, just DNS and TLS.

Recipe 1: Verify the OTA/update server, from the device

Before a device fetches an image, it should know it is talking to the genuine update host, even if a resolver, a CDN edge, or a middlebox has been tampered with. With the update endpoint carrying a Whisper identity, that check is a DANE pin comparison, and the keyless HTTPS form of it runs on any client with a TLS stack, ESP32-class and up, today.

Boundary. This complements your firmware image signing; it does not replace it. Signed images answer "is this image genuine"; this recipe answers "is this server genuine, before I even fetch." Keep both.

# On a Linux-class device (or your workstation), the strong trustless form:
whisper verify --trustless ota.demo.agents.whisper.online
dnssec   pass   DNSSEC-root   AAAA, PTR and TLSA(3 1 1) all DNSSEC-validated to the IANA root
dane     pass   DNSSEC-root   served leaf SPKI-SHA256 == TLSA pin
CRYPTOGRAPHICALLY PROVEN · trust anchor: DNSSEC root (IANA) + DANE-EE

# The same check with stock tools, the form a CI job or a gateway script runs:
dig +short TLSA _443._tcp.ota.demo.agents.whisper.online
3 1 1 9c41d2ab…77e01f36
openssl s_client -connect ota.demo.agents.whisper.online:443 </dev/null 2>/dev/null \
  | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der \
  | openssl dgst -sha256        # must equal the TLSA pin above

# The keyless HTTPS verdict, runnable from any TLS-capable embedded client today:
curl -s https://whisper.online/verify-identity/ota.demo.agents.whisper.online | jq .
{"is_whisper_agent": true, "dane_ok": true, "jws_ok": true, "evidence": { … }}

An MITM'd or spoofed host answering on that name fails immediately: it cannot present the pinned key, and the pin it fails against is DNSSEC-signed, so the failure is provable, not a heuristic.

Recipe 2: A governed /128 for a fleet gateway (shipped, end to end)

The concentrator that fronts your fleet, a LoRaWAN gateway, a cellular router, an edge box, is a Linux computer, which means the full product works on it today: a derived identity, routed egress that sources from it, default-deny policy, and per-identity logs.

Boundary. Node↔gateway auth stays with the local bus (OTAA, Modbus, BLE). This recipe anchors the gateway↔cloud IP boundary: the only place those nodes touch the internet.

# 1. Provision (above) returns a wireguard_config. Bring it up on the gateway:
wg-quick up ./whisper-gw.conf        # or feed it to wireproxy for a no-root userspace tunnel

# 2. Every packet now sources from the gateway's own /128. Prove it from anywhere:
dig +short -x 2a04:2a01:e5a7:41c9:04d0:c85f:3a1b:77e2
04d0c85f3a1b77e2.demo.agents.whisper.online.

# 3. Default-deny egress: the gateway reaches its platform and nothing else:
whisper policy --agent 2a04:2a01:e5a7:41c9:04d0:c85f:3a1b:77e2 \
  --default deny --allow telemetry.example-maker.com,ota.example-maker.com
policy updated · effective at DNS-TTL speed across both servers

# 4. What did it actually reach last night? Per-identity, one keyed call:
CALL whisper.agents({op:'logs', args:{agent:'<the gateway /128>', kind:'dns', from:'-24h'}})

A hijacked gateway can't quietly call home to a new owner (default-deny blocks it, and the attempt is logged per-identity), your backend can allowlist the gateway's provable address, and the whole segment behind it inherits an attributable, revocable front door.

Recipe 3: The anti-counterfeit module

A genuine module or smart consumable holds a secure-element key from the factory; a counterfeit doesn't hold the one that was registered. Bind the genuine key to a Whisper /128 at manufacture (the ecu_serial component separator gives each part its own identity) and that difference collapses into a one-line check anyone in the supply chain can run, plus a single revoke that blacklists a burned key worldwide.

Boundary. This complements the secure element already in the part; it does not replace the chip. It makes the chip's identity network-verifiable and instantly revocable.

# Genuine part: a registered, DANE-anchored identity. It proves itself:
curl -s https://whisper.online/verify-identity/2a04:2a01:e5a7:41c9:04d0:c85f:3a1b:77e2
{"is_whisper_agent": true, "dane_ok": true, "jws_ok": true, …}

# Counterfeit: no registered /128 for its key, no DANE pin it can present.
# The public surface says so plainly, no account required:
curl -s https://whisper.online/verify-identity/2a04:2a01::1
{"is_whisper_agent": false,
 "detail": "no Whisper agent identity anchors this address"}

# A distributor or service centre confirms genuineness with RDAP, no vendor call:
curl -s https://whisper.online/ip/2a04:2a01:e5a7:41c9:04d0:c85f:3a1b:77e2 | jq

One revoke, blacklisted worldwide, thrown by the owner

When a part is flagged counterfeit, a key is burned, or a device is RMA'd, resold, or decommissioned, a single call tears down its /128, its PTR, and its DANE pin everywhere at once, bounded only by DNS TTL. No CRL to distribute, no per-vendor blocklist to push, no fleet-wide firmware flash, and the act leaves a public trail: anyone can check, with dig, that the identity is gone.

# Retire an identity worldwide. Every keyless check above starts returning "not a
# Whisper identity" within the DNS TTL. No coordination, no distribution list.
curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  -d '{"query":"CALL whisper.agents({op:'"'"'revoke'"'"', args:{agent:'"'"'2a04:2a01:e5a7:41c9:04d0:c85f:3a1b:77e2'"'"'}})"}'

# With the CLI, the same teardown is one verb:
whisper kill --revoke 2a04:2a01:e5a7:41c9:04d0:c85f:3a1b:77e2

After that, Recipe 3's genuine-part check flips to is_whisper_agent:false for that address everywhere: revocation and counterfeit-detection are, deliberately, the same mechanism. On a legitimate resale, revoke and re-register under the new owner.

Attribution: which fleet, which destinations (keyed)

The three recipes above need no key. If you hold one, the same public API answers a deeper question: the attribution graph. Passing an address to whisper.identify over POST https://graph.whisper.security/api/query returns what the graph knows about it as read-only Cypher:

curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  -d '{"query":"CALL whisper.identify(\"2a04:2a01:e5a7:41c9:04d0:c85f:3a1b:77e2\")"}' | jq .

This is the graph API, not a CLI subcommand: there is no whisper identify verb; the call above is the interface. It feeds a device-security SOC: which destinations a fleet's gateways resolve, OTA-CDN reputation, a concentrator beaconing somewhere it shouldn't.

Where each recipe stops (and what's roadmap)

RecipeComplementsDoes not touch / replace
Verify the OTA server your firmware image signing image formats, signing keys, the update orchestration itself
Governed gateway /128 the local bus's own auth (OTAA, Modbus, BLE) node↔gateway security; the radio stack
Anti-counterfeit module the secure element in the part the chip itself; any bus-level pairing

Roadmap, clearly labelled. The MCU-native SDKs (C, ESP-IDF, Arduino, Zephyr, Yocto, OpenWrt/Buildroot, pico-sdk), the manufacture-line provisioning recipe, and the MQTT-DANCE broker recipe are phase-2 deliverables: proposed, not yet installable, and each will be proven with a full end-to-end test (provision a real device → run it → verify the routable /128 with dig -x/RDAP → prove the egress) before its guide appears here. Streaming evidence into a SIEM ships today for Splunk, Microsoft Sentinel and OpenCTI; STIX/TAXII is roadmap. Nothing on this list is required for the three recipes; they run on shipped primitives alone.

Next