# Vehicle & ECU identity

**Give a car or an ECU a routable IPv6 `/128` derived from the hardware key it already holds and its VIN. The address _is_ the vehicle: forge-proof, tenant-private, DNSSEC-anchored, and revocable worldwide in one call.**

This is the spine of the automotive vertical, and it is **shipped and live**. Everything else (the fleet-API-abuse cure, the platform integrations, the R155 evidence) builds on the one idea below: a vehicle's network address stops being a disposable label a proxy can rotate and becomes a cryptographic fact only that vehicle's silicon can stand behind.

> **Two tiers, per Postel's Law.** With **no API key** anyone can _verify_ a vehicle's identity from stock tools (`dig`, `curl`, RDAP) because the identity is public by design. With **your key** you _provision_ and _govern_: mint the `/128`, source-bind its egress, pull its logs, and revoke it. Verification never needs an account; the control plane does.

## The address is the car

Almost every connected-vehicle backend authenticates a _claim_, never a _machine_. A bearer token, a static API-key header, an owner password: each says "the holder of this string may act," and a string travels. It can be phished, replayed, lifted from a reverse-engineered app, and carried to any IP on earth. That is the root cause behind the whole family of connected-car API abuses: VIN enumeration, broken object-level authorization (BOLA), one operator quietly driving a whole fleet from a residential-proxy swarm. The API is used exactly as designed; the last IP your SOC records means nothing, because the caller was never the car.

Whisper closes the gap by making the **network address itself the credential**. A vehicle gets a real, routable `/128` out of `2a04:2a01::/32` (announced by AS219419) that is a deterministic function of the hardware key the device was born with and its VIN. Because the address is _derived from_ a key only that device holds, you cannot present a vehicle identity whose key you don't have. One IP mapping to thousands of cars becomes physically impossible; a stolen session off the wrong address is inert; and every forgery is a DNSSEC/DANE inconsistency any verifier catches for free.

## How the derivation works

The `/128` is not assigned from a pool and written into a database. It is _computed_, the same way on every node, from inputs the vehicle already has. Three things go in:

| Input | What it is | Where it lives |
|---|---|---|
| **Device public key** | the `SubjectPublicKeyInfo` (SPKI) of the key the device was born with: an IEEE [802.1AR IDevID](https://1.ieee802.org/security/802-1ar/), a TPM, or a secure-element key | the **public** half is submitted; the private key **never leaves the device** |
| **VIN** | the [ISO 3779](https://www.iso.org/standard/52200.html) Vehicle Identification Number, canonicalized | submitted with the request; the public index |
| **ECU serial** _(optional)_ | a per-ECU domain separator, so one car can hold many addressable identities (TCU, gateway, a specific controller) | optional; omit it for a single per-vehicle address |

Those inputs are combined by a one-way derivation (with a Whisper-held secret mixed in) into a stable, unguessable interface identifier scoped to your fleet:

```
# inputs -> a stable, forge-proof interface identifier
derive( device public key,  VIN [, ECU serial],  your fleet )  -->  64 uniform bits

# the /64 prefix is your tenant block; the low 64 bits are the derived id
/128 = < your tenant /64 prefix > : < derived interface id >
```

Four properties fall straight out of that derivation, and each one is load-bearing:

- **Deterministic.** The same `(key, VIN[, ECU])` yields a byte-identical `/128` every time, on every server: exactly one candidate, never a random retry. A reconnecting car re-derives its own address; both of our authoritative nodes mint the identical identity with zero replication between them.
- **Forge-proof.** The address is a function of a key only the device holds. An attacker with a VIN and even the device's _public_ key still cannot become that car: the server-side secret and the DANE pin (below) are the parts they can never produce.
- **Tenant-bound & fleet-unlinkable.** Your tenant's own `/64` is folded into the derivation. The same key+VIN under a _different_ tenant produces a _different_ address, so an outsider cannot derive or enumerate a car's address in a fleet they don't control, and the classic VIN-enumeration walk is a dead end. A car is not linkable across fleets by address suffix.
- **Liberal in, strict out.** The VIN is accepted generously, whitespace stripped and case-folded, then held to exactly 17 ISO 3779 characters (`A-H J-N P R-Z 0-9`; no `I`/`O`/`Q`). A malformed VIN fails closed with a clear message, never a silent wrong address.

The moment the address is derived it is published as a full identity, atomically: an `AAAA`, a forward-confirmed `PTR`, and a **DANE-EE `TLSA 3 1 1`** record that pins the device's leaf key directly. All of it is DNSSEC-signed to the IANA root and registered in [RDAP](/docs/rdap). That TLSA pin is what turns "the address is derived from a key" into "the address is _provable_ against that key by anyone." See [DANE & TLSA](/docs/dane) for the byte-for-byte record and [DNSSEC](/docs/dnssec) for the chain it hangs from.

> **The private key never moves.** The device submits only its public SPKI: the same public half of the 802.1AR / TPM / secure-element key it holds. The server derives a public _address_ from public inputs plus a server-side secret; it never sees, holds, or derives the vehicle's private key. The car proves ownership later by presenting its own key against the DANE pin.

## Provision a vehicle identity

Provisioning is one control-plane call: `whisper.agents` with `op:'connect'`, `tier:'wireguard'`, the device's public SPKI, and the VIN. It returns the deterministic `/128` and a ready WireGuard configuration so the vehicle's traffic sources _from_ its own identity. The endpoint is `POST https://graph.whisper.security/api/query`, authed with an `X-API-Key` header. No key ever travels in the body.

```
CALL whisper.agents({op:'connect', args:{
  tier:                'wireguard',
  identity_public_key: '<base64 SubjectPublicKeyInfo of the device key>',
  vin:                 '1HGCM82633A004352'
  // ecu_serial: 'TCU-0007'   // optional: a distinct /128 per ECU
}}) YIELD op, ok, status, result, error
   RETURN op, ok, status, result, error
```

**With stock tools:** just `curl`, no Whisper software. A quoted heredoc keeps the Cypher single-quotes intact so it pastes and runs as-is:

```
curl -s https://graph.whisper.security/api/query \
  -H "X-API-Key: whisper_live_xxx" \
  -H "content-type: application/json" \
  -d @- <<'JSON' | jq .
{"query":"CALL whisper.agents({op:'connect', args:{tier:'wireguard', identity_public_key:'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...<SPKI>...', vin:'1HGCM82633A004352'}}) YIELD op, ok, status, result, error RETURN op, ok, status, result, error"}
JSON
```

The response is the standard envelope; `result` carries the derived address and the transport. Because the device holds its own key, no private key is ever returned. Only the public identity and the config that binds egress to it:

```
{
  "op": "connect", "ok": true, "status": 200,
  "result": {
    "tier":               "wireguard",
    "address":            "2a04:2a01:eb5a:ca74:c0de:1e0f:9a3d:71b2",   // the deterministic /128
    "fqdn":               "c0de1e0f9a3d71b2.<tenant>.agents.whisper.online",
    "server_public_key":  "…",
    "endpoint":           "…:51820",
    "dns":                "2a04:2a01:0:53::53",
    "wireguard_config":   "[Interface]\nAddress = 2a04:2a01:eb5a:ca74:c0de:1e0f:9a3d:71b2/128\n…"
  },
  "error": null
}
```

Drop the returned `wireguard_config` onto the vehicle's telematics unit (or feed it to `wireproxy` for a no-root, userspace tunnel) and every packet it sends now sources from its own `/128`. The backend authorizes on that address (a forge-proof, attributable, revocable network identity) instead of a token anyone could carry. For the full transport mechanics and the SOCKS5 / AnyIP alternatives, see [Connect & egress](/docs/connect); for every other op on this same endpoint, the [Control plane](/docs/control-plane) reference.

> The `whisper` CLI ships `create --register`, `verify --trustless`, `policy`, `logs`, and `kill --revoke`. A dedicated `--vin` flag is **on the roadmap, not shipped**. Provision vehicles today via the control-plane call above, which is live. When the flag lands it will be a thin wrapper over exactly this call.

## Idempotent, with honest errors

Because the address is _derived_, provisioning is naturally idempotent, and the failure modes are clear rather than surprising, Postel all the way down:

| You send | You get |
|---|---|
| the **same** key + VIN again (same tenant) | the **same** `/128`: a re-derivation, not a new allocation |
| the same key with a **different** VIN (same tenant) | `409`: the reused identity is never silently re-pinned to a mismatched address |
| a **non-string** `vin` (or `ecu_serial`) | `400` with a helpful `detail`, never an opaque `500` |
| `vin` without `identity_public_key` | `400`: a device derives its address from its _own_ key |

## Verify: keyless, no account

The identity half is public on purpose: anyone (an OEM allowlisting a sanctioned consumer, an auditor, a suspicious peer) can prove a vehicle's `/128` without a Whisper account and without trusting Whisper's word. Four independent checks, all from tools already on the machine:

```
# 1. Forward-confirmed reverse DNS: the address names the vehicle, the name resolves back
dig -x 2a04:2a01:eb5a:ca74:c0de:1e0f:9a3d:71b2 +short
c0de1e0f9a3d71b2.<tenant>.agents.whisper.online.

# 2. The keyless verdict endpoint (takes an address or an FQDN; ?ip=<target> also accepted)
curl -s https://whisper.online/verify-identity/2a04:2a01:eb5a:ca74:c0de:1e0f:9a3d:71b2 | jq .
{
  "is_whisper_agent": true,
  "dane_ok": true,
  "jws_ok": true,
  "evidence": { "aaaa": "...", "ptr": "...", "tlsa": "3 1 1 b653a4ef…fcb82d1d" }
}

# 3. The registry record: RDAP, IP-anchored to the /128
curl -s https://whisper.online/ip/2a04:2a01:eb5a:ca74:c0de:1e0f:9a3d:71b2 | jq '.handle, .parentHandle'

# 4. The full chain re-derived on YOUR machine, against the IANA root: Whisper NOT in the trust path
whisper verify --trustless c0de1e0f9a3d71b2.<tenant>.agents.whisper.online
```

A target that isn't a Whisper identity gets a clean `200 {"is_whisper_agent": false}`. A negative verdict is a successful answer, not an error; only genuinely malformed input draws a `400`, never a `500`. `--trustless` is the strong form: it validates DNSSEC from the root _in-process_, on your resolver, so the proof holds even for a party that won't take Whisper's word for anything. The full seven-proof walk lives in [Verify an agent](/docs/verify).

## Revoke: worldwide, in one call

A compromised or decommissioned vehicle is one `revoke` away from having no network identity anywhere. The call tears down the `/128`, its `PTR`, and its DANE pin across both authoritative servers, and the change propagates at DNS-TTL speed:

```
CALL whisper.agents({op:'revoke', args:{agent:'2a04:2a01:eb5a:ca74:c0de:1e0f:9a3d:71b2'}})

# prove it, zero Whisper software, the same stock tools that proved it existed:
dig -x 2a04:2a01:eb5a:ca74:c0de:1e0f:9a3d:71b2 +short           # -> nothing
curl -s https://whisper.online/verify-identity/2a04:2a01:eb5a:ca74:c0de:1e0f:9a3d:71b2
# -> {"is_whisper_agent": false, ...}
```

Revocation isn't a database flag you have to trust; it's provable the same way the identity was: the reverse lookup goes empty and the keyless verdict flips to `false` for everyone, everywhere, at once. Contrast a shared bearer token, where a rotation reaches only the callers you can still reach.

## Attribution: name whoever already scraped you

Identity stops the _next_ forgery. To name whoever _already_ scraped a fleet (across rotating clouds and residential proxies), the same API key opens the read-only attribution graph on the same endpoint. `whisper.identify` takes an address and returns the operator behind it, stitched across Amazon → Google → Azure hops that a raw last-IP loses:

```
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(\"203.0.113.45\")"}' | jq .
```

The read-only Cypher surface (`identify`, `origins`, `walk`, `variants`, `history`) runs over the same `POST https://graph.whisper.security/api/query` with your key. There is no `whisper identify` CLI subcommand. This is the API call, and it is live. Full verbs and shapes in [Graph & cognition](/docs/graph-api).

## Where this fits (and where it doesn't)

Whisper anchors the vehicle's identity at the **car↔cloud IP boundary**: the network endpoint your backend authorizes. It is additive: it complements the roots of trust you already run and does not try to replace them, and it deliberately stops at the plug.

- **802.1AR IDevID / TPM / secure element.** The device's birth key is the _input_ to the derivation. Whisper adds a routable, DNSSEC/DANE-provable name on top of the silicon root of trust. It complements that root; it does not replace it.
- **COVESA uProtocol / Eclipse SDV.** A `UUri.authority_name` may be a domain name, a VIN, or an IPv6 address, so a Whisper `/128` or FQDN drops straight into that authority field as a transport-independent, verifiable identity. Standards-based, not a vendor endorsement.
- **AWS IoT Core / IoT FleetWise.** There the cloud is the CA and mutual TLS rides X.509. Whisper adds an _out-of-tenancy_ identity the backend can allowlist and any third party can verify against DANE without cross-signing. It complements your IoT Core CA; it does not replace it.
- **Where Whisper does _not_ go.** Not inside SecOC (in-vehicle bus message authentication), not the V2X/SCMS air interface, and not the ISO 15118 Plug&Charge handshake at the connector. Whisper is the network identity of the endpoint on the IP fabric: the doorway, not the plug.

For mapping these identities to UN **R155** / ISO 21434 evidence and SOC export, see [R155 · ISO 21434 · ATM](/docs/automotive-compliance). The Splunk, Microsoft Sentinel and OpenCTI connectors are shipped; STIX/TAXII + Auto-ISAC ATM JSON export is on the roadmap and labelled as such there. No specific OEM is named, endorsed, or implicated as a breach victim anywhere in these docs; the abuse patterns cited are the public, cross-industry ones.

## Next

- [Fleet-API-abuse cure](/docs/fleet-api-abuse-cure): this identity, applied to the exact VIN-enumeration / BOLA problem it was built for
- [Platform integrations](/docs/automotive-integrations): dropping the `/128` into 802.1AR, uProtocol, AWS IoT Core, and Android Automotive
- [DANE & TLSA](/docs/dane): the byte-for-byte record that makes the address provable against the device key
