In Hypermedia there is no sign-up form and no user table. An account is a key pair. The private half stays with you and signs everything you publish. The public half is your account ID, the address other people use to find you. A profile, a linked device and a browser session are all signed blobs that point back at that key.
This page explains how keys become accounts, how the account ID is written, how keys are stored, how a profile attaches a name to a key, and how several keys are linked into one identity. It ends with the limited options for recovery and rotation, and with what each Seed surface offers for working with identities.
How it works
An account is a key
The Seed daemon registers two key types. Every key it creates itself is Ed25519. It also accepts ECDSA P-256 keys, which exist so that keys created by a browser's WebCrypto API can sign blobs. Older web identities on hyper.media were P-256 keys. The current web sign-in flow generates Ed25519 keys in WebCrypto, so new browser identities are Ed25519 too.
There is no account object anywhere in the daemon. An account, and the space of documents it owns, is the public key. Every resource in that space has an address of the form hm://<account>/<path>, and the account's home document is the address with an empty path. See Sites for how a space becomes a website.
Term | Meaning |
|---|---|
account | the key pair, named by its public key |
account ID, principal | the public key in its packed, base58 form ( |
space | the namespace of documents that the account owns |
site | a space published at a web domain |
device key | the libp2p peer identity of one daemon; it never signs content |
Principals
The wire form of a public key is a principal: the key's multicodec code as an unsigned varint, followed by the raw key bytes. For Ed25519 the code is 0xed, so a principal is 34 bytes: the two bytes ed 01 and then the 32-byte key. Inside DAG-CBOR blobs a principal is a byte string.
The text form is multibase base58btc of those bytes, so every account ID begins with z. An Ed25519 principal always starts with z6Mk, which matches the did:key encoding of an Ed25519 key. A P-256 principal starts with zDn. Anything that accepts a principal accepts either the text form or the raw bytes, and rejects a key of the wrong length.
z6Mko5npVz4Bx9Rf4vkRUf2swvb568SDbhLwStaha3HzgrLS an Ed25519 account
zDnaeTtfA5… a P-256 (WebCrypto) accountThe daemon also derives a 56-bit actor ID from each principal (the first bytes of its SHA-256 hash) and uses it inside CRDT operation IDs.
Mnemonics and derivation
A human-held account starts as a BIP-39 mnemonic of 12, 15, 18, 21 or 24 words, with an optional passphrase. The daemon turns the words and passphrase into a seed, walks the SLIP-10 path m/44'/104109'/0' (104109 is the Unicode code points of h and m written one after the other), and uses the resulting 32 bytes directly as the Ed25519 private seed. A different passphrase gives a different, equally valid account. Nothing in the code increments the last path segment, so one mnemonic is one account.
Device keys
Device keys are separate from account keys. Each daemon has its own Ed25519 key that is its libp2p peer identity. It authenticates connections and nothing else. It never signs a blob, and a peer is not tied to an account in any table. When a peer needs to prove that it holds an account, it signs a short-lived ephemeral capability naming the account and the remote peer. That proof lives in memory for the length of the connection. Network covers peer authentication, and Privacy covers what access it grants.
Where private keys live
Store | Used by | Notes |
|---|---|---|
local vault | the Seed app's daemon, read by the Seed CLI | an encrypted |
OS keyring | the Seed CLI, older app installs | the legacy store: one keychain item per environment ( |
file keystore | self-hosted sites, tests | a plaintext |
web vault | the Seed web app on hyper.media | an encrypted vault on the vault service; the private key never reaches the server in the clear |
key file | the Seed CLI, CI | an exported |
environment | the Seed CLI in scripts |
|
browser IndexedDB | the Seed web app | a non-extractable WebCrypto session key, delegated from a vault account |
The vault is the browser-side identity store on hyper.media. Your account keys sit in an encrypted blob that only a password or passkey can decrypt, and the server holding the blob cannot read it. When a site wants to act for you, the vault keeps the account key. It signs a capability that delegates a fresh browser session key, as described under key linking below and in Sign in with Seed.
Profiles
A profile is a small signed blob that attaches a display name, an avatar and a description to an account. It is a snapshot blob. Every update is a whole new blob, and readers merge them. When the daemon serves an account it collects every profile blob signed for that space and merges them field by field, keeping the newest value of each field by timestamp. So a name from one device and an avatar from another combine, and neither overwrites the other.
Profiles are always public. A profile signed by a delegated key carries an account field naming the account it describes, and the daemon rejects it unless that account issued the signer an AGENT capability. The home document is separate from the profile. The profile gives the name and picture. The home document gives the site's content and its siteUrl.
Key linking: several keys, one identity
Most people end up with more than one key. A desktop app, a phone, and every browser origin you comment from each hold their own. Linking them uses the same blobs as everything else:
The main account signs a capability with role AGENT and an empty path, naming the other key as delegate. The other key may now write anywhere in the main space and sign profiles for it.
The other key publishes a profile whose only field is alias, pointing at the main account. The daemon accepts this alias only if step 1 exists. Otherwise it holds the profile aside until the capability arrives.
Optionally the other key signs a reverse AGENT capability back to the main account. The web sign-in flow publishes this reverse capability so the record is bidirectional, but no code reads it today.
Once an alias exists, anything the delegated key signs is shown under the main account, and asking for the delegated account returns only the alias. This is what happens when you sign in to a site with the vault. The vault signs an AGENT capability to a session key, and the site's callback page publishes that capability, the reverse capability and the alias profile together.
The Permissions page has the exact rule for how far a delegated key's authority reaches. In brief: an AGENT key does what the issuer can do in the issuer's own space. It also inherits the issuer's direct grants elsewhere, but only one hop.
Proving identity to a server
Signing blobs proves authorship. Reading private content over HTTP needs something else, because an HTTP request carries no signature. The daemon issues a bearer token. You sign an ephemeral capability that names the daemon's peer. The daemon checks that it is fresh (within five minutes) and that it already knows your principal, and returns a symmetric token valid for thirty days. The Seed web app stores that token in a cookie and forwards it on every request. The token only widens what you can read on a public-only node. It never authorizes a write. See Integrity for the practical effect.
Recovery and rotation
The protocol has no key recovery, rotation or revocation beyond what linking gives you.
If you lose the private key and its mnemonic, the account is gone. Nothing can re-issue it.
If a key is stolen, nothing can revoke it. A capability it holds stays valid forever.
The only migration path is to keep the old key, delegate a new key with AGENT, and alias the new key to the old. The old key stays valid; the new key is linked to it.
Mitigations today: back up the mnemonic, keep the account key in the vault or the OS keyring and off servers, and give servers and bots their own keys with narrow WRITER grants. Team notes describe a clear rotation ("I stop using key A, start using key B, signed by both") that the existing blobs could express. No client builds it, and the daemon has no notion of a "current" key.
Working with identities
In the Seed app
In the Seed app, creating an account generates a mnemonic and stores the key in the OS keyring or the local vault. Settings hold the profile (name, avatar, description) and the list of local keys. The app can export a key file and import one. Signing in on the web goes through the vault. You join or comment, create or open your identity with an email code and a passkey or password, and the site receives a delegated session key. Linking a web key to the desktop app uses the three blobs above.
CLI
seed-cli key generate --name main --words 24 --show-mnemonic # new account in the OS keyring
seed-cli key import "<twelve or twenty-four words>" --name imported
seed-cli key list # name, account ID, source (vault or keyring)
seed-cli key derive "<words>" # print the account ID, store nothing
seed-cli key export main -o main.hmkey.json --password '…' # portable key file
seed-cli account get z6Mk… # merged profile
seed-cli account profile set --name "Ada" --icon ipfs://… --description "…"
seed-cli account profile set -a z6MkOwner… --name "Ada" # sign a profile for an account that made you its AGENTThe CLI reads vault keys and keyring keys, and SEED_CLI_KEYFILE or SEED_CLI_MNEMONIC override both for headless use. The full reference is in Seed CLI.
SDK
The SDK, @seed-hypermedia/client, has the primitives under its blobs subpath: principalFromEd25519, principalToString, principalFromString, createProfile, createProfileAlias, and NobleKeyPair or WebCryptoKeyPair as signers. keyfile reads and writes .hmkey.json. Browser sign-in is startAuth, handleCallback and createSessionSigner under the auth subpath. Every signed blob is published with client.publish. See SDK.
Web API
On the Seed API, GET /api/Account?id=<uid> returns the merged profile, following an alias to the account it points at. GET /api/ListAccounts lists the accounts a site knows. POST /hm/api/auth exchanges a signed authentication request for the bearer cookie, and DELETE clears it. GET /hm/api/config tells you which account a site is registered to (registeredAccountUid) and which key the server signs with (signerAccountUid). See Web API.
Agents
Seed Agents hold their own account keys. An agent's signing identity is a server-held Ed25519 key created with CreateSigningIdentity or imported from a seed. Creating one publishes a profile so the agent has a name. An agent writes as itself and publishes into a person's space only when that space delegated it a capability. Through the write verb an agent updates its profile with the profile.update action and can publish an alias with profile.alias; both accept dryRun. See Write.
An external agent such as Claude Code with the seed-cli skill uses the CLI commands above: generate a key once, keep it in the keyring or a key file, and ask the human to grant it a capability. It should never borrow the human's key. Building agents walks through it.
Where this is going
As of September 2026 these are directions the team is exploring. None of them is code yet:
Domains as a second identity vector next to the key, with both saved in links and contacts and a warning when they disagree. Today the only binding is the site's registeredAccountUid.
Session keys scoped to a target instead of full AGENT delegations.
Capability revocation, which would make rotation meaningful; see Permissions.
The HM26 redesign folds home documents and profiles into nodes owned by the space; see Roadmap.
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime