Reference decoders
Tiny, dependency-free implementations of a SPORE Tier-0 node — enough to
receive, verify, and display public messages — for machines that don't have a
Rust toolchain. They double as cross-language conformance oracles for
docs/REBUILD.md: each reproduces the same addresses, IDs,
and signature checks as the reference library.
| File | What |
|---|---|
spore_t0.py |
Pure-Python T0 node — standard library only; Ed25519 verify is the inlined public-domain reference. |
spore_t0.c |
Pure-C T0 node — zero dependencies; its own SHA-256, SHA-512, and Ed25519 verify (256-bit field arithmetic mod 2²⁵⁵−19). cc -O2 -o spore_t0 reference/spore_t0.c. |
spore_t0.sh |
Shell T0 node — parses + content-addresses with sha256sum/base32, verifies via openssl (3.0+). For a box with the usual Unix tools but no Rust/Python. |
vectors.json |
Deterministic test vectors, generated by cargo run --example gen_vectors. The source of truth. |
test_t0.py |
Checks spore_t0.py against vectors.json (address, IDs, a valid signature, a rejected tampered one, armor round-trip). |
test_decoders.sh |
The same conformance for the C and shell decoders (run in CI by reference.yml). |
spore_t1.py |
Pure-Python T1 decoder — everything above the envelope: link fragmentation, KISS, INV/WANT payloads, manifest encodings. |
versioned_vectors.json |
Vectors for that tier, generated by cargo run --example gen_versioned_vectors. |
test_t1.py |
Checks spore_t1.py against them, plus the forwarding decision table. |
All three T0 decoders do the same job — parse an envelope (hex or ~S1.…~
armor), derive the sender's address, recompute the ID, and verify the Ed25519
signature — and all reproduce the vectors byte for byte.
Two tiers, two promises
T0 is where interop starts, not where it finishes. A node that reproduces
every byte in vectors.json and still cannot parse an INV, reassemble a frame
its link had to split, or read a manifest is wire-compatible and unable to talk
to anyone. That is what T1 covers.
They are pinned differently on purpose:
| file | changes how | |
|---|---|---|
| T0 — §1, §2, the content id | vectors.json |
a ver bump. CI refuses to edit the file at all without the allow-frozen-change label |
| T1 — link framing, INV/WANT, file-layer tags | versioned_vectors.json |
a minor release, so long as both ends of one link agree. CI regenerates it and refuses a stale diff, but does not freeze it |
Build against both. Treat only the first as permanent.
The forwarding table in versioned_vectors.json is observed, not written:
each case drives a real node and records what it did. The first draft was prose
and claimed a frame is forwarded while hops < max — hops counts down and
stops at zero. It also claimed a failed signature stops a relay. It does not, and
must not: verify before binding trust state, do not verify to forward (SPEC
§5). An implementation that "hardens" by dropping unverifiable frames would also
drop every envelope it merely lacks the key for, which is most of them.
Use it
# Dump + verify an envelope (hex or ~S1.…~ armor) — pick any decoder:
python3 reference/spore_t0.py '~S1.AEAB….~'
cc -O2 -o /tmp/spore_t0 reference/spore_t0.c && /tmp/spore_t0 '010012…'
echo '010012…' | bash reference/spore_t0.sh
# Conformance tests:
python3 reference/test_t0.py # Python
bash reference/test_decoders.sh # C + shell
Why this exists
A node you can read in one sitting, in a language that's on almost every machine,
with no packages to install, is the most portable seed of the protocol. If the
Rust build is unavailable, this file — plus docs/REBUILD.md
— is enough to rebuild a compatible node and rejoin the mesh. The Python verify is
correct but intentionally simple (and slow); it's a reference, not a fast library.
The C and shell ports extend this to boxes with a compiler or just the usual Unix tools; the vectors here make them straightforward to validate.