fumi/README.md
randogoth 1690aed3dc feat: add fumi client implementation plan for Smol Mail 1.2
Add comprehensive plan for Rust client implementation including:
- TCP transport (v1.1) support
- RNS transport (v1.2) support via FFI with microReticulum
- Architecture with transport abstraction
- Module structure and dependencies
- CLI command design
- Testing strategy
- Implementation priority

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-09-28 11:21:58 +03:00

16 KiB

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, 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

[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

# 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