# fumi A Rust client for [Smol Mail](https://code.randogoth.com/randogoth/smolmail): a minimalist, end-to-end encrypted mail protocol over a Noise-secured TCP connection. `fumi` derives an identity from one 32-byte master secret, seals and opens mail, and talks to a mailbox; it is the counterpart to [bunshin](https://code.randogoth.com/randogoth/bunshin), the server. ## Status Phase 1, the TCP carrier (SPEC.md 1.1), is implemented and interoperates in both directions with the reference client `smolmail.py` and the reference server `smolmaild.py`, and with `bunshin`. The Reticulum carrier of RNS.md (1.2) is not built yet: `smol+rns://` addresses parse and fail with a message naming the `rns` feature, and the flake does not yet vendor microReticulum. ## Addressing ``` alice@example.org[:1961] short form, resolved via the server smol://alice@example.org/mfrggzdfzt... self-certifying, 52-char base32 identity smol+rns://alice@8f2c1d0a7b6e5f4c3d2a1b0e9f8c7d6a short form over Reticulum (rns feature) smol+rns://alice@8f2c1d0a7b6e5f4c3d2a1b0e9f8c7d6a/mfrggzdfzt... self-certifying over Reticulum ``` ## Usage One master per store, selected by the global `--key` and `--db` flags; a second identity is a second pair of files. ``` fumi [--key identity.key] [--db fumi.db] [--timeout 30] # identity fumi keygen [--force] create a master secret fumi whoami address, public key, fingerprint, rotation index fumi restore
recover local state from the master alone fumi rotate advance the rotation index, sign and push the certificate # servers and contacts fumi trust [--force] pin a server's Noise static key fumi register
[--invite TOKEN] bind this identity to a username fumi resolve
look up a contact's key, walk the chain, pin it fumi import add a contact from a self-certifying address fumi contacts known keys and how each was learned # accept tokens fumi accept
issue a token, admit to the main tier, sync the set fumi block
withdraw it, sync the set # mail fumi send
[--subject S] [--body TEXT | --file F | -] [--header K:V] [--reply-to ID] [--anonymous] [--no-pad] fumi fetch [--keep] [--reset] retrieve, verify, store, acknowledge fumi delete ... remove from the server explicitly fumi list [--sent] [--requests] fumi read [--sent] ``` Mail is stored sealed and opened on demand; there is no plaintext at rest. A first contact is learned by trust on first use and marked unverified when the server's key was not pinned; a key change is accepted only when a signed rotation chain leads from the key held to the one offered, and is surfaced rather than applied silently. ## Build ``` nix build # or: nix develop -c cargo build nix develop -c cargo test ``` `bunshin` and the reference clients make a complete test rig: register against a local server, exchange mail in both directions, then rotate and fetch the mail that the superseded key still receives. ## Modules | File | Contents | |---|---| | `src/crypto.rs` | base32, HKDF, HMAC, SHA-256, the Ed25519→X25519 map with SPEC.md §2's checks | | `src/address.rs` | parsing for all four address forms, username validation, fingerprints | | `src/account.rs` | master, `seed_n` derivation, accept-key and tokens, rotation certificates | | `src/message.rs` | envelope seal/open (§5.1–§5.3), message id (§5.4), frontmatter (§5.5) | | `src/transport.rs` | `Transport` trait, bind values, operation and status constants | | `src/tcp.rs` | Noise_NX initiator, `len u32 \|\| op u8 \|\| body` framing, static-key pinning | | `src/client.rs` | the six operations, AUTH, chain walking, token sync, fetch pipeline | | `src/store.rs` | SQLite: state, contacts, accepted, tokens, seen ids, inbox, sent | Unit tests pin every derivation against vectors generated from `smolmail.py`, including a full envelope reproduced byte for byte. ## References - [SPEC.md](https://code.randogoth.com/randogoth/smolmail/src/branch/main/SPEC.md) — protocol 1.1 - [RNS.md](https://code.randogoth.com/randogoth/smolmail/src/branch/main/RNS.md) — the 1.2 Reticulum addendum - [bunshin](https://code.randogoth.com/randogoth/bunshin) — the server