# Fumi - Smol Mail Client (Rust) A Rust implementation of the Smol Mail client protocol, compatible with version 1.2 (including RNS transport). Fumi is the client counterpart to [bunshin](../bunshin), the Smol Mail server implementation. ## Overview Smol Mail is a minimalist, end-to-end encrypted mail protocol. This client implementation supports: - **Full protocol v1.1**: All operations over TCP with Noise_NX_25519_ChaChaPoly_SHA256 - **Full protocol v1.2**: Reticulum Network Stack (RNS) transport for mesh networking - **Shared codebase**: Single binary supports both transports (via feature flags) - **Interoperability**: Works with reference Python implementations and bunshin server ## Protocol Compatibility | Feature | v1.1 (TCP) | v1.2 (RNS) | |---------|------------|------------| | AUTH | ✅ | ✅ | | RESOLVE | ✅ | ✅ | | SEND | ✅ | ✅ | | FETCH | ✅ | ✅ | | DELETE | ✅ | ✅ | | REGISTER | ✅ | ✅ | | Accept Tokens | ✅ | ✅ | | Key Rotation | ✅ | ✅ | | Message Encryption | ✅ | ✅ | ## Address Formats ### TCP Transport (v1.1) ``` smol://alice@example.com[:1961] smol://alice@example.com/abcdefghijklmnopqrstuvwxyz234567 # self-certifying ``` ### RNS Transport (v1.2) ``` smol+rns://alice@8f2c1d0a7b6e5f4c3d2a1b0e9f8c7d6a smol+rns://alice@8f2c1d0a7b6e5f4c3d2a1b0e9f8c7d6a/abcdefghijklmnopqrstuvwxyz234567 ``` The RNS address uses the server's 32-character hex destination hash as the host. --- ## Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ fumi client │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ CLI (main.rs) │ │ │ │ keygen, import, trust, register, send, │ │ │ │ fetch, delete, resolve, show │ │ │ └──────────────────────────────┬──────────────────────────┘ │ │ │ │ │ ┌──────────────────────────────▼──────────────────────────┐ │ │ │ Client Core (client.rs) │ │ │ │ - Session management with servers │ │ │ │ - Message composition and parsing │ │ │ │ - Envelope sealing/unsealing │ │ │ └──────────────────────────────┬──────────────────────────┘ │ │ │ │ │ ┌──────────────────────────────▼──────────────────────────┐ │ │ │ Account (account.rs) │ │ │ │ - Master secret management │ │ │ │ - Identity derivation (rotation chains) │ │ │ │ - Token generation │ │ │ └──────────────────────────────┬──────────────────────────┘ │ │ │ │ │ ┌─────────────────┐ ┌─────────────────────────────┐ │ │ │ │ TCP Transport │ │ RNS Transport │ │ │ │ │ (tcp_transport) │ │ (rns_transport + FFI) │ │ │ │ └────────┬────────┘ └──────────┬───────────────────┘ │ │ │ │ │ │ │ ▼ ▼ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ Transport Trait (transport.rs) │ │ │ │ - connect(address) : Establish session │ │ │ │ - request(op, body) : Send frame, get response │ │ │ │ - bind_values() : Get auth binding values │ │ │ │ - close() : Clean up connection │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Store (store.rs) │ │ │ │ - SQLite local mailbox │ │ │ │ - Accounts, identities, tokens, messages │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## Implementation Plan ### Phase 1: Core Client (TCP Only) #### Dependencies ```toml [package] name = "fumi" version = "0.1.0" edition = "2021" description = "Smol Mail client" [dependencies] snow = "0.9" # Noise protocol ed25519-dalek = "2" # Ed25519 signatures x25519-dalek = "2" # X25519 key agreement sha2 = "0.10" # SHA-256 rand_core = "0.6" # Randomness hmac = "0.12" # HMAC-SHA256 clap = "4" # CLI parsing log = "0.4" # Logging env_logger = "0.11" # Logger setup anyhow = "1" # Error handling rusqlite = "0.32" # SQLite local store hex = "0.4" # Hex encoding data-encoding = "2" # Base32 encoding ``` #### Modules 1. **crypto.rs** - Cryptographic primitives - Base32 encoding/decoding - HKDF-SHA256 - HMAC-SHA256 - Identity derivation - Accept token generation - Message ID computation 2. **address.rs** - Address parsing - Parse TCP addresses: `smol://user@host[:port][/identity]` - Parse RNS addresses: `smol+rns://user@desthash[/identity]` - Validate usernames - Generate self-certifying URIs 3. **account.rs** - Account management - Master secret storage - Identity key derivation with rotation - Current identity tracking - Token generation for correspondents 4. **transport.rs** - Transport trait - Define `Transport` trait with connect/request/close - Define `TransportBindValues` for auth bindings - Define operation and status constants 5. **tcp_transport.rs** - TCP transport - Noise_NX handshake - Frame reading/writing - Server key pinning (optional) 6. **client.rs** - Core client logic - Session management - REGISTER operation - AUTH operation - RESOLVE operation - SEND operation (envelope creation) - FETCH operation - DELETE operation - Message sealing/unsealing 7. **store.rs** - Local SQLite store - Account storage - Trusted server keys - Accept tokens - Outbound message queue - Inbound message storage 8. **main.rs** - CLI - keygen: Generate new identity - import: Import existing identity - accounts: List accounts - trust: Trust server key (TCP) - register: Register username - send: Send message - fetch: Fetch messages - delete: Delete messages - resolve: Resolve username - show: Display message info --- ### Phase 2: RNS Transport Same approach as bunshin's RNS integration: 1. **FFI with microReticulum** (recommended for production) - Use C++ microReticulum via FFI bindings - Official implementation, full compliance - Complex but most reliable 2. **Python IPC** (quick validation) - Call `smolmail_rns.py` as subprocess - Share data via files or pipes - Fast to implement 3. **Native Rust** (future) - Wait for mature Rust RNS crate - Or implement minimal subset --- ## Key Differences from Server (bunshin) | Aspect | bunshin (server) | fumi (client) | |--------|------------------|---------------| | Noise role | Responder (has static key) | Initiator (no static key) | | Session | Accepts connections | Initiates connections | | Trust | Server key pinned by client | Client pins server key | | Auth | Verifies client signatures | Signs requests | | Rate limiting | Enforces limits | Respects limits | --- ## CLI Commands ```bash # Identity management fumi keygen # Generate new identity fumi keygen --name alice # Generate with name fumi import --name alice -m ABCD... # Import master secret fumi accounts # List accounts # Server trust (TCP only) fumi trust smol://alice@example.com ABCD... # Trust server key # Account registration fumi register smol://alice@example.com --username alice fumi register smol://alice@example.com --username alice --invite TOK... # Messaging fumi send smol://bob@example.com --subject "Hello" --body "World" fumi send smol://bob@example.com --file message.txt fumi fetch smol://alice@example.com fumi delete smol://alice@example.com --ids ABCD... fumi resolve smol://alice@example.com --username bob fumi show ABCD... # Show message details ``` --- ## Project Structure ``` fumi/ ├── Cargo.toml # Project configuration ├── Cargo.lock ├── src/ │ ├── main.rs # CLI entry point │ ├── lib.rs # Library exports │ ├── client.rs # Core client logic │ ├── account.rs # Account/identity management │ ├── address.rs # Address parsing │ ├── store.rs # Local SQLite store │ ├── crypto.rs # Cryptographic primitives │ ├── transport.rs # Transport trait │ ├── tcp_transport.rs # TCP transport implementation │ └── error.rs # Error types ├── tests/ │ └── integration/ # Integration tests ├── flake.nix # Nix flake (optional) └── README.md # This file ``` --- ## Implementation Priority ### Phase 1: TCP Client (High Priority) | # | Module | Description | Dependencies | |---|--------|-------------|--------------| | 1 | crypto.rs | Base32, HKDF, HMAC, identity derivation | sha2, hmac, data-encoding | | 2 | address.rs | Address parsing and validation | regex, anyhow | | 3 | account.rs | Account and identity management | ed25519-dalek, rand_core | | 4 | transport.rs | Transport trait and types | None | | 5 | tcp_transport.rs | TCP/Noise transport | snow, std::net | | 6 | client.rs | Core operations (REGISTER, AUTH, etc.) | All above | | 7 | store.rs | SQLite local storage | rusqlite | | 8 | main.rs | CLI | clap, all above | | 9 | Tests | Unit and integration tests | All above | ### Phase 2: RNS Support (Medium Priority) | # | Module | Description | Dependencies | |---|--------|-------------|--------------| | 1 | rns/ffi.rs | FFI bindings to microReticulum | libffi | | 2 | rns/transport.rs | RNS transport implementation | ffi.rs | | 3 | rns/mod.rs | RNS module exports | transport.rs | | 4 | Update transport.rs | Add RNS feature flag | None | | 5 | Update client.rs | Handle RNS-specific logic | rns module | | 6 | Update main.rs | Add RNS CLI commands | rns module | | 7 | Tests | RNS integration tests | All above | --- ## Testing Strategy ### Unit Tests - Address parsing (TCP and RNS) - Identity generation and rotation - Message sealing and unsealing - Envelope format validation - Token generation and verification ### Integration Tests - Register with bunshin server (TCP) - Send and receive messages (TCP) - Key rotation interoperability - Accept token usage - Error response handling ### Interoperability Tests - Against `smolmail.py` reference client - Against `smolmaild.py` reference server - Against `smolmail_rns.py` reference client - Against `smolmaild_rns.py` reference server --- ## Compatibility Matrix | Client \ Server | bunshin (TCP) | bunshin (RNS) | smolmaild.py | smolmaild_rns.py | |------------------|---------------|---------------|---------------|------------------| | fumi (TCP) | ✅ Full | ❌ No | ✅ Full | ❌ No | | fumi (RNS) | ❌ No | ✅ Full | ❌ No | ✅ Full | Both transports share the same account and store, so a user can use both transports with the same identity. --- ## Key Implementation Notes ### Message Sealing (SPEC.md S5) 1. Generate ephemeral X25519 keypair 2. Convert recipient Ed25519 public key to X25519 (per SPEC.md S2) 3. Perform X25519 key agreement 4. Derive two 32-byte keys using HKDF-SHA256: - `k1 = HKDF(shared, "", "smolmail/1 msg", 64)[0:32]` - `k2 = HKDF(shared, "", "smolmail/1 msg", 64)[32:64]` 5. Build payload with frontmatter (unencrypted): version, sender, timestamp 6. Build body with: version, timestamp, body_length, body (padded to 1024-byte boundary) 7. Encrypt body with ChaCha20-Poly1305 using k1 8. Build envelope: magic ("SMOL"), version, recipient, ephemeral_pub, payload ### Transport Differences #### TCP (v1.1) - Noise_NX handshake (client is initiator, server has static key) - Frames: u32 length || u8 op || body (split across Noise messages) - Binding values: Noise handshake hash and server static public key - Server pinning: Client verifies server's Noise static key #### RNS (v1.2) - Reticulum link establishment - Frames: u8 op || body (RNS delimits messages) - Binding values: SHA-256("smolmail/1 bind" || dest_hash || link_id) and SHA-256("smolmail/1 bind" || dest_hash) - Server pinning: Destination hash IS the server's identity (no separate pinning) --- ## Next Steps 1. **Implement Phase 1 (TCP Client)** - Start with crypto, address, and account modules - Then transport and tcp_transport - Then client core operations - Finally store and CLI - Test against bunshin server 2. **Implement Phase 2 (RNS Support)** - Research microReticulum C API - Create FFI bindings - Implement RNS transport - Add tests with reference implementations 3. **Polish and Package** - Documentation - Nix flake integration (optional) - Performance optimization - Error handling improvements --- ## References - [Smol Mail SPEC.md](https://code.randogoth.com/randogoth/smolmail/src/branch/main/SPEC.md) - [Smol Mail RNS.md (v1.2)](https://code.randogoth.com/randogoth/smolmail/src/branch/main/RNS.md) - [smolmail.py - Reference TCP client](https://code.randogoth.com/randogoth/smolmail/src/branch/main/smolmail.py) - [smolmail_rns.py - Reference RNS client](https://code.randogoth.com/randogoth/smolmail/src/branch/main/smolmail_rns.py) - [bunshin - Rust server implementation](../bunshin) - [microReticulum - C++ RNS implementation](https://github.com/attermann/microReticulum)