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: theVALISS_SEED_<pubkey>convention thatexamples/minterreads 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.