# DER & inverter identity

**Bind the IEEE 2030.5 `LFDI` a DER already carries to a routable IPv6 `/128` derived from the device's own public key, so the address _is_ the inverter: forge-proof, tenant-private, DNSSEC-anchored, DANE-pinned, and revocable worldwide in one call. It's the off-switch its own PKI never had.**

This is the spine of the energy vertical, and it is **shipped and live**. Everything else (the grid-API-abuse cure, the platform integrations, the NERC-CIP evidence) builds on one idea: a DER's network address stops being a disposable label a proxy can rotate and becomes a cryptographic fact only that device's silicon can stand behind. IEEE 2030.5 _already_ computes a genuinely good key-derived name for every inverter, the `LFDI`, and then locks it in a private root that can never revoke it. Whisper keeps that property and adds the two things it lacks.

> **Two tiers, per Postel's Law.** With **no API key** anyone can _verify_ a DER'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, watch who's enumerating it, and revoke it. Verification never needs an account; the control plane does.

## The LFDI: a key-derived name, in a cage

Start with what the grid already has, because it's better than most people realize. Under CA **Rule 21** and the IEEE 2030.5 **CSIP** profile, every `EndDevice` is named by its **LFDI**, the _Long Form Device Identifier_. The LFDI is **not** a serial number a factory typed into a spreadsheet. It is the **leftmost 160 bits (the first 20 octets) of the SHA-256 hash of the device's own DER-encoded X.509 certificate**, written as 40 hex digits grouped in fours. Its short form, the **SFDI**, is the leftmost 36 bits of the same hash plus a single decimal check digit. In other words, the standard already hashes the device's own key material into a globally-unique, structured name: the same instinct Whisper is built on. That's a real head start.

The trouble isn't the name. It's the four walls around it. The LFDI's trust hangs off a **private** hierarchy (`SERCA → MCA → MICA`, the SunSpec/Kyrio managed PKI) where devices carry SERCA trust anchors and each path chains to exactly one root. That's deliberately _not_ Web PKI, so nothing outside the utility's own ecosystem can validate it. Aggregators register LFDIs **out-of-band**, publishing an LFDI-plus-site-ID pairing to the utility server by hand. And the certificates are, by design, permanent:

> Per the CSIP managed-PKI model: _"no certificates within the MPKI can be revoked because they are embedded permanently within devices; CAs are not required to issue CRLs nor provide OCSP"_. In its own words, _"life-long certificates that cannot be updated or revoked and must be known privately."_

So the LFDI is a good key-derived name that is nonetheless boxed in on four sides at once:

| The LFDI already has | The LFDI still lacks |
|---|---|
| **Key-derived identity**: 160 bits of SHA-256 over the device's own certificate; the `EndDevice` primary key | **Not addressable**: an application-layer string, never a routable network endpoint you can reach or authorize against |
| **Globally unique & structured**: flows through every CSIP / Rule 21 deployment | **Not publicly verifiable**: trust lives in the private `SERCA→MCA→MICA` root, not Web PKI; useless off-ecosystem |
| **Mandated**: the default identifier under CA Rule 21 and IEEE 2030.5 | **Allow-listed by hand**: LFDI + site ID registered out-of-band, per utility, per aggregator |
| **Bound to a cert only the device holds** | **Non-revocable**: life-long, with _no CRL, no OCSP_, and no off-switch at all |

Read the right-hand column as a to-do list. Whisper does exactly those four things, without touching the left-hand column and without re-keying the 25M+ inverters already in the field.

## The cure: a routable, revocable `/128`

Whisper keeps the LFDI's one good property (_identity derived from the device's own key_) and adds the two the private root withholds: **public verifiability** and **DNS-TTL, cross-operator revocation**. It does this by giving the device a real, routable IPv6 `/128` out of `2a04:2a01::/32` (announced by **AS219419**) that is a deterministic function of the device's _public_ key with the LFDI as the domain separator, then anchoring that address into DNS so anyone can prove it and one call can retire it.

```
already in the device                                 public · verifiable · revocable: what Whisper adds
        │                                                                   │
┌───────────────────┐   public key    ┌──────────┐  DNSSEC +    ┌─────────────────────────────┐
│  Device key       │   + LFDI         │  /128    │  DANE-EE     │  A name anyone can verify   │
│  LFDI=SHA-256(cert)│ ───────────────▶│ 2a04:    │  3 1 1       │  whisper verify --trustless │
│  first 20 bytes   │                  │ 2a01:    │ ────────────▶│  RDAP-registered · DNSSEC   │
│  EndDevice/TPM/SE │                  │ 5e0::50c │              │  to the IANA root           │
│  private key      │                  │ routable │              │  our API is NOT in the      │
│  sealed on-device │                  │ tenant-  │              │  trust path                 │
└───────────────────┘                  │ bound    │              └──────────────┬──────────────┘
                                        └──────────┘                             │
                                                        op:'revoke' ──▶ gone worldwide at DNS-TTL
```

Because the address is _derived from_ a key only that device holds, you cannot present a DER identity whose key you don't have. "One token → a whole fleet" becomes physically impossible; a stolen bearer off the wrong address is inert; and every forgery is a DNSSEC/DANE inconsistency any verifier catches for free. That is the whole point of moving trust from a _claim_ the caller carries to the _machine_ on the other end.

## How the derivation works

The `/128` isn't drawn from a pool and written into a database. It is _computed_, the same way on every node, from inputs the device already has. Three things go in:

| Input | What it is | Where it lives |
|---|---|---|
| **Device public key** | the `SubjectPublicKeyInfo` (SPKI) of the device's key: the public half of its IEEE 2030.5 `EndDevice` certificate, a SunSpec/Kyrio device cert, a TPM, or a secure element | the **public** half is submitted; the private key **never leaves the device** |
| **LFDI** | the IEEE 2030.5 Long Form Device Identifier the unit already carries, used here as the domain separator (a bare DER serial or SFDI is accepted too) | submitted with the request; the public index |
| **ECU / component serial** _(optional)_ | a per-component domain separator, so one site can hold many addressable identities (the inverter, a battery gateway, a specific controller) | optional; omit it for a single per-device 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,  LFDI [, component 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, LFDI[, serial])` yields a byte-identical `/128` every time, on every server: exactly one candidate, never a random retry. A reconnecting inverter 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 the LFDI and even the device's _public_ key still cannot become that inverter: 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 + LFDI under a _different_ aggregator produces a _different_ address, so an outsider cannot derive or enumerate a device's address in a fleet they don't control, and a unit is not linkable across operators by address suffix. The LFDI flows through every CSIP deployment and is _not_ a secret, yet **the LFDI alone yields nothing**: you cannot go LFDI → `/128` without the key, there is no enumerable directory, and RDAP / reverse-DNS return the registry object, never the device's whereabouts.
- **Liberal in, strict out.** The LFDI is accepted generously (whitespace stripped, case-folded, the display grouping removed), then held to exactly 40 hex characters; a bare DER serial is accepted where a unit has no LFDI. A malformed identifier 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 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 `EndDevice` / SunSpec-Kyrio / TPM / secure-element key it already holds. The server derives a public _address_ from public inputs plus a server-side secret; it never sees, holds, or derives the device's private key. The inverter proves ownership later by presenting its own key against the DANE pin.

**You can pin the cert you already speak.** If your CSIP head-end already terminates the device's IEEE 2030.5 `EndDevice` certificate, you can publish a [DANE](/docs/dane) `TLSA` record over DNSSEC that pins _that_ endpoint certificate. That turns the private single-root trust into something any relying party can validate against the IANA root, and cuts single-CA trust risk, with no change to the device. Whisper's derived `/128` and a DANE-pin of your existing 2030.5 cert are complementary: one gives the device a publicly verifiable _address_, the other makes its existing _certificate_ publicly verifiable.

## Provision a DER identity

Provisioning is one control-plane call: `whisper.agents` with `op:'connect'`, `tier:'wireguard'`, the device's public SPKI, and the LFDI passed as `device_id`. It returns the deterministic `/128` and a ready WireGuard configuration so the DER'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>',
  device_id:           '3F2504E04F8911D39A0C0305E82C33018B2E44F9'   // the IEEE 2030.5 LFDI
  // ecu_serial: 'INV-GW-07'   // optional: a distinct /128 per component
}}) 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>...', device_id:'3F2504E04F8911D39A0C0305E82C33018B2E44F9'}}) 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:5e0::50c",                 // the deterministic /128
    "fqdn":               "lfdi-3f2504e0.der.<tenant>.agents.whisper.online",
    "server_public_key":  "…",
    "endpoint":           "…:51820",
    "dns":                "2a04:2a01:0:53::53",
    "wireguard_config":   "[Interface]\nAddress = 2a04:2a01:5e0::50c/128\n…"
  },
  "error": null
}
```

Drop the returned `wireguard_config` onto the DER's communication card or 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 DERMS head-end 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 `--lfdi` flag is **on the roadmap, not shipped**. Provision DERs today via the control-plane call above, which is live. When the flag lands it will be a thin wrapper over exactly this call. The `device_id` argument is generic on purpose: pass an LFDI, an SFDI, or a bare DER serial, whatever native identifier the unit carries.

## 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 + LFDI again (same tenant) | the **same** `/128`: a re-derivation, not a new allocation |
| the same key with a **different** LFDI (same tenant) | `409`: the reused identity is never silently re-pinned to a mismatched address |
| a **non-string** `device_id` (or `ecu_serial`) | `400` with a helpful `detail`, never an opaque `500` |
| `device_id` 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 (a utility allow-listing a sanctioned DER, an ISO reconciling a dispatch, an auditor, a suspicious peer) can prove a device'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 DER, the name resolves back
dig -x 2a04:2a01:5e0::50c +short
lfdi-3f2504e0.der.<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:5e0::50c | 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:5e0::50c | 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 lfdi-3f2504e0.der.<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 keyless walk lives in [Verify an agent](/docs/verify).

## Revoke: worldwide, in one call

A compromised or decommissioned inverter 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:5e0::50c'}})

# prove it: zero Whisper software, the same stock tools that proved it existed:
dig -x 2a04:2a01:5e0::50c +short                       # -> nothing
curl -s https://whisper.online/verify-identity/2a04:2a01:5e0::50c
# -> {"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. This is precisely what the LFDI's own MPKI forbids: _life-long certificates that cannot be updated or revoked_. Contrast a shared bearer token, where a rotation reaches only the callers you can still reach. Compromise one inverter and you've compromised _that inverter_, not the fleet. The fleet-takeover failure mode is structurally removed.

## The lifecycle, end to end

A DER identity is not a one-shot registration; it tracks the device through its whole service life. Because every `/128` is _derived_, each stage is a plain control-plane call, and each mint or revoke is written to a public, tamper-evident log. Nothing is issued in the dark.

```
 Factory key ──▶ In-life dispatch ──▶ Module swap / ──▶ Incident revoke ──▶ Decommission /   time ▶
 born on-device   /128 + egress        repower           cut off worldwide    resale
 LFDI computed    op:'connect'         re-key → new /128  op:'revoke'          retire → re-register
      │                │                    │                  │                    │
   (no ledger)         └──────────┬─────────┴─────────┬────────┴──────────┬─────────┘
                                  ▼                    ▼                   ▼
      ┌───────────────────────────────────────────────────────────────────────────────┐
      │  public Merkle transparency log · every mint & revoke                           │
      │  Ed25519-signed · Bitcoin-anchored                                              │
      └───────────────────────────────────────────────────────────────────────────────┘
```

| Stage | What happens | Call |
|---|---|---|
| **Factory key** | The device is born with its key; its LFDI is the SHA-256 of the resulting certificate. Nothing is minted yet. | _native (no Whisper call)_ |
| **In-life dispatch** | At commissioning, derive the `/128` and source-bind egress; the DERMS head-end now authorizes the address, not a bearer. | `op:'connect'` |
| **Module swap / repower** | A replaced comms card or a repowered unit carries a new key, so it re-keys to a fresh `/128`; the old address is revoked in the same breath. | `op:'connect'` + `op:'revoke'` |
| **Incident revoke** | A compromised unit (or a whole compromised model) is cut off worldwide at DNS-TTL; verify flips to `false` everywhere. | `op:'revoke'` |
| **Decommission / resale** | A change of aggregator or an end-of-life retire is one revoke and a re-register to the new owner; `op:'erase'` renders the log leaf's meaning unrecoverable while its proofs stay valid. | `op:'revoke'` (+ `op:'erase'`) |

**Nothing issued in the dark.** Every mint and every revoke along that timeline lands in a public, append-only [Merkle transparency log](/docs/transparency) (RFC 6962 `tlog-tiles` with signed-note checkpoints), Ed25519-signed and anchored to Bitcoin via OpenTimestamps: an auditable, non-repudiable issuance-and-revocation trail your PSIRT and your regulator can replay. _Honest status:_ the log is tamper-evident, signed, and Bitcoin-anchored today, but it is **not yet independently witnessed**. Our two authoritative nodes co-signing is availability, not independence. The log already speaks the C2SP `tlog-witness` protocol, so any external witness can co-sign; that step is on the roadmap, and we label it as such rather than overclaim.

## Govern a live DER: egress, budget, lookups

Revocation is the kill-switch. In between, the same control plane governs exactly what a _live_ DER may reach, caps it, and tells you who is looking at it. Because egress is source-bound to the device's `/128`, policy is enforced by name and by address, default-deny:

```
# default-deny: this inverter may reach ONLY its DERMS head-end and OTA endpoint
whisper policy set --default deny --allow derms.example-vpp.com,ota.example-vpp.com

# per-device firewall (allow/deny by host, cidr, or port) + a daily budget with a kill-switch
CALL whisper.agents({op:'firewall', args:{agent:'2a04:2a01:5e0::50c',
     deny:['0.0.0.0/0'], allow:['derms.example-vpp.com:443','ota.example-vpp.com:443']}})
CALL whisper.agents({op:'budget',   args:{agent:'2a04:2a01:5e0::50c', max_mb_per_day:50}})
```

**See who's enumerating your fleet, before the command lands.** An identity you can prove is also an identity you can _watch_. Because every DER's name resolves through Whisper's own authoritative DNS and RDAP, the owner can ask who looked: a reconnaissance tripwire the LFDI's private, out-of-band registry never gave you. `op:'lookups'` returns who resolved or RDAP-queried a device's identity, so you catch an attacker _enumerating_ the fleet in recon, not in the post-mortem:

```
# who has been resolving / RDAP-querying this DER's identity, and when
CALL whisper.agents({op:'lookups', args:{agent:'2a04:2a01:5e0::50c', window:'24h'}})

# the same reverse-observability view, keyless, per address
curl -s https://whisper.online/ip/2a04:2a01:5e0::50c/lookups | jq .
# -> who resolved this DER's PTR/AAAA/TLSA and hit its RDAP object
```

Paired with `op:'logs'` (the device's own outbound activity) you have both halves of the picture: what a DER reaches out to, and who is reaching in to look at it. And because each dispatch and telemetry stream can be [signed under](/docs/sign-outputs) the DER's forge-proof `/128`, the ISO, the utility, and your own market settlement can trust the numbers came from the real device: non-repudiable telemetry, not a spoofed feed. The full policy surface is in [Egress governance](/docs/egress-governance).

## 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 the 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(\"34.90.x.x\")"}' | jq .
# operator fingerprinted across AWS / GCP / Azure; residential swarm collapsed by JA4
```

The read-only Cypher surface (`identify`, `origins`, `walk`, `variants`, `history`) runs over the same `POST https://graph.whisper.security/api/query` with your key, and each verb returns a reproducible, replayable JSON evidence chain your OT SOC, your PSIRT, and a regulator can replay. 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 DER's identity at the **device↔cloud IP boundary**: the network endpoint your DERMS or aggregator 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 substation fence.

- **IEEE 2030.5 / SunSpec-Kyrio `EndDevice` cert (the SERCA→MCA→MICA root).** The device's certificate key is the _input_ to the derivation. Whisper adds a routable, DNSSEC/DANE-provable name on top of that private root of trust, and can DANE-pin the same cert your CSIP head-end already speaks. It complements that PKI; it does not replace it.
- **DERMS / VPP and OpenADR VTN head-ends.** These _consume_ the manufacturer's LFDI and authorize a bearer or a venID + JWT. Whisper gives them a forge-proof address to authorize _instead of_ the token, and an off-switch the mandated PKI lacks: a drop-in for the identity the head-end already trusts, made revocable.
- **ISO 15118 Plug&Charge / OCPP for EVSE.** There the trust rides a hierarchical V2G root with fragile optional OCSP. Whisper adds an _out-of-tenancy_, DANE-verifiable identity per EVSE that any party can check without cross-signing, alongside your 15118 PKI, not inside its handshake.
- **Where Whisper does _not_ go.** Not inside the IEC 61850 GOOSE/MMS substation bus, not the DNP3 outstation link, and not the ISO 15118 charging handshake at the connector. Whisper is the network identity of the endpoint on the IP fabric: the doorway, not the bus.

For mapping these identities to **NERC CIP-013 / CIP-005** vendor-remote-access evidence, IEC 62351, CA Rule 21, EU NIS2 / NCCS, and NISTIR 7628, see [NERC CIP · IEC 62351 · Rule 21](/docs/industries/energy/energy-compliance). The Splunk, Microsoft Sentinel and OpenCTI connectors are shipped; STIX 2.1 over TAXII + E-ISAC machine-readable export is on the roadmap and labelled as such there. No specific operator or vendor is named, endorsed, or implicated as a breach victim anywhere in these docs; the abuse patterns cited are the public, cross-industry ones.

## Next

- [Grid-API-abuse cure](/docs/industries/energy/grid-api-abuse-cure): this identity, applied to the exact fleet-takeover / BOLA problem it was built for
- [Platform integrations](/docs/industries/energy/energy-integrations): dropping the `/128` into IEEE 2030.5 / CSIP head-ends, DERMS & VPP, OpenADR, and SunSpec
- [DANE & TLSA](/docs/dane): the byte-for-byte record that makes the address provable against the device key
