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>
This commit is contained in:
parent
6435c0a0a0
commit
cf169b2a9e
10 changed files with 788 additions and 61 deletions
105
tools/verify_path_request.py
Normal file
105
tools/verify_path_request.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
"""
|
||||
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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue