Technical documentationThe protocol and the wire, written down. This page is the reference; the white paper is the reasoning; the source is the proof.
A 12-word BIP-39 phrase seeds an HKDF-SHA-256 tree. Derived on the device from the phrase, which the platform keystore holds, and never stored themselves: the X25519 identity key and the Ed25519 signing key, the X25519 signed prekey and one-time prekeys, the ML-KEM-1024 signed prekey, the ML-DSA-65 signing key, the SQLCipher database key, the backup key and the discovery key. Only public halves are published.
identity_key · signing_key · pq_signing_key · signed_prekey + signature + pq_signature · pq_signed_prekey (ML-KEM-1024) + signature + pq_signature · one_time_prekey (unsigned) · devices[] (each prekey signed by the account's Ed25519 key). No signature covers the bundle as a whole; Ed25519 and ML-DSA-65 each sign the account's two signed prekeys. A relay cannot substitute the keys that matter: signed prekeys must verify under the account's pinned Ed25519 and ML-DSA-65 keys, and the safety number commits to the identity key and, when both of you have one, the ML-DSA-65 key.
The initiator's first envelope: its identity and ephemeral public keys, the prekey ids consumed, the ML-KEM ciphertext (pqct), and the first ratchet message. The responder derives the same root key without a round trip. A replayed pkm is detected by the ephemeral key and refused.
Header: DH ratchet public key, previous chain length, message number, and the post-quantum ratchet leg, a fresh ML-KEM-768 encapsulation key on every new sending chain and the ciphertext to the peer’s last one. Body: AES-256-GCM under the message key with the whole header, KEM leg included, as associated data.
Around every ratchet message, except those to and from the official :Spojiti_Beta account, one more layer the relay cannot open: an ephemeral X25519 key against the recipient’s identity key and an ML-KEM-1024 encapsulation to the recipient’s post-quantum prekey, HKDF-SHA-256 into one key, AES-256-GCM over {from, message} with the recipient’s routing id as associated data. Any device of the recipient opens it (both keys come from the twelve words); nobody else can; a quantum adversary recording the relay cannot unwrap the sender later.
A frame is {to: routing id, envelope: base64}, no sender. It arrives over an anonymous connection, is stored with the sender left empty, and the recipient learns who wrote inside the sealed layer. Routing ids are random per account (rotating them is on the roadmap); there is no name, number or e-mail anywhere in the frame or the database. Frames are deleted once your devices collect them. What the relay can still see is which account an envelope is for, its size and when it travels, and the network address a connection came from, the last until the private route ships.
blob = nonce(12) ‖ AES-256-GCM(plaintext) ‖ tag(16) under a fresh 256-bit key; the key travels inside the ratcheted message. The store holds the encrypted blob by random id, with the uploader's account id, its size and type, and one expiry for every plan.
RSABSSA (RFC 9474) with PSS-SHA-384: the app blinds a random message, the payment leg signs it, the app unblinds. For these tokens the relay reads tier and epoch from which key verifies, never from the payload; the welcome trial is a relay-signed token that names its tier. Token = v2.<tier>.<epoch>.<msg>.<sig>.
Per-Echo Ed25519 pseudonym derived from the identity seed and the Echo id. Attunement token = blind signature over spojiti-echo-attune-v1:<echo>:<day>, carried in x-spojiti-attune. Every write is signed: posts spojiti-echo-post-v2|echo|id|parent|sha256(body)|sha256(title)|media, media spojiti-echo-media-v1|echo|sha256, moderation spojiti-echo-mod-v1|echo|op|target|until, marks spojiti-echo-pn-avatar-v1|echo|code.
WebSocket over TLS to the relay; the app races the relay's IPv4 and IPv6 addresses, and its pinned addresses if DNS fails, and keeps the first that answers. Push is a content-free nudge over APNs/FCM; content is fetched over the socket after wake.
WebRTC with DTLS-SRTP, signalled through the ratcheted channel; ICE over our own STUN and TURN (TLS on 443) with a fallback TURN on a second box; a surge valve degrades to STUN-only rather than failing.
Caddy edge → Elixir relay + control plane → PostgreSQL + object store, public Echoes served by the relay from tables of their own, a disaster-recovery box with monthly restore drills, GeoIP for pricing only, encrypted backups. Operational switches in the Ops console need a signed-in staff account and every change lands in an audit log; the rest are set in each box's own configuration.