Test vectors
These are the golden vectors that prove the binary claims in this specification.
They are the genuine output of the reference code, generated by
vectors/gen_vectors.py and stored in machine-readable
form in vectors/vectors.json.
Regenerate and verify (from the repository root):
PYTHONPATH=reference/Reticulum:reference/LXMF \
python3 docs/src/appendix/lxmf/vectors/gen_vectors.py
Pinned to LXMF 795fdaa (1.1.0) and Reticulum d5e62d4 (RNS 1.3.5). Fixed
inputs: source identity private = 00010203…3f, destination identity private =
404142…7f, message timestamp = 1700000000.0.
Vector kinds
- frozen — deterministic; the hex is the proof; reproduces byte for byte.
- roundtrip — encryption uses ephemeral key material. The fixture omits the random ciphertext and ciphertext-derived hashes, retaining deterministic lengths, structure, and decrypt assertions. The complete JSON therefore reproduces byte for byte.
VEC-MSG-1 (frozen) — minimal opportunistic message
title=b"Hi", content=b"Hello", fields={}. (LXMessage.py:355-388)
packed (118 B):
cf0b2a4a8d2a0b6978b71290da7cc80e destination_hash(16)
fae321c442e3c9bdcd7a3e79d850e03c source_hash(16)
fb321978105a4c709c3b86930ff15a9d
7b53b3485517ec19e2083b39f7661e6e
531c78fb71d932f0baf13794c42234ab
9320f1ab5b7688e93eaf5960810ece00 signature(64)
94cb41d954fc40000000c4024869c405
48656c6c6f80 packed_payload (msgpack)
message_id = 9aec506b63deab21d8fa4954d9f743cf20f5adeeb1abd1c7429bb3f832dc287b
signature_valid = true
VEC-MSG-2 (frozen) — fields dict with integer key
title=b"", content=b"body text", fields={0x0F: 0x02}. (LXMessage.py:355)
packed_payload: 94cb41d954fc40000000c400c409626f64792074657874810f02
94 array(4)
cb 41d954fc40000000 timestamp 1700000000.0
c4 00 title bin(0) = ""
c4 09 626f6479... content bin(9) = "body text"
81 0f 02 fields {0x0F: 0x02}
VEC-MSG-NEGATIVE-FIELD (frozen) — negative-fixint field key
fields={-1: b"negative field"} proves that unknown signed integer keys retain
their MessagePack type. The 0xff byte below is negative fixint -1, not an
unsigned field ID or a string key. (LXMessage.py:215-219,355-387)
packed_payload:
94cb41d954fc40000000c40c6e65676174697665206b6579c404626f6479
81ffc40e6e65676174697665206669656c64
^^ key = -1
VEC-MSG-TICKET (frozen) — ticket-stamped message, five-element payload
The only deterministic stamped message: an outbound ticket yields
truncated_hash(ticket || message_id) instead of a mined proof of work, and the
carried FIELD_TICKET value is the [expires, ticket] list generate_ticket
returns. outbound_ticket = 101112…1f, carried ticket secret 202122…2f,
expires = 1700000000.0 + TICKET_EXPIRY.
(LXMessage.py:355-388,293-302; LXMRouter.py:1096-1100,1770-1772)
packed_payload:
95cb41d954fc40000000c40154c4087469636b65746564
810c92cb41d95be820000000c410202122232425262728292a2b2c2d2e2f
c41054f9096872188db445566721224cca74
95 array(5) <- stamp present
cb 41d954fc40000000 timestamp 1700000000.0
c4 01 54 title bin(1) = "T"
c4 08 7469636b65746564 content bin(8) = "ticketed"
81 0c 92 … fields {FIELD_TICKET: [expires, ticket]}
92 array(2)
cb 41d95be820000000 expires 1701814400.0
c4 10 202122…2f ticket bin(16)
c4 10 54f909…a74 stamp bin(16) = truncated_hash(ticket || message_id)
message_id = 190d522017c991b7e74bd958faadfc780dced409514904e6f7fd514b4a84d974
stamp_value = 256 (COST_TICKET)
The message ID and the signature are computed over the four-element payload; the stamp is appended afterwards and is covered by neither.
VEC-MSG-3 (frozen) — unpack + verify of VEC-MSG-1
After making the source identity recallable, unpack_from_bytes of VEC-MSG-1
yields signature_validated = true, matches_source = true, recovered
title="Hi", content="Hello". (LXMessage.py:747-822)
VEC-MSG-FOREIGN-* (frozen) — reader tolerance for payload[0]
Three payloads built with payload[0] spliced in as a non-float64 msgpack
number — uint32, float32 and a negative fixint — and signed over the bytes
the writer packed. Each vector records the reference decoder's own verdict:
reference_signature_validated = true and reference_message_id_matches = true in all three cases, with reference_timestamp_type showing int or
float as packed. They exist because LXMF's writer cannot produce these forms
(time.time() is always a Python float) while its reader accepts them, so
nothing generated from the reference's writer covers the case. See
Reader tolerance.
(LXMessage.py:747-766)
VEC-DLV-OPP (frozen) — opportunistic on-air payload
on_air = packed[16:] (destination hash omitted). (LXMessage.py:633-634)
VEC-DLV-DIRECT (frozen) — direct on-air payload
on_air = packed (full bytes over a link). (LXMessage.py:635-636)
VEC-PROP-ENVELOPE (roundtrip) — propagation envelope
Structure destination_hash(16) || destination.encrypt(packed[16:]); envelope
msgpack([timestamp, [lxmf_data]]); transient_id = full_hash(lxmf_data);
destination.decrypt(pn_encrypted) == packed[16:] holds. The random transient
ID itself is intentionally not stored. (LXMessage.py:426-436)
VEC-PAPER-URI (roundtrip) — paper URI
lxm://base64url(destination_hash(16) || destination.encrypt(packed[16:])) with
= padding stripped; prefix lxm://; decoding and decrypting recovers
packed[16:]. Random URI body bytes are intentionally not stored.
(LXMessage.py:446-451,698-713)
VEC-STAMP-1 (frozen) — stamp workblock, validity, value
material = full_hash(b"lxmf-spec-stamp-material") = 1c91877ffb9797aa6f33064586b47a3c41f6dfa75e10aa17bc24bf0ac6833712,
expand_rounds=4, target_cost=8, workblock 1024 B. Deterministic search
stamp = full_hash(material || counter_be8) finds counter=377:
stamp = 9b79689af899049accea13624a3c59221603117e81086a86a3249ce278acc35e
target = 0100000000000000000000000000000000000000000000000000000000000000 (1 << 248)
valid = true
value = 8
(LXStamper.py:49-77)
VEC-STAMP-PN (frozen) — propagation-node stamp
This vector pins the full propagation-node workblock rather than only the
common stamp algorithm: material 00..1f, expand_rounds=1000, and
target_cost=8 produce a 256000-byte workblock whose SHA-256 is
36224067c2ebd1a40b0dc8908bd34dc4b7cd84be57f68a765dbaf3298bcd9719.
Searching counter_be32 finds counter 17:
stamp = 0000000000000000000000000000000000000000000000000000000000000011
digest = 00420981fc866c09cb2f28e7503147d56bae0b3a6fc69f00478cb247f8d95b91
valid = true
value = 9
(LXStamper.py:13,53-63,122-155)
Propagation mailbox /get vectors (frozen)
These are the complete client-only exchange at /get
(LXMPeer.MESSAGE_GET_PATH): initial list, selected download,
acknowledgement/purge, and both response shapes. The implementation does not
expose /offer.
VEC-PROP-GET-LIST
[None, None] (LXMRouter.py:511-520):
request: 92c0c0
VEC-PROP-GET-DOWNLOAD
One wanted ID (20..3f), one already-held ID (40..5f), transfer limit 1000
KB (LXMRouter.py:1577-1595):
request:
9391c420202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f
91c420404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f
cd03e8
VEC-PROP-GET-ACK
[None, [transient_id]], which asks the node to purge the acknowledged entry
(LXMRouter.py:1625-1637):
request: 92c091c420000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
VEC-PROP-LIST-RESPONSE
response:
92c420202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f
c420404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f
VEC-PROP-GET-RESPONSE
Two binary downloaded entries, b"one" and b"two"; error responses are
ccf0 (NO_IDENTITY) and ccf1 (NO_ACCESS).
response: 92c4036f6e65c40374776f
VEC-ANN-DELIVERY (frozen) — delivery announce app_data
LXMF 1.0.1 advertises Resource compression in the third element:
msgpack([b"Alice", 8, [SF_COMPRESSION]]).
app_data: 93c405416c696365089100
93 array(3)
c4 05 416c696365 display_name bin(5) = "Alice"
08 stamp_cost = 8
91 00 supported functionality = [SF_COMPRESSION]
first_byte = 0x93 (new-format sniff)
decoded: display_name="Alice", stamp_cost=8, compression_supported=true
(LXMRouter.py:1034-1050; LXMF.py:151-200)
VEC-ANN-PROPAGATION (frozen) — propagation node announce app_data
7-element list, metadata={PN_META_NAME: b"Node"}, stamp_costs=[16,3,18],
fixed timebase:
app_data: 97c2ce6553f100c3cd0100cd2800931003128101c4044e6f6465
valid = true; pn_name = "Node"; pn_stamp_cost = 16
(LXMRouter.py:324-336; LXMF.py:202-250)