# Reticulum Specifications

Byte-level interoperability specifications for the [Reticulum Network Stack](https://reticulum.network/) and [LXMF](https://github.com/markqvist/LXMF) — the parts that aren't in the upstream manuals but are needed to build a working client from scratch.

Upstream Reticulum has excellent operator-facing documentation (config, deployment, design philosophy). What's missing — and what every alternative implementation has had to reverse-engineer from the Python source — is an authoritative wire-level spec: header bit layouts, msgpack field types, signature input formats, the exact behavior of `Transport.outbound`, and the long list of "would never guess from reading the manual" gotchas that cost hours of debugging each.

This repo collects those findings in one place. The hope is that future client authors (Kotlin, Swift, Rust, Go, embedded C — pick your stack) can read this instead of re-deriving everything from `RNS/Transport.py`.

## Status

**Early days, contributions welcome.** Current content was bootstrapped from the working notes of two reverse-engineering efforts:

- The web-based Reticulum client at [`reticulum-lora-webclient`](https://github.com/thatSFguy/reticulum-lora-webclient)
- The native Android client at [`reticulum-mobile-app`](https://github.com/thatSFguy/reticulum-mobile-app)

Each finding is grounded in upstream source citations (file + line) so it can be re-verified as RNS evolves.

**Current upstream target (verified 2026-06-09):**
`markqvist/Reticulum@422dc055` (`RNS 1.3.5`) and
`markqvist/LXMF@fab12ad` (`LXMF 1.0.1`). Full commit hashes are pinned in
[`tools/requirements.txt`](tools/requirements.txt); all 28 verifiers pass.

## Method

This fork proceeds with a three-tier evidence model that preserves the existing Claude-assisted work while promoting claims only after verification:

1. **Source analysis report** — answer one narrow protocol question from pinned upstream source, with exact citations and unresolved cases.
2. **Executable verification** — add runnable evidence under `tools/` and deterministic vectors under `test-vectors/` where bytes are involved.
3. **Specification promotion** — update `SPEC.md` as normative prose only after the verifier/vector exists; add correction or incident notes when earlier text would mislead implementers.

See [`agent.md`](agent.md) §3 for the detailed rules.

## Verify a Clone

The verifier suite has no dependency on a particular user's virtual
environment. From a fresh clone, create any isolated Python environment and
install the exact upstream Git commit pins:

```sh
python3 -m venv .venv
.venv/bin/python -m pip install -r tools/requirements.txt
.venv/bin/python tools/verify_all.py
```

On Windows, use `.venv\Scripts\python.exe` in place of `.venv/bin/python`.
Activation is optional; `verify_all.py` uses the interpreter that launched it
and refuses to run when installed versions or Git commit origins do not match.

## What's here

- [`SPEC.md`](SPEC.md) — the single combined spec document, organized by protocol layer
- [`playbook.md`](playbook.md) — how to troubleshoot interop bugs, design tests that don't lie to you, and navigate the protocol's code-as-spec parts. **Read this if you're starting any Reticulum implementation work, not just contributing to this repo.** Includes an incident registry of past wire-format bugs and their fixes.
- [`agent.md`](agent.md) — verification rules for adding to this repo (markers, tools/, test-vectors)
- [`templates/`](templates/) — drop-in `AGENTS.md` for new Reticulum implementation projects in any language. Copy into your project root, edit the marked sections, and the next agent or contributor lands on the right docs automatically.
- [`flows/`](flows/) — chronological end-to-end narratives (e.g. "send a message"), cross-referencing SPEC.md sections
- [`audits/provenance-and-anomaly-ledger.md`](audits/provenance-and-anomaly-ledger.md) — inherited baseline, current verification high-watermark, promoted-anomaly index, and the required record for future upstream migrations
- [`audits/executive-report-2026-06-09.md`](audits/executive-report-2026-06-09.md) — executive project status, inherited-versus-current assessment, major corrections, migration status, and recommended path
- [`audits/leviculum-spec-assessment-2026-06-09.md`](audits/leviculum-spec-assessment-2026-06-09.md) — assessment of Lew Palm's staged Leviculum implementation against this specification, including comparison with the sibling `Reticulum-rs`
- [`audits/microreticulum-spec-assessment-2026-06-09.md`](audits/microreticulum-spec-assessment-2026-06-09.md) — assessment of Chad Attermann's staged microReticulum implementation against this specification, including comparison with Exercise 205 sustained Links
- [`audits/retinet-spec-assessment-2026-06-09.md`](audits/retinet-spec-assessment-2026-06-09.md) — assessment of the staged RetiNet Python fork against this specification, including its AES-256 migration and current RNS compatibility
- [`audits/lxmf-project-reconciliation-2026-06-09.md`](audits/lxmf-project-reconciliation-2026-06-09.md) — reconciliation with the separate LXMF extraction, including the LXMF 1.0.1 field-allocation delta
- [`audits/github-head-promotion-2026-06-09.md`](audits/github-head-promotion-2026-06-09.md) — exact Mark Qvist GitHub-head pins, migration findings, and 28/28 verification result
- [`audits/baseline-completion-rns-1.2.4-lxmf-0.9.7.md`](audits/baseline-completion-rns-1.2.4-lxmf-0.9.7.md) — section-by-section evidence matrix, deliberately unresolved external callouts, and the gate for beginning an upstream-version migration
- [`tools/`](tools/) — self-contained Python verifier scripts that test SPEC.md claims against upstream RNS / LXMF. Pinned via [`tools/requirements.txt`](tools/requirements.txt) to the upstream versions the scripts were last re-verified against
- [`test-vectors/`](test-vectors/) — known-good byte sequences each implementation should be able to round-trip (intent: grow into a compliance suite)

As content grows, `SPEC.md` will be split into per-layer files (packet header, identity, announce, token-crypto, LXMF, link, resource, transport).

## Spec corrections

Errata that may invalidate code built against an earlier revision of `SPEC.md`. Newest first. Feature additions and ordinary edits live in `git log` — this section is reserved for cases where the spec said one thing, that turned out to be wrong, and an implementer who pulled the bad version needs to fix their code.

- **2026-06-09 — §4.3 LXMF delivery announces now emit capabilities.**
  Earlier prose correctly described LXMF 0.9.7's 2-element delivery announce
  but incorrectly left it as the current producer form. Mark's LXMF commit
  `599406e`, present at GitHub head `fab12ad`, activates the third element:
  `[display_name, stamp_cost, [SF_COMPRESSION]]`. The spec, verifier, and
  deterministic announce vectors now use the live form while preserving
  receiver tolerance for historical forms.

- **2026-06-09 — §5.9 interaction fields after Mark's `764758d` commit.**
  Earlier prose said reply keys `0x30`/`0x31` and reactions were outside the
  upstream LXMF allocation range. Mark's LXMF commit `764758d` (2026-05-24),
  present in LXMF 1.0.1, standardizes replies at `0x30`/`0x31`, reactions at
  `0x40`, comments at `0x41`, and continuations at `0x42`. The standardized
  reaction shape differs from the legacy ecosystem `fields[0x10]` shape.
  The commit landed while the source still reported version 0.9.9, so the
  verifier checks field-set presence instead of relying only on version text.

- **2026-06-08 — §12.6 tunnel state and shared-instance framing.**
  Earlier prose gave the wrong tunnel-entry shape, claimed the eight-hour tunnel timeout was substantially longer than path TTLs, and described shared-instance communication as ordinary packets over TCP loopback with no framing. RNS 1.2.4 stores `[tunnel_id, interface, paths, expires]`; its eight-hour tunnel/path timeout is shorter than AP/default path lifetimes; and `LocalInterface` normally uses an abstract Unix-domain socket where supported and wraps packets in simplified HDLC. Corrected and runtime-locked by `tools/verify_transport_tunnel.py`.

- **2026-06-08 — §12.3/§12.4 announce rebroadcast and path state.**
  Earlier prose described relayed announces as HEADER_1, said ingress was skipped, assigned four retries, treated `IDX_PT_PACKET` as a Packet object, set `MAX_RANDOM_BLOBS` to 32, and gave path TTLs as 1h/4h/30d. RNS 1.2.4 reconstructs HEADER_2 TRANSPORT announces across all eligible full-mode interfaces, stores a 32-byte cached-packet hash, uses in-memory/persisted blob caps of 64/32, and expires AP/roaming/default paths after 1d/6h/7d. Corrected and runtime-locked by `tools/verify_transport_announce.py`.

- **2026-06-08 — §12.5 ordinary DATA reverse routing and timeout.**
  Earlier prose stated that reverse-table entries expire after 30 seconds and the opportunistic receive flow showed HEADER_2 arriving at a relayed endpoint. RNS 1.2.4 defines `REVERSE_TIMEOUT = 8*60`, and the final relay strips HEADER_2 before endpoint delivery. The audit also found that a wrong-interface PROOF consumes the one-shot reverse entry before being dropped. Corrected and runtime-locked by `tools/verify_transport_data.py`.

- **2026-06-08 — §12.5 transport-relayed Link forwarding gates and proof routing.**
  Earlier prose implied that established-Link forwarding begins only after LRPROOF validation, that relay lookup requires `destination_type == LINK`, and that Link proofs use `reverse_table`. Upstream RNS 1.2.4 forwards matching link-id traffic based on interface and hop count without checking `IDX_LT_VALIDATED` or destination type; endpoints still require LINK destination type for active-Link DATA dispatch. Link-addressed proofs use `link_table`, while `reverse_table` is for ordinary destination-routed DATA proofs. Corrected and runtime-locked by `tools/verify_transport_link.py`.

- **2026-06-08 — §5.8 propagated-LXMF transient IDs, `/get` framing, and error constants.**
  Earlier §5.8 text described transient IDs as 16-byte truncated hashes; upstream LXMF 0.9.7 uses the full 32-byte `SHA256(lxmf_data)`. It also incorrectly described `/get` responses as propagation bundles shaped `[time, [messages]]`; the `/get` handler actually returns a plain message list carried by the generic `[request_id, response]` Link RESPONSE. Accepted submission and peer-transfer entries always append a required 32-byte propagation stamp, including at cost zero. The section also incorrectly placed operator handlers on the public propagation destination, described the announce parser as exact/strict, and stated a 30-minute peer throttle; operator handlers use `lxmf.propagation.control`, the parser is deliberately permissive, and `PN_STAMP_THROTTLE` is 180 seconds. Finally, `ERROR_THROTTLED` is `0xf6`, `ERROR_NOT_FOUND` is `0xfd`, and `0xf5` is `ERROR_INVALID_STAMP`. Corrected and runtime-locked by `tools/verify_propagated_lxmf.py` and `tools/verify_propagation_peer.py`.

- **2026-06-08 — §11.4 REQUEST authorization constants were reversed.**
  Earlier §11.4 text assigned `ALLOW_LIST = 0x01` and `ALLOW_ALL = 0x02`. Upstream RNS 1.2.4 defines `ALLOW_NONE = 0x00`, `ALLOW_ALL = 0x01`, and `ALLOW_LIST = 0x02` in `RNS/Destination.py`. An implementation following the prior table would expose list-restricted handlers publicly and incorrectly restrict public handlers. §11.4 is corrected and runtime-locked by `tools/verify_request_response.py`. The same audit corrected §11.2 file metadata wording: Resource advertisement field `m` is always the hashmap; file metadata is carried inside Resource plaintext and signaled by flag `x`.

- **2026-05-17 — §10.2 Resource integrity hash: the 4-byte prefix is NOT `r`, and is NOT in the hash input.**
  Bad text introduced in [`95823ad`](../../commit/95823ad); on master from 2026-05-03 to 2026-05-17. §10.2 step 3 wrongly equated the random-hash *prefix* prepended to the Resource body with the advertisement's `r` field, and step 5 wrongly fed that prefix into `hash`/`expected_proof` (claiming `hash = SHA256(random_hash || body || random_hash)`). Upstream `RNS/Resource.py` (1.2.4) uses *two distinct* `get_random_hash()[:4]` values: a throwaway prefix the receiver strips and discards (`:405`/`412`, `:682`), and `self.random_hash` — the advertisement's `r` field (`:440`, `:1285`). The integrity hash is `SHA256(uncompressed_plaintext || r)` over the prefix-stripped, decompressed body (`:441`, `:694`) — exactly as §10.8 already stated. An implementer who trusted §10.2 step 5 computes a hash no spec-compliant peer accepts; every Resource is rejected as `CORRUPT`. §10.2 corrected to agree with §10.8; §10.12's wire-layering block fixed to match. Surfaced by [issue #9](../../issues/9).

- **2026-05-06 — §2.1 flag byte: bit 7 is the IFAC flag, not part of `header_type`.**
  Bad text introduced in [`8c4d550`](../../commit/8c4d550), corrected in [`0c2021e`](../../commit/0c2021e); on master from 2026-05-04 to 2026-05-06. The corrected layout is `ifac_flag(bit 7) | header_type(bit 6) | context_flag(5) | transport_type(4) | destination_type(3-2) | packet_type(1-0)`, matching the official manual §4.6.3 and upstream `RNS/Packet.py:246` (parse mask `0b01000000 >> 6`) / `RNS/Transport.py:1003` (IFAC setter `raw[0] | 0x80`). Implementers who consumed the bad version will mis-parse every IFAC-protected packet as `header_type ∈ {2, 3}` and drop it. Surfaced by [issue #4](../../issues/4) item #1.

## Scope

**In scope:**
- Wire formats: byte layouts, field encodings, framing
- Signing inputs and what's hashed where
- Cross-cutting behaviors required for interop (path requests, ratchet rotation, retransmit semantics)
- "Gotchas" — things upstream code does that aren't obvious from the manual or RFC-style sketches
- Test vectors that any implementation must be able to round-trip

**Out of scope:**
- Operator/user documentation — see [the official manual](https://markqvist.github.io/Reticulum/manual/)
- API design choices for any specific implementation
- Networking layer config (interfaces, transport modes) — already well documented

## Source citations

Where a finding cites upstream Python code, the path is relative to a standard `pip install rns lxmf` installation, e.g. `RNS/Transport.py`, `LXMF/LXMF.py`. Where the bundled `umsgpack` is referenced, the path is `RNS/vendor/umsgpack.py`.

When upstream code changes such that a citation no longer matches, file an issue or PR — the goal is to track the de-facto wire spec as it actually behaves, not as it was at any single snapshot.

## Contributing

If you've debugged a Reticulum interop problem and the answer wasn't in the upstream docs, please add it. Format:

```markdown
### N.M Short description of the finding

**Symptom:** what you observed that prompted the investigation.

**What's happening:** the actual mechanism, ideally with upstream source citation (file + line).

**Implication / fix:** what an implementation must do to interop.

**Source:** upstream file paths and approximate line numbers.
```

Add a worked test vector to `test-vectors/` if the finding is byte-level.

## Provenance
This project is a fork (June 8, 2026) of https://github.com/thatSFguy/reticulum-specifications.  thatSFguy's approach was to use Claude, I use Codex and have applied the approach I used for the LXMF-specification project here.
## License

[CC BY 4.0](LICENSE) — use freely, attribution appreciated.
