# RetiNet Assessment Against `SPEC.md`

Date: 2026-06-09

## Executive conclusion

RetiNet is a broad Python Reticulum implementation derived from the canonical
Python RNS codebase. It retains substantial implementations of identity,
destinations, packets, announces, Links, Resources, Channel,
REQUEST/RESPONSE, transport relay, tunnels, shared instances, IFAC, KISS,
HDLC, RNode, and AutoInterface.

The staged `dev` head is not currently interoperable with RNS 1.x as claimed,
however. Its independent AES-256 migration introduced blocking defects in
Token validation and packet-size calculations:

1. Every valid minimum-size 64-byte Token is rejected. This includes the
   encrypted LRRTT packet required to complete Link activation and short
   SINGLE, GROUP, and Link payloads.
2. AES-256-CBC is incorrectly assigned a 32-byte block size. AES-CBC has a
   16-byte block size regardless of key length. This reduces RetiNet's packet
   and Link MDUs and moves the direct-LXMF packet/Resource boundary from the
   specified 319/320 content bytes to 303/304.
3. Link mode signalling is hard-coded rather than implemented as a negotiated
   and validated field. RetiNet does not expose the current Link mode
   constants or reject unsupported inbound modes.
4. The fork predates current transport hardening: per-interface path-request
   limiting and blackhole enforcement are absent, and invalid-signature
   announces can consume held-announce state.
5. Native LXMF is absent.

The practical verdict is:

- **Foundational RNS architecture and protocol breadth:** strong.
- **Current opportunistic packet interoperability:** broken for short
  encrypted payloads.
- **Current Link interoperability:** broken at LRRTT activation.
- **Resource, Channel, and REQUEST/RESPONSE implementation depth:** broad,
  but unusable until Link activation and MDU defects are corrected.
- **Full `SPEC.md` clean-room implementation:** incomplete.
- **Native LXMF implementation:** absent.

## Assessment basis

This assessment used:

- specification target:
  `Reticulum@422dc055` (`RNS 1.3.5`) and
  `LXMF@fab12ad` (`LXMF 1.0.1`);
- staged RetiNet commit:
  `6039094fbd8fd40aa8bdca75db0697b1f5672cc5`;
- RetiNet-reported version: `0.9.4`;
- source inspection, repository tests from a writable temporary copy, focused
  specification verifiers, and comparison with `SPEC.md`.

RetiNet states that it began as a fork of Python `rns`, that nodes should
interoperate with `rns` v1.x, and that software using the RNS API should work
unchanged:

- `README.md:10-11`
- `README.md:25-34`

No source files in RetiNet were modified.

## Major findings

### Critical: Valid minimum-size Tokens are rejected

`SPEC.md` section 3 permits a Token containing:

```
iv(16) || one ciphertext block(16) || hmac(32)
```

for a total length of 64 bytes. This is the normal encoding for plaintexts of
zero through fifteen bytes after PKCS#7 padding.

RetiNet's `Token.verify_hmac()` rejects every token whose length is less than
or equal to 64 bytes:

- `RNS/Cryptography/Token.py:58-60`

The rejection breaks all short encrypted payloads, including:

- the msgpack-encoded LRRTT value sent after the initiator validates LRPROOF;
- short Link DATA, Channel control, SINGLE, and GROUP packets;
- the repository's own fixed identity-encryption vector.

`tools/verify_link_lrrtt.py` reproduced the Link failure directly: the
specified LRRTT Token is 64 bytes and RetiNet raises
`ValueError: Cannot verify HMAC on token of only 64 bytes`.
`tools/verify_token_crypto.py` failed on the same condition.

This is a current wire-interoperability blocker, not an optional feature gap.

The same Token constructor accepts only 64-byte keys:

- `RNS/Cryptography/Token.py:48-56`

It therefore also rejects the 32-byte GROUP keys that `SPEC.md` section 1.4
and current RNS continue to support.

### High: AES-256 block size is incorrectly defined as 32 bytes

AES-256 changes the key length, not AES's fixed 16-byte block size. RetiNet
defines `AES256_BLOCKSIZE = 32` and uses it when calculating Packet and Link
MDUs:

- `RNS/Identity.py:88-92`
- `RNS/Packet.py:114-130`
- `RNS/Link.py:93-106`
- `RNS/Link.py:665-677`

This produces smaller payload limits than current RNS and changes protocol
cutovers derived from those limits. The specification Resource verifier
reported:

```
S10 default direct-LXMF threshold changed: got 303, want 319
```

The direct-LXMF verifier likewise found that the specified 319-byte boundary
does not fill RetiNet's Link MDU. Resource and Channel code are present, but
their packet sizing is not compatible with the current wire contract.

### High: The repository's Link integration suite is not green

From a writable temporary copy with local socket access, RetiNet's own suite
reported:

```
Ran 34 tests
FAILED (failures=11, skipped=1)
```

The failures included the fixed identity encryption vector and all tested
Link, packet, Resource, Channel, and Buffer round trips after Link activation
failed. A spawned target process also failed to import the archived compiled
RNS module because `AES256` was unavailable from
`RNS.Cryptography.aes`.

The repository therefore does not currently have a passing regression gate
for its stated RNS v1.x and RNS API compatibility claims.

### Medium: Link mode signalling is hard-coded and not validated

`SPEC.md` section 6.6 defines the top three signalling bits as a negotiated
mode field. Current RNS exposes mode constants, emits only enabled modes,
decodes the inbound mode, and rejects unsupported modes during handshake.

RetiNet instead:

- has no `MODE_AES256_CBC`, `ENABLED_MODES`, `signalling_bytes()`, or inbound
  mode-decoding API;
- sets mode `0x01` by OR-ing `0x200000` into MTU signalling;
- masks mode bits out when reading MTU, then always derives a 64-byte key.

Evidence:

- `RNS/Link.py:152-189`
- `RNS/Link.py:352-361`
- `RNS/Link.py:409-419`

The emitted mode bits match today's AES-256-CBC value when signalling is
present, but unsupported or future inbound modes are silently treated as
AES-256-CBC instead of being rejected. The Link handshake verifier could not
run because the required public mode API is absent.

### Medium: Current transport abuse controls are incomplete

RetiNet retains an older announce ingress limiter and held-announce queue, but
its announce receive path only returns early when signature validation
succeeds. An invalid-signature announce can still reach
`interface.hold_announce()` while ingress limiting is active:

- `RNS/Transport.py:2215-2229`

This is the state-exhaustion hazard corrected in RNS 1.3.4 and documented in
`SPEC.md` section 4.5.

No current per-interface path-request methods or timestamp queues
(`received_path_request()`, `should_ingress_limit_pr()`,
`should_egress_limit_pr()`) were found. Recursive unknown-path discovery
fans out without those controls:

- `RNS/Transport.py:4127-4162`

No blackhole identity state, persistence, updater, or announce enforcement was
found. These omissions affect busy transport nodes and operator security
controls rather than ordinary packet bytes.

### Medium: Native LXMF is absent

RetiNet implements RNS rather than LXMF. No native LXMF message
packer/parser, fields, stamps/tickets, propagation-node protocol, or peer-sync
implementation was found.

This is a scope gap rather than a defect in the RNS core, but it remains a
major gap against the combined RNS/LXMF `SPEC.md`.

### Low: Packaging metadata conflicts with project identity

The README identifies the project as AGPL-licensed RetiNet and says no Python
package is available. `setup.py` still publishes package names `rns` and
`rnspure`, identifies Mark Qvist as author, links to `reticulum.network`, and
declares the MIT license:

- `README.md:13,47`
- `setup.py:16-22`
- `setup.py:27-40`

This is not a wire-protocol defect, but it creates avoidable installation,
licensing, and provenance risk for a fork intended to coexist with `rns`.

## Strongly covered areas

Source and focused verifiers show substantial inherited implementation depth
in:

- identity serialization, hashing, signatures, X25519, ratchets, and
  destination hashing;
- GROUP destination structure and 64-byte AES-256 Token keys;
- packet headers, announces, replay handling, path requests, and ordinary
  transport relay;
- Link request/proof layout, link-id derivation, lifecycle, receipts, and
  identify;
- Resource multi-segment transfer, compression, retransmission, and proofs;
- Channel, Buffer, and Link REQUEST/RESPONSE;
- tunnels, shared-instance operation, interface modes, IFAC, KISS, HDLC,
  RNode, AutoInterface, TCP, UDP, I2P, and SOCKS interfaces.

Focused specification verifiers passed for identity/destination hashes,
announces, Channel mechanics, msgpack behavior, path-request wire form,
proof packets, ratchet replay behavior, REQUEST/RESPONSE, RNode split framing,
stamps, and several LXMF structures supplied by the installed LXMF package.

## Verification performed

### RetiNet repository suite

Run from a writable temporary copy using `specenv`:

- **34 tests run**
- **22 passed**
- **11 failed**
- **1 skipped**

The source checkout itself was not modified.

### Specification verifier matrix

With RetiNet injected as the active `RNS` package:

- **14 verifier scripts exited successfully**
- **13 verifier scripts failed**

The highest-confidence direct conformance failures were:

- `verify_token_crypto.py`: valid 64-byte Token rejected;
- `verify_link_lrrtt.py`: LRRTT Token rejected;
- `verify_link_handshake.py`: Link mode API absent;
- `verify_resource.py`: direct-LXMF threshold is 303 instead of 319;
- `verify_link_lxmf.py`: 319-byte boundary does not fill Link MDU.

Several transport verifier failures were caused by RetiNet exposing the older
`Transport.destinations` list instead of the current
`Transport.destinations_map` API. They show current API drift but do not by
themselves establish transport wire-byte defects.

## Recommended conformance order

1. Correct `Token.verify_hmac()` to accept valid 64-byte Tokens and add
   zero-through-fifteen-byte plaintext vectors for SINGLE, GROUP, and Link
   encryption.
2. Set the AES-256-CBC block size to 16 bytes and restore current Packet,
   Link, Resource, Channel, and direct-LXMF boundaries.
3. Port the complete Link mode signalling API and reject unsupported inbound
   modes.
4. Make the repository suite green in both ordinary Python and any supported
   compiled-module mode.
5. Import this repository's deterministic vector domains as a release gate,
   then run current-RNS interoperability tests in both directions.
6. Port current announce signature prevalidation, per-interface path-request
   limiting, and blackhole enforcement.
7. Correct package name, author, URL, and license metadata before publishing
   distributions.
8. Treat native LXMF as a separate project milestone; until then, describe
   RetiNet as an RNS implementation intended to carry external LXMF.
