Skip to content
TP Social
Sections

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 claim
  • slug — addressable path component, [a-zA-Z0-9-_.]+
  • mime — content type (text/markdown, text/html, …)
  • content_hash — 32-byte BLAKE3 of the content body
  • signature — 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: escape and allow_unsafe_links: false. The output is sanitised at the renderer level — no script tags, no javascript: links.
  • HTML is rendered in an iframe whose src points 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:

  1. Resolve the handle to fetch its public key (§9.3).
  2. BLAKE3-hash the wire bytes.
  3. 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.