ESP32 hardware running Offline Protocol
ESP32 hardware runs Offline Protocol today as a mesh relay. The IntelligenceMesh Relay is an M5StickC Plus2 with an ESP32-PICO-V3-02 (dual-core, Wi-Fi, Bluetooth 4.2), pre-flashed with IntelligenceMesh by Offline Protocol firmware. According to its product page:
- It relays encrypted Offline Protocol mesh traffic over Bluetooth LE, using BLE GATT in both roles, peripheral and central, and relays Bitchat mesh traffic at the same time.
- It works with what you build. The page lists it as compatible with every Offline Protocol app and every project built on the open SDKs.
- It needs no setup. When it is switched on it starts discovering nearby mesh devices and relaying their messages, with no Wi-Fi and no account.
- It pairs with the IntelligenceMesh companion app for relay stats, themes and over-the-air firmware updates.
- It has a developer module system. A JSON-based API lets a React Native app push data over Bluetooth LE for the relay to render on its screen.
A relay like this extends range for the phones and hosts around it, which is the job a fixed node does in a mesh network. For what an ESP32 is and how the chips in the family differ, see what is an ESP32?
offline-protocol-leaf for your own device
offline-protocol-leaf is the Rust crate for firmware teams who want their own device, such as a door lock, a sensor or a mains-powered relay, to be an encrypted Offline Protocol peer. It parses frames, validates addressing and runs RFC 9420 MLS through mls-rs, holding an end-to-end encrypted conversation with a phone under the same guarantees a phone gets: the same frames, the same envelope and the same trust gates. Its API centres on two types:
LeafDevice, a frame-level state machine. Hand it an inbound message and it returns the frames to send and what happened.LeafStore, one blob-storage seam the device implements over its secure key storage, with persist-before-emit enforced.
The leaf joins a group as a member that never issues commits. The phone, or another full peer such as a Linux host, creates the group and issues every commit; the device joins, opens, answers and persists. A Raspberry Pi running the Python package is one such full peer. Each leaf pairs with a full peer over the radio path its firmware provides.
The crate builds without the standard library for bare-metal targets, as what is no_std Rust? describes. With Rust 1.87 or newer:
cargo add offline-protocol-leaf@=0.31.0 --no-default-features --features bare-metal-rng
Keep bare-metal-rng and the default std feature apart.
offline-protocol-leaf build targets
As of 0.31.0 (October 2026), offline-protocol-leaf builds for these targets. CI builds and lints the crate for the four bare-metal rows and gates the WASI build:
| Chip family | Rust target | Inside an esp-hal 1.2.2 firmware |
|---|---|---|
| Cortex-M33 (for example Silicon Labs xG24) | thumbv8m.main-none-eabihf | Not applicable |
| ESP32-C6, ESP32-H2 | riscv32imac-unknown-none-elf | Type-checks |
| ESP32-C3, ESP32-C2 | riscv32imc-unknown-none-elf | Settle one portable-atomic backend (see the notes below) |
| Cortex-M0 and M0+ (for example RP2040) | thumbv6m-none-eabi | Not applicable |
| WebAssembly with WASI | wasm32-wasip1 | Not applicable |
The ESP32 rows cover the RISC-V members of the family. The Xtensa chips (the original ESP32, the ESP32-S2 and the ESP32-S3) use Rust targets outside rustup and are not in the crate’s target list.
ESP32 RISC-V and Cortex-M integration notes
Each target family has one or two integration details for the firmware build:
- ESP32-C6 and ESP32-H2. The crate type-checks in a firmware crate that also depends on esp-hal 1.2.2, checked with
cargo check. - ESP32-C3 and ESP32-C2. mls-rs and esp-hal 1.2.2 select different portable-atomic backends on these chips, so a firmware that combines the two settles which backend it uses.
- ESP32-C3, ESP32-C2 and Cortex-M0. These targets have no atomic compare-and-swap, so the firmware links a critical-section implementation.
- Every target. Build the store handle with
shared_store, which works everywhere.
WASI build of the leaf
The leaf also builds for wasm32-wasip1, and CI gates both halves:
- The
no_stdbuild withoutbare-metal-rng. On WASI,getrandomreads the runtime’srandom_get, so the host registers no symbol, and keys are as strong as the runtime’s entropy source. - The
stdbuild through thewasi_host_shimexample. The shim takes the time and the frames from the runtime over its standard streams and prints the frames the device owes.
From an SDK checkout:
rustup target add wasm32-wasip1
cargo build -p offline-protocol-leaf --example wasi_host_shim --target wasm32-wasip1 --release
The WASI target is wasm32-wasip1; wasm32-unknown-unknown is a different target and is not one the crate supports.
Firmware integration for an Offline Protocol leaf
A leaf device’s firmware integrates five things around the crate:
| Integration | What firmware supplies |
|---|---|
| Radio | Receive and send protocol frames over the device’s chosen link. |
| Entropy | Wire getrandom to the part’s hardware source with getrandom::register_custom_getrandom!. The crate leaves this to the firmware on purpose, so each device uses its own hardware source. |
| Time | Pass now_unix_secs at pairing, so key packages carry the right validity and are accepted. |
| Durable storage | Implement LeafStore atomically per entry, so ratchet state survives a power cut intact. |
| Authorization | Decide when the radio accepts a pairing and what a given peer may read or actuate. A session proves who a peer is; the firmware decides what that peer is allowed to do. |
For internet transport, the firmware either implements a relay client with connection, authentication and address proof, or works beside a companion full peer that already has one. An existing direct upload from the device to its own backend can stay as it is. Measure flash, RAM, latency, power, storage endurance and restart behaviour on the part you choose, as the embedded docs recommend. For the general picture of fitting end-to-end encryption onto a microcontroller, see can a microcontroller run encrypted mesh messaging?