Added deterministic `resources.json` and `regen_resources.py`.
Extended `verify_resource.py` with receiver assembly/proof and requested negative cases.
Updated specification, audit, status, and tool documentation.
Fixed an unrelated nondeterministic wrong-ticket test in verify_stamps.py.
Confirmed vector regeneration is byte-identical.
Confirmed no tracked reliance on specenv or user-specific paths.
git diff --check: pass.
Complete pinned suite: 16 passed, 0 failed.
Upstream RNS enforces two requirements in code that SPEC.md left implicit;
both caused silent message loss in a clean-room Go LXMF service against
upstream Python rns 1.2.4 / lxmf 0.9.7.
§6.4.2 LRRTT — initiator's link-activation packet
- HEADER_1, DATA, dest_type=LINK (0x03), ctx=0xfe; body is
`umsgpack.packb(rtt_seconds)` encrypted with the link's session keys.
- The responder transitions HANDSHAKE→ACTIVE only on LRRTT receipt
(Link.py:534-553), which is also what fires the link_established
callback. LXMF's set_resource_strategy(ACCEPT_APP) is installed
from that callback; without it, every RESOURCE_ADV the initiator
sends hits the silent ACCEPT_NONE branch at Link.py:1087.
§6.4.3 Header type for post-handshake DATA and Resource
- Link-addressed packets are routed via link_table, which forwards
header bytes verbatim (Transport.py:1587-1622). HEADER_2 with a
relay's transport_id therefore arrives at the destination intact
and is dropped by packet_filter (Transport.py:1283-1285) as
"for another transport instance".
- Mandates HEADER_1 with no transport_id for all post-handshake
link DATA / Resource / control packets regardless of hop count.
- Asymmetry with LINKREQUEST (which IS path_table-routed and so
HEADER_2-eligible) is spelled out.
Companion changes:
- §6.4 renamed to "Session keys and link activation"; existing
HKDF content moved into §6.4.1.
- §2.5 LRRTT context-byte entry points at §6.4.2.
- §12.5.2 (Link DATA forwarding) cross-references §6.4.3.
- §14 failure-modes table: two new entries for the silent-drop
chains documented above.
- flows/send-link-lxmf.md step 4 strengthened (LRRTT is mandatory,
not informational); step 6 corrected (Transport.outbound does NOT
apply HEADER_1→HEADER_2 for link DATA — that conversion is
path_table-keyed, link DATA is link_table-keyed).
- test-vectors/links.json extended with an LRRTT entry: pinned
rtt_seconds=0.05 + pinned 16-byte IV produces deterministic
wire bytes for the encrypted body.
- tools/regen_links.py drives the LRRTT generation with an
os.urandom patch for the Token IV.
- tools/verify_link_lrrtt.py (new) locks the wire claims:
HEADER_1, ctx=0xfe, dest=link_id, body decrypts under
derived_key to msgpack float64 matching rtt_seconds.
Citations all verified against installed RNS 1.2.4 / LXMF 0.9.7.
All 14 verifiers PASS.
Upstream RNS 1.2.4 (2026-05-07) announces it is "probably the last
release that is also published to GitHub" — pip continues until rnpkg
is complete and RNS is self-hosting. All 13 verifiers pass against
1.2.4 / 0.9.7; no wire-format, signing, or protocol behavior changed
between 1.2.0 and 1.2.4, so the changes here are purely currency:
- Pin tools/requirements.txt to rns==1.2.4 / lxmf==0.9.7 so the
verifier stays reproducible if upstream stops mirroring to PyPI
before the migration is ready.
- Add an "Upstream distribution shift" watch-list to todo.md (local
Reticulum node, repo destination hash, rnpkg install/upgrade
commands, rsg signature verification, mirroring source citations).
- Bump SPEC.md frontmatter and re-anchor ~50 line citations across
Identity.py, Transport.py, Resource.py, Link.py, Reticulum.py,
Packet.py, and LXMF/* (Identity.py drift was the heaviest at +13
to +31 lines; Transport.py was variable). Fix one numeric
(MAX_RANDOM_BLOBS = 32 → 64) and one semantic (§6.6.3 LRPROOF MTU
clamp citation pointed at the wrong location — corrected to point
at the transit-relay clamp at Transport.py:1539-1556).
- Update §10.4 decompression-bomb hazard to note upstream's 1.1.9 cap
adoption, with citations to Resource.py:686-691 and Buffer.py:95-97
plus a "do not use one-shot bz2.decompress()" warning.
- Re-anchor 11 flows/ files (version pins + ~30 line citations).
- Bump version labels in tools/README.md, test-vectors/README.md, and
4 verifier docstrings + 2 hardcoded print strings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three deterministic vector files complete the test-vectors/ bootstrap.
Each regenerator pins every random source so output is byte-identical
across runs against a fixed upstream RNS / LXMF version.
- announces.json: two vectors (no-ratchet + with-ratchet) signed by
Alice. Determinism via patched Identity.get_random_hash + module-
local time.time shim inside RNS.Destination.
- lxmf.json: two opportunistic-LXMF vectors Alice -> Bob, captures
full plaintext (S5.2 layout) plus Token-encrypted ciphertext (S3).
Determinism via fixed LXMessage.timestamp, ephemeral X25519 priv,
and Token CBC IV.
- links.json: full Link handshake — LINKREQUEST + LRPROOF wire bytes,
derived link_id, ECDH shared secret, and HKDF-derived session key
that both initiator and responder MUST agree on. Determinism via
three queued ephemeral priv-key blobs (initiator X25519, initiator
Ed25519, responder X25519) consumed in source-call order at
RNS/Link.py:285, :286, :278.
Status table in test-vectors/README.md and tools/README.md updated to
reflect the completed bootstrap. todo.md cleaned up to reflect actual
state (the previous "Open ⚠️ items needing a runtime verifier" section
was stale — all three verifiers were completed earlier).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds tools/ verifier scripts that exercise upstream RNS / LXMF and confirm
(or correct) the SPEC.md callouts:
- §2.3 HEADER_1→HEADER_2 conversion: verified by stubbing Transport.transmit
and seeding a multi-hop path_table entry.
- §4.3 app_data 3-element variant: producer in LXMF 0.9.6 actually emits
2 elements only (supported_functionality at LXMRouter.py:999 is dead
code); parser tolerates 1/2/3-element + raw UTF-8.
- §7.1 path? always-precedes claim: actually conditional on
not has_path() AND method==OPPORTUNISTIC.
- §7.4 ratchet ring default 8: actually Destination.RATCHET_COUNT = 512
at RNS/Destination.py:85.
Also fixes a documentation bug in §1.2: the rnstransport.path.request row
of the well-known-hash table had the dest-hash prefix where the name_hash
should be (correct name_hash is 7926bbe7dd7f9aba88b0).
Seeds test-vectors/identities.json (Alice + Bob) with a regenerator
(tools/regen_identities.py) and verifier (tools/verify_destination_hash.py)
covering §1.1 and §1.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bootstrapped from the working notes of two reverse-engineering efforts:
- reticulum-lora-webclient (web/Capacitor)
- reticulum-mobile-app (Kotlin Multiplatform / Android)
SPEC.md consolidates byte-level wire format findings that aren't in the
upstream Reticulum manual. Each section grounded in upstream Python
source citations (file + line) where possible.
agent.md establishes the verification rules:
- Every claim is verified, unverified, or speculation; markers required
- Verification means a runnable script or a source citation
- PRs that quietly remove markers get rejected
tools/ and test-vectors/ are placeholder scaffolding with READMEs
describing the work needed.
Sections in SPEC.md flagged as currently UNVERIFIED:
- §2.3 Originator HEADER_1 -> HEADER_2 conversion
- §4.3 app_data 3-element variant with capabilities
- §7.1 path? always precedes LXMF (vs only on stale paths)
- §7.4 ratchet ring count default = 8
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>