# Wallet login (`user-sig`)

A human signs in with their wallet and your app receives a stable,
app-scoped **pseudonym** — not their address. There is no browser redirect,
no issuer, and no session-granting server in between: the wallet signs, the
SDK mints a **deed**, and your app verifies it directly against the
on-chain registry. If you haven't already, read [Concepts](concepts.md) for
the mental model.

## The flow

1. **Your app issues a challenge** — `GET /challenge` on your own
   [deed guard](verify-tokens.md).
2. **The wallet signs.** The SDK has the wallet sign a human-readable
   message naming the origin (twice, compared for determinism) to derive a
   `root_seed`, then derives a per-`(tenant, origin)` app key from it and
   signs the deed with that key. One wallet popup per origin, not one for
   the whole web.
3. **Your app verifies** the deed with `DeedGuard`/`DeedVerifier` and mints
   its own session, however it already does. See [Verify a
   deed](verify-tokens.md).

The full walkthrough — the exact derivation
(`derive_root_seed` → `derive_app_key` → `mint_user_deed`), what is and is
not in the key-derivation scope (`tenant` + `origin`, deliberately not
`aud`), and what a `sub` looks like across audiences/origins/tenants — is
documented once, in [Sovereign tier § User login
(`user-sig`)](../sovereign-tier.md#user-login-user-sig). Read that rather
than a second copy here.

## TypeScript: `signInWithDeed`

`@grantor/sdk` exports `signInWithDeed`, which composes the wallet-signing
steps above (and the origin-provenance check, below) into one call:

```ts
import { signInWithDeed } from "@grantor/sdk";

const deed = await signInWithDeed({
  signMessage,      // wraps the wallet's personal_sign
  tenantId,
  audience,
  origin,           // the origin YOU are actually running on
  challenge,        // from your app's own /challenge
  exp,
  vouch,            // the RP's published origin_vouch
  chainRegistry: REGISTRY,
  chainReader,
});
```

Python, Go and Rust expose the same steps (`deriveRootSeed`/`deriveAppKey`/
`mintUserDeed`, in their language-specific casing) as separate calls rather
than one wrapper. The Rust snippet in [Sovereign tier § User
login](../sovereign-tier.md#user-login-user-sig) is the source of truth;
`sdk/README.md` maps the equivalent calls per language.

## Origin provenance: required before signing

`signInWithDeed` (and `authenticate()`, the equivalent agent-side helper)
refuses to sign **before** it signs anything — not after — if the origin
cannot show a tenant admin's on-chain vouch that it speaks for
`(tenant, audience)`. This is what stops a hostile origin that republishes
a victim tenant's `tenant`/`audience` from harvesting a pseudonym by simply
asking a holder to sign in. `chainReader`/`chainRegistry` are REQUIRED —
omitting either is a fail-closed error, not a skipped check. See [Sovereign
tier § Origin provenance](../sovereign-tier.md#origin-provenance) and [§
Constructing a `chainReader`](../sovereign-tier.md#constructing-a-chainreader).

## What this proves — and what it does not

`user-sig` proves *"I control a key"*, not *"I control a wallet"*.
Permissionless login is open by definition — anyone can generate a key. The
wallet's role is **portability of identity across devices**, not
gatekeeping. There is no on-chain user revocation, because there is no
on-chain user state to revoke; an RP that wants to ban someone bans the
`sub` on its own side.

`personal_sign` also means the wallet cannot tell who is asking: a hostile
page can request the *victim's* origin-scoped message, and the only control
left is a human reading a prompt that names an origin they are not on. See
[Sovereign tier § What S1 cost, and what it did not
buy](../sovereign-tier.md#what-s1-cost-and-what-it-did-not-buy) for the full
residual-risk statement — do not describe this mode as a hard
phishing-resistance guarantee.

## See also

- [Verify a deed](verify-tokens.md) — the relying-party side.
- [Agent tokens](agent-tokens.md) — the `agent-zk` deed, for enrolled agents.
- [Sovereign tier](../sovereign-tier.md) — the full reference.
- [Errors](errors.md) — every error code a relying party branches on.
