{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreih3hfu37kpqvkjfxd574pbnlwdmoabrvi5b6zgez6dwdnakjqfxie",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mpv4dvvx75y2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreiavdkuvh3g7poxgc6furc3tfdzbhzl2nj3ear3k75wmernblf43bi"
    },
    "mimeType": "image/webp",
    "size": 65268
  },
  "path": "/gitsergecrypto/two-signatures-are-better-than-one-bilateral-provenance-for-ai-agents-3668",
  "publishedAt": "2026-07-05T07:44:52.000Z",
  "site": "https://dev.to",
  "tags": [
    "web3",
    "base",
    "ai",
    "cryptography",
    "pdr-spec.md",
    "pdr_parser.py",
    "api.aotrust.link",
    "docs.aotrust.link",
    "api.aotrust.link/mcp",
    "github.com/GitSerge-crypto/aotrust-skills"
  ],
  "textContent": "#  Two signatures are better than one — bilateral provenance for AI agents\n\nAn AI agent produces a financial report. You notarize it — a 239-byte cryptographic record, signed by an independent notary, anchored on NEAR. The record proves: _this hash existed at this timestamp, and $0.01 USDC was paid for the attestation._\n\nA week later, someone asks the obvious question: **who proved the agent wrote the report?**\n\nNobody. The notary signed a hash the client submitted. Anyone could have submitted that hash. The PDR proves the hash existed — not that the agent authored the content behind it.\n\nThis is the gap **Bilateral Signature (v0x04)** closes. The agent signs its own work hash with its Ed25519 key. That signature gets fused into the PDR. Now the record carries two independent signatures — agent and notary — and neither party can repudiate.\n\n##  The binding hash\n\nThe mechanism is a single hash:\n\n\n\n    binding_hash = sha256(work_hash + sig_A + agent_pubkey)\n\n\n  * `work_hash` — SHA-256 of the agent's output (32 bytes)\n  * `sig_A` — agent's Ed25519 signature over `work_hash`, NEP-413 standard (64 bytes)\n  * `agent_pubkey` — agent's Ed25519 public key (32 bytes)\n\n\n\nThis binding hash replaces `work_hash` in the PDR's `payload_hash` field. The notary then signs the full 175-byte payload — which already contains the binding hash. So the notary's signature covers a record that embeds the agent's signature inside it.\n\nTo forge a v0x04 PDR, you need **two** private keys: the notary's Ed25519 seed and the agent's Ed25519 key. With the ordinary v0x03, you only need the notary's. Bilateral doubles the compromise requirement.\n\n##  What actually changed\n\nAlmost nothing structurally — and that's the point.\n\n| v0x03 (ordinary) | v0x04 (bilateral)\n---|---|---\nVersion byte | `0x03` | `0x04`\n`payload_hash` | `sha256(work_result)` | binding hash\nAgent signature | not required | Ed25519 over work_hash\nSize | 239 bytes | 239 bytes\nPrice | $0.01 | $0.01\n\nSame binary layout. Same 239 bytes. Same NEP-413 notary signature. The parser handles both by checking the version byte. All 9 existing mainnet PDRs (v0x03) remain valid — no migration, no fork.\n\nThe notary auto-selects the version: if the request includes `agent_sig` + `agent_pubkey` → v0x04. If not → v0x03. No explicit version parameter.\n\n##  The agent signs first\n\nBefore anything reaches the notary, the agent signs its own work hash:\n\n\n\n    import hashlib, struct, base64\n    from nacl.signing import SigningKey\n\n    # Agent's Ed25519 key\n    agent_key = SigningKey(bytes(32))  # use your real key\n    agent_pubkey_hex = agent_key.verify_key.encode().hex()\n\n    # Agent produced this output\n    report = b\"Q3 revenue projection: $2.4M based on agent analysis...\"\n    work_hash = hashlib.sha256(report).digest()\n\n    # NEP-413: Tag(u32 LE) + Len(u32 LE) + message — raw, no pre-hash\n    nep413_buffer = struct.pack(\"<II\", 2147484061, len(work_hash)) + work_hash\n    agent_sig = agent_key.sign(nep413_buffer).signature\n    agent_sig_b64 = base64.b64encode(agent_sig).decode()\n\n\nNEP-413 signs the **raw** buffer — no SHA-256 pre-hash. The signature covers `Tag + Len + work_hash_bytes` directly. Same standard NEAR uses for transaction signing.\n\n##  Notarize with the agent signature\n\n\n    curl -s https://api.aotrust.link/v1/notarize \\\n      -H \"Content-Type: application/json\" \\\n      -H \"X-Payment: <base64-encoded EIP-3009 payload>\" \\\n      -d '{\n        \"work_hash\": \"a1b2c3d4...sha256_of_output...\",\n        \"agent_sig\": \"BASE64_AGENT_ED25519_SIGNATURE\",\n        \"agent_pubkey\": \"HEX_AGENT_ED25519_PUBLIC_KEY\"\n      }'\n\n\nInside the notary:\n\n  1. **Verifies`sig_A` against `agent_pubkey`** — before payment, before anything else\n  2. Verifies EIP-3009 payment on Base (~2s)\n  3. Computes `binding_hash = sha256(work_hash + sig_A + agent_pubkey)`\n  4. Builds 239-byte PDR — version `0x04`, binding hash in `payload_hash`\n  5. Signs the 175-byte payload with notary Ed25519 key\n  6. Returns base64 PDR\n\n\n\n\n    {\n      \"job_id\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n      \"status\": \"notarized\",\n      \"pdr_b64\": \"BQFCAUJ...239_bytes_base64...\"\n    }\n\n\nThe PDR starts with `BQ` — base64 for `0x04 0x01` (version=bilateral, scheme=Ed25519). An ordinary PDR starts with `Aw` (`0x03 0x01`). You can tell which version at a glance.\n\n##  Two independent verification paths\n\nThis is the real payoff. After notarization, you have **two separate cryptographic proofs** — each verifiable independently, each requiring a different public key.\n\n###  Path 1: Notary signature (the PDR itself)\n\n\n    curl -s \"https://api.aotrust.link/v1/pdr/verify/BQFCAUJ...your_pdr_b64...\"\n\n\n\n    {\n      \"valid\": true,\n      \"version\": 4,\n      \"binding_hash\": true,\n      \"payload_hash\": \"b3c4d5e6...\",\n      \"timestamp_utc\": 1751700000,\n      \"payment_anchor_type\": 5,\n      \"anchor\": { \"near_tx\": \"H4MaR5...\", \"confirmed\": true }\n    }\n\n\nOr fully offline — the parser is a single Python file, zero dependencies:\n\n\n\n    curl -sO https://raw.githubusercontent.com/GitSerge-crypto/aotrust-skills/main/pdr_parser.py\n\n    python3 pdr_parser.py --pdr \"BQFCAUJ...\" \\\n      --pubkey \"490f51f23b993eacaff54fc977d9a7689ab7d4ae91504dc6cbdeadb2dbf1f462\"\n\n\n\n    PDR v2.4 — Valid ✓\n      version:           4 (bilateral)\n      payload_hash:      b3c4d5e6... (binding hash)\n      signature:         VALID (Ed25519/NEP-413)\n\n\nThis proves: the notary signed the record, the payment was verified, the timestamp is attested, and the record is anchored on NEAR.\n\n###  Path 2: Agent signature (stored in ledger)\n\nThe agent's `sig_A` isn't in the PDR binary — it's in the notary ledger (the PDR contains the binding hash, not the raw signature). To verify agent authorship:\n\n\n\n    import struct, base64\n    from nacl.signing import VerifyKey\n\n    agent_sig_b64 = \"BASE64_AGENT_SIG\"      # from notarize response / DB\n    agent_pubkey_hex = \"HEX_AGENT_PUBKEY\"\n    work_hash_hex = \"a1b2c3d4...your_work_hash...\"\n\n    work_hash = bytes.fromhex(work_hash_hex)\n    nep413_buffer = struct.pack(\"<II\", 2147484061, len(work_hash)) + work_hash\n\n    vk = VerifyKey(bytes.fromhex(agent_pubkey_hex))\n    vk.verify(nep413_buffer, base64.b64decode(agent_sig_b64))\n    # → no exception = valid\n\n\nThis proves: the agent's Ed25519 key signed this specific `work_hash`. The agent cannot deny authorship.\n\n**Two signatures. Two keys. Two independent proofs.** Forged by compromising either party alone? No. The binding hash ties them together — the notary's signature covers a payload that contains the agent's signature fused into the hash.\n\n##  When to use bilateral\n\nIf the agent has an Ed25519 key — use v0x04. The cost is identical ($0.01), the size is identical (239 bytes), and you get non-repudiation for free.\n\nIf the agent has no key (third-party notarizing someone else's work, bulk timestamping) — v0x03 is fine. It's proof-of-existence without identity binding.\n\nThe interesting case is **multi-agent pipelines**. Agent A produces output, agent B refines it, agent C compiles the final report. Each stage can be notarized with a bilateral PDR signed by that stage's agent key. You get a chain of custody where every link has its own Ed25519 signature bound into the record — and the notary attests each link independently.\n\n##  Backward compatibility\n\nThe parser handles both versions transparently:\n\n\n\n    from pdr_parser import parse_external_pdr\n\n    parsed = parse_external_pdr(base64.b64decode(pdr_b64))\n\n    if parsed.version == 0x03:\n        payload = \"work_hash\"\n    elif parsed.version == 0x04:\n        payload = \"binding hash\"\n\n\n9 existing mainnet PDRs are v0x03. They stay v0x03. New bilateral PDRs are v0x04. No migration, no re-issuance. The version byte is the only thing the parser branches on.\n\n##  Spec and parser\n\n  * **PDR spec (v2.3 + v2.4):** pdr-spec.md\n  * **Standalone parser:** pdr_parser.py — MIT, zero dependencies, handles v0x02/v0x03/v0x04\n\n\n\n##  Try it\n\n\n    # Health check (no auth)\n    curl -s https://api.aotrust.link/health\n\n    # Get a quote — see the 402 response\n    curl -s https://api.aotrust.link/v1/notarize/quote \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\"work_hash\":\"0000000000000000000000000000000000000000000000000000000000000000\"}'\n\n    # MCP discovery (4 tools: notary_quote, notary_notarize, notary_verify, notary_notarize_paid)\n    curl -s https://api.aotrust.link/.well-known/mcp.json\n\n    # Verify page — paste any PDR base64\n    # https://verify.aotrust.link\n\n\n  * **API:** api.aotrust.link\n  * **Docs:** docs.aotrust.link\n  * **MCP:** api.aotrust.link/mcp\n  * **Source:** github.com/GitSerge-crypto/aotrust-skills\n\n\n\n_The service is live on mainnet. Bilateral PDRs (v0x04) are supported as of July 2026 — same price, same size, same verification flow. The agent proves it wrote the work. The notary proves it was notarized. Both are math._",
  "title": "Two signatures are better than one — bilateral provenance for AI agents"
}