Closes Tier 1 #6 and the entire Tier 1 sweep. Previous §1.3 said the on-disk byte order was Ed25519_priv(32) || X25519_priv(32) ("opposite of the public_key concatenation"). That was WRONG. Verified empirically against RNS 1.2.0 by round-tripping the existing test vectors through Identity.to_file and reading the bytes back: disk = X25519_priv(32) || Ed25519_priv(32) # same as public_key This matches Identity.get_private_key() at RNS/Identity.py:694-698: return self.prv_bytes + self.sig_prv_bytes where prv_bytes is X25519 (line 679) and sig_prv_bytes is Ed25519 (line 682). It also matches load_private_key at line 706-717. Implementations following the prior spec wording would have written identity files that fail to load on upstream RNS — a real interop break that would have been very hard to debug because the failure is in keypair-loading, before any signature operation runs. §1.3 rewritten and expanded: - Correct byte order with citation to upstream code. - 64-byte raw-blob format with explicit "no header / no version / no checksum / no encryption". - File-system facts: no chmod, expected to live in OS-protected storage, filename is caller-controlled. - from_bytes HAZARD note: feeding raw random bytes skips the `cryptography` library's keypair-generation invariants (X25519 RFC 7748 §5 scalar clamping etc). - Cross-implementation portability follows automatically because there's nothing in the file but the bytes. - ⚠️ Spec correction callout warning future readers about the previous wording so the bug history is on record. tools/verify_destination_hash.py extended with a §1.3 to_file / from_file round-trip section. For each test vector it now: - writes the identity via to_file - asserts the on-disk file is exactly 64 bytes - asserts disk[:32] hex == expected x25519_priv_hex - asserts disk[32:64] hex == expected ed25519_priv_hex - reloads via from_file and asserts identity_hash invariance This is what would have caught the bug if it had been there from the start. tools/README.md updated to reflect §1.3 coverage. Cumulative Tier 1 status: 6 of 6 done. A from-scratch client built from §1-§9 + §10 + §11 + flows/ can now interop with upstream Reticulum / LXMF / RNode for identity, announce, opportunistic LXMF DATA, Resource fragmentation, regular PROOF receipts, link handshakes with MTU/mode signalling, path-? discovery, and KISS/HDLC/RNode-air-frame framing. Tiers 2 and 3 remain open in the todo for follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| flows | ||
| test-vectors | ||
| tools | ||
| agent.md | ||
| LICENSE | ||
| README.md | ||
| SPEC.md | ||
| todo.md | ||
Reticulum Specifications
Byte-level interoperability specifications for the Reticulum Network Stack and 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 - The native Android client at
reticulum-mobile-app
Each finding is grounded in upstream source citations (file + line) so it can be re-verified as RNS evolves.
What's here
SPEC.md— the single combined spec document, organized by protocol layerflows/— chronological end-to-end narratives (e.g. "send a message"), cross-referencing SPEC.md sectionstools/— self-contained Python verifier scripts that test SPEC.md claims against upstream RNS / LXMFtest-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).
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
- 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:
### 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.
License
CC BY 4.0 — use freely, attribution appreciated.