rusty_esp_mid: ESP32 Device Identity Your Household Owns
rusty_esp_mid gives each ESP32 its own P-256 did:mata, a signed manifest and owner adoption, so the household, not a vendor cloud, decides who controls it.

How does an ESP32 get its own identity with rusty_esp_mid?
On first boot the ESP32 generates a P-256 key from its hardware random generator and derives a did:mata from the public key. The key is written to a dedicated identity partition that reflashes and Wi-Fi changes do not touch. The device signs its capability manifest with it, and an owner adopts the device with a signed grant the device pins.
rusty_esp_mid gives every Janus ESP32 its own identity. On first boot the device generates a P-256 key from its hardware random generator, derives a did:mata from it, and keeps the key in a partition of its own that reflashes and Wi-Fi changes never touch. It signs what it claims about itself, and it can be adopted by an owner it then recognises and a stranger cannot impersonate. Pure Rust, no C, no FFI, no_std by default.
The idea underneath is simple: the household owns the device. There is no vendor account to claim it, no cloud certificate to renew, and no server that can quietly take it back. The code is at github.com/Remade-With-Rust/rusty_esp_mid, and it speaks the same identity model as the wider MATA stack.
A device that knows who it is
did:mata on P-256
Every device in the family holds its own did:mata: the text did:mata: followed by the base58 encoding of a 33-byte compressed P-256 public key. Because the identifier is the key, it is self-certifying. Anyone holding a signature can check it against the DID without asking a certificate authority.
The curve is fixed on purpose. mID, the identity system the MATA home computer already uses, is P-256 throughout, so rusty_esp_mid uses P-256 too: never ed25519, never secp256k1. The rule is blunt: the family interoperates with mID or it is not mID. Signatures from this crate match the reference signer byte for byte for the same key, a device-signed assertion verifies in the reference verifier with a replay refused, and the capability check agrees with the reference implementation on 144 of 144 cases.
Signatures are always low-s, and every verifier in the Janus family rejects the malleable high-s twin rather than quietly normalising it. An audit found one upstream verifier that still accepts it; that is pinned by a test and filed as an issue.
A signed manifest, measured on a chip
A device describes itself with the capability manifest defined in rusty_esp_core: model, firmware, chip and each capability as available, preview or planned. rusty_esp_mid signs those exact bytes with the device key. A maker can add a second signature over the model and firmware, which lets the home computer tier a device as maker-certified rather than bring-your-own.
On a Seeed XIAO ESP32-S3 Sense at 240 MHz, a signature costs about 95 ms and a verification about 151 ms, across 100 of each with 100 of 100 verifying. That shapes design: a sensor cannot sign every reading at ten a second, and a mesh where every peer checks every peer is bounded by verification. So Janus signs per session and uses cheaper per-frame authentication, as the rusty_esp_signal link does.
Adoption: the household owns the device
Owner adoption, and the two refusals that matter
Adoption is how a rusty_esp_mid device learns who its owner is. The owner signs a compact record naming the device, the owner, the owner's genesis key, the capabilities granted and a roster version. It is about 300 bytes, small enough for radio links where a full sign-in token (1,511 bytes for one device, 23,472 at the 64-device cap) would not fit.
The device verifies the record and pins the owner's key on first adoption. After that it refuses any adoption not signed by that owner. Two refusals decide whether this is real security:
- A stranger replaying the owner's record. An adoption record is public once sent, so only the key named inside it may use it.
- A superseded roster version. If an older record were accepted, rotating a key would revoke nothing.
Both refusals were measured on silicon, on the XIAO, at the first attempt, and the owner then read private telemetry. The owner in that run was a deterministic bench key, not a person, and the tooling says so in its own output.
Rehoming, revoking and resetting
Ownership has to change sometimes. Rehoming is a new adoption signed by the pinned owner. Revocation is the owner rotating their roster: the device sees a version it cannot accept and drops back to unadopted. A physical factory reset clears the pin; that step is planned and waits on the board. There is no "scan and pick a broker" path in a field build.
This is the counterpart to vendor claiming flows, where a device proves itself to a cloud and the cloud decides who controls it. Here the device and the household decide, and the home computer checks signatures it can verify on its own. For the wider argument, read smart home without the cloud and the Own vision.
use rusty_esp_mid::prelude::*;
let identity = NodeIdentity::load_or_create(&mut kv, &mut rng, "janus")?;
println!("{}", identity.did_string());
Keys that stay put, and updates you can trust
Hardware-bound keys: eFuse-wrapped NVS or a secure element
Where the key rests decides how strong ESP32 device identity really is, and the plan is honest about each tier. Espressif's Digital Signature peripheral signs RSA, not P-256, so it cannot hold a did:mata key directly. The planned mapping for maker-certified devices is to keep the P-256 key in encrypted NVS under a key-encryption key held in an eFuse block, so the key never sits in flash in plaintext. A true "never leaves silicon" key needs a secure element such as the ATECC608, which is P-256 native. Both paths are written into the plan and wait on hardware.
Today the identity survives six whole-image reflashes, a re-provisioning and three flashes from another session. The storage layer reports its real protection and refuses a plaintext partition unless the allow-insecure-dev feature is on. Current development firmware runs with that feature because flash encryption is off. Turning it on burns eFuses permanently, so proving encryption at rest needs a sacrificial board. See Espressif's flash encryption documentation for why that step is one-way.
Signed OTA checks
A device the household owns should only accept updates the household allows. The update path, carried over the mesh by rusty_esp_iroh, checks every condition before reading one byte of the image: the caller is the owner, the manifest declares the update capability, the maker is trusted, and the chip, model, size and maker's signature all match. Only then does the device write into the inactive slot, hashing as it goes.
On silicon, a maker-signed update committed into the other slot, booted, and stayed after a hard reset, with the device identity untouched. A bad signature never reaching the write step, and an unvalidated image rolling back, are proven on the host so far. When you are ready to put rusty_esp_mid on a board, espino generates the identity partition for you. The project lives at Remade With Rust.
FAQ
Quick answers for builders evaluating this technology.
What is a did:mata?
A decentralised identifier derived from a P-256 public key: did:mata followed by the base58 encoding of the 33-byte compressed key. It is self-certifying, so anyone can check a signature against it without a certificate authority or a vendor server.
How does adoption work with rusty_esp_mid?
The owner signs an adoption record naming the device, the owner and the capabilities granted. The device verifies it and pins the owner's key on first adoption, then refuses adoptions from anyone else. On silicon, a stranger presenting the owner's own record was refused, and so was an older roster version.
Is the device key stored in hardware on current Janus firmware?
Not yet. Current development firmware runs with flash encryption off and opts in to a plaintext identity partition through a loudly named dev feature. The planned path wraps the key under an eFuse-held key, and a secure element such as the ATECC608 is the path for a key that never leaves silicon. Both wait on hardware.
How fast is signing on an ESP32-S3?
On a XIAO ESP32-S3 Sense at 240 MHz, a P-256 signature takes about 95 ms and a verification about 151 ms. That is too slow to sign every reading at ten per second, so Janus signs per session and uses cheaper message authentication per frame.
Can someone push a malicious update to an adopted device?
The update path checks the owner, the device's ota capability, a trusted maker, the chip, the model, the size and the maker's signature before reading a byte of the image. A maker-signed update has run on silicon. Refusal of a bad signature and rollback are tested on the host so far.