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.

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.