# Agent Trust Layer

A working trust layer for autonomous AI agents, built for the **[Agent That Earns Trust](https://build.doo.ooo/challenges/agent-earns-trust)** challenge.

**Live demo:** https://AliJalil.github.io/agent-trust-layer/ · **90-second version:** open the demo, click **▶ Attempt task** (accepted), then **🎭 Run attack scenario** (refused, twice, with reasons).

No backend, no database, no third-party crypto library. Every signature in this app is generated and verified by the browser's native `crypto.subtle` Ed25519 implementation.

## What this is

A trust profile per agent (identity, scope of authority, history, vouches), verifiable credentials signed with real Ed25519 keys, a way to grant/revoke/scope trust, and a live demo where one agent accepts or refuses a task based on cryptographic proof — not a vibe.

The demo world runs in a single browser tab: several agent identities (each a real keypair), a credential issuer, two resource agents with their own policies, and one deliberately malicious agent, all playing out in one `localStorage`-backed session. That's a scope simplification for a zero-backend build — see **Out of scope** below — but every credential, signature, and verification inside it is real.

## Architecture snapshot: identity → claims → verification → policy

```mermaid
flowchart LR
    A["<b>Identity</b><br/>did:key — an Ed25519<br/>keypair generated in-browser<br/>(crypto.subtle, non-exportable*)"] --> B
    B["<b>Claims</b><br/>Verifiable Credential:<br/>issuer, subject, scope,<br/>constraints, expiry"] --> C
    C["<b>Verification</b><br/>signature checked against<br/>issuer's real public key ·<br/>subject binding · expiry ·<br/>revocation registry"] --> D
    D["<b>Policy</b><br/>resource agent's own rules:<br/>trusted-issuer allowlist ·<br/>scope/amount match ·<br/>min. vouch count"] --> E{Decision}
    E -->|all checks pass| F["✅ ACCEPT"]
    E -->|any check fails| G["🚫 REFUSE + reason"]
```

_*Private keys are exported as JWK to persist the demo in `localStorage` across reloads — a production system would keep them non-extractable. See **Out of scope**._

Each arrow is a real function call in this codebase: `did.js` (identity) → `state.js#issueCredential` (claims) → `state.js#verifyCredentialSignature` + `policy.js` revocation/expiry/subject checks (verification) → `policy.js#RESOURCE_POLICIES` (policy). The full pipeline and every intermediate pass/fail is rendered in the UI as a **verification trace** — that's what makes the decision explainable in 90 seconds instead of a black box.

## Why it's not "just a score"

Trust here is never reduced to a single number that gates a decision. A task is accepted only when a chain of *separately inspectable, cryptographically checked* facts all hold: the credential's signature is valid, it was actually issued to the agent presenting it (not stolen), it comes from an issuer the resource agent already trusts, its scope and constraints (e.g. dollar limit) cover the requested action, it hasn't expired, it hasn't been revoked, and — for some resources — the agent has a minimum number of independently signed vouches. Any one failure is a refusal, with the specific reason shown. A numeric "trust score" (visible on each agent's profile) is derived *from* these facts for human skimming, never used *as* the gate.

## Failure test: two concrete attacks, both defended

Click **🎭 Run attack scenario** to run both, live, against `PaymentBot`:

1. **Forged issuer.** `SpooferBot` builds a credential claiming `payments:execute` up to $500, and *claims* Root Notary as the issuer — but signs it with its own private key, because it doesn't have Root Notary's. Signature verification against Root Notary's real public key fails immediately. **Refused at step 2.**
2. **Stolen credential.** `SpooferBot` gets a byte-for-byte copy of `TravelBot`'s real, validly-signed, Root-Notary-issued credential (e.g. intercepted in transit) and presents it as its own. The signature checks out fine — but the credential's `subject` field is `TravelBot`'s DID, not `SpooferBot`'s. **Refused at step 3** (subject binding), which is precisely the check that stops a stolen credential from being reused by whoever holds a copy of it, since possession without the matching private key still can't produce a valid *new* signature and the original signature is bound to the wrong subject.

You can also revoke a credential from the **Issue & Vouch** tab and re-run a previously-accepted task in **Task Simulator** to watch it flip to refused with "revoked" as the explicit reason.

## Bonus signals implemented

- **Extends an existing standard:** identities are [`did:key`](https://w3c-ccg.github.io/did-method-key/) DIDs (W3C-style), and credentials follow the shape of a [Verifiable Credential](https://www.w3.org/TR/vc-data-model/) (issuer/subject/claim/proof) with an Ed25519 detached-signature proof.
- **Cross-agent, not single-agent:** the demo world has six independent identities (issuer, two requesters, two resource agents with their own policies, one adversary) interacting through signed messages, not one agent talking to itself.

## Running it locally

No build step, no dependencies to install:

```bash
git clone https://github.com/AliJalil/agent-trust-layer.git
cd agent-trust-layer
python3 -m http.server 8000   # or any static file server
# open http://localhost:8000
```

Requires a browser with WebCrypto Ed25519 support (current Chrome, Edge, Safari; Firefox support is landing — if `crypto.subtle.generateKey({name:"Ed25519"}, ...)` isn't available, agent generation will throw and the console will say so).

## Two-year thesis: agent identity & reputation (≤300 words)

Within two years, agents acting on behalf of people and businesses will need portable, cryptographic identity that outlives any single platform — not a username in one vendor's database. `did:key`-style identity (identity *is* a keypair) is the minimum viable version: an agent proves who it is by proving control of a private key, not by presenting a bearer token a platform can unilaterally revoke.

Reputation will split into two layers that must not be conflated: **capability** — narrow, expiring, revocable claims about what an agent may do, bounded by amount, time, or action — and **track record** — an attributable, append-only history of past actions and vouches from identifiable other parties. Collapsing both into one opaque "trust score" makes it either gameable or useless; the industry converges instead on structured, inspectable claims, closer to TLS certificates or OAuth scopes than to a credit score.

Verification will be delegated, not centralized. No single company becomes "the" agent trust registry. Expect a small number of interoperable standards (extending W3C DIDs/VCs) with many independent issuers: enterprises vouching for their own agents, marketplaces vouching for listed agents, and peer agents vouching for counterparties they've transacted with successfully — exactly the vouch layer in this build.

The unsolved problem isn't cryptography — signing and verification are solved, as this project shows with zero external dependencies. It's **revocation latency and sybil resistance**: how fast a compromised agent's credentials can be invalidated everywhere they're accepted, and how expensive it is to mint a thousand fresh, reputable-looking identities. Whoever solves cheap, fast, cross-issuer revocation — not whoever ships the prettiest trust score — defines the layer every other agent economy gets built on top of.

## Notes

**AI tools used:** Built with Claude (Anthropic), which wrote the identity/credential/policy modules, the UI, the test suite, and this document in one session, based on the challenge brief.

**Key decisions:** (1) Native `crypto.subtle` Ed25519 instead of a bundled JS crypto library — zero third-party dependencies, real signatures, and nothing to audit but the browser itself. (2) `did:key` over a hosted DID method — no registry to stand up for a demo. (3) A fully static, client-only app so the live demo needs no server and "clean clone runs" trivially.

**Out of scope (by design, for a zero-backend demo):**
- **Single-browser simulation.** All agents' private keys live in one browser's `localStorage` for demo purposes. A real deployment would give each agent its own key custody (a secure enclave, HSM, or per-agent wallet) and exchange only public credentials over the network.
- **No distributed revocation service.** The revocation registry here is local app state, not a fetchable, signed status list (e.g. [Bitstring Status List](https://www.w3.org/TR/vc-bitstring-status-list/)) that other parties could check independently — that's the natural next step.
- **No sybil resistance / rate limiting** on identity creation or credential issuance — intentionally out of scope for a UI demo, called out above as the actual hard problem.
- **No did:web / did:ion resolution** — only the self-contained `did:key` method is implemented.

## License

MIT
