reticiulum-specification/tools/verify_path_request.py
Rob cf169b2a9e Verify §2.3, §4.3, §7.1, §7.4 against upstream RNS 1.2.0 / LXMF 0.9.6
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>
2026-05-03 10:14:51 -04:00

105 lines
4.6 KiB
Python

"""
Verifier for SPEC.md S7.1, S7.2 (path requests).
Verifies:
- The well-known path-request destination hash:
SHA256(SHA256("rnstransport.path.request")[:10])[:16]
== 6b9f66014d9853faab220fba47d02761
- The full SPEC table for `name_hash` values.
- The path-request payload format constructed by upstream
`RNS.Transport.request_path` (LXMF/LXMRouter.py:1672-1674 calls path):
transport-disabled (leaf): destination_hash(16) || tag(16) -> 32B
transport-enabled : destination_hash(16) || transport_id(16)
|| tag(16) -> 48B
- That LXMF triggers a path? request from `LXMRouter.handle_outbound` at
line 1672 ONLY when `not has_path(destination_hash) and method ==
OPPORTUNISTIC`. The "always-precedes" claim in older spec drafts is too
strong: the request is conditional on the path-table miss.
Exit code 0 on PASS, non-zero on FAIL.
"""
from __future__ import annotations
import hashlib
import inspect
import sys
import RNS
import LXMF
from LXMF.LXMRouter import LXMRouter
def fail(msg: str) -> None:
print(f"FAIL: {msg}")
sys.exit(1)
def verify_well_known_hashes():
cases = [
("lxmf.delivery", "6ec60bc318e2c0f0d908"),
("lxmf.propagation", "e03a09b77ac21b22258e"),
("nomadnetwork.node", "213e6311bcec54ab4fde"),
("nomadnetwork.gossip", "0ad8bff9ff75737c058e"),
("rnstransport.broadcasts", "9efb9c771eeb5ae90ea6"),
("rnstransport.remote.management", "4848a053c16415bed6c8"),
("rnstransport.path.request", "7926bbe7dd7f9aba88b0"),
]
for name, expected_namehash_hex in cases:
actual = hashlib.sha256(name.encode("utf-8")).digest()[:10]
if actual.hex() != expected_namehash_hex:
fail(f"S1.2 name_hash for {name!r}: "
f"got {actual.hex()} want {expected_namehash_hex}")
print("PASS S1.2 well-known name_hash table")
# Full path-request dest_hash: SHA256(name_hash || identity_hash)[:16]
# with identity=None -> identity_hash = b"" (Destination.hash w/ identity=None
# at RNS/Destination.py:121 has addr_hash_material = name_hash only).
expected_pr_dest_hash = "6b9f66014d9853faab220fba47d02761"
namehash = hashlib.sha256(b"rnstransport.path.request").digest()[:10]
pr_dest_hash = hashlib.sha256(namehash).digest()[:16]
if pr_dest_hash.hex() != expected_pr_dest_hash:
fail(f"S7.1 path-request dest_hash: got {pr_dest_hash.hex()} "
f"want {expected_pr_dest_hash}")
print("PASS S7.1 path-request dest_hash 6b9f66014d9853faab220fba47d02761")
def verify_request_path_payload_format():
# Read source — verify the body construction matches the SPEC's claim.
src = inspect.getsource(RNS.Transport.request_path)
# transport-disabled: destination_hash + request_tag
# transport-enabled : destination_hash + Transport.identity.hash + request_tag
if "destination_hash+request_tag" not in src.replace(" ", ""):
fail(f"S7.1 transport-disabled payload form not found in request_path source:\n{src}")
if "destination_hash+Transport.identity.hash+request_tag" not in src.replace(" ", ""):
fail(f"S7.1 transport-enabled payload form not found in request_path source:\n{src}")
print("PASS S7.1 request_path payload format "
"(leaf: dest+tag; transport: dest+xport_id+tag)")
def verify_lxmf_only_when_pathless():
# Confirm the LXMF outbound path-request is gated on
# `not has_path(destination_hash) and method == OPPORTUNISTIC`.
src = inspect.getsource(LXMRouter.handle_outbound)
if "not RNS.Transport.has_path(destination_hash) and lxmessage.method == LXMessage.OPPORTUNISTIC" not in src:
fail("S7.1 LXMF path-request gating expression not found in "
"LXMRouter.handle_outbound — upstream may have changed the rule. "
f"Source excerpt:\n{src[:2000]}")
if "Pre-emptively requesting unknown path" not in src:
fail("S7.1 LXMF 'Pre-emptively requesting unknown path' log line not found "
"in handle_outbound")
print(f"PASS S7.1 LXMF {LXMF.__version__}: path? issued only when "
"has_path()=False AND method=OPPORTUNISTIC "
"(LXMF/LXMRouter.py handle_outbound)")
def main():
print(f"verify_path_request.py against RNS {RNS.__version__} / LXMF {LXMF.__version__}")
verify_well_known_hashes()
verify_request_path_payload_format()
verify_lxmf_only_when_pathless()
print("ALL PASS")
if __name__ == "__main__":
main()