141 lines
6.1 KiB
Markdown
141 lines
6.1 KiB
Markdown
|
|
# Tier 1 Audit: Resource Fragmentation
|
||
|
|
|
||
|
|
Question: Does `SPEC.md` §10 accurately describe the wire-visible Resource
|
||
|
|
fragmentation behavior of upstream RNS 1.2.4?
|
||
|
|
|
||
|
|
Evidence baseline:
|
||
|
|
|
||
|
|
- RNS package: `rns==1.2.4`
|
||
|
|
- Source: installed package paths `RNS/Resource.py`, `RNS/Packet.py`,
|
||
|
|
`RNS/Link.py`, and `LXMF/LXMessage.py`
|
||
|
|
- Audit date: 2026-06-08
|
||
|
|
|
||
|
|
This began as a Tier 1 source-analysis report. The focused Tier 2 checks now
|
||
|
|
live in `tools/verify_resource.py`; the confirmed F1-F6 corrections were
|
||
|
|
promoted into `SPEC.md` §10 on 2026-06-08.
|
||
|
|
|
||
|
|
## Confirmed Core Model
|
||
|
|
|
||
|
|
The central §10 model matches RNS 1.2.4:
|
||
|
|
|
||
|
|
- Resource prepends a throwaway 4-byte prefix, encrypts the complete stream
|
||
|
|
once with the Link, then slices the ciphertext into parts
|
||
|
|
(`Resource.py:404-428`, `453-472`; `Packet.py:201-204`).
|
||
|
|
- Advertisement `r` is a distinct 4-byte salt used for the resource hash and
|
||
|
|
per-part map hashes (`Resource.py:440-443`, `505-506`).
|
||
|
|
- Resource integrity is `SHA256(uncompressed_segment_plaintext || r)`, and the
|
||
|
|
expected proof is `SHA256(uncompressed_segment_plaintext || resource_hash)`
|
||
|
|
(`Resource.py:440-443`, `681-695`, `752-758`).
|
||
|
|
- RESOURCE_REQ may carry requested part hashes while also requesting a hashmap
|
||
|
|
continuation; the sender fulfils parts before emitting RESOURCE_HMU
|
||
|
|
(`Resource.py:994-1027`, `1027-1064`).
|
||
|
|
- RESOURCE parts are matched only within the receiver's current window, while
|
||
|
|
sender lookup is bounded by `COLLISION_GUARD_SIZE`
|
||
|
|
(`Resource.py:863-890`, `999-1010`).
|
||
|
|
|
||
|
|
## Findings Requiring Correction or Clarification
|
||
|
|
|
||
|
|
### F1 — Direct LXMF threshold is 319 bytes, not approximately 360
|
||
|
|
|
||
|
|
`SPEC.md` §10 introduction says Resource carries an LXMF body larger than
|
||
|
|
approximately 360 bytes. With default RNS 1.2.4 / LXMF 0.9.7 parameters:
|
||
|
|
|
||
|
|
```text
|
||
|
|
RNS.Link.MDU = 431
|
||
|
|
LXMessage.LXMF_OVERHEAD = 112
|
||
|
|
LXMessage.LINK_PACKET_MAX_CONTENT = 319
|
||
|
|
```
|
||
|
|
|
||
|
|
`LXMessage.pack()` selects Resource representation for DIRECT delivery when
|
||
|
|
`content_size > 319` (`LXMF/LXMessage.py:80-89`, `414-421`).
|
||
|
|
|
||
|
|
Recommended Tier 3 correction: say 319 bytes with default parameters, while
|
||
|
|
noting that the threshold derives from Link MDU and may vary with protocol
|
||
|
|
parameters.
|
||
|
|
|
||
|
|
### F2 — Resource is not the only possible way to carry larger application data
|
||
|
|
|
||
|
|
The §10 introduction calls Resource "the only way" to carry payloads exceeding
|
||
|
|
one Link packet. Resource is the standard RNS mechanism used by LXMF, Link
|
||
|
|
REQUEST/RESPONSE, and `rncp`, but applications can also stream or sequence data
|
||
|
|
over Channel or their own Link DATA protocol.
|
||
|
|
|
||
|
|
Recommended Tier 3 correction: replace "the only way" with "the standard RNS
|
||
|
|
mechanism used by LXMF, REQUEST/RESPONSE, and file-transfer utilities".
|
||
|
|
|
||
|
|
### F3 — Advertisement `d` is total logical-resource size
|
||
|
|
|
||
|
|
For multi-segment resources, advertisement field `d` is `resource.total_size`,
|
||
|
|
the total uncompressed logical transfer size including metadata, not the
|
||
|
|
plaintext size of the advertised segment (`Resource.py:281-314`,
|
||
|
|
`Resource.py:1281-1283`).
|
||
|
|
|
||
|
|
Each segment still has its own:
|
||
|
|
|
||
|
|
- `t`: encrypted transfer size for this segment
|
||
|
|
- `n`: part count for this segment
|
||
|
|
- `h`: integrity hash for this segment
|
||
|
|
- `r`: salt for this segment
|
||
|
|
|
||
|
|
Recommended Tier 3 correction: define `d` as total logical-resource size and
|
||
|
|
explicitly distinguish it from the current segment's uncompressed plaintext
|
||
|
|
length, which is not directly advertised.
|
||
|
|
|
||
|
|
### F4 — `RESOURCE_RCL` is not a general receiver-side cancel notification
|
||
|
|
|
||
|
|
`Resource.reject(advertisement_packet)` sends `RESOURCE_RCL`
|
||
|
|
(`Resource.py:154-163`). A corrupt receiver also calls `reject()` and tears
|
||
|
|
down the Link (`Resource.py:1081-1084`).
|
||
|
|
|
||
|
|
However, ordinary receiver-side `Resource.cancel()` only removes the incoming
|
||
|
|
resource locally; it does not send `RESOURCE_RCL`
|
||
|
|
(`Resource.py:1086-1097`). The current §10.9 wording implies either side can
|
||
|
|
always notify cancellation on the wire.
|
||
|
|
|
||
|
|
Recommended Tier 3 correction: describe `RESOURCE_RCL` as advertisement
|
||
|
|
rejection / corrupt-resource rejection. Do not claim ordinary receiver cancel
|
||
|
|
always emits it.
|
||
|
|
|
||
|
|
### F5 — Resource-part packet storage wording is imprecise
|
||
|
|
|
||
|
|
`SPEC.md` §10.2 says packed wire bytes are stored in `parts[i]`. Upstream stores
|
||
|
|
pre-packed `RNS.Packet` objects in `parts`; each packet's `data` is the raw
|
||
|
|
ciphertext slice and its `raw` field is the packed Reticulum packet
|
||
|
|
(`Resource.py:450-472`).
|
||
|
|
|
||
|
|
Recommended Tier 3 clarification: distinguish the stored Packet object, its
|
||
|
|
Resource body (`part.data`), and complete Reticulum wire packet (`part.raw`).
|
||
|
|
|
||
|
|
### F6 — Pinned-source mismatch in §10.7 citation
|
||
|
|
|
||
|
|
The exhausted-REQ callout cites RNS 1.2.9 while this repository is pinned to
|
||
|
|
RNS 1.2.4. The behavior is already present in RNS 1.2.4
|
||
|
|
(`Resource.py:994-1064`).
|
||
|
|
|
||
|
|
Recommended Tier 3 correction: cite the pinned 1.2.4 behavior first; retain a
|
||
|
|
later-version note only when documenting an actual version change.
|
||
|
|
|
||
|
|
## Tier 2 Verifier Scope
|
||
|
|
|
||
|
|
The first focused verifier is implemented in `tools/verify_resource.py` and
|
||
|
|
avoids a live threaded transfer. It currently covers items 1-6 below:
|
||
|
|
|
||
|
|
1. Construct a Resource with a deterministic fake Link encryption key, fixed
|
||
|
|
throwaway prefix, and fixed advertisement `r`.
|
||
|
|
2. Verify whole-stream encryption occurs before slicing.
|
||
|
|
3. Verify Resource-part Packet bodies are raw slices and are not packet-level
|
||
|
|
re-encrypted.
|
||
|
|
4. Verify advertisement dictionary fields, flags, and hashmap first segment.
|
||
|
|
5. Verify `hash`, `truncated_hash`, `expected_proof`, and map-hash formulas.
|
||
|
|
6. Verify RESOURCE_REQ parsing for both normal and exhausted-with-parts forms,
|
||
|
|
including simultaneous part fulfilment and RESOURCE_HMU generation.
|
||
|
|
7. Verify receiver assembly strips the throwaway prefix, decrypts once,
|
||
|
|
validates the hash, and emits the expected RESOURCE_PRF bytes.
|
||
|
|
8. Add a multi-segment fixture proving `d` remains total logical size while
|
||
|
|
`t`, `n`, `h`, and `r` describe the current segment.
|
||
|
|
9. Add negative cases: malformed ADV, wrong `r`, corrupt part, invalid HMU
|
||
|
|
boundary, and oversized decompression.
|
||
|
|
|
||
|
|
Remaining Tier 2 work is a deterministic `test-vectors/resources.json` plus
|
||
|
|
items 7-9. The confirmed first claim set has already been promoted into
|
||
|
|
`SPEC.md` and the Resource flow documents.
|