Skip to content

For the complete documentation index, see llms.txt. The full corpus is available at llms-full.txt. This page is also available as Markdown by appending .md to the URL.

The signed manifest

The manifest is your identity. It is a compact JWS, signed by your key, whose payload lists your accounts, your disavowals, your anchor, your position in your own history, and the recovery policy in force. Our database is a cache of it. Everything we serve could be rebuilt from the signed file, and nothing we hold outranks it.

{
"$schema": "https://realhandles.com/schema/realhandles-v1.json",
"jws": "eyJ...", // authoritative: compact JWS (EdDSA), payload = the manifest
"manifest": { ... }, // human-readable mirror, never trusted on its own
"publicKeyJwk": { ... }, // convenience copy of the signing public key
"keyId": "base64url(SHA-256(pubkey))"
}

Only jws counts. The manifest field is there so a person can read the file without decoding anything, and a verifier that finds the two disagreeing ignores the mirror and believes the signature. That is why verification re-parses the manifest from the bytes the signature covered, rather than from the copy sitting next to it.

Is this signed by the key I already trust?

Never “does a file exist at this domain”, never “did the API return 200”, never “is this person logged in”. Those questions all have answers that somebody else can arrange. A signature has exactly one producer.

The keyId is base64url(SHA-256(...)) of the public key, canonicalized to {crv, kty, x} so it comes out the same on every platform. That fingerprint is what a verifier pins when it decides to trust you.

The server does exactly three things with a manifest: verify it, decide whether the key that signed it is allowed to, and store it.

It holds no private key, so it has no way to produce a manifest on your behalf even if it wanted to. That is what makes “the key is the root of trust” a fact about the system rather than a promise about our conduct.

  1. The signature verifies, and the keyId matches the embedded public key.
  2. The chain position is right. Its seq and prev must continue from the head we actually hold. Get this wrong and you are told to reload and sign again, which is the normal outcome when two devices publish close together. Nothing is lost.
  3. The signing key is allowed. Either it is the key already registered to you, or the manifest carries an authorized rotation or recovery.

A login gets you nowhere here. It decides which identity row you may write to, and then the signature decides whether the write is legitimate.

Why your edit is not public until you re-sign

Section titled “Why your edit is not public until you re-sign”

This is the single most confusing thing about the model, so here it is plainly.

Adding an account, verifying a domain, writing a disavowal, or reordering a list changes our copy immediately. Your profile on realhandles.com shows it right away. Your signed proof does not contain it, because nothing has signed it yet.

Until you publish and your key signs the new version:

  • Anyone verifying you independently sees the previous version, which is the correct answer to give.
  • The change exists only as our word for it, which is exactly the kind of claim this product exists to stop people from having to accept.
  • The dashboard says so, and the mobile app says so, because the alternative would be showing you a proof that is not one.

The fix is not a background job. It is a signature, and only you can produce it. Publish.

One manifest lists every published account and the anchor

One manifest lists every published account and the anchor. Re-signing is what moves an edit from the server into something anyone can check.

FieldPurpose
subjectHandle, display name, public key, keyId
accountsVerified and claimed accounts, in the order you set
disavowedSigned “not me” statements
anchorOne rotatable pointer to a hosted copy. Not the identity
seq, prevPosition in your history and the hash of the version before it
rotationPresent only on a version that changes the signing key
recoveryKeys authorized in advance to take the identity back
issued, statementWhen, and the human-readable claim

seq and prev are optional for backward compatibility: a manifest without them is treated as the first version. Everything else about the format is additive by design, so an old verifier does not break when a new field appears.

The reference verifier is published as @realhandles/verify and is the exact code this site runs, extracted verbatim. The spec, JSON Schema, and conformance vectors, including invalid ones that must fail, live in realhandles/spec.

The verify page on realhandles.com, which fetches a manifest and checks its signature

The page above runs the same check in your browser. It fetches the manifest and tests the signature against the key in the file, so a wrong answer from this server would be visible rather than persuasive.

Neither is required. It is a standard EdDSA JWS, so any JOSE library reaches the same conclusion, which is the entire point of choosing a boring format.

The app signs manifests. The rule that the server never signs is unchanged by that, and so is the rule that only your key can produce a version: the app has to hold the key and have it unlocked, and it says which of those is missing rather than offering a publish button that could only fail.

The Identity tab carries the same split this page describes. A banner reads Saved, but not published yet while your changes are on realhandles.com and not yet in anything signed. Publish & re-sign is what closes it: your key signs the current state and a new entry joins your history.

One thing the app does that the dashboard does not. If the version about to be signed would drop something the last one carried, it asks first and names what is going. Publishing cannot be undone, and the correct moment to notice a missing account is before the signature, not after.

Two devices publishing close together produce the ordinary chain-position rejection described above. The app answers it the same way: reload and sign again. Nothing is lost.