# Concepts

The mental model. If you read one page, read this one.

Grantor's premise is that **a credential can certify itself**. A caller mints a
**deed** that proves what it needs to prove, and your app checks it against a
public on-chain registry — with no authorization server in between, because
there is nothing in between at all.

## What a deed is

A deed is a small JSON envelope carrying a proof, an audience, a challenge and
an expiry. It is **self-certifying**: everything needed to check it is either
inside it or on a public chain. Nothing issued it, so nothing can forge it,
observe it, or link its uses.

```
{ "v": 1,
  "mode": "user-sig",          // or "agent-zk" / "admin-sig"
  "tenant": 42,
  "aud": "api.example.com",    // who it is FOR — a deed for someone else is invalid here
  "challenge": "9f2c…",        // the value YOUR app issued, one use only
  "exp": 1753700000,
  … per-mode proof material … }
```

Three properties do the work:

- **Audience-bound.** A deed minted for `api.example.com` fails at
  `admin.example.com`. A malicious RP cannot replay what it receives.
- **Challenge-bound.** Your app picks the challenge; the caller cannot. This is
  the OIDC `nonce` shape and it is the entire replay defence.
- **Expiring.** Short-lived by construction.

## The exchange

```
your app                                        caller
   │  1. challenge (random, single-use)  ──────▶ │
   │                                             │  2. sign / prove against it
   │  ◀──────────────────────────  3. the deed   │
   │
   │  4. verify_deed(deed, policy, challenge, now, gate)
   │       ├─ cryptography: signature or ZK proof
   │       ├─ audience, challenge, expiry
   │       └─ eth_call ─────▶ GrantorRegistry ── is the tenant paid up?
   │                                              is the member un-revoked?
   │  5. mint your own session, however you already do
```

Step 4 is a **read**. It costs no gas, needs no API key, and any RPC provider
serves it. Step 5 is yours — Grantor has no opinion about your session format
and never sees it.

## Three kinds of deed

| Mode | Who holds it | Proves | On-chain check |
|---|---|---|---|
| `user-sig` | a human with a wallet | control of a wallet-derived, app-scoped key | tenant billing status |
| `agent-zk` | an enrolled agent | ZK membership of the tenant's registry, **without revealing which member** | root recency (revocation) + billing |
| `admin-sig` | a Grantor dashboard admin | control of a wallet address | **none, deliberately** |

`user-sig` and `agent-zk` are what your app accepts, and one call —
`verify_deed` — handles both. `admin-sig` authenticates Grantor's own dashboard
admins and verifies through a **separate entry point**; `verify_deed` rejects
it outright. That isolation is structural rather than configurable: there is no
flag to flip. See [why](../sovereign-tier.md#dashboard-login-admin-sig).

## Pseudonymous by construction

Your app receives a **pseudonym**, not a wallet address.

For `user-sig`, the wallet signs one fixed domain string to derive a
`root_seed`, and per-app keys come from it:

```
app_key = HKDF(root_seed, "tenant:{tenant}|audience:{aud}")
sub     = derived from the app key's public key
```

Because the tenant and audience are both in the derivation scope, the same
wallet yields a **different, unlinkable `sub`** at every app — pairwise subjects
computed rather than stored. The verifier **recomputes `sub` from the public
key** in the deed rather than trusting a claim, so it cannot be forged.

The seed derivation signs twice and compares: a wallet with a non-deterministic
signature fails loudly instead of silently re-identifying its owner as somebody
new on every login.

For `agent-zk` the proof reveals membership without revealing *which* member, so
the agent is anonymous even to the tenant that enrolled it. `admin-sig` is the
deliberate exception — it reveals the address, because administration is an
identified context, not an anonymous one.

## The trust anchor: `GrantorRegistry`

A Solidity contract that answers, publicly:

- **Is this tenant paid up?** `status(id)` → `Active` / `Grace` / `Inactive`.
- **Is this agent still a member?** via the membership tree's current root.

Anyone can call it from any RPC provider with no API key and no operator in the
loop. Trust is a public fact rather than a row in a company database — which is
what lets the verifier be a library instead of a service.

## Tenants, tiers, and billing

A **tenant** is a billing and ownership unit, represented on-chain as an
ERC-721 "org pass." It has a **tier** — a capacity plan (Free/Pro/Scale/
Enterprise), not a deployment mode — a prepaid **balance**, a set of
registered keys, and a derived **status**.

Billing is a **flat subscription with prepaid funding**, not usage metering.
The tier caps *capacity*, not consumption — and it must, because deed
verification is an `eth_call` the contract cannot observe. Metering deeds would
require your app to report back, which would reintroduce exactly the server
this product removes.

- `topUp` credits the tenant's balance (it does not pay the operator).
- `drawPeriod` moves one period's fee to the treasury. It is **permissionless**
  — a keeper, a bot or anyone may call it once the period lapses, which is what
  makes renewal unattended. The balance is the renewal engine.
- `withdrawBalance` returns **undrawn** balance. Only consumed periods are
  non-refundable; unbilled prepayment is your money for service not rendered.

**Grace, never a cliff.** When balance runs out a tenant enters `Grace` before
`Inactive`; existing credentials keep working throughout. Even at `Inactive`,
deeds already minted stay valid until their own `exp` — going unpaid never
invalidates a credential already in the world.

A tenant that tops up but never draws keeps its balance and gets nothing:
status stays `Inactive` until a period is drawn, so funds are never held while
the product is in use.

## Every capability, every language

**All languages or no go** — a capability that ships in one language has not
shipped. `just capability-matrix` enforces it against the *generated binding
surfaces*, not the Rust source, so it measures what a consumer actually
installs.

| | TypeScript | Python | Go | Rust |
|---|---|---|---|---|
| `user-sig` holder | ✅ | ✅ | ✅ | ✅ |
| `agent-zk` holder (ZK proving) | ✅ | ✅ | ✅ | ✅ |
| `admin-sig` holder | ✅ | ✅ | ✅ | ✅ |
| Deed verification | ✅ | ✅ | ✅ | ✅ |
| Deed guard | ✅ | ✅ | ✅ | ✅ |

## The polyglot split

- **On-chain = Solidity.** `GrantorRegistry` is money-handling code, so it gets
  the most conservative toolchain: Foundry, heavy invariant and fuzz coverage,
  minimal owner powers, an immutable treasury.
- **Off-chain = Rust**, compiled once and shipped everywhere — wasm-bindgen for
  TypeScript, UniFFI for Python and Go. One implementation of the crypto, four
  languages, proven identical by shared conformance vectors.

---

## See also

[Wallet login](wallet-login.md), [Agent tokens](agent-tokens.md) and
[Verify a deed](verify-tokens.md) walk through minting and verifying each
kind of deed. [Sovereign tier](../sovereign-tier.md) is the full reference.
