# Privacy & Credentials

ChainRender's privacy model is about **identity and claims**: proving something about a user ("verified member,"
"over 18," "holds this credential") *without revealing the underlying data*. This is distinct from transaction
privacy (hiding value transfers), which is a separate layer.

## Reveal the claim, not the witness

The core principle (structural data minimization): the render only ever sees the *result* of a verification, never
the private input. Heavy verification happens off-chain (at attestation time) or via a zero-knowledge proof; on
chain, the resolver reads only a boolean or a statement label. The interpreter, the RPC, and the rendered output
never touch the witness.

## The built primitives

- **Attested claims** — identity as attested statements; trust delegated to an ownerless attester registry.
- **Selective disclosure** — member-of-set proofs (Semaphore-style, Groth16): prove you're in a set without
  revealing *which* member.
- **Bound, single-use credentials** — non-transferable (bound to a holder) and single-use (a nullifier prevents
  replay), fail-closed on a tampered proof.

These are unified behind a common convention (`IPrivacyResolver`) so an app gates on a *family* of privacy proof
without knowing which circuit backs it.

## The built privacy primitives (in detail)

- **`MemberSetVerifier`** — a Groth16 verifier for member-of-set proofs (Semaphore-style). A user proves they're in
  a set (an allowlist, a DAO, a credential set) **without revealing which member** — the proof's public signals are
  the set root + a nullifier, never the identity.
- **`CredentialResolver`** — renders a selective-disclosure credential: a valid proof resolves to the checkmarked
  statement label; a tampered proof or wrong root renders "Unverified," never a badge.
- **`BoundCredentialResolver` + `NullifierRegistry`** — bound (non-transferable, tied to a holder via the signal
  hash) and single-use (a nullifier prevents replay). Consuming the credential (spending the nullifier) is a
  separate, honestly-graded WRITE. A tampered proof, a wrong root, or an unknown statement all fail closed.
- **`AttesterRegistry` / `AttestationAdapter`** — attested claims: identity as statements an attester vouched for,
  with trust delegated to the ownerless attester set (+ EAS via the adapter).
- **`IPrivacyResolver`** — a unifying convention over the above, so an app gates on a *family* of privacy proof
  (member-of-set / attested-claim / selective-disclosure) without knowing which resolver backs it.

## Adding a new statement (the `zkAgeOver` family) — generic by construction

The credential path is **generic**: `CredentialResolver` (kind `credential`) takes
`abi.encode(statementId, a, b, c, pub)`, resolves the verifier for `statementId` from the **immutable**
`StatementRegistry` (the caller never supplies the verifier — anti-spoof), calls `verifyProof` **on chain**, and renders
`✓ <label>` or fails closed to `Unverified`. Adding a new statement — `zkAgeOver`, `zkBalanceOver`,
`zkHoldsCredential` — therefore needs **only** a circuit + its trusted setup + the generated Groth16 verifier contract,
and a registry entry `(statementId, verifier, label, nPublic)`. **No change to the resolver, the `credential` kind, or
the viewer** — rendering a new statement is already wired (proven by `test/ZkStatementGeneric.test.js`, which registers
a new `age-over-18` statement against an unchanged resolver and renders `✓ Age over 18`).

The honest status: these statements are **resolver-ready, circuit-pending**. The trust-bearing artifacts are the
circuit and its trusted setup, which must be authored and audited (or produced by a ceremony) — never fabricated. Until
a real verifier is registered, the viewer renders `Unverified` (fail-closed), never a fabricated badge. The resolver's
one caveat is `nPublic`: it supports single-public-input statements today; a statement needing more than one public
input either folds them into one committed field or takes a small, additive registry/resolver extension.

## The honest boundary

Privacy of the *claim* is provided; privacy of the *read path* is not — an RPC still sees which app you loaded.
ChainRender states this plainly rather than overclaiming. See the internal privacy audit for the full analysis.

## Related
- **[Resolvers & Data Binding](./resolvers.md)** — credentials are resolvers.
- **[Reference → Resolver Catalog](../reference/resolvers.md)**


## Optional project encryption (builder)

The visual builder can **encrypt your saved projects at rest** in the browser (IndexedDB) with a password you set
(WebCrypto AES-GCM + PBKDF2). It is opt-in and honest about the tradeoff: there is **no password recovery** -- if you
forget it, the encrypted projects are cryptographically unrecoverable. The tool offers genuine (non-fake) safety nets
instead: change-password (while unlocked), export a decrypted backup (while unlocked), and delete-and-restart (if
locked out). It protects your in-progress creative work; it is not tied to keys or funds (AI keys are session-only,
never persisted).

