fumi/README.md

89 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 文 fumi
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) [![AI-DECLARATION: auto](https://img.shields.io/badge/%E4%B7%BC%20AI--DECLARATION-auto-ede9fe?labelColor=ede9fe)](https://ai-declaration.md)
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
Both carriers are implemented: the TCP carrier (SPEC.md 1.1) and the Reticulum carrier of RNS.md (1.2), the latter behind the `rns` cargo feature (`nix build .#rns`). `fumi` interoperates in both directions with the reference stack — `smolmail.py`/`smolmaild.py` over TCP and `smolmail_rns.py`/`smolmaild_rns.py` over Reticulum — and with `bunshin` over both carriers, including the cross-transport case upstream section 15 promises: send over TCP, fetch over the mesh, one mailbox.
## 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] <command>
# identity
fumi keygen [--force] create a master secret
fumi whoami address, public key, fingerprint, rotation index
fumi restore <address> recover local state from the master alone
fumi rotate advance the rotation index, sign and push the certificate
# servers and contacts
fumi trust <host> <key-b32> [--force] pin a server's Noise static key
fumi register <address> [--invite TOKEN] bind this identity to a username
fumi resolve <address> look up a contact's key, walk the chain, pin it
fumi import <smol-uri> add a contact from a self-certifying address
fumi contacts known keys and how each was learned
# accept tokens
fumi accept <address> issue a token, admit to the main tier, sync the set
fumi block <address> withdraw it, sync the set
# mail
fumi send <address> [--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 <id>... remove from the server explicitly
fumi list [--sent] [--requests]
fumi read <id> [--sent]
# RNS carrier (built with --features rns / nix build .#rns); addresses select
# it by scheme, so these flags apply only to smol+rns:// dials:
fumi --rns-udp <host:port> --rns-udp-forward <host:port> [--rns-storage DIR] <command>
```
On the mesh the destination hash is the pin (RNS.md 13.4): there is no `trust`, no static key, and a client creates no Reticulum identity (13.8). The `--rns-udp-forward` target is effectively required for a client, which speaks first; it should point at an `rnsd` or at the server's UDP interface. Requests carry an explicit 30-second timeout (13.5), links are torn down when a session ends (13.10), and a resend is safe because ids are derived.
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/rns/` | the Reticulum carrier behind the `rns` feature: FFI over `shim/`, path discovery, one link per session |
| `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