Skip to content

Tokens

A token is the signed artifact that binds one key to another. Structurally it is a JWT: a JWS Compact Serialization string of three base64url parts, header.payload.signature, where the payload is JSON using the familiar registered claim names (iss, sub, exp, nbf, iat, jti) plus a nested valiss object that carries the level-specific body.

What makes valiss a chain rather than a pile of independent tokens is who signs what.

Parent-signed chains

Each token is signed by the key one level up, and the signing rules are strict:

tokensigned bysubject (sub) is
operatoritselfthe operator key
accountan operator keythe tenant’s key
useran account keythe user’s key
messageitselfthe emitting user’s key

An account token is only valid if its issuer is the operator you pinned. A user token is only valid if its issuer is the sub of a valid account token. So a verifier does not trust a user token on its own: it walks the chain from the pinned operator key down to the presented credential, checking the signature and the role of each key at every hop. Trust is a property of the whole chain, not of any single token.

This is also why delegation cannot widen. An account key can sign user tokens, but it can never sign another account token, because the verifier requires an account token’s issuer to be an operator key. The role prefix baked into each nkey (see Entities) is what makes that check cheap and total.

Operator and message tokens are self-signed (iss == sub): the operator token is a policy statement the operator makes about its own domain, and a message token is a proof the user makes about a payload it is emitting.

The token id is a content hash

The jti claim is not random. It is derived from the token’s own contents: serialize the payload with jti blanked, take the SHA-256 of those bytes, and encode the digest as base32. The result is a stable 52-character identifier.

Two consequences matter in practice:

  • The id is reproducible. Anyone who can see a token can recompute its jti from the payload and check that it matches. Because SHA-256 is second-preimage resistant, no different set of claims realistically hashes to the same id, so a matching jti was computed from exactly these claims. This is a consistency check, not the integrity boundary: what actually detects tampering is the signature, since altered claims break the operator’s or account’s signature over them.
  • Identical claims yield an identical id. Re-issuing a token with the same contents produces the same jti, which is what lets keyrings and allowlists deduplicate by id, and what lets account token ids serve as allowlist keys collision-resistant enough to share one list across operators (see Allowlist).

The valiss CLI (early development) is designed to expose this reproducibility as inspect: an offline decode of any token that prints its claims and derived id without evaluating trust, the quick way to see what a token carries and confirm its jti. That command is not yet runnable (a stub today); until it lands, the library’s valiss.Decode does the same untrusted decode from any token, and examples/minter prints an issued token’s jti as metadata.

Reproducing a jti byte-for-byte across languages requires reproducing the exact JSON serialization the reference implementation uses (field order, no insignificant whitespace, and Go’s HTML-style escaping of <, >, and &). A verifier that only checks signatures may treat jti as opaque; one that re-derives it must match the serialization exactly.

Note

Porting jti derivation means reproducing the reference serialization exactly: field order, no insignificant whitespace, and Go’s HTML-style escaping of <, >, and &. A verifier that only checks signatures may treat jti as opaque; one that re-derives it must match that byte shape or every id it computes will differ.

Validity is optional and absolute

exp and nbf are absolute Unix-second timestamps and both are optional. An absent exp means the token never expires; an absent nbf means it is valid immediately. Window checks apply a small clock-skew tolerance (2 minutes by default) so that a slightly fast or slow clock does not spuriously reject a fresh token.

Not a stock JWT, on purpose

valiss tokens are shaped like JWTs but are not verifiable with off-the-shelf JWT libraries:

  • the algorithm identifier is ed25519-nkey, not the registered EdDSA, so a standards-compliant library rejects the header outright; and
  • iss and sub carry nkey-encoded public keys, not JWKs.

Important

Do not reach for a stock JWT library to verify a valiss token. The header declares alg as ed25519-nkey, not the registered EdDSA, so a standards-compliant verifier rejects it outright, and iss/sub carry nkey-encoded keys rather than JWKs. A port implements the verification algorithm directly; the Go library is the canonical verifier.

This is a considered trade. Putting the key’s role into the key material is what makes the strict signing hierarchy checkable at every hop, and no stock JWT middleware can walk a multi-level chain, enforce epochs, or check payload checksums anyway. The signature check is the only layer such a library could have reused. The library is the canonical verifier; other languages port the verification algorithm rather than reuse a JWT stack.

Related

  • Allowlist: how issued account tokens are revoked before they expire, keyed by the content-hash jti derived here.