Skip to content
Seed custody and recovery

Seed custody and recovery

A seed is a private signing key, and the whole valiss model rests on seeds staying secret: a verifier holds only public keys and an allowlist, never a seed, so all of the system’s signing power lives with the parties that keep them (Security, Custody). This page is about keeping them, and about the two ways keeping them can fail. Losing a seed and leaking a seed are different disasters with different recoveries, and both are covered here.

Where seeds come from

Seeds are Ed25519 private keys generated by the platform’s cryptographic random source through the nkeys library (nkeys.CreateOperator, CreateAccount, CreateUser); valiss adds no key generation of its own.

Custody tiers

The three seed levels do not warrant the same handling, because what each one can do differs by an order of magnitude.

  • The operator seed is the root secret of the whole trust domain: whoever holds it can issue any account, and any user beneath it. It belongs offline, in cold storage or a secrets manager, and never on a production host. It comes out only to issue new credentials and to advance an epoch, and then goes back.
  • Account seeds are issuer-side. Each one signs its tenant’s requests and issues that tenant’s user tokens, so it lives with the issuing process, not scattered across the services that merely consume what it produces.
  • User seeds live with the client that signs as that user, inside its credentials file. On disk that file is owner-only (0600) and is never committed to version control. Watch the environment-variable path especially: the VALISS_SEED_<pubkey> convention that examples/minter reads is convenient, but an env var leaks into process listings, CI logs, and crash dumps far more readily than a file read on demand, so a seed passed that way through shared CI is a seed you should assume was captured.

Caution

A seed passed through an environment variable (the VALISS_SEED_<pubkey> convention) leaks into process listings, CI logs, and crash dumps far more readily than a file read on demand. Assume a seed exposed to shared CI that way was captured.

Backing up the operator seed

The operator seed is a single secret, 32 bytes of Ed25519 key material, and its loss is terminal for the trust domain. Nothing regenerates it; nkeys cannot recover a seed from a public key. With it gone you can issue nothing new: no new tenants, no renewals, no epoch rotation. The domain does not fail at once. It dies by attrition, each outstanding token still verifying until its own exp, with the operator token’s exp bounding the whole domain past which nothing verifies at all (Rotation). A slow, unstoppable shutdown is the shape of operator-seed loss.

Warning

Operator-seed loss is terminal: nothing regenerates it, so you can issue nothing new and the domain dies by attrition until the operator token’s exp closes it entirely. An offline, durable backup made at generation time is mandatory, not a nice-to-have.

So an offline, durable backup of the operator seed, made at generation time, is mandatory, not a nice-to-have. For a high-value operator the mature posture is to remove the single point of failure entirely: split custody so no one party holds the whole seed (secret-sharing across several holders, for example), or keep the key in an HSM that signs without ever exporting it. The shape is what matters here; the vendor and the threshold are yours to choose.

Recovery from loss

There is no recovery of a lost seed itself. The only path back to a working domain is to stand up a new anchor: generate a new operator key, pin its public key on every verifier, and re-issue the entire tree of accounts and users beneath it. That is a heavy ceremony, touching every server that pins the anchor and every credential in the domain, and it is the same ceremony as recovering from a compromised operator seed, only without an adversary racing you.

Recovery from compromise

A leaked operator seed is full domain compromise, and neither of the ordinary levers contains it. The allowlist does not: it rejects account ids you never deposited, but it cannot stop the holder from signing message tokens that offline receivers accept with no allowlist in the path. Epoch rotation does not either: the holder can re-issue at whatever epoch you advance to. The remedy is the same re-anchoring as recovery from loss, pinning a new operator public key everywhere and re-issuing the world, done under time pressure with an active adversary. The operational runbook for doing that fast is emergency revocation.

Detection is not free

The gap to be honest about: misissuance by a stolen operator seed is not detectable by the protocol itself. A token the thief signs verifies against the real anchor, because it was signed by the real key. There are two partial compensations, and both are operational, not cryptographic.

At an allowlist-enforcing request verifier a forged account token shows up as a rejection, because its content-hash id was never deposited in your list, so a spike of unknown-id rejections is a monitorable signal that someone is presenting tokens you did not issue (Monitoring). But offline message verification has no such net: VerifyMessage roots trust in the operator key alone and holds no allowlist, so there a forgery is silent. The second compensation is epoch hygiene: keeping epochs disciplined means a rotation actually retires a whole generation of credentials, which both bounds how long a forged token stays useful and makes a token stamped at a stale epoch stand out. Neither closes the gap. They are the net you string under a hole the cryptography leaves open, and the reason the operator seed’s custody is the tier that matters most.