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.
146 lines
5 KiB
Python
146 lines
5 KiB
Python
"""
|
|
Regenerator for test-vectors/resources.json.
|
|
|
|
Builds a deterministic multi-part Resource over a fixed fake Link. The link
|
|
Token key, Token IV, throwaway prefix, and Resource random-hash salt are pinned
|
|
so every ciphertext byte, part packet, map hash, advertisement, and proof is
|
|
reproducible against the pinned RNS version.
|
|
|
|
Run from repo root:
|
|
|
|
python tools/regen_resources.py
|
|
|
|
Updates `test-vectors/resources.json` in place. Exit 0 on success.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
import RNS
|
|
from RNS.Cryptography.Token import Token
|
|
from RNS.Resource import Resource, ResourceAdvertisement
|
|
|
|
|
|
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
OUT_PATH = os.path.join(REPO_ROOT, "test-vectors", "resources.json")
|
|
|
|
FIXED_TOKEN_KEY = bytes(range(64))
|
|
FIXED_TOKEN_IV = bytes.fromhex("404142434445464748494a4b4c4d4e4f")
|
|
FIXED_PREFIX = bytes.fromhex("10111213")
|
|
FIXED_RANDOM_HASH = bytes.fromhex("20212223")
|
|
PLAINTEXT = bytes(range(256)) * 5
|
|
|
|
|
|
class FakeLink:
|
|
"""Minimum deterministic Link surface needed by Resource construction."""
|
|
|
|
def __init__(self):
|
|
self.type = RNS.Destination.LINK
|
|
self.status = RNS.Link.ACTIVE
|
|
self.mtu = RNS.Reticulum.MTU
|
|
self.mdu = RNS.Link.MDU
|
|
self.hash = bytes.fromhex("00112233445566778899aabbccddeeff")
|
|
self.link_id = self.hash
|
|
self.rtt = 0.1
|
|
self.traffic_timeout_factor = 1
|
|
self.last_outbound = 0
|
|
self.tx = 0
|
|
self.txbytes = 0
|
|
self._token = Token(FIXED_TOKEN_KEY)
|
|
|
|
def encrypt(self, data: bytes) -> bytes:
|
|
return self._token.encrypt(data)
|
|
|
|
def decrypt(self, data: bytes) -> bytes:
|
|
return self._token.decrypt(data)
|
|
|
|
|
|
def main() -> None:
|
|
print(f"regen_resources.py against RNS {RNS.__version__}")
|
|
token_mod = sys.modules["RNS.Cryptography.Token"]
|
|
real_urandom = token_mod.os.urandom
|
|
real_get_random_hash = RNS.Identity.get_random_hash
|
|
random_hashes = [
|
|
FIXED_PREFIX + bytes(28),
|
|
FIXED_RANDOM_HASH + bytes(28),
|
|
]
|
|
|
|
def fixed_urandom(length: int) -> bytes:
|
|
if length == 16:
|
|
return FIXED_TOKEN_IV
|
|
return real_urandom(length)
|
|
|
|
def fixed_random_hash() -> bytes:
|
|
if not random_hashes:
|
|
raise RuntimeError("unexpected additional Resource random-hash request")
|
|
return random_hashes.pop(0)
|
|
|
|
token_mod.os.urandom = fixed_urandom
|
|
RNS.Identity.get_random_hash = staticmethod(fixed_random_hash)
|
|
try:
|
|
link = FakeLink()
|
|
resource = Resource(PLAINTEXT, link, advertise=False, auto_compress=False)
|
|
finally:
|
|
token_mod.os.urandom = real_urandom
|
|
RNS.Identity.get_random_hash = staticmethod(real_get_random_hash)
|
|
|
|
encrypted_stream = b"".join(part.data for part in resource.parts)
|
|
advertisement = ResourceAdvertisement(resource).pack()
|
|
proof = resource.hash + resource.expected_proof
|
|
|
|
vector = {
|
|
"label": "fixed_link_uncompressed_multi_part",
|
|
"inputs": {
|
|
"plaintext_hex": PLAINTEXT.hex(),
|
|
"token_key_hex": FIXED_TOKEN_KEY.hex(),
|
|
"token_iv_hex": FIXED_TOKEN_IV.hex(),
|
|
"throwaway_prefix_hex": FIXED_PREFIX.hex(),
|
|
"random_hash_hex": FIXED_RANDOM_HASH.hex(),
|
|
"link_hash_hex": link.hash.hex(),
|
|
"mtu": link.mtu,
|
|
"auto_compress": False,
|
|
},
|
|
"expected": {
|
|
"encrypted_stream_hex": encrypted_stream.hex(),
|
|
"resource_hash_hex": resource.hash.hex(),
|
|
"expected_proof_hex": resource.expected_proof.hex(),
|
|
"resource_prf_body_hex": proof.hex(),
|
|
"hashmap_hex": resource.hashmap.hex(),
|
|
"advertisement_plaintext_hex": advertisement.hex(),
|
|
"parts": [
|
|
{
|
|
"body_hex": part.data.hex(),
|
|
"map_hash_hex": part.map_hash.hex(),
|
|
"raw_hex": part.raw.hex(),
|
|
}
|
|
for part in resource.parts
|
|
],
|
|
},
|
|
"rns_version_at_generation": RNS.__version__,
|
|
"generator_script": "tools/regen_resources.py",
|
|
"verifies_spec_sections": ["10.2", "10.3", "10.4", "10.8", "10.12"],
|
|
}
|
|
payload = {
|
|
"_about": (
|
|
"Deterministic Resource transfer vector over a fixed Link Token key. "
|
|
"The Resource is uncompressed and spans multiple RESOURCE packets. "
|
|
"A clean-room implementation should encrypt `throwaway_prefix || "
|
|
"plaintext` once with the recorded Token key and IV, slice the "
|
|
"ciphertext at the Resource SDU, reproduce every part and map hash, "
|
|
"pack the advertisement, then assemble and emit the recorded PRF body."
|
|
),
|
|
"vectors": [vector],
|
|
}
|
|
with open(OUT_PATH, "w", encoding="utf-8", newline="\n") as output:
|
|
json.dump(payload, output, indent=2, sort_keys=False)
|
|
output.write("\n")
|
|
|
|
print(f"Wrote {OUT_PATH} with 1 vector")
|
|
print("ALL PASS")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|