Private Group Chat over Nostr on Microcontrollers

What it takes to run end-to-end encrypted group chat on an ESP32 badge: NIP-17 rooms vs NIP-29 vs Marmot/MLS, measured costs, and the gaps in MicroPythonOS's Nostr Chat app.

Richard Nakamoto

15. Oktober 2026October 15, 2026 · 8 Min. Lesezeit8 min read

Yes: a MicroPythonOS badge can take part in end-to-end encrypted group chats over Nostr today, using the same NIP-17 "chat room" scheme that 0xchat and Amethyst use, and about 80% of the code is already in its Nostr Chat app. What is missing is a way to start a group from the badge and a handful of spec details that other clients rely on. The stronger alternative, MLS over Nostr (Marmot), needs cryptography and code that do not exist for microcontrollers yet.

Where MicroPythonOS stands today#

The Nostr Chat app already holds most of a private-group implementation; it only lacks a way to start a group. MicroPythonOS is an open-source operating system for ESP32 boards (240 MHz, 8 MB PSRAM) written in MicroPython, and its chat app runs on a hand-held badge with a small touch screen.

Piece State today
Direct messages NIP-04, and NIP-17 (gift-wrapped, NIP-44 encrypted)
Public groups NIP-28 channels, the "group chat" users see now
Private groups NIP-17 rooms work on the wire: the sender can address several members with a subject, incoming messages are grouped by their member set, and replies go to every member. There is no screen to start one, so the badge can only join rooms that other clients open.
Relay authentication (NIP-42) Not implemented, so inbox relays that gate private messages behind AUTH cannot be read

The gaps that matter for talking to other clients are listed under Gaps found in the current implementation.

The options on Nostr#

Three schemes exist, and only NIP-17 rooms are both end-to-end encrypted and usable from a microcontroller today.

NIP-17 rooms NIP-29 relay groups Marmot (MLS over Nostr)
How it works One message per room, sealed and gift-wrapped once per member with NIP-44 Plain signed events on one relay that enforces membership MLS group keys; messages encrypted under a per-epoch key
End-to-end encrypted Yes, pairwise; relays see only wrapped events from throwaway keys No: the relay operator reads content, senders and membership Yes
Forward secrecy No: a leaked key decrypts past and future messages Not applicable Yes, plus post-compromise security
Membership control None: the member set is the room; changing it starts a new room Admins, invites, kicks and roles, enforced by the relay Admins, add and remove through MLS commits
Group size Spec advises at most 10 members; 0xchat reports under 100 Large Large; welcome messages grow with the group
Works today with 0xchat, Amethyst, Coracle, Snort; Damus in draft; Primal only NIP-04 0xchat, Amethyst, Flotilla, groups.nip29.com; the reference relay was archived in April 2026 White Noise only; 0xchat and Amethyst in beta
Fits a microcontroller Yes, with the cryptography already on board Yes, the cheapest: one signature per message No: needs X25519, Ed25519, AES-GCM, HPKE and a full MLS state machine, none of which exist for MicroPython

Older tricks do not help: NIP-04 has no multi-recipient form and Amethyst stopped sending it in March 2026, and the per-client schemes of Keychat and Iris do not interoperate with anything else.

How a NIP-17 room message travels#

A room message is sealed and wrapped once per member

A room message starts as an unsigned "rumor" (kind 14) that carries the text, one p tag per member and an optional subject. For every member, the sender signs a seal (kind 13) around it and wraps that seal in a gift wrap (kind 1059) addressed to that member, signed by a fresh throwaway key with a randomised timestamp. The sender also makes a wrap for their own key, so their other devices see the message. Relays store the wraps and see only random keys and who receives, never the content. Each member fetches and unwraps only their own copy.

Sending costs one seal and one wrap per member, so a ten-member room means ten encrypted copies. Receiving costs the same whatever the group size.

What it costs on a microcontroller#

Sending to a room costs one encrypted wrap per member, and on the badge the largest cost today is a slow base64 routine, not the cryptography.

Per member, the sender does 1 key generation, 2 ECDH agreements, 2 NIP-44 encryptions and 2 Schnorr signatures; the elliptic-curve work runs in C, the rest in MicroPython. Receiving is the same for any group size: 1 signature check, 2 ECDH agreements and 2 decryptions.

Measured on a laptop running the same MicroPython code (Apple M1 Max, two independent runs), building the wraps for a 100-character message took:

Members Today's base64 With the C base64
1 3–5 ms 1 ms
3 19–26 ms 3–4 ms
5 45–65 ms 6–7 ms
10 281–399 ms 16–17 ms

At 280 characters and 10 members the current code takes 0.6–1.4 s, against 20 ms with the C base64. The cause is a pure-Python base64 in the firmware's compatibility layer that builds one unmasked big integer over the whole input, so its cost grows with the square of the size; it shadows the C implementation on the device as well. Removing that override is a one-file change and is under way.

On the ESP32-S3 itself nothing has been measured yet. Scaling the operation counts to that chip gives an estimate of 0.3–0.8 s per member after the base64 fix, dominated by the six elliptic-curve operations, and likely several seconds for a 5–10 member room with today's code. Treat those figures as an order of magnitude until a device run exists. A wrapped message is 1.7 KB for one member and 3.9 KB for ten members at 280 characters, well inside common relay limits.

Gaps found in the current implementation#

The existing NIP-17 code has only ever been tested against itself, and it misses several requirements that other clients rely on. Each applies to one-to-one messages as much as to rooms.

What happens next#

A "New group" mode and the library fixes are being implemented now, as two pull requests: one on the MicroPythonOS app and one on its micropython-nostr library.

Longer term the ecosystem is moving to Marmot. Running it on this hardware would need an MLS implementation that fits a microcontroller and X25519, Ed25519, AES-GCM and HPKE in the firmware; none of these exist today, so the plan is to track the protocol rather than build it.

Sources#

#esp32#marmot#micropython#micropythonos#mls#nip17#nostr

Written by

Comments

No comments yet. Start the conversation.

Contents

7 sections

  1. Where MicroPythonOS stands today
  2. The options on Nostr
  3. How a NIP-17 room message travels
  4. What it costs on a microcontroller
  5. Gaps found in the current implementation
  6. What happens next
  7. Sources