Reserved Names
Reserved names are a policy layer on top of the protocol. The indexer has no concept of “reserved” - to it, a name is either in registrations or it isn’t. Reservation is enforced by the signing service: the admin key will not produce a CLAIM signature for a reserved name unless the requester presents a valid unlock code.
For the end-user perspective see Reserved Names (Use ZNS).
Storage
Reserved names live in a Supabase table (zn_reserved_names) queried by the web app’s server actions. The schema:
| Column | Type | Description |
|---|---|---|
name | text, primary key | Lowercased normalized name |
category | text | One of brand, protocol, community, offensive |
redeemed | boolean | True once the name has been claimed by its rightful owner |
The table is outside the indexer’s scope. Running your own indexer does not give you access to the reserved list - it’s a policy-layer table specific to the zcashnames.com deployment. Third-party signing services can implement their own reservation policy (or skip it entirely).
Unlock codes
An unlock code is a deterministic HMAC fingerprint that the signing service can regenerate for any reserved name. It is distributed out-of-band (Discord, email) to the rightful owner.
Properties:
- Deterministic. Same secret + same name = same code forever (until the secret is rotated).
- One per name. There is no per-user code - the secret is shared across the operator’s infrastructure.
- 12 hex chars, formatted
XXXX-XXXX-XXXX. Provides ~48 bits of entropy per code given the secret is unknown - enough to prevent online guessing, not enough to resist a secret leak. - Not a capability. The unlock code is a verifier input; it does not by itself authorize anything on-chain. The on-chain CLAIM still requires the admin Ed25519 signature.
Claim flow
- User types a reserved name in the web UI.
- Server action calls
getReservedName(name)and finds a non-redeemed row. - UI prompts for the unlock code.
- User submits code. Server calls
verifyUnlockCode(name, code). - If the code is valid and the category is not
offensive, the server proceeds with the normalbuildSignedClaimMemoflow: resolves (expects null), builds the canonicalCLAIM:{name}:{address}pre-image, signs with the admin key, returns the memo to the client. - Client shows the ZIP-321 URI. User sends the tx. Indexer processes the block normally - it has no idea this name was reserved.
- After confirmation, the server should mark
redeemed = truein the reservation row so subsequent claim attempts are not gated on the unlock code. (Redemption tracking is operator-specific and not part of the protocol.)
Category semantics
| Category | Claim gated on | Blocks forever? |
|---|---|---|
brand | Valid unlock code | No - once redeemed, the row is out of the way |
protocol | Valid unlock code | No |
community | Valid unlock code | No |
offensive | Nothing can unlock it | Yes - the signing service will not produce a CLAIM signature, period |
offensive is the one place where the web UI acts as a censor. The isBlocked helper checks for this category explicitly. Other reservation tiers are discretionary: if a brand doesn’t want their reserved name, they can simply not redeem it and it stays blocked by policy until the operator removes the row.
Security properties and weaknesses
- Policy, not protocol. Anyone running their own signing service can implement a different reservation policy - or none. The indexer will accept any validly-signed CLAIM.
- Admin-key trust. This scheme only works because the admin key is the sole source of CLAIM signatures. If the signing key leaks, reserved names are no longer protected.
- Secret-rotation burden. Rotating
ZNS_UNLOCK_SECRETinvalidates every previously issued unlock code. Do this only in concert with re-issuing codes to holders. - Single point of failure. The signing service’s refusal is the entire enforcement mechanism. There is no on-chain way to distinguish a reserved name from any other name after it’s been claimed.