Protocol
Protocol
Handles, resolve endpoint, signing, sandboxing — Protocol Spec §9.
Identity
Every TP Social identity is an Ed25519 keypair. The 32-byte public key is the canonical identifier; the seed is held only by the desktop client. A signature over a canonical payload binds that identity to anything the holder publishes.
The registry does not assign keys. It accepts whatever key shows up with a valid claim and binds a human-readable handle to it.
Handles
Handles are short, memorable names that point at a public key. They
live in a flat namespace — there is no @org/alice nesting. Per
Protocol Spec §9.3.1, the allowed shape is:
^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$
This is enforced server-side at claim time. Reserved names that collide with platform routes (see the registry config) are not available.
Claiming
A claim is a CBOR-encoded record signed by the keypair that will own
the handle. The record fields and their byte layout are documented in
the spec; the short version is { handle, pubkey, timestamp }. The
registry verifies the signature before writing the row.
Resolving — §9.3
Any client can resolve a handle into its public key by GETting:
GET /.well-known/tpsocial/resolve/{handle}
The response is a CBOR document containing the public key and the
claim's attestation wire, so the caller can re-verify the binding
without trusting the registry. The endpoint is content-typed
application/cbor and includes an ETag derived from the
attestation hash for cheap caching.
Pages
A page is a signed, immutable publication under a handle. The CBOR wire format covers:
handle_pubkey— 32 bytes, must match the claimslug— addressable path component,[a-zA-Z0-9-_.]+mime— content type (text/markdown,text/html, …)content_hash— 32-byte BLAKE3 of the content bodysignature— 64-byte Ed25519 over the canonical CBOR
The registry verifies the signature, hashes the wire with BLAKE3, and
stores the bundle. Re-publishing under the same slug overwrites
the previous version (per the upsert behaviour of POST /pages); the
spec keeps no history at the registry layer.
Sandboxing — §9.4
Pages with mime: text/html can't be inlined into the registry shell
without exposing the shell's cookies to any escape. So:
- Markdown is rendered inline through CommonMark with
html_input: escapeandallow_unsafe_links: false. The output is sanitised at the renderer level — no script tags, nojavascript:links. - HTML is rendered in an iframe whose
srcpoints at a separate eTLD+1 (tpsocial-content.{tld}in prod). That origin has no cookies and serves only page bundles, so a sandbox escape gets you nothing.
The content origin is configured at tpsocial.content_origin; in
local development it's https://tpsocial-content.test.
Verifying a page yourself
Anyone holding the page wire can re-verify it without the registry:
- Resolve the handle to fetch its public key (§9.3).
- BLAKE3-hash the wire bytes.
- Verify the Ed25519 signature on that hash with the public key.
If steps 2 and 3 both pass, the page is provably signed by whoever owns the handle — independent of the registry that delivered it.
What this spec deliberately doesn't include
- Federation. The flat namespace is per-server in v1. Cross-server resolution is targeted for v1.4.
- Mutable feeds. Pages are individually addressed and immutable. Aggregation (e.g. "all pages under @alice") is a client concern, not a protocol concern.
- Encryption. Pages are public. End-to-end encrypted channels are out of scope for the publishing protocol.