Privacy
Privacy in Tenure is structural, not policy. The two load-bearing mechanisms: after mint, no plaintext claim values exist registrar-side (derive-then-purge), and worker keys are per-employer so cross-employer correlation is impossible by construction. This page describes the data split and the honest limits of each promise.
The data split
| Visibility | Data |
|---|---|
| Public | Signed log heads, checkpoints, the epoch chain, delegation existence (type-level only), revocation commitments (BLAKE3(attestation_id)), and KYB attestations. Employer identity is public; workers never are. |
| Authorized-only | Claim payloads, the subject↔employer mapping, and share contents — visible to the employer, the subject, grant audiences, and authorized auditors |
| Holder-held | Inclusion receipts — witnessing without publishing the employment graph |
A revocation commitment is BLAKE3(attestation_id): a grant holder who knows
the attestation_id can check whether it is revoked, but the commitment is
unlinkable to anyone who doesn’t.
Derive-then-purge
The registrar must see exact values at mint — it derives the band and threshold variants of a claim family from the exact figure. The moment that is done, it purges the plaintext, retaining only:
- subject-encrypted blobs — the claim payloads sealed to the worker’s key,
- a recovery escrow exactly as strong as the descriptor’s recovery policy (no stronger), and
- commitments.
“Encrypted at rest with registrar-held keys” was the earlier posture and is explicitly rejected: it protects against disk theft, not against compromise or legal compulsion. Derive-then-purge means a compromised or compelled registrar has no plaintext to give up. The further hardening — the employer encrypts before submission, so the registrar never sees plaintext at all, and recovery becomes employer re-furnish — is a later commit-only storage path.
Per-employer keys
A worker has a fresh Ed25519 keypair per employer, generated silently when they claim a wallet. This is the privacy spine:
- there is no key that links a worker’s records across two employers;
- bundles are sealed to their audience;
- nothing about a worker is publicly enumerable.
Because correlation is impossible at the key layer, the product never aggregates, scores, or hints that two employers’ statements corroborate each other — triangulation is impossible by design, and the design leans into that.
Disclosure is consented, logged, and decaying
- Consented. A disclosure happens only when the worker signs a
ShareGrant. The wallet shows a preview of exactly what the verifier will see before the grant is signed. - Logged. Every fetch the registrar serves — link opens, bundle downloads, portal verifications — lands in the worker-visible access log. This is fetch-scoped: what happens after the bytes leave (screenshots, a saved bundle, the self-hosted page) is governed by grant terms and verifier policy — contract, not cryptography, and labeled as such in the UI. The self-hosted verify page structurally cannot feed the access log, and the protocol names which promise covers which event class rather than pretending both are absolute.
- Decaying. Revoking a grant stops future access through Tenure and makes
already-saved copies fail re-verification: past the freshness window a saved
bundle reads
StaleHead, notVerified. It does not delete copies already viewed or downloaded, and the copy says exactly that.
Monitoring carries no values
A scope = monitor grant lets a verifier receive event classes only —
employment_status_changed, attestation_superseded, grant_revoked — never
new claim values. This is enforced at the type level: the monitoring event enum
has no fields, so a push cannot carry a value. Learning what changed
requires a fresh share the worker consents to. Consent duration lives inside the
grant, and the consent copy is verbatim and explicit, including that revoking
“may affect your application.”