Rebuild SPORE from scratch
This is the "reimplement it in any language" guide. It describes the wire format
and the cryptographic identity precisely enough that you can write a compatible
node in C, Python, Go, JavaScript — anything — using only this page and standard
crypto libraries. Every hex value below is generated by the reference code
(cargo run --example worked), so it's authoritative: reproduce these bytes and
you are wire-compatible.
The one-page protocol spec is Spec; this guide walks the bytes by hand. For keeping a running node alive rather than reimplementing the protocol, see Continuity, which also indexes how the pieces fit together.
The primitives you need
SPORE uses five standard building blocks. Any conformant library will do — the algorithms are what matter, not the implementation.
| Purpose | Algorithm | Reference |
|---|---|---|
| Signatures / identity | Ed25519 | RFC 8032 |
| Key agreement (sealed boxes, ratchet) | X25519 | RFC 7748 |
| Sealed-box AEAD | XSalsa20-Poly1305 (NaCl crypto_box) |
NaCl / libsodium |
| Symmetric AEAD (topics, sessions) | ChaCha20-Poly1305 / XChaCha20-Poly1305 | RFC 8439 |
| Hashing (IDs, addresses, fragment selection) | SHA-256 | FIPS 180-4 |
| Ratchet KDF | BLAKE2b | RFC 7693 |
A node that only needs to receive, verify, and display public messages needs just Ed25519 and SHA-256 — everything in sections 1–4 below.
1. Identity — addresses are hashes of keys
A node's address is the first 8 bytes of the SHA-256 of its Ed25519 public key. There is no registry: the address is the key's fingerprint, so it's self-authenticating.
signing seed : 0707070707070707070707070707070707070707070707070707070707070707
public key (32): ea4a6c63e29c520abef5507b132ec5f9954776aebebe7b92421eea691446d22c
SHA-256(pubkey): fe812c12f3ab4ce6ac5db69ac352f906cb1b11ef43fb33e252ef7ff552263889
address [..8] : fe812c12f3ab4ce6
Topics live in the same 8-byte space: a topic address is SHA-256(name)[..8].
So "news" → 19fba0e995b9794f. A message is "public" when its destination is the
all-zero address 0000000000000000.
2. The envelope — the one wire format
Everything on the wire is an envelope: a small header, an optional source key, the payload, and an optional signature. All multi-byte integers are big-endian.
| Offset | Size | Field | Notes |
|---|---|---|---|
| 0 | 1 | ver |
always 0x01 |
| 1 | 1 | typ |
0=DATA, 1=INV, 2=WANT, 3=ANNOUNCE |
| 2 | 1 | flags |
bitfield, below |
| 3 | 1 | hops |
TTL; decremented by each relay |
| 4 | 4 | expiry |
unix seconds, big-endian |
| 8 | 8 | dest |
destination address (all-zero = public) |
| 16 | 32 or 8 or 0 | src |
present only if SIGNED: 32-byte key, or 8-byte address if SRC8 |
| … | 2 | plen |
payload length, big-endian |
| … | plen |
payload |
the bytes |
| … | 64 or 0 | sig |
present only if SIGNED |
Flag bits: 0x01 ENCRYPTED · 0x02 SIGNED · 0x04 FRAGMENT · 0x08 ACKREQ ·
0x10 FLOOD · 0x20 SRC8.
A public, unsigned DATA message to "news" carrying "the dam holds":
ver typ flags hops expiry dest plen payload
01 00 10 10 6553f100 19fba0e995b9794f 000d 7468652064616d20686f6c6473
full wire: 010010106553f10019fba0e995b9794f000d7468652064616d20686f6c6473
(flags=0x10 is FLOOD; hops=0x10 is 16; 000d is 13, the payload length.)
3. Message ID — content addressing
An envelope's ID is SHA-256(envelope with the hops byte set to 0)[..16], taken
over the full bytes including the signature if present. Zeroing hops before
hashing is what keeps the ID stable while relays decrement the TTL. For the
envelope above:
id = 1ff3a7d10b117b007309f1164c3998f7
The ID is used for dedup (drop an ID you've seen), for content-addressed file chunks, and for the priority stamp (§10: the stamp is the count of leading zero bits of the ID — free proof-of-work).
4. Signing and verifying
To sign: set the SIGNED flag, put your 32-byte public key in src, and compute
Ed25519.sign(secret_key, preimage) where preimage = the envelope body with
hops=0 and no signature yet. The 64-byte signature is appended last. To verify,
recompute the same preimage and check it against the src key.
preimage : 010012006553f10019fba0e995b9794fea4a6c63e29c520abef5507b132ec5f99
54776aebebe7b92421eea691446d22c000d7468652064616d20686f6c6473
signature: daa7ab3bd3c46dda41fd7d95800b91e242f95e43185e4cd1f394bfda7b00cac8
065ecb4c63af711aa2462b950a933215a3234c6ef6b14fc55d495d179cdf3906
(Note the preimage's third byte is 0x12 = FLOOD|SIGNED, and its fourth byte —
hops — is 0x00.)
5. Text armor — the paper/voice channel
Any envelope has a text form that survives SMS, handwriting, a read-aloud phone
call, or a photograph: ~S1.<base32(wire)>.<base32(SHA-256(wire)[..4])>~ using the
RFC 4648 base32 alphabet (A–Z2–7), no padding. The trailing 4-byte checksum
catches transcription errors. Type it into any node and the message enters the
mesh.
~S1.AEABEEDFKPYQAGP3UDUZLOLZJ7VEU3DD4KOFECV66VIHWEZOYX4ZKR3WV27L464SIIPOU2IU
I3JCYAANORUGKIDEMFWSA2DPNRSHHWVHVM55HRDN3JA727MVQAFZDYSC7FPEGGC6JTI7HFF73J5QB
SWIAZPMWTDDV5YRVISGFOKQVEZSCWRSGTDO62YU7RK5JFORPHG7HEDA.G23QRDA~
Build it in tiers
You don't need the whole system to be useful — each tier is a working node.
- Tier 0 — read & verify. Parse the envelope (§2), recompute the ID (§3), verify the signature (§4). You can now receive and trust public messages. Needs only Ed25519 + SHA-256. Small enough to hand-type.
- Tier 1 — originate. Build and sign your own envelopes; encode armor (§5). You can now send.
- Tier 2 — relay. Keep a
seenset of IDs, drop duplicates and expired envelopes, decrementhops, and re-broadcast. You are now a router node. The flood/dedup/store rules are in Spec §4–§6. - Beyond. Fragmentation (§3 of the spec, a GF(2) fountain code), sealed boxes, the Double Ratchet, encrypted topics, sessions, files, and mix mode are all layered on the same envelope — add them as you need them.
Conformance
A reimplementation is wire-compatible if it reproduces the hex in sections 1–4
exactly. Run cargo run --example worked in this repo to regenerate the reference
values (they're deterministic — fixed key seeds), and diff against your output.