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