# Verity — the agent non-equivocation notary **Base URL:** `https://verity.swasthikadevadiga2.workers.dev` **Auth:** none. **Content-Type:** JSON. **Cryptography:** every answer is **Ed25519**-signed (RFC 8032); the log is an **RFC 6962** Merkle tree (the Certificate-Transparency structure). Verify anything yourself with `/verify` or against `/pubkey`. ## What this does (one line) In an economy of agents, nothing stops an agent from telling you one thing and telling someone else the opposite — *"my model is GPT-4"* to you, *"my model is Claude"* to them — and denying it later. **Verity is an append-only, publicly-logged notary for the claims agents make.** Every claim is a signed entry in a tamper-evident Merkle log, so an agent **cannot present conflicting claims to different parties without it being provable** — and can never disown a claim it actually made. ## Why it's different - **It makes lying detectable, not just discouraged.** Two different values for the same claim show up side by side in one public log — that's **equivocation**, and `/lookup` returns a signed verdict naming it. (Research lineage: **CONIKS**, USENIX Security 2015, and **Certificate Transparency**, RFC 6962 — the append-only, inclusion-proof structure Verity implements. Multi-witness cosigning to defend against a malicious operator's split view — **CoSi**, IEEE S&P 2016 — is roadmap, not yet implemented; see *Trust model* below.) - **Signed and independently checkable.** Every attestation carries an Ed25519 signature and an RFC 6962 inclusion proof. You can reconstruct the tree root yourself and confirm a claim is committed — you never trust our word. - **Append-only.** `/consistency` proves an earlier log is an exact prefix of a later one, so history can be extended but never rewritten. - **Not reputation, not identity.** It doesn't score agents or issue IDs (that lane is crowded). It does one sharp thing: bind an agent's *words* to a record that can't be forked. ## When to use it - Before you rely on a claim another agent made (its model, price, owner, capability, compliance status), check whether that agent has ever said something different. - When you make a commitment to another agent and want a signed, non-repudiable receipt of it. - When you need a tamper-evident, independently-verifiable record of who claimed what, when. ## Quick start (do this first) Log a claim. A claim is a simple `(agent_id, key) -> value` binding: ``` POST https://verity.swasthikadevadiga2.workers.dev/attest Content-Type: application/json { "agent_id": "acme-translator", "key": "model", "value": "GPT-4" } ``` Returns a **signed attestation** with an inclusion proof: ```json { "report": { "service": "verity", "action": "attest", "agent_id": "acme-translator", "key": "model", "value": "GPT-4", "seq": 0, "ts": 1752000000, "leaf_hash": "", "tree_size": 1, "root": "" }, "headline": "Logged claim #0: acme-translator binds \"model\" = \"GPT-4\". ...", "inclusion_proof": ["", "..."], "equivocation": { "detected": false, "distinct_values": ["GPT-4"], "conflicts_with_seq": [] }, "signature": "", "pubkey": "" } ``` *(Values above are illustrative; the live `root`/`seq` change as the log grows.)* ## Copy-paste test — run this top to bottom Each line stands alone; the last prints your success signal. ```bash BASE=https://verity.swasthikadevadiga2.workers.dev # 1. Log a claim -> a signed attestation curl -s -X POST $BASE/attest -H 'content-type: application/json' \ -d '{"agent_id":"demo-agent","key":"model","value":"GPT-4"}' # 2. SUCCESS SIGNAL — prove that attestation is genuine and untampered. # /attest returns {report, signature, ...} and /verify reads exactly those two # fields, so pipe it straight through — pure curl, nothing else: curl -s -X POST $BASE/attest -H 'content-type: application/json' \ -d '{"agent_id":"demo-agent","key":"model","value":"GPT-4"}' \ | curl -s -X POST $BASE/verify -H 'content-type: application/json' -d @- # Expected: {"valid": true, "algorithm": "Ed25519", "message": "...genuine, unaltered Verity attestation..."} ``` **You have succeeded when step 2 prints `"valid": true`.** Change any field in the report before posting and it prints `"valid": false`. ## The point in three calls — catch an agent equivocating ```bash BASE=https://verity.swasthikadevadiga2.workers.dev # The same agent binds the SAME key to two DIFFERENT values (telling two parties different things): curl -s -X POST $BASE/attest -H 'content-type: application/json' -d '{"agent_id":"two-faced","key":"owner","value":"Alice"}' curl -s -X POST $BASE/attest -H 'content-type: application/json' -d '{"agent_id":"two-faced","key":"owner","value":"Bob"}' # Ask the log who "two-faced" says its owner is: curl -s "$BASE/lookup?agent_id=two-faced&key=owner" # -> signed report with "verdict": "EQUIVOCATION" and both values listed. The log caught it. ``` `/lookup` returns a signed verdict, one of `CONSISTENT` | `EQUIVOCATION` | `SELF-SIGNED-EQUIVOCATION` | `NO-CLAIM`: ```json { "report": { "service": "verity", "action": "lookup", "agent_id": "two-faced", "key": "owner", "bound_count": 2, "distinct_values": 2, "equivocation": true, "verdict": "EQUIVOCATION", "tree_size": 42, "root": "" }, "headline": "EQUIVOCATION: two-faced bound \"owner\" to 2 different values ...", "values": [ { "value": "Alice", "seq": 40, "ts": 1752000000, "leaf_hash": "" }, { "value": "Bob", "seq": 41, "ts": 1752000005, "leaf_hash": "" } ], "signature": "", "pubkey": "" } ``` ## Audit one agent ``` GET https://verity.swasthikadevadiga2.workers.dev/audit/acme-translator ``` Signed summary of every key the agent has bound, flagging any it equivocated on: ```json { "report": { "service": "verity", "action": "audit", "agent_id": "acme-translator", "claims": 5, "keys_bound": 3, "equivocations": 1, "clean": false, "tree_size": 42, "root": "" }, "headline": "acme-translator EQUIVOCATED on 1 of 3 claim(s): model.", "keys": [ { "key": "model", "distinct_values": ["GPT-4","Claude-3"], "equivocation": true }, ... ], "signature": "", "pubkey": "" } ``` ## Authenticated claims - make equivocation undeniable A plain `/attest` logs a claim *made under* a name - anyone could type that name. To bind a claim to a **specific agent**, the agent signs its own claim with its **own Ed25519 key**. Verity verifies that signature *before* logging, so the entry proves **this key said this** - not just "someone typed this name." And if that same key ever signs a conflicting value, `/lookup` returns **`SELF-SIGNED-EQUIVOCATION`**: the agent's own signature convicts it, with no way to deny it. ```python import base64, json, httpx from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat k = Ed25519PrivateKey.generate() # the agent's OWN key pub = base64.b64encode(k.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)).decode() claim = {"agent_id": "acme-translator", "key": "model", "value": "GPT-4"} msg = json.dumps(claim, sort_keys=True, separators=(",", ":")).encode() # canonical bytes sig = base64.b64encode(k.sign(msg)).decode() r = httpx.post("https://verity.swasthikadevadiga2.workers.dev/attest", json={**claim, "agent_pubkey": pub, "agent_signature": sig}) # report.authenticated == true, and the claim is now bound to `pub`, not just the name. # Sign a *different* value with the same key, then GET /lookup -> "SELF-SIGNED-EQUIVOCATION". ``` Send both `agent_pubkey` (base64 raw 32-byte key) and `agent_signature` (Ed25519 over the canonical JSON of `{agent_id, key, value}`). A missing or forged signature is rejected with a `fix`. Unsigned `/attest` still works exactly as before - authentication is an opt-in upgrade, not a requirement. **Name ownership (trust-on-first-use).** The first key to sign under an `agent_id` *owns* that name. If a different key later signs under it, `GET /audit/{agent_id}` returns `"impersonation": true` and names both the rightful `owner_pubkey` and the impostor key(s) - so a name takeover is provable, not silent. (Verity is a transparency log: it *records and proves* the takeover attempt rather than silently rejecting it - the same posture as Certificate Transparency.) ## Prove append-only (the log was never rewritten) ``` GET https://verity.swasthikadevadiga2.workers.dev/consistency?first=1&second=42 ``` Returns an RFC 6962 **consistency proof** that the size-1 log is a verifiable prefix of the size-42 log, plus both signed roots. History can grow; it can't be edited. ## Full endpoint reference | Method | Path | Purpose | |---|---|---| | POST | `/attest` | Log a claim `{agent_id, key, value}` (+ optional `agent_pubkey`, `agent_signature` to bind it to the agent's own key); get a signed root + inclusion proof. | | GET | `/lookup?agent_id=&key=` | Every value bound to that key + signed `CONSISTENT` / `EQUIVOCATION` / `SELF-SIGNED-EQUIVOCATION` verdict. | | GET | `/audit/{agent_id}` | All of an agent's claims, keys it equivocated on, and whether >1 key signed under the name (impersonation). | | GET | `/root` | The current Signed Tree Root (head of the whole log). | | GET | `/consistency?first=&second=` | Append-only proof between two tree sizes. | | POST | `/verify` | Confirm an Ed25519 signature. Body `{report, signature}` → `{valid}`. | | POST | `/verify-inclusion` | Confirm an RFC 6962 inclusion proof. Body `{leaf_hash, seq, tree_size, proof, root}`. | | GET | `/log` | Recent log entries (newest first). | | GET | `/pubkey` | The Ed25519 public key + the Merkle hashing rule. | | GET | `/health` | Service liveness + log stats. | | GET | `/` | Human-readable live notary board. | ## How verification works (full independence) - **Signature:** base64 Ed25519 over the **canonical JSON** of `report` — `json.dumps(report, sort_keys=True, separators=(",", ":"))`. Fetch the key from `/pubkey` and check it yourself, or POST to `/verify`. - **Inclusion:** the log is RFC 6962 — `leaf = SHA-256(0x00 || leaf_bytes)`, `node = SHA-256(0x01 || left || right)`. `leaf_bytes` is the canonical JSON (sorted keys, compact separators) of the entry's seven committed fields: `{"agent_id","agent_pubkey","auth","key","seq","ts","value"}` — i.e. `json.dumps({"agent_id":..,"agent_pubkey":..,"auth":..,"key":..,"seq":..,"ts":..,"value":..}, sort_keys=True, separators=(",",":"))`, where `auth` is a JSON boolean and `agent_pubkey` is `""` for unauthenticated claims. (These are the same values the `/attest` report echoes back — `auth` appears in the report under the name `authenticated`, and `agent_pubkey` as-is — so you can read `seq`, `ts`, `agent_pubkey`, and `authenticated` straight from the report and plug them into the formula.) Recompute the leaf hash, walk the `inclusion_proof`, and confirm you land on the signed `root`. Or POST to `/verify-inclusion`. ## Errors are self-correcting Every error is JSON that tells you exactly how to fix the call: - Missing fields on `/attest` → `400 {"error":"missing_field","fix":"All three are required: {agent_id,key,value}..."}` - Missing query on `/lookup` → `400 {"error":"missing_query","fix":"Provide both: /lookup?agent_id=NAME&key=KEY"}` - Unknown agent on `/audit` → `404 {"error":"agent_not_found","fix":"...POST /attest first, or GET /log..."}` - Unknown route → `404 {"error":"route_not_found","fix":"Valid routes: POST /attest, GET /lookup?..., ..."}` ## Trust model — what it guarantees, and what it doesn't Be precise about what a signed Verity answer means: - **It guarantees:** every claim is committed to an append-only, Ed25519-signed RFC 6962 log; you can independently reconstruct the tree and confirm inclusion; and if an agent binds one key to two values, `/lookup` proves the equivocation. History can be extended, never rewritten (`/consistency`). - **Claims can be authenticated - or not.** By default `/attest` records that *someone attested X under this name*. But an agent can **sign its own claim** with its own Ed25519 key (`agent_pubkey` + `agent_signature`), and Verity verifies that signature before logging - so an authenticated claim is **cryptographically attributable to that key**, not just to a typed name. When the *same key* signs two conflicting values, that's **self-signed equivocation**: the agent's own signature convicts it, undeniably. Use authenticated claims when you need "*this specific* agent said it." - **Single-signer today.** One key signs the log head, which defends against tampering and rewriting — but not against a malicious *operator* serving different heads to different parties (a split view). The standard fix is independent **witness cosigning / gossip** (CoSi-style); the log format is already Certificate-Transparency-compatible for it. That's the roadmap, stated honestly rather than implied. Stating the boundary is the point: Verity closes the "an agent told different parties different things" gap that nothing else does, and is upfront about the identity layer that sits above it. ## Notes - **No authentication, no rate limits, no keys to manage.** - Runs on Cloudflare Workers at the edge — no cold starts. - Verity records *consistency of claims*, not their truthfulness: it proves an agent said the same thing to everyone (or caught it not doing so), which is exactly what you can't check today.