Release Notes: v0.2.0-beta
Release Date: March 26, 2026
Previous Version: v0.1.17-alpha (February 28, 2026)
Codename: Queue Rotation + Multi-Server + Web Installer
Highlights
- Queue Rotation - Live server migration without disconnection. Four consecutive rotations verified with bidirectional chat, delivery receipts, and post-quantum encryption on 512 KB SRAM.
- Multi-Server Infrastructure - 21 preset SMP relay servers from three operators (SimpleX Chat, Flux, SimpleGo). Single active server with radio-button selection. Server switch triggers live Queue Rotation.
- Web Serial Installer - Flash SimpleGo directly from the browser via WebSerial. Open and Vault mode selection, eFuse status check, erase flash, reboot. No CLI tools required.
- 5 Encryption Layers - Post-quantum key exchange (sntrup761) now active and verified. Hybrid X448 + sntrup761 KEM from the first message.
- TLS Fingerprint Verification (SEC-07) - Server identity verified via SHA-256 key hash pinning at all four TLS connection points (main server, peer server, rotation server, reconnect).
- eFuse Vault Mode - Hardware-secured NVS encryption with HMAC key burned into ESP32-S3 eFuse BLOCK_KEY1. Device generates its own key on first boot. Irreversible.
- Delivery Receipts - Two-checkmark system. Sent, delivered, failed, pending states stored in SD card history (History v2 format).
- 8 Queue Rotation Bugs Fixed - Consecutive rotations work reliably. Root causes identified and surgically fixed via DIAG-based byte-level analysis.
New Features
Queue Rotation (Sessions 49-50)
Live migration of message queues between SMP servers. The 7-phase protocol (QADD, QKEY, KEY, QUSE, Live-Switch, QTEST, Cleanup) is fully implemented in C.
| Phase |
What happens |
| QADD |
Create new queue on target server, send replacement info to peer |
| QKEY |
Receive peer's new E2E key for the replacement queue |
| KEY |
Secure the new queue with peer's sender key |
| QUSE |
Activate the new queue, peer switches to sending there |
| Live-Switch |
Overwrite credentials in RAM and NVS, reconnect to new server |
| QTEST |
Verify bidirectional communication on new queue |
| Cleanup |
Clear rotation state, resume normal operation |
Queue Rotation uses a second TLS connection to the target server. SRAM budget confirmed: ~1,500 bytes per additional TLS connection. Maximum 2 simultaneous TLS connections safe on ESP32-S3.
Multi-Server Infrastructure (Session 49)
| Detail |
Value |
| Total preset servers |
21 |
| SimpleX Chat servers |
14 (Storage + Proxy) |
| Flux servers |
6 (Storage + Proxy) |
| SimpleGo server |
1 (smp.simplego.dev) |
| Maximum servers |
32 (including custom) |
| Selection model |
Radio-button (one active at a time) |
| Persistence |
NVS with version-checked blob serialization |
| Server switch |
Live Queue Rotation (no reboot) |
Web Serial Installer
Browser-based firmware installer at simplego.dev/installer.
| Feature |
Details |
| Browser |
Chrome 89+ or Edge 89+ (WebSerial API) |
| Modes |
Open Mode and Vault Mode selection |
| Flash method |
Merged binary at offset 0x0, eraseAll enabled |
| Baud rate |
921,600 with compression |
| eFuse check |
Reads ESP32-S3 eFuse register (EFUSE_RD_REPEAT_DATA1) to detect HMAC_UP |
| Additional tools |
Erase Flash (with confirmation), Reboot Device, Check Security |
| Flash time |
~60-90 seconds for ~1.9 MB binary |
Delivery Receipts (Session 43)
History v2 format adds 1-byte delivery status after direction byte.
| Status |
Value |
Meaning |
| SENT |
0 |
Message sent, no confirmation yet |
| DELIVERED |
1 |
Peer acknowledged receipt |
| FAILED |
2 |
Send failed |
| PENDING |
3 |
Queued for sending |
Old v1 history files remain v1 forever (no migration). New files use HISTORY_VERSION 0x02.
TLS Fingerprint Verification - SEC-07 (Session 49)
Server identity verified at four connection points:
- Main server connection (boot + reconnect)
- Peer server connection (sending messages)
- Rotation server connection (Queue Rotation target)
- Re-subscription after network loss
SHA-256 hash of server's DH public key compared against stored key_hash from preset or NVS. Connection rejected on mismatch.
Bugs Fixed
Queue Rotation Bugs (Session 50) - 8 Fixes
| Fix |
Problem |
Solution |
| A |
s_complete_logged function-local static, never reset |
File-static variable + smp_tasks_reset_rotation_guard() API |
| B |
Auth/DH backup overwritten every rotation |
Conditional backup: if (!has_peer_auth) - only originals saved |
| C |
rq->snd_id set to Reply Queue snd_id instead of Main Queue |
rq->snd_id = rd->new_snd_id |
| D |
CQ E2E peer cache never invalidated |
Reset in rotation_start() + after Phase 1b |
| Phase 1b |
CQ pipeline needs new E2E keys after rotation |
our_queue.e2e_private = rd->new_e2e_private + peer key from QKEY |
| Cache |
Phase 1b writes after cache fill |
Second smp_tasks_reset_rotation_guard() call after Phase 1b |
| Queue 2 |
App processes only Queue 1 from QADD |
Confirmed via Haskell NonEmpty head pattern - no code change needed |
| DIAG |
Root cause found via byte-level comparison |
Stale peer key (82488db3) used instead of new key (8fca4b2e) |
Other Fixes
| Fix |
Description |
| SEC-03 |
memset replaced with mbedtls_platform_zeroize in smp_storage.c (CWE-14) |
| SMP server fingerprint |
Updated smp.simplego.dev fingerprint after certificate renewal |
| LVGL memory |
Screen lifecycle fix - LVGL pool stable at 31% (was 86%) |
| subscribe_all |
Removed redundant calls - O(N*M) scaling was root cause of Bug #30 |
| smp_app_run |
Refactored from 530 to 118 lines, 5 static helpers extracted |
| smp_handshake |
74 debug statements removed, 1281 to 1207 lines |
Architecture Changes
FreeRTOS Task Architecture
| Task |
Core |
Stack |
Responsibility |
| network_task |
0 |
16 KB PSRAM |
TLS read/write, subscriptions, PINGs |
| smp_app_task |
0 |
64 KB SRAM |
Protocol logic, NVS, Ratchet, Rotation |
| lvgl_task |
1 |
8 KB SRAM |
UI rendering |
| wifi_manager |
0 |
4 KB PSRAM |
WiFi connection management |
New Files
| File |
Purpose |
| smp_servers.c/h |
Multi-server management, 21 presets, NVS persistence |
| smp_rotation.c/h |
Queue Rotation protocol (QADD/QKEY/KEY/QUSE/QTEST) |
| ui_settings_info.c |
Info panel with eFuse status, server management, timezone |
| ui_settings_bright.c |
Backlight control tab |
| ui_settings_wifi.c |
WiFi management tab |
New Patterns
| Pattern |
Description |
| Conditional backup |
if (!has_peer_auth) - peer credentials saved only on first rotation |
| Double cache invalidation |
Static caches reset at rotation_start() AND after Phase 1b |
| DIAG-based debugging |
Hex dump of actual keys in decrypt path for byte-level comparison |
| Lazy TLS |
Rotation TLS opened only when needed (NEW, KEY), closed immediately |
| Live-Switch |
Credentials overwritten in RAM + NVS, reconnect without reboot |
Protocol Knowledge
Documented during implementation and verified against Haskell reference:
| Topic |
Detail |
| X3DH |
4-DH scheme (not 3 like Signal). HKDF-SHA512, salt=32 zeros, info="SimpleXX3DH", output=96 bytes |
| Ratchet |
AES-256-GCM. Message Key via HKDF-SHA256 info="SimpleXMK". Chain Key via "SimpleXCK". 12-byte counter nonce. |
| Header |
2346 bytes fixed (56B X448 key + 4B pn + 4B ns + padding). Encrypted with header key. |
| E2E Layer 2 |
Non-standard XSalsa20: HSalsa20(key, zeros[16]) instead of HSalsa20(key, nonce[0:16]) |
| X448 |
Haskell cryptonite outputs reversed byte order. SimpleGo reverses in smp_x448.c. |
| Zstd |
Level 3 (default). Always active, including first connection request. |
| SMP version |
Client range v1-v4 for QADD. App negotiates v7 for main connection. |
| corrId encoding |
0x30 = Maybe Nothing (pre-shared key). 0x31 = Maybe Just (inline SPKI key). |
| QADD |
App processes only Queue 1 (Haskell NonEmpty head pattern). Queue 2 ignored. |
| findQ |
Compares (SMPServer, SenderId) tuples. keyHash NOT compared by sameSrvAddr. |
Codebase Statistics
| Metric |
Value |
| Source files |
47+ |
| Lines of C |
22,000+ |
| Encryption layers |
5 |
| Maximum contacts |
128 |
| Preset SMP servers |
21 |
| SMP block size |
16,384 bytes |
| Ratchet states in PSRAM |
128 (permanently resident) |
| SD card encryption |
AES-256-GCM with HKDF per-contact key |
Build Information
| Detail |
Value |
| ESP-IDF |
5.5.2 |
| Target |
ESP32-S3 |
| Flash mode |
DIO, 80 MHz |
| Flash size |
16 MB |
| App offset |
0x110000 |
| PSRAM |
8 MB OPI |
| Partition |
Custom (OTA_0 3MB, OTA_1 3MB, NVS 128KB, NVS Keys 256KB) |
Merged Binary Creation
python -m esptool --chip esp32s3 merge_bin \
-o simplego-tdeck-plus-v0.2.0-beta-open.bin \
--flash_mode dio --flash_freq 80m --flash_size 16MB \
0x0 build/bootloader/bootloader.bin \
0x8000 build/partition_table/partition-table.bin \
0x110000 build/simplex_client.bin
Security Status
| ID |
Status |
Description |
| SEC-01 |
Open |
Decrypted messages in PSRAM never zeroed |
| SEC-02 |
Open |
NVS keys plaintext without Vault mode |
| SEC-03 |
Closed |
mbedtls_platform_zeroize in smp_storage.c |
| SEC-04 |
Open |
No memory wipe on screen lock |
| SEC-05 |
Deferred |
HKDF info parameter (resolved by eFuse binding) |
| SEC-06 |
Deferred |
Post-quantum toggle per contact... |