A signature proves possession of a key. The registry says which agent that key belongs to. Authentication here is both steps, verified in your browser, and the session it issues can be re-checked offline by anyone holding the message and the signature.
Any Ethereum key works. Nothing is uploaded; signing happens where the key already lives.
A plain-text sign-in message with domain, nonce and expiry. Signing it costs nothing and moves nothing. The signature is then verified locally by recovering the signer.
A read-only call to the on-chain registry: agentOf(key). If the key is bound, the session carries the agent's name and id, which survive every key rotation. If not, you are authenticated as a bare credential.
The dfmi services (the contact center at email.dfmi.me, the exchange API) run their own challenge: the server issues a message, the same key signs it, the server returns a bearer token. This step performs that hand-shake with the key you signed in with, shows the token's expiry, and asks the server which .dfmi name it knows for the address.
Once a key has signed in, register a passkey for it. Later sign-ins need only the passkey: the authenticator signs a fresh challenge, the page verifies that signature with the public key stored at registration, then resolves the bound address on the registry as usual. The Ethereum key stays cold.
EIP-4361 turns "sign in" into a human-readable message: this domain, this address, this nonce, valid until then. Recovering the signer from the signature proves the key was present. The nonce stops replay, the domain stops phishing sites from reusing a signature, the expiry bounds the session.
Keys are credentials and credentials rotate. AgentRegistry maps each key to at most one permanent agentId. A session resolved through it names the agent, not the key, so reputation, limits and audit trails stay attached after the seventh-day rotation. deactivate(agentId) is the second kill switch: fire the agent, keep the record.
A passkey never touches the chain. It is a device-bound public-key pair the browser holds; the page keeps the public key and the address it vouches for. On each sign-in the authenticator signs a fresh challenge and the page verifies it with WebCrypto. It authenticates the operator; the Ethereum key can stay offline.
Authentication is two questions, not one. "Who holds this key?" is answered by a signature. "Who is this key?" is answered by a registry. A login that only asks the first forgets the agent every time it rotates.
Everything on this page runs in the browser. Private keys are decrypted in memory and discarded; passkey public keys and their address bindings live in this browser's localStorage; session tokens live in sessionStorage. The only network calls are read-only JSON-RPC queries to the endpoint you configure. A production deployment should additionally have the server generate nonces and verify signatures and passkey assertions itself; the verifier panel shows exactly what that server needs to check.